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));
Related
Using mock to launch a salesforce streaming route as shown here fails for the following route:
from("salesforce:AccountUpdateTopic?notifyForFields=ALL¬ifyForOperations=ALL")
.tracing().convertBodyTo(String.class).to("file:D:/tmp/")
.to("mock:output")
.log("SObject ID: ${body}");
in
package org.apache.camel.component.salesforce;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.component.salesforce.internal.OperationName;
import org.junit.Test;
public class StreamingApiIntegrationTest extends AbstractSalesforceTestBase {
#Test
public void testSubscribeAndReceive() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:AccountUpdateTopic");
mock.start();
Thread.sleep(10000);
mock.stop();
}
#Override
protected RouteBuilder doCreateRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
// test topic subscription
from("salesforce:AccountUpdateTopic?notifyForFields=ALL¬ifyForOperations=ALL").tracing().convertBodyTo(String.class).to("file:D:/tmp/").to("mock:output").log("SObject ID: ${body}");
}
};
}
}
Running this test does not start the route (updates are not fetched from Salesforce and stored in /tmp/).
Can mock run a route and wait for updates from Salesforce? Is there a shorter example that allows for testing salesforce routes without making use of spring?
You misunderstood the Camel Mock component. Mocks are not starting anything. They are just endpoints who record and assert the messages they receive.
To trigger a Camel route you have to send a message to it. You can do this easily using a ProducerTemplate.
It is this line from the example you mention that does exactly that.
CreateSObjectResult result = template().requestBody(
"direct:upsertSObject", merchandise, CreateSObjectResult.class);
template is the ProducerTemplate and requestBody the method to send a message to the endpoint direct:upsertSObject and wait for a response. See the Javadocs of ProducerTemplate for the various existing signatures.
I'm trying to set up a Camel route for transferring files over HTTP. I'm also trying to understand the concept as I'm new to this.
When I code something like below, does that mean I'm routing a simple message over HTTP? Could I call Jetty the consumer in this case? I'm able to run the below code and call the browser and see the message successfully.
from("jetty://http://localhost:32112/greeting")
.setBody(simple("Hello, world!"));
However, I want to send a simple message(eventually an XML) over HTTP following which I would want to save it on disk and analyse it further. Should the code like below work?
CamelContext context = new DefaultCamelContext();
ProducerTemplate template = context.createProducerTemplate();
template.sendBody("direct:start", "This is a test message");
from("direct:start")
.to("jetty://localhost:32112/greeting");
from("jetty://http://localhost:32112/greeting")
.to("direct:end");
Should I be not using direct:start here for parsing XMLs?
Thanks a lot for the help.
first you have to create your routes and start your context. Then you can send messages via your template.
The route could look like this
from("jetty:http://0.0.0.0:32112/greeting")
.routeId("xml-converter-route").autoStartup(false)
.bean(xmlConverterBean, "convertXmlMethodToBeCalledInBean()")
;
If you just want to transfer data and nothing else use restlet or netty-http4. More lightweight than jetty.
from("restlet:/http://localhost:32112/greeting").convertBodyTo(String.class).log(LoggingLevel.INFO, "filetransfer", "log of body: ${body} and headers ${headers}").to("file://C:/test?fileName=body.txt");
Here's a camel test which may help you understand how these components work.
public class CamelRESTExampleTest extends CamelTestSupport {
Logger LOG = LoggerFactory.getLogger(CamelRESTExampleTest.class);
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
// Create a service listening on port 8080
from("restlet:http://localhost:8080/xmlFileService?restletMethod=post")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
String rawXML = exchange.getIn().getBody(String.class);
LOG.info("rawXML=" + rawXML);
}
});
// Read files from the local directory and send to the service.
// Create a test.xml file in this directory and it will be read in
from("file:src/test/resources/data?noop=true")
.to("restlet:http://localhost:8080/xmlFileService?restletMethod=post");
}
};
}
#Test
public void test() throws InterruptedException {
// Give the route time to complete
TimeUnit.SECONDS.sleep(5);
}
}
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...
I am trying to learn more about Apache Camel. I found the documentation somewhat helpful but leaves alot of guessing for beginner Camel riders that do not know how small code segments should easily fit into fully functioning programs. Hopefully most people know what I am trying to describe. I have been lost lots of other times in some programming books where segments of code are shown outside of the context of a fully running program.
Anyway here is my program that does not aggregate messages for some reason. I was hoping it would aggregate all my messages but this program does not do that. When running the program I receive an empty file as the output which is not my goal.
package laser.helmet.camel.friend;
import org.apache.camel.builder.RouteBuilder;
public class AggregatingMessagesRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:start")
.aggregate().constant(true)
.completionTimeout(100L)
.groupExchanges()
.to("file:target/this_folder/result?allowNullBody=true");
}
}
Then I call this program from the below class which has the main method of course.
package laser.helmet.camel.friend;
import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.ProducerTemplate;
public class Main {
public static void main(String[] args) throws Exception {
CamelContext c = new DefaultCamelContext();
c.addRoutes(new AggregatingMessagesRoute());
ProducerTemplate pt = c.createProducerTemplate();
c.start();
pt.sendBody("direct:start", "1");
pt.sendBody("direct:start", "2");
Thread.sleep(5000);
c.stop();
}
}
I was expecting the body of the two messages I create with the ProducerTemplate to be both in the file after the route finishes but it is just a blank file. I had to add the part allowNullBody=true to the route because for some reason the body is null when running this program.
Also if you are a beginner and wondering. I am bringing in the dependencies with Maven instead of putting the camel.jars on my Java classpath.
Thank-you for reading this everyone. :D
So how can I start aggregating messages Stackoverflow? 0_o
Peace,
user_loser
This is an easy one since you provided the code snippet, nice one!
Remove .groupExchanges() since it is becoming deprecated and it is hardly adequate. You always need a AggregationStrategy to have fine grained control over how to aggregate your exchanges. So, add the following class:
class StringAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (oldExchange == null) {
return newExchange;
}
String oldBody = oldExchange.getIn().getBody(String.class);
String newBody = newExchange.getIn().getBody(String.class);
oldExchange.getIn().setBody(oldBody + "+" + newBody);
return oldExchange;
}
}
And then reference it in your route; so it becomes something like this:
public class AggregatingMessagesRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:start")
.aggregate().constant(true)
.completionTimeout(100L)
.aggregationStrategy(new StringAggregationStrategy())
.to("file:target/this_folder/result?allowNullBody=true");
}
}
Hope this helps!
R.
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.