Is it possible to enforce message order on ActiveMQ topics using Spring Boot and JmsTemplate? - spring

In playing around with Spring Boot, ActiveMQ, and JmsTemplate, I noticed that it appears that message order is not always preserved. In reading on ActiveMQ, "Message Groups" are offered as a potential solution to preserving message order when sending to a topic. Is there a way to do this with JmsTemplate?
Add Note: I'm starting to think that JmsTemplate is nice for "getting launched", but has too many issues.
Sample code and console output posted below...
#RestController
public class EmptyControllerSB {
#Autowired
MsgSender msgSender;
#RequestMapping(method = RequestMethod.GET, value = { "/v1/msgqueue" })
public String getAccount() {
msgSender.sendJmsMessageA();
msgSender.sendJmsMessageB();
return "Do nothing...successfully!";
}
}
#Component
public class MsgSender {
#Autowired
JmsTemplate jmsTemplate;
void sendJmsMessageA() {
jmsTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.TEST-TOPIC"), "message A");
}
void sendJmsMessageB() {
jmsTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.TEST-TOPIC"), "message B");
}
}
#Component
public class MsgReceiver {
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";
private final String consumerTwo = "Consumer.myConsumer2.VirtualTopic.TEST-TOPIC";
#JmsListener(destination = consumerOne )
public void receiveMessage1(String strMessage) {
System.out.println("Received on #1a -> " + strMessage);
}
#JmsListener(destination = consumerOne )
public void receiveMessage2(String strMessage) {
System.out.println("Received on #1b -> " + strMessage);
}
#JmsListener(destination = consumerTwo )
public void receiveMessage3(String strMessage) {
System.out.println("Received on #2 -> " + strMessage);
}
}
Here's the console output (note the order of output in first sequence)...
\Intel\Intel(R) Management Engine Components\DAL;C:\WINDOWS\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\gnupg\bin;C:\Users\LesR\AppData\Local\Microsoft\WindowsApps;c:\Gradle\gradle-5.0\bin;;C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\bin;;.]
2019-04-03 09:23:08.408 INFO 13936 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-04-03 09:23:08.408 INFO 13936 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 672 ms
2019-04-03 09:23:08.705 INFO 13936 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-03 09:23:08.845 INFO 13936 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-04-03 09:23:08.877 INFO 13936 --- [ main] mil.navy.msgqueue.MsgqueueApplication : Started MsgqueueApplication in 1.391 seconds (JVM running for 1.857)
2019-04-03 09:23:14.949 INFO 13936 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-04-03 09:23:14.949 INFO 13936 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-04-03 09:23:14.952 INFO 13936 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
Received on #2 -> message A
Received on #1a -> message B
Received on #1b -> message A
Received on #2 -> message B
<HIT DO-NOTHING ENDPOINT AGAIN>
Received on #1b -> message A
Received on #2 -> message A
Received on #1a -> message B
Received on #2 -> message B

BLUF - Add "?consumer.exclusive=true" to the declaration of the destination for the JmsListener annotation.
It seems that the solution is not that complex, especially if one abandons ActiveMQ's "message groups" in favor or "exclusive consumers". The drawback to the "message groups" is that the sender has to have prior knowledge of the potential partitioning of message consumers. If the producer has this knowledge, then "message groups" are a nice solution, as the solution is somewhat independent of the consumer.
But, a similar solution can be implemented from the consumer side, by having the consumer declare "exclusive consumer" on the queue. While I did not see anything in the JmsTemplate implementation that directly supports this, it seems that Spring's JmsTemplate implementation passes the queue name to ActiveMQ, and then ActiveMQ "does the right thing" and enforces the exclusive consumer behavior.
So...
Change the following...
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";
to...
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";?consumer.exclusive=true
Once I did this, only one of the two declared receive methods were invoked, and message order was maintained in all my test runs.

Related

How to implement Retry logic in RabbitMQ?

