I have a camel route as follows which is transacted.
from("jms:queue:start")
.transacted()
.bean(new FirstDummyBean(), "setBodyToHello")
.bean(new SecondDummyBean(), "setBodyToWorld")
.to("jms:queue:end")
The bean methods due as their name suggests, set body to "Hello" and "World" respectively.
I also have a onException clause setup as well as follows:
onException(Exception.class)
.useOriginalMessage()
.handled(true)
.to("jms:queue:deadletter")
.markRollbackOnlyLast();
Assume, I drop a message on queue "start" with body as "test message". After successfully processing in FirstDummyBean, I throw a RuntimeException in SecondDummyBean.
I was expecting to the see the actual message or (the original message contents intact ie "test message") being sent to my dead letter queue.
However the contents of the message on deadletter queue are "Hello".
Why is this happening?..
I am using apache camel 2.10.0.
Also can anyone provide more information on how I can use both errorhandler and onexception clause together.
The document says :
If you have marked a route as transacted using the transacted DSL then Camel
will automatic use a TransactionErrorHandler. It will try to lookup the global/per
route configured error handler and use it if its a TransactionErrorHandlerBuilder
instance. If not Camel will automatic create a temporary TransactionErrorHandler that
overrules the default error handler. This is convention over configuration.
Example of how to use transactionerrorhandler with JavaDSL would be great.
I've seen this in non-transaction examples and it appears that useOriginalMessage() does use the original exchange, but if you've modified any objects that this references then you still get the modifications. It doesn't appear that useOriginalMessage goes back to the queue to get the original data.
Example code to show problem
The code below includes a set of route to demonstrate the problem. The timed route sends an ArrayList containing the String "Test message" to a queue read by a second route. This second route passes the message to ModifyBody which changes the content of the list. Next the message goes to TriggerException with throws a RuntimeException. This is handled by the onException route, which despite using useOriginalMessage is passed the updated body.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.camel.spring.SpringRouteBuilder;
import org.springframework.stereotype.Component;
#Component
public class TimedRoute extends SpringRouteBuilder {
private static final String QUEUE = "jms:a.queue";
private static final String QUEUE2 = "jms:another.queue";
// The message that will be sent on the route
private static final ArrayList<String> payLoad = new ArrayList<String>(
Arrays.asList("test message"));
public static class ModifyBody {
public List<String> modify(final List<String> list) {
final List<String> returnList = list;
returnList.clear();
returnList.add("Hello");
return returnList;
}
}
public static class TriggerException {
public List<String> trigger(final List<String> list) {
throw new RuntimeException("Deliberate");
}
}
#Override
public void configure() throws Exception {
//#formatter:off
onException(Exception.class)
.useOriginalMessage()
.log("Exception: ${body}")
.handled(true)
.setHeader("exception", constant("exception"))
.to(QUEUE2);
// Timed route to send the original message
from("timer://foo?period=60000")
.setBody().constant(payLoad)
.to(QUEUE);
// Initial processing route - this modifies the body.
from(QUEUE)
.log("queue ${body}")
.bean(new ModifyBody())
.log("after firstDummyBean: ${body}")
.bean(new TriggerException())
.stop();
// Messages are send here by the exception handler.
from(QUEUE2)
.log("queue2: ${body}")
.stop();
//#formatter:on
}
}
Workaround
If you replace ModifyBody with the code below then the original message is seen in the exception handling route.
public static class ModifyBody {
public List<String> modify(final List<String> list) {
final List<String> returnList = new ArrayList<String>(list);
returnList.clear();
returnList.add("Hello");
return returnList;
}
}
By changing the body to a new list the original Exchange can be left unmodified.
Doing a general solution is awkward as the mechanism for copying will depend on the objects that you have in flight. You might find that you can extend the RouteBuilder class to give yourself some custom DSL that copies your objects.
Related
I've got a fairly simple route:
PerfStubRouteBuilder.java
public class PerfStubRouteBuilder extends SpringRouteBuilder {
/* (non-Javadoc)
* #see org.apache.camel.builder.RouteBuilder#configure()
*/
#Override
public void configure() throws Exception {
from("direct:test-aces-input")
.log("Creating test Accident Number header")
.setHeader("AccidentNumber", simple("AB999999999"))
.log("Test Accident Number header created : ${header.AccidentNumber}")
.end();
}
}
and I'm testing it as follows:
PerfStubRouteBuilderTest.java
public class PerfStubRouteBuilderTest extends CamelTestSupport {
#Produce(uri = "direct:test-aces-input")
ProducerTemplate template;
PerfStubRouteBuilder route = new PerfStubRouteBuilder();
Exchange exch = new DefaultExchange(context);
#Override
protected RouteBuilder createRouteBuilder() {
return route;
}
#Test
public void test_PerfStubRouteBuilder_happyPath_addsAccidentNumberHeaderToExchange() throws Exception {
startCamelContext();
NotifyBuilder notify = new NotifyBuilder(context).from("direct:test-aces-input").whenDone(1).filter(header("AccidentNumber").isEqualTo("AB999999999")).create();
template.send(exch);
assertTrue(notify.matches(10, TimeUnit.SECONDS));
stopCamelContext();
}
}
assertTrue always come back as false, so I suspect I'm not using the NotifyBuilder correctly although at this point I can't be certain. Essentially I want to check that an exchange makes it through the route and the header specified in the actual route is added to the exchange. What I want to happen is for a match to occur if an exchange with that header value combo makes it to the end of the route, hence the filter step. I want to avoid adding an endpoint at the end of the route, for example, via AdviceWith given how small and simple the route itself is , it seems a bit heavyweight to start adding in mock endpoints for such a minor test
UPDATE:
Tried removing the filter portion from the expression, leaving the NotifyBuilder as NotifyBuilder notify = new NotifyBuilder(context).from("direct:test-aces-input").whenDone(1).create();
, the test still fails
Create the exchange from the endpoint in the test like
Endpoint endpoint = context.getEndpoint("direct:test-aces-input");
Exchange exchange = endpoint.createExchange();
template.send(exchange);
or just directly use sendBody
tempalte.sendBody("")
while doing logs in the multiple module of vertx, it is a basic requirement that we should be able to correlate all the logs for a single request.
as vertx being asynchronous what will be the best place to keep logid, conversationid, eventid.
any solution or patterns we can implement?
In a thread based system, you current context is held by the current thread, thus MDC or any ThreadLocal would do.
In an actor based system such as Vertx, your context is the message, thus you have to add a correlation ID to every message you send.
For any handler/callback you have to pass it as method argument or reference a final method variable.
For sending messages over the event bus, you could either wrap your payload in a JsonObject and add the correlation id to the wrapper object
vertx.eventBus().send("someAddr",
new JsonObject().put("correlationId", "someId")
.put("payload", yourPayload));
or you could add the correlation id as a header using the DeliveryOption
//send
vertx.eventBus().send("someAddr", "someMsg",
new DeliveryOptions().addHeader("correlationId", "someId"));
//receive
vertx.eventBus().consumer("someAddr", msg -> {
String correlationId = msg.headers().get("correlationId");
...
});
There are also more sophisticated options possible, such as using an Interceptor on the eventbus, which Emanuel Idi used to implement Zipkin support for Vert.x, https://github.com/emmanuelidi/vertx-zipkin, but I'm not sure about the current status of this integration.
There's a surprising lack of good answers published about this, which is odd, given how easy it is.
Assuming you set the correlationId in your MDC context on receipt of a request or message, the simplest way I've found to propagate it is to use interceptors to pass the value between contexts:
vertx.eventBus()
.addInboundInterceptor(deliveryContext -> {
MultiMap headers = deliveryContext.message().headers();
if (headers.contains("correlationId")) {
MDC.put("correlationId", headers.get("correlationId"));
deliveryContext.next();
}
})
.addOutboundInterceptor(deliveryContext -> {
deliveryContext.message().headers().add("correlationId", MDC.get("correlationId"));
deliveryContext.next();
});
If by multiple module you mean multiple verticles running on the same Vertx instance, you should be able to use a normal logging library such as SLF4J, Log4J, JUL, etc. You can then keep the logs in a directory of your choice, e.g. /var/logs/appName.
If, however, you mean how do you correlate logs between multiple instances of Vertx, then I'd suggest looking into GrayLog or similar applications for distributed/centralised logging. If you use a unique ID per request, you can pass that around and use it in the logs. Or depending on your authorization system, if you use unique tokens per request you can log those. The centralised logging system can be used to aggregate and filter logs based on that information.
The interceptor example presented by Clive Evans works great. I added a more details example showing how this might work:
import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.time.Duration;
import java.util.UUID;
public class PublisherSubscriberInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(PublisherSubscriberInterceptor.class);
public static final String ADRESS = "sender.address";
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
createInterceptors(vertx);
vertx.deployVerticle(new Publisher());
vertx.deployVerticle(new Subscriber1());
//For our example lets deploy subscriber2 2 times.
vertx.deployVerticle(Subscriber2.class.getName(), new DeploymentOptions().setInstances(2));
}
private static void createInterceptors(Vertx vertx) {
vertx.eventBus()
.addInboundInterceptor(deliveryContext -> {
MultiMap headers = deliveryContext.message().headers();
if (headers.contains("myId")) {
MDC.put("myId", headers.get("myId"));
deliveryContext.next();
}
})
.addOutboundInterceptor(deliveryContext -> {
deliveryContext.message().headers().add("myId", MDC.get("myId"));
deliveryContext.next();
});
}
public static class Publisher extends AbstractVerticle {
#Override
public void start(Promise<Void> startPromise) throws Exception {
startPromise.complete();
vertx.setPeriodic(Duration.ofSeconds(5).toMillis(), id -> {
MDC.put("myId", UUID.randomUUID().toString());
vertx.eventBus().publish(Publish.class.getName(), "A message for all");
});
}
}
public static class Subscriber1 extends AbstractVerticle {
private static final Logger LOG = LoggerFactory.getLogger(Subscriber1.class);
#Override
public void start(Promise<Void> startPromise) throws Exception {
startPromise.complete();
vertx.eventBus().consumer(Publish.class.getName(), message-> {
LOG.debug("Subscriber1 Received: {}", message.body());
});
}
}
public static class Subscriber2 extends AbstractVerticle {
private static final Logger LOG = LoggerFactory.getLogger(Subscriber2.class);
#Override
public void start(Promise<Void> startPromise) throws Exception {
startPromise.complete();
vertx.eventBus().consumer(Publish.class.getName(), message-> {
LOG.debug("Subscriber2 Received: {}", message.body());
});
}
}
}
you can see the log example for publishing 2 messages:
13:37:14.315 [vert.x-eventloop-thread-3][myId=a2f0584c-9d4e-48a8-a724-a24ea12f7d80] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
13:37:14.315 [vert.x-eventloop-thread-1][myId=a2f0584c-9d4e-48a8-a724-a24ea12f7d80] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber1 - Subscriber1 Received: A message for all
13:37:14.315 [vert.x-eventloop-thread-4][myId=a2f0584c-9d4e-48a8-a724-a24ea12f7d80] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
13:37:19.295 [vert.x-eventloop-thread-1][myId=63b5839e-3b0b-43a5-b379-92bd1466b870] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber1 - Subscriber1 Received: A message for all
13:37:19.295 [vert.x-eventloop-thread-3][myId=63b5839e-3b0b-43a5-b379-92bd1466b870] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
13:37:19.295 [vert.x-eventloop-thread-4][myId=63b5839e-3b0b-43a5-b379-92bd1466b870] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
Surprised no one mentioned this Reactiverse project Contextual logging for Eclipse Vert.x
From their page:
In traditional Java development models (e.g. Spring or Java EE), the
server implements a one thread per request design. As a consequence,
it is possible to store contextual data in ThreadLocal variables and
use it when logging. Both logback and log4j2 name this Mapped
Diagnostic Context (MDC).
Vert.x implements the reactor pattern. In practice, this means many
concurrent requests can be handled by the same thread, thus preventing
usage of ThreadLocals to store contextual data.
This project uses an alternative storage method for contextual data
and makes it possible to have MDC logging in Vert.x applications.
Use vertx-sync and a ThreadLocal for the correlation ID. (i.e., a "FiberLocal"). Works great for me.
This seems like an incredibly simple problem but I've tried everything I can think of. Basically I have a timer route that sends its message to a bunch of different beans. Those beans set a property on the exchange (I've also tried a header on the message) and I want the exchange output from all of those beans to be directed to a filter (which checks for the property or header) and then optionally another endpoint. Something like this:
---> Bean A ---
/ \
timer --> multicast ------> Bean B ------> end --> filter --> endpoint
\ /
---> Bean C ---
Currently the route looks like this, and it works for multicasting to the beans:
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("bean:beanA", "bean:beanB", "bean:beanC");
Here are the some of the solutions I've tried:
Solution 1
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("bean:beanA", "bean:beanB", "bean:beanC")
.filter(new myPredicate())
.to("myOptionalEndpoint");
This puts the filter in parallel with the beans instead of after them.
Solution 2
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("bean:beanA", "bean:beanB", "bean:beanC")
.end()
.filter(new myPredicate())
.to("myOptionalEndpoint");
Does the beans in parallel and then does the filter. However, the properties/headers are not set. It seems like the exchange is fresh off the timer and is not the one that went through the beans...
Edit: I tried setting the body and in fact the message that arrives at the filter has no body. I can't imagine Camel would somehow shuck the payload of the message so I have to assume that this exchange is a new one from the timer, not one that went through the beans. However, it happens after the beans are done.
Solution 3
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.beanRef("beanA").to("direct:temp")
.beanRef("beanB").to("direct:temp")
.beanRef("beanC").to("direct:temp")
.end()
from("direct:temp")
.filter(new myPredicate())
.to("myOptionalEndpoint");
Messages reach the filter as expected but the properties/headers that I set are gone so no messages pass the filter.
Edit: The body is gone here too so clearly I am not getting the same exchange that is coming from the beans...
To clarify, I am looking for a solution where the a single exchange from the timer is multicasted to each bean (so now we have 3 exchanges) and each of these 3 is then sent to the filter.
Can anybody help me figure out how to build this route?
You need to use an aggregation strategy in order to aggregate all the results into one.
Below is a great example from http://javarticles.com/2015/05/apache-camel-multicast-examples.html (See the Multicast with a Custom Aggregation Strategy section)
public class CamelMulticastAggregationExample {
public static final void main(String[] args) throws Exception {
JndiContext jndiContext = new JndiContext();
jndiContext.bind("myBean", new MyBean());
CamelContext camelContext = new DefaultCamelContext(jndiContext);
try {
camelContext.addRoutes(new RouteBuilder() {
public void configure() {
from("direct:start")
.multicast()
.aggregationStrategy(new JoinReplyAggregationStrategy())
.to("direct:a", "direct:b", "direct:c")
.end()
.to("stream:out");
from("direct:a")
.to("bean:myBean?method=addFirst");
from("direct:b")
.to("bean:myBean?method=addSecond");
from("direct:c")
.to("bean:myBean?method=addThird");
}
});
ProducerTemplate template = camelContext.createProducerTemplate();
camelContext.start();
template.sendBody("direct:start", "Multicast");
} finally {
camelContext.stop();
}
}
}
where JoinReplyAggregationStrategy class looks as follows
public class JoinReplyAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange exchange1, Exchange exchange2) {
if (exchange1 == null) {
return exchange2;
} else {
String body1 = exchange1.getIn().getBody(String.class);
String body2 = exchange2.getIn().getBody(String.class);
String merged = (body1 == null) ? body2 : body1 + "," + body2;
exchange1.getIn().setBody(merged);
return exchange1;
}
}
}
UPDATE In your case, your aggregation strategy might be to gather all of your exchanges together as follows:
public class ListAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
Message newIn = newExchange.getIn();
Object newBody = newIn.getBody();
List list = null;
if (oldExchange == null) {
list = new ArrayList();
list.add(newBody);
newIn.setBody(list);
return newExchange;
} else {
Message in = oldExchange.getIn();
list = in.getBody(List.class);
list.add(newBody);
return oldExchange;
}
}
}
Use scatter gather EIP instead of multicast !
Here is the solution, inspired by Kalman's:
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("direct:a", "direct:b", "direct:c")
.end()
from("direct:a").beanRef("beanA").to("direct:temp")
from("direct:b").beanRef("beanB").to("direct:temp")
from("direct:c").beanRef("beanC").to("direct:temp")
from("direct:temp")
.filter(new myPredicate())
.to("myOptionalEndpoint");
This was a more complicated solution that I was expecting. There must be a more elegant way to achieve this but the above solution works. Obviously use different names than a, b, c and temp though...
Functionally I wish to check a URL is active before I consume from a JMS (WMQ) endpoint.
If the URL cannot be reached or a server error, then I do not want to pick up from the queue. So I want to keep trying (with unlimited retries) the URL via a polling consumer. So as soon as it is available I can pick up from JMS.
I have a RouteBuilder that is set up with a direct endpoint, that is configured to run a Processor that will ping a service.
So:
public class PingRoute extends RouteBuilder {
#Override
public void configureCamel() {
from("direct:pingRoute").routeId(PingRoute.class.getSimpleName())
.process(new PingProcessor(url))
.to("log://PingRoute?showAll=true");
}
}
In another route I am setting up my timer:
#Override
public void configureCamel() {
from(timerEndpoint).beanRef(PollingConsumerBean.class.getSimpleName(), "checkPingRoute");
...
}
And with the PollingConsumerBean I am attempting to receive the body via a consumer:
public void checkPingRoute(){
// loop to check the consumer. Check we can carry on with the pick up from the JMS queue.
while(true){
Boolean pingAvailable = consumer.receiveBody("direct:pingRoute", Boolean.class);
...
}
I add the route to the context and use a producer to send:
context.addRoutes(new PingRoute());
context.start();
producer.sendBody(TimerPollingRoute.TIMER_POLLING_ROUTE_ENDPOINT, "a body");
And I get the following IllegalArgumentException:
Cannot add a 2nd consumer to the same endpoint. Endpoint Endpoint[direct://pingRoute] only allows one consumer.
Is there a way to setup the direct route as a polling consumer?
Business logic is not quite clear, unfortunately. As I understand it - you need to wait for a response from the service. IMHO you have to use Content Enricher EIP http://camel.apache.org/content-enricher.html . pollEnrich is what you need at timer route.
.pollEnrich("direct:waitForResponce", -1) or
.pollEnrich("seda:waitForResponce", -1)
public class PingRoute extends RouteBuilder {
#Override
public void configureCamel() {
from("direct:pingRoute").routeId(PingRoute.class.getSimpleName())
.process(new PingProcessor(url))
.choice().when(body())
.to("log://PingRoute?showAll=true")
.to("direct:waitForResponce")
.otherwise()
.to("direct:pingRoute")
.end();
}
};
timer:
#Override
public void configureCamel() {
from(timerEndpoint)
.inOnly("direct:pingRoute")
.pollEnrich("direct:waitForResponce", -1)
...
}
Based on the OP's clarification of their use case, they have several problems to solve:
Consume the message from the JMS queue if, and only if, the ping to the URL is positive.
If the URL is unresponsive, the JMS message should not disappear from the queue and a retry must take place until the URL becomes responsive again, in which case the message will be ultimately consumed.
The OP has not specified if the amount of retries is limited or unlimited.
Based on this problem scenario, I suggest a redesign of their solution that leverages ActiveMQ retries, broker-side redelivery and JMS transactions in Camel to:
Return the message to the queue if the URL ping failed (via a transaction rollback).
Ensure that the message is not lost (by using JMS persistence and broker-side redeliveries, AMQ will durably schedule the retry cycle).
Be able to specify a sophisticated retry cycle per message, e.g. with exponential backoffs, maximum retries, etc.
Optionally sending the message to a Dead Letter Queue if the retry cycle was exhausted without a positive result, so that some other (possibly manual) action can be planned.
Now, implementation-wise:
from("activemq:queue:abc?transacted=true") // (1)
.to("http4://host.endpoint.com/foo?method=GET") // (2) (3)
.process(new HandleSuccess()); // (4)
Comments:
Note the transacted flag.
If the HTTP invocation fails, the HTTP4 endpoint will raise an Exception.
Since there are no configured exception handlers, Camel will propagate the exception to the consumer endpoint (activemq) which will rollback the transaction.
If the invocation succeeded, the flow will continue and the exchange body will now contain the payload returned by the HTTP server and you can handle it in whichever way you wish. Here I'm using a processor.
Next, what's important is that you configure the redelivery policy in ActiveMQ, as well as enable broker-side redeliveries. You do that in your activemq.xml configuration file:
<plugins>
<redeliveryPlugin fallbackToDeadLetter="true" sendToDlqIfMaxRetriesExceeded="true">
<redeliveryPolicyMap>
<redeliveryPolicyMap>
<redeliveryPolicyEntries>
<redeliveryPolicy queue="my.queue"
initialRedeliveryDelay="30000"
maximumRedeliveries="17"
maximumRedeliveryDelay="259200000"
redeliveryDelay="30000"
useExponentialBackOff="true"
backOffMultiplier="2" />
</redeliveryPolicyEntries>
</redeliveryPolicyMap>
</redeliveryPolicyMap>
</redeliveryPlugin>
</plugins>
And make sure that the scheduler support is enabled in the top-level <broker /> element:
<broker xmlns="http://activemq.apache.org/schema/core"
brokerName="mybroker"
schedulerSupport="true">
...
</broker>
I hope that helps.
EDIT 1: OP is using IBM WebSphere MQ as a broker, I missed that. You could use a JMS QueueBrowser to peek at messages and try their corresponding URLs before actually consuming a message, but it is not possible to selectively consume an individual message – that's not what MOM (messaging-oriented middleware) is about.
So I insist that you should explore JMS transactions, but rather than leaving it up to the broker to redeliver the message, you can start the pinging cycle to the URL within the TX body itself. With regards to Camel, you could implement it as follows:
from("jms:queue:myqueue?transacted=true")
.bean(new UrlPinger());
UrlPinger.java:
public class UrlPinger {
#EndpointInject
private ProducerTemplate template;
private Pattern pattern = Pattern.compile("^(http(?:s)?)\\:");
#Handler
public void pingUrl(#Body String url, CamelContext context) throws InterruptedException {
// Replace http(s): with http(s)4: to use the Camel HTTP4 endpoint.
Matcher m = pattern.matcher(url);
if (m.matches()) {
url = m.replaceFirst(m.group(1) + "4:");
}
// Try forever until the status code is 200.
while (getStatusCode(url, context) != 200) {
Thread.sleep(5000);
}
}
private int getStatusCode(String url, CamelContext context) {
Exchange response = template.request(url + "?method=GET&throwExceptionOnFailure=false", new Processor() {
#Override public void process(Exchange exchange) throws Exception {
// No body since this is a GET request.
exchange.getIn().getBody(null);
}
});
return response.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
}
}
Notes:
Note the throwExceptionOnFailure=false option. An Exception will not be raised, therefore the loop will execute until the condition is true.
Inside the bean, I'm looping forever until the HTTP status is 200. Of course, your logic will be different.
Between attempt and attempt, I'm sleeping 5000ms.
I'm assuming the URL to ping is in the body of the incoming JMS message. I'm replacing the leading http(s): with http(s)4: in order to use the Camel HTTP4 endpoint.
Performing the pinging inside the TX guarantees that the message will only be consumed once the ping condition is true (in this case HTTP status == 200).
You might want to introduce a desist condition (you don't want to keep trying forever). Maybe introduce some backoff to not overwhelm the other party.
If either Camel or the broker goes down within a retry cycle, the message will be automatically rolled back.
Take into account that JMS transactions are Session-bound, so if you want to start many concurrent consumers (concurrentConsumers JMS endpoint option), you'll need to set cacheLevelName=CACHE_NONE for each thread to use a different JMS Session.
I am having a bit of difficulty figuring out exactly what you want to do, but it appears to me that you want to consume data from an endpoint on an interval. For this the best pattern is a polling consumer: http://camel.apache.org/polling-consumer.html
The error you are currently receiving is because you have two consumers both trying to read from the "direct://pingRoute" If this was intended you could change the direct to a seda://pingRoute so its an in memory queue your data will be in.
All the answers here pointed me on the right direction but I finally came up with a solution that managed to fit our code base and framework.
Firstly, I discovered there isn't a need to have bean to act as a polling consumer but a processor could be used instead.
#Override
public void configureCamel() {
from("timer://fnzPoller?period=2000&delay=2000").processRef(UrlPingProcessor.class.getSimpleName())
.processRef(StopStartProcessor.class.getSimpleName()).to("log://TimerPollingRoute?showAll=true");
}
Then in the UrlPingProcessor there is CXF service to ping the url and can check the response :
#Override
public void process(Exchange exchange) {
try {
// CXF service
FnzPingServiceImpl fnzPingService = new FnzPingServiceImpl(url);
fnzPingService.getPing();
} catch (WebApplicationException e) {
int responseCode = e.getResponse().getStatus();
boolean isValidResponseCode = ResponseCodeUtil.isResponseCodeValid(responseCode);
if (!isValidResponseCode) {
// Sets a flag to stop for the StopStartProcessor
stopRoute(exchange);
}
}
}
Then in the StopStartProcessor it is using a ExecutorService to stop or start a route via new thread.:
#Override
public void process(final Exchange exchange) {
// routeBuilder is set on the constructor.
final String routeId = routeBuilder.getClass().getSimpleName();
Boolean stopRoute = ExchangeHeaderUtil.getHeader(exchange, Exchange.ROUTE_STOP, Boolean.class);
boolean stopRoutePrim = BooleanUtils.isTrue(stopRoute);
if (stopRoutePrim) {
StopRouteThread stopRouteThread = new StopRouteThread(exchange, routeId);
executorService.execute(stopRouteThread);
} else {
CamelContext context = exchange.getContext();
Route route = context.getRoute(routeId);
if (route == null) {
try {
context.addRoutes(routeBuilder);
} catch (Exception e) {
String msg = "Unable to add a route: " + routeBuilder;
LOGGER.warn(msg, e);
}
}
}
}
I am pretty new with Camel. I have been trying to submit a data (Json from a file) to a webservice. This is my code:
public static void main(String args[]) throws Exception {
// create CamelContext
CamelContext context = new DefaultCamelContext();
// add our route to the CamelContext
context.addRoutes(new RouteBuilder() {
#Override
public void configure() {
from("file:data/inbox?noop=true")
.marshal()
.string()
.setHeader(Exchange.CONTENT_TYPE,constant("application/json"))
.to("http://www.a-service.com");
}
});
// start the route and let it do its work
context.start();
Thread.sleep(10000);
// stop the CamelContext
context.stop();
}
Then the webservice will response with Json which can be
{result:OK}
or
{result:FAIL}
Now, if a response has responseCode as 200, Camel will consider as success.
My question is, how can I have a validating process for responsed JSon so that if it is FAIL, Camel should not consider as success?
Solution Credit #Namphibian:
By adding processor and the end. This code has been tested:
from("file:data/inbox?noop=true")
.marshal()
.string("UTF-8")
.setHeader(Exchange.CONTENT_TYPE,constant("application/json"))
.to("http://monrif-test.userspike.com/monrif/rss/monrif_-all-global")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
String msg = in.getBody(String.class);
System.out.println("Response: " + msg);
if(msg.contains("OK")){
// go to party
}else{
throw new Exception("test exception");
}
}
});
There are two broad strategies you can use to achieve this.
Processor Based:
Add a processor to the end of the route. In this processor do the check if the webservice then responds with a true or false value.
A processor would look something like this:
package com.example;
import java.util.Map;
import org.apache.camel.Body;
import org.apache.camel.Exchange;
import org.apache.camel.Handler;
import org.apache.camel.Headers;
import org.apache.camel.Message;
public class GeneralProcessor {
#Handler
public void PrepapreErrorImportReport
(
#Headers Map hdr
, Exchange exch
)
{
//todo: Get the message as a string;
Message in = exch.getIn();
String msg = (String)in.getBody();
// Now check if body contains failed or ok.
if(msg.contains("OK")){
//todo: go party the message was OK
}
else{
//todo: Oh Oh! Houston we have a problem
}
}
}
You can then modify your route to use this processor.
The Simple Expression Language
This is one way the other way is to use the simple expression language. See the example below on how to use this.
from("file:data/inbox?noop=true")
.marshal()
.string()
.setHeader(Exchange.CONTENT_TYPE,constant("application/json"))
.to("http://www.a-service.com")
.choice()
.when(simple("${body} contains 'OK'")).to("activemq:okqueue")
.otherwise().to("activemq:queue:other");
Notice the simple("${body} contains 'OK'") piece of code. That is the power of simple.
Both approaches have uses.
In the Process method , you can use below method and it will work
LOGGER.info("Response code " + message.getHeader(exchange.HTTP_RESPONSE_CODE, Integer.class));