In my project I'm setting SimpleRetryPolicy to add custom exception and RetryOperationsInterceptor which is consuming this policy.
#Bean
public SimpleRetryPolicy rejectionRetryPolicy() {
Map<Class<? extends Throwable>, Boolean> exceptionsMap = new HashMap<Class<? extends Throwable>, Boolean>();
exceptionsMap.put(DoNotRetryException.class, false);//not retriable
exceptionsMap.put(RetryException.class, true); //retriable
return new SimpleRetryPolicy(3, exceptionsMap, true);
}
#Bean
RetryOperationsInterceptor interceptor() {
return RetryInterceptorBuilder.stateless()
.retryPolicy(rejectionRetryPolicy())
.backOffOptions(2000L, 2, 3000L)
.recoverer(
new RepublishMessageRecoverer(rabbitTemplate(), "dlExchange", "dlRoutingKey"))
.build();
}
But with these configurations retry is not working for both RetryException and DoNotRetryException, where I want RetryException to be retried finite number of time and DoNotRetryException to send to DLQ
Please help with the issue, I'm attaching repo link if in case of need.
https://github.com/aviralnimbekar/RabbitMQ/tree/main/src
Your GlobalErrorHandler does its logic before the retry happens and you override there an exception with the AmqpRejectAndDontRequeueException. And looks like you do there a publish to DLX. Consider to move your GlobalErrorHandler logic to a more general ErrorHandler for the factory.setErrorHandler(); instead.
See more info in docs: https://docs.spring.io/spring-amqp/reference/html/#exception-handling
UPDATE
After removing errorHandler = "globalErrorHandler" from your #RabbitListener, I got this in logs:
2022-08-03 16:02:08.093 INFO 16896 --- [nio-8080-exec-4] c.t.r.producer.RabbitMQProducer : Message sent -> retry
2022-08-03 16:02:08.095 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : Retrying message...
2022-08-03 16:02:10.096 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : Retrying message...
2022-08-03 16:02:13.099 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : Retrying message...
2022-08-03 16:02:13.100 WARN 16896 --- [ntContainer#0-1] o.s.a.r.retry.RepublishMessageRecoverer : Republishing failed message to exchange 'dlExchange' with routing key dlRoutingKey
2022-08-03 16:02:17.736 INFO 16896 --- [nio-8080-exec-5] c.t.r.producer.RabbitMQProducer : Message sent -> 1231231
2022-08-03 16:02:17.738 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : sending into dlq...
2022-08-03 16:02:17.739 WARN 16896 --- [ntContainer#0-1] o.s.a.r.retry.RepublishMessageRecoverer : Republishing failed message to exchange 'dlExchange' with routing key dlRoutingKey
Which definitely reflects your original requirements.

Spring boot pub/sub subcriber gets removed immediately after deployment

I have a spring boot app deployed to google cloud (via automatic github build).
Unfortunately my subscriber to pub/sub is being removed immediately after deployment :( Does anyone know reason for that?
Removing {service-activator:investobotAPIs.messageReceiver.serviceActivator} as a subscriber to the 'inputMessageChannel' channel
I've created a topic in google pub/sub and a subscription:
I've added these methods:
#RestController
#EnableAsync
public class InvestobotAPIs {
...
// [START pubsub_spring_inbound_channel_adapter]
// Create a message channel for messages arriving from the subscription `sub-one`.
#Bean
public MessageChannel inputMessageChannel() {
return new PublishSubscribeChannel();
}
// Create an inbound channel adapter to listen to the subscription `sub-one` and send
// messages to the input message channel.
#Bean
public PubSubInboundChannelAdapter inboundChannelAdapter(
#Qualifier("inputMessageChannel") MessageChannel messageChannel,
PubSubTemplate pubSubTemplate) {
PubSubInboundChannelAdapter adapter =
new PubSubInboundChannelAdapter(pubSubTemplate, "fetch-gpw-sub");
adapter.setOutputChannel(messageChannel);
adapter.setAckMode(AckMode.MANUAL);
adapter.setPayloadType(String.class);
return adapter;
}
// Define what happens to the messages arriving in the message channel.
#ServiceActivator(inputChannel = "inputMessageChannel")
public void messageReceiver(
String payload,
#Header(GcpPubSubHeaders.ORIGINAL_MESSAGE) BasicAcknowledgeablePubsubMessage message) {
logger.info("Message arrived via an inbound channel adapter from fetch-gpw! Payload: " + payload);
message.ack();
}
// [END pubsub_spring_inbound_channel_adapter]
and this is my build.graddle:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.5'
// get url and download files
implementation 'commons-io:commons-io:2.6'
implementation 'org.asynchttpclient:async-http-client:2.12.3'
// parse xls files
implementation 'org.apache.poi:poi:5.0.0'
implementation 'org.apache.poi:poi-ooxml:5.0.0'
// firebase messaging and db
implementation 'com.google.firebase:firebase-admin:8.1.0'
// subscribe pub/sub topic
implementation 'com.google.cloud:spring-cloud-gcp-starter-pubsub:2.0.4'
implementation 'com.google.cloud:spring-cloud-gcp-pubsub-stream-binder:2.0.4'
implementation 'com.google.cloud:spring-cloud-gcp-dependencies:2.0.4'
}
Unfortunately when it is deployed the logs show that just after deployment is gets removed:
2021-10-20 11:51:16.190 CEST2021-10-20 09:51:16.190 INFO 1 --- [ main] o.s.i.channel.PublishSubscribeChannel : Channel 'application.inputMessageChannel' has 1 subscriber(s).
Default
2021-10-20 11:51:16.190 CEST2021-10-20 09:51:16.190 INFO 1 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean 'investobotAPIs.messageReceiver.serviceActivator'
Default
2021-10-20 11:51:16.214 CEST2021-10-20 09:51:16.214 INFO 1 --- [ main] .g.c.s.p.i.i.PubSubInboundChannelAdapter : started bean 'inboundChannelAdapter'; defined in: 'class path resource [com/miloszdobrowolski/investobotbackend/InvestobotAPIs.class]'; from source: 'com.miloszdobrowolski.investobotbackend.InvestobotAPIs.inboundChannelAdapter(org.springframework.messaging.MessageChannel,com.google.cloud.spring.pubsub.core.PubSubTemplate)'
Default
2021-10-20 11:51:16.295 CEST2021-10-20 09:51:16.294 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
Default
2021-10-20 11:51:19.076 CEST2021-10-20 09:51:19.075 INFO 1 --- [ main] c.m.i.InvestobotBackendApplication : Started InvestobotBackendApplication in 14.475 seconds (JVM running for 19.248)
Info
2021-10-20 11:51:22.386 CESTCloud Runinvestobot-backend-autobuild {#type: type.googleapis.com/google.cloud.audit.AuditLog, resourceName: namespaces/our-shield-329019/services/investobot-backend-autobuild, response: {…}, serviceName: run.googleapis.com, status: {…}}
Debug
2021-10-20 11:51:54.075 CESTContainer Sandbox: Unsupported syscall setsockopt(0x16,0x29,0x31,0x3e96fd1f3a9c,0x4,0x0). It is very likely that you can safely ignore this message and that this is not the cause of any error you might be troubleshooting. Please, refer to https://gvisor.dev/c/linux/amd64/setsockopt for more information.
Debug
2021-10-20 11:51:54.075 CESTContainer Sandbox: Unsupported syscall setsockopt(0x16,0x29,0x12,0x3e96fd1f3a9c,0x4,0x0). It is very likely that you can safely ignore this message and that this is not the cause of any error you might be troubleshooting. Please, refer to https://gvisor.dev/c/linux/amd64/setsockopt for more information.
Default
2021-10-20 11:52:59.196 CEST2021-10-20 09:52:59.195 WARN 1 --- [ault-executor-0] i.g.n.s.i.n.u.internal.MacAddressUtil : Failed to find a usable hardware address from the network interfaces; using random bytes: d1:79:21:a2:71:29:47:49
Default
2021-10-20 11:53:01.010 CEST2021-10-20 09:53:01.010 INFO 1 --- [ionShutdownHook] .g.c.s.p.i.i.PubSubInboundChannelAdapter : stopped bean 'inboundChannelAdapter'; defined in: 'class path resource [com/miloszdobrowolski/investobotbackend/InvestobotAPIs.class]'; from source: 'com.miloszdobrowolski.investobotbackend.InvestobotAPIs.inboundChannelAdapter(org.springframework.messaging.MessageChannel,com.google.cloud.spring.pubsub.core.PubSubTemplate)'
Default
2021-10-20 11:53:01.010 CEST2021-10-20 09:53:01.010 INFO 1 --- [ionShutdownHook] o.s.i.endpoint.EventDrivenConsumer : Removing {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
Default
2021-10-20 11:53:01.010 CEST2021-10-20 09:53:01.010 INFO 1 --- [ionShutdownHook] o.s.i.channel.PublishSubscribeChannel : Channel 'application.errorChannel' has 0 subscriber(s).
Default
2021-10-20 11:53:01.011 CEST2021-10-20 09:53:01.011 INFO 1 --- [ionShutdownHook] o.s.i.endpoint.EventDrivenConsumer : stopped bean '_org.springframework.integration.errorLogger'
Default
2021-10-20 11:53:01.011 CEST2021-10-20 09:53:01.011 INFO 1 --- [ionShutdownHook] o.s.i.endpoint.EventDrivenConsumer : Removing {service-activator:investobotAPIs.messageReceiver.serviceActivator} as a subscriber to the 'inputMessageChannel' channel
Default
2021-10-20 11:53:01.011 CEST2021-10-20 09:53:01.011 INFO 1 --- [ionShutdownHook] o.s.i.channel.PublishSubscribeChannel : Channel 'application.inputMessageChannel' has 0 subscriber(s).
Default
2021-10-20 11:53:01.011 CEST2021-10-20 09:53:01.011 INFO 1 --- [ionShutdownHook] o.s.i.endpoint.EventDrivenConsumer : stopped bean 'investobotAPIs.messageReceiver.serviceActivator'
I've tried to change dependencies in gradle from com.google.cloud to org.springframework.cloud (not sure how they differ)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.5'
// get url and download files
implementation 'commons-io:commons-io:2.6'
implementation 'org.asynchttpclient:async-http-client:2.12.3'
// parse xls files
implementation 'org.apache.poi:poi:5.0.0'
implementation 'org.apache.poi:poi-ooxml:5.0.0'
// firebase messaging and db
implementation 'com.google.firebase:firebase-admin:8.1.0'
// subscribe pub/sub topic
// implementation 'com.google.cloud:spring-cloud-gcp-starter-pubsub:2.0.4'
// implementation 'com.google.cloud:spring-cloud-gcp-pubsub-stream-binder:2.0.4'
// implementation 'com.google.cloud:spring-cloud-gcp-dependencies:2.0.4'
implementation 'org.springframework.cloud:spring-cloud-gcp-starter-bus-pubsub:1.2.8.RELEASE'
implementation 'org.springframework.cloud:spring-cloud-gcp-pubsub:1.2.8.RELEASE'
implementation 'org.springframework.cloud:spring-cloud-gcp-autoconfigure:1.2.8.RELEASE'
implementation 'org.springframework.integration:spring-integration-core:5.5.4'
}
But it only causes a different error during deployment:
ERROR: (gcloud.run.services.update) Cloud Run error: Container failed to start. Failed to start and then listen on the port defined by the PORT environment variable. Logs for this revision might contain more information.

Simple unit test for Apache Camel SNMP route

I'm having some trouble getting a working Camel Spring-Boot unit test written, that tests a simple SNMP route. Here is what I have so far:
SnmpRoute.kt
open class SnmpRoute(private val snmpProperties: SnmpProperties, private val repository: IPduEventRepository) : RouteBuilder() {
#Throws(Exception::class)
override fun configure() {
logger.debug("Initialising with properties [{}]", snmpProperties)
from("snmp:0.0.0.0:1161?protocol=udp&type=TRAP")
.process { exchange ->
// do stuff
}
.bean(repository, "save")
}
}
SnmpRouteTest.kt
#CamelSpringBootTest
#SpringBootApplication
#EnableAutoConfiguration
open class SnmpRouteTest : CamelTestSupport() {
object SnmpConstants {
const val SNMP_TRAP = "<snmp><entry><oid>...datadatadata...</oid><value>123456</value></entry></snmp>"
const val MOCK_SNMP_ENDPOINT = "mock:snmp"
}
#Mock
lateinit var snmpProperties: SnmpProperties
#Mock
lateinit var repository: IPduEventRepository
#InjectMocks
lateinit var snmpRoute: SnmpRoute
#EndpointInject(SnmpConstants.MOCK_SNMP_ENDPOINT)
lateinit var mock: MockEndpoint
#Before
fun setup() {
initMocks(this)
}
#Throws(Exception::class)
override fun createRouteBuilder(): RouteBuilder {
return snmpRoute
}
#Test
#Throws(Exception::class)
fun `Test SNMP endpoint`() {
mock.expectedBodiesReceived(SnmpConstants.SNMP_TRAP)
template.sendBody(SnmpConstants.MOCK_SNMP_ENDPOINT,
SnmpConstants.SNMP_TRAP)
mock.assertIsSatisfied()
verify(repository).save(PduEvent(1234, PDU.TRAP))
}
}
However, when I run this test, it fails as the repository mock never has any interactions:
Wanted but not invoked:
repository.save(
PduEvent(requestId=1234, type=-89)
);
-> at org.meanwhile.in.hell.camel.snmp.route.SnmpRouteTest.Test SNMP endpoint(SnmpRouteTest.kt:61)
Actually, there were zero interactions with this mock.
Can someone help me understand why this isn't interacting correctly? When run manually, this works and saves as expected.
Now I see what is going on here!
Your RouteBuilder under test has a from("snmp"). If you wish to deliver a mock message there for testing, you need to swap the snmp: component with something like a direct: or seda: component, during test execution.
Your current test is delivering a message to a Mock endpoint and verifying if it was received there. It does not interact with the real route builder. That's why your mock endpoint assertions do passed but Mockito.verify() failed.
TL;DR
Presuming that you are using Apache Camel 3.x, here is how to do it. I'm not fluent in Kotlin so, I'll show how to do that in Java.
AdviceWithRouteBuilder.adviceWith(context, "route-id", routeBuilder -> {
routeBuilder.replaceFromWith("direct:snmp-from"); //Replaces the from part of the route `route-id` with a direct component
});
You need to modify your route builder code to assign an ID to the route (say, route-id)
Replace the SNMP component at the start of the route with a direct component
Deliver test messages to the direct: component instead of SNMP
TL;DR ends.
Full blown sample code below.
PojoRepo.java
#Component
public class PojoRepo {
public void save(String body){
System.out.println(body);
}
}
SNMPDummyRoute.java
#Component
public class SNMPDummyRoute extends RouteBuilder {
PojoRepo pojoRepo;
public SNMPDummyRoute(PojoRepo pojoRepo) {
this.pojoRepo = pojoRepo;
}
#Override
public void configure() throws Exception {
from("snmp:0.0.0.0:1161?protocol=udp&type=TRAP")
.id("snmp-route")
.process(exchange -> {
exchange.getMessage().setBody(String.format("Saw message [%s]", exchange.getIn().getBody()));
})
.to("log:snmp-log")
.bean(pojoRepo, "save");
}
}
SNMPDummyRoteTest.java
Note: This class uses CamelSpringBootRunner instead of extending CamelTestSupport, but the core idea is same.
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#DisableJmx(false)
#MockEndpoints("log:*")
public class SNMPDummyRouteTest {
#MockBean
PojoRepo repo;
#EndpointInject("mock:log:snmp-log")
MockEndpoint mockEndpoint;
#Produce
ProducerTemplate testTemplate;
#Autowired
CamelContext camelContext;
#Test
public void testRoute() throws Exception {
AdviceWithRouteBuilder.adviceWith(camelContext,"snmp-route",routeBuilder -> {
routeBuilder.replaceFromWith("direct:snmp-from");
});
testTemplate.sendBody("direct:snmp-from","One");
testTemplate.sendBody("direct:snmp-from","Two");
mockEndpoint.expectedMinimumMessageCount(2);
mockEndpoint.setAssertPeriod(2_000L);
mockEndpoint.assertIsSatisfied();
Mockito.verify(repo, Mockito.atLeast(2)).save(anyString());
}
}
Logs from test run below. Take a closer look at the XML piece where the SNMP endpoint gets swapped in with a direct component.
2019-11-12 20:52:57.126 INFO 32560 --- [ main] o.a.c.component.snmp.SnmpTrapConsumer : Starting trap consumer on udp:0.0.0.0/1161
2019-11-12 20:52:58.363 INFO 32560 --- [ main] o.a.c.component.snmp.SnmpTrapConsumer : Started trap consumer on udp:0.0.0.0/1161 using udp protocol
2019-11-12 20:52:58.364 INFO 32560 --- [ main] o.a.c.s.boot.SpringBootCamelContext : Route: snmp-route started and consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:58.368 INFO 32560 --- [ main] o.a.c.s.boot.SpringBootCamelContext : Total 1 routes, of which 1 are started
2019-11-12 20:52:58.370 INFO 32560 --- [ main] o.a.c.s.boot.SpringBootCamelContext : Apache Camel 3.0.0-M4 (CamelContext: MyCamel) started in 2.645 seconds
2019-11-12 20:52:59.670 INFO 32560 --- [ main] o.a.c.i.engine.DefaultShutdownStrategy : Starting to graceful shutdown 1 routes (timeout 10 seconds)
2019-11-12 20:52:59.680 INFO 32560 --- [ - ShutdownTask] o.a.c.component.snmp.SnmpTrapConsumer : Stopped trap consumer on udp:0.0.0.0/1161
2019-11-12 20:52:59.683 INFO 32560 --- [ - ShutdownTask] o.a.c.i.engine.DefaultShutdownStrategy : Route: snmp-route shutdown complete, was consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:59.684 INFO 32560 --- [ main] o.a.c.i.engine.DefaultShutdownStrategy : Graceful shutdown of 1 routes completed in 0 seconds
2019-11-12 20:52:59.687 INFO 32560 --- [ main] o.a.c.s.boot.SpringBootCamelContext : Route: snmp-route is stopped, was consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:59.689 INFO 32560 --- [ main] o.a.c.s.boot.SpringBootCamelContext : Route: snmp-route is shutdown and removed, was consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:59.691 INFO 32560 --- [ main] o.apache.camel.builder.AdviceWithTasks : AdviceWith replace input from [snmp:0.0.0.0:1161?protocol=udp&type=TRAP] --> [direct:snmp-from]
2019-11-12 20:52:59.692 INFO 32560 --- [ main] org.apache.camel.reifier.RouteReifier : AdviceWith route after: Route(snmp-route)[From[direct:snmp-from] -> [process[Processor#0x589dfa6f], To[log:snmp-log], Bean[org.foo.bar.POJORepo$MockitoMock$868728200]]]
2019-11-12 20:52:59.700 INFO 32560 --- [ main] org.apache.camel.reifier.RouteReifier : Adviced route before/after as XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="snmp-route">
<from uri="snmp:0.0.0.0:1161?protocol=udp&type=TRAP"/>
<process id="process1"/>
<to id="to1" uri="log:snmp-log"/>
<bean id="bean1" method="save"/>
</route>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="snmp-route">
<from uri="direct:snmp-from"/>
<process id="process1"/>
<to id="to1" uri="log:snmp-log"/>
<bean id="bean1" method="save"/>
</route>
2019-11-12 20:52:59.734 INFO 32560 --- [ main] .i.e.InterceptSendToMockEndpointStrategy : Adviced endpoint [log://snmp-log] with mock endpoint [mock:log:snmp-log]
2019-11-12 20:52:59.755 INFO 32560 --- [ main] o.a.c.s.boot.SpringBootCamelContext : Route: snmp-route started and consuming from: direct://snmp-from
2019-11-12 20:52:59.834 INFO 32560 --- [ main] snmp-log : Exchange[ExchangePattern: InOnly, BodyType: String, Body: Saw message [One]]
2019-11-12 20:52:59.899 INFO 32560 --- [ main] snmp-log : Exchange[ExchangePattern: InOnly, BodyType: String, Body: Saw message [Two]]
2019-11-12 20:52:59.900 INFO 32560 --- [ main] o.a.camel.component.mock.MockEndpoint : Asserting: mock://log:snmp-log is satisfied
2019-11-12 20:53:01.903 INFO 32560 --- [ main] o.a.camel.component.mock.MockEndpoint : Re-asserting: mock://log:snmp-log is satisfied after 2000 millis
2019-11-12 20:53:01.992 INFO 32560 --- [ main] o.a.c.s.boot.SpringBootCamelContext : Apache Camel 3.0.0-M4 (CamelContext: MyCamel) is shutting down
2019-11-12 20:53:01.993 INFO 32560 --- [ main] o.a.c.i.engine.DefaultShutdownStrategy : Starting to graceful shutdown 1 routes (timeout 10 seconds)
2019-11-12 20:53:01.996 INFO 32560 --- [ - ShutdownTask] o.a.c.i.engine.DefaultShutdownStrategy : Route: snmp-route shutdown complete, was consuming from: direct://snmp-from
2019-11-12 20:53:01.996 INFO 32560 --- [ main] o.a.c.i.engine.DefaultShutdownStrategy : Graceful shutdown of 1 routes completed in 0 seconds

AWS Lambda - Spring boot is not handling the request

I am trying to run spring boot application as serverless in AWS lambda and I am getting below exception while calling lambda function. Spring boot application successfully ran but it seems that it is going to fail to map the request
2018-09-25 06:11:50.717 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2018-09-25 06:11:50.823 INFO 1 --- [ main] **my.service.Application : Started Application in 7.405 seconds (JVM running for 8.939)**
START RequestId: decfc13c-c089-11e8-bacd-a37f1ba65629 Version: $LATEST
2018-09-25 06:11:50.994 ERROR 1 --- [ main] **c.a.s.p.i.s.AwsProxyHttpServletRequest : Called set character encoding to UTF-8 on a request without a content type. Character encoding will not be set
2018-09-25 06:11:51.175 ERROR 1 --- [ main] o.s.boot.web.support.ErrorPageFilter : Forwarding to error page from request [/] due to exception [null]**
java.lang.NullPointerException: null
at com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest.getRemoteAddr(AwsProxyHttpServletRequest.java:575) ~[task/:na]
at org.springframework.web.servlet.FrameworkServlet.publishRequestHandledEvent(FrameworkServlet.java:1075) ~[task/:na]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[task/:na]
.........
2018-09-25 06:11:51.535 ERROR 1 --- [ main] s.p.i.s.AwsLambdaServletContainerHandler : Could not forward request
This is my StreamLambdaHandler java file.
public class StreamLambdaHandler implements RequestStreamHandler {
private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
} catch (ContainerInitializationException e) {
throw new RuntimeException("Could not initialize Spring Boot application", e);
}
}
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
handler.proxyStream(inputStream, outputStream, context);
outputStream.close();
}
}
Looks like you might be hitting https://github.com/awslabs/aws-serverless-java-container/issues/172. According to the ticket, the fix will be available as part of the upcoming 1.2 release.

CamelContextStartedEvent called twice

The CamelContextStartedEvent is called twice for the same camel context (camel-1). The issue might be the way I register the EventNotifier. You can reproduce the issue with Spring Initializr with Spring Boot 1.5.14, Spring Boot Camel Starter 2.21.1 and Spring Boot Web Starter.
See the logs:
2018-07-06 11:04:41.104 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : Apache Camel 2.21.1 (CamelContext: camel-1) is starting
2018-07-06 11:04:41.106 INFO 19092 --- [ main] o.a.c.m.ManagedManagementStrategy : JMX is enabled
2018-07-06 11:04:41.191 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2018-07-06 11:04:41.193 INFO 19092 --- [ main] o.a.camel.spring.boot.RoutesCollector : Starting CamelMainRunController to ensure the main thread keeps running
2018-07-06 11:04:41.193 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : Total 0 routes, of which 0 are started
2018-07-06 11:04:41.194 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : Apache Camel 2.21.1 (CamelContext: camel-1) started in 0.090 seconds
2018-07-06 11:04:41.195 INFO 19092 --- [ main] c.e.bug.service.StartupEventNotifier : CamelContextStartedEvent for SpringCamelContext(camel-1) with spring id application:11223
2018-07-06 11:04:41.195 INFO 19092 --- [ main] c.e.bug.service.StartupEventNotifier : CamelContextStartedEvent for SpringCamelContext(camel-1) with spring id application:11223
2018-07-06 11:04:41.216 INFO 19092 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 11223 (http)
2018-07-06 11:04:41.221 INFO 19092 --- [ main] com.example.bug.BugApplication : Started BugApplication in 4.684 seconds (JVM running for 6.773)
The service that initializes the EventNotifier:
#Service
public class SchedulerService {
private final CamelContext camelContext;
private final StartupEventNotifier startupEventNotifier;
public SchedulerService(CamelContext camelContext, StartupEventNotifier startupEventNotifier) {
this.camelContext = camelContext;
this.startupEventNotifier = startupEventNotifier;
}
#PostConstruct
public void init() {
camelContext.getManagementStrategy().addEventNotifier(startupEventNotifier);
}
}
The EventNotifier:
#Component
public class StartupEventNotifier extends EventNotifierSupport {
private static final Logger logger = LoggerFactory.getLogger(StartupEventNotifier.class);
#Override
public void notify(EventObject event) throws Exception {
if (event instanceof CamelContextStartedEvent) {
logger.info("CamelContextStartedEvent for {}", event.getSource());
}
}
#Override
public boolean isEnabled(EventObject event) {
if (event instanceof CamelContextStartedEvent) {
return true;
}
return false;
}
}
application.yml:
camel:
springboot:
main-run-controller: true
server:
port: 11223
It is called twice, because it is registered twice. Once by you and once by Apache Camel. EventNotifier is registered automatically, if is found in Registry. Since your StartupEventNotifier is annotated as Component, it is part of Registry and Apache Camel registered it during CamelContext startup (You can see it in CamelAutoConfiguration line 424).
You have four options:
Remove your custom registration from SchedulerService.
Remove #Component annotation from StartupEventNotifier and register it with with camelContext.getManagementStrategy().addEventNotifier(new StartupEventNotifier())
Add duplicity check to your SchedulerService. Something like:
if (!context.getManagementStrategy().getEventNotifiers().contains(startupEventNotifier)){
context.getManagementStrategy().addEventNotifier(startupEventNotifier);
}
Register EventNotifier in #PostConstruct of RouteBuilder. It will be registered before automatic discovery is started and then it will be skipped in CamelAutoConfiguration (See line 422)

Resources