I would like to process both request and response messages at the end of my route. However, I do not see a way how to access the original request message.
I have the terrible feeling I am struggling with some basic concept.
Here is a simple example route in DSL to outline my problem (streamCaching is enabled for the whole context):
from("activemq:queue:myQueue")
.to("log:" + getClass().getName() + "?showOut=true")
.to("http://localhost:8080/someBackend")
.log("Now in.Body returns this: ${in.body} and out.Body this: ${out.body}")
.to("log:" + getClass().getName() + "?showOut=true");
Here is an according excerpt from my logs (line-breaks edited for better reading). As one can see, the original SOAP message is lost once the http server replied, and the SOAP response object is stored in the inBody of the message.
2012-09-25 17:28:08,312 local.bar.foo.MyRouteBuilder INFO -
Exchange[ExchangePattern:InOut, BodyType:byte[],
Body:<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Header /><env:Body><urn:someRequest xmlns:urn="http://foo.bar.local/ns"></urn:someRequest></env:Body></env:Envelope>,
Out: null]
2012-09-25 17:28:08,398 org.apache.camel.component.http.HttpProducer DEBUG -
Executing http POST method: http://localhost:8080/someBackend
2012-09-25 17:28:09,389 org.apache.camel.component.http.HttpProducer DEBUG -
Http responseCode: 200
2012-09-25 17:28:09,392 route2 INFO -
Now in.Body returns this: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:someResponse xmlns:ns2="http://foo.bar.local/ns"</ns2:someResponse></soap:Body></soap:Envelope>
and out.Body this:
2012-09-25 17:28:09,392 local.bar.foo.MyRouteBuilder INFO -
Exchange[ExchangePattern:InOut,
BodyType:org.apache.camel.converter.stream.InputStreamCache,
Body:[Body is instance of org.apache.camel.StreamCache],
Out: null]
I would have expected to have in.body and out.body be preserved across the whole route?
Alternative solutions I am considering:
Make use of the Correlation Identifier pattern to correlate both request and reply. But would this preserve the message bodies as well? Also, my request/reply messages do not have unique identifiers for correlation.
Write a custom bean, which performs the call to the http backend, processing both request and reply objects (but this is basically a no-Camel solution, reinventing the wheel and hence not preferred)
Already failed approaches:
I tried to access the original request message using a Processor like this at the end of my route, with no success:
process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message originalInMessage = exchange.getUnitOfWork().getOriginalInMessage();
logger.debug(originalInMessage.getBody(String.class));
logger.debug(exchange.getIn().getBody(String.class));
}
});
Thanks for any help
Simply store the original body of the in message in a header or a property and retrieve it at the end:
from("activemq:queue:myQueue")
.setProperty("origInBody", body())
.to("http://localhost:8080/someBackend")
After the http call you can then access the property origInBody.
First, this article shows very well how in and out works in camel: http://camel.apache.org/using-getin-or-getout-methods-on-exchange.html
Typically, the out message is not always used, but rather copied from the in-message in each step.
In your case, where you want the original message to stay around til the end of the route, you could go ahead with the Enrichment EIP. http://camel.apache.org/content-enricher.html
Your route would be something like this:
public class MyAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange orig, Exchange httpExchange){
// if you want to check something with the Http request, you better do that here
if( httpExchange is not correct in some way )
throw new RuntimeException("Something went wrong");
return orig;
}
}
AggregationStrategy aggStrategy = new MyAggregationStrategy();
from("activemq:queue:myQueue")
.enrich("http://localhost:8080/someBackend",aggStrategy)
.//keep processing the original request here if you like, in the "in" message
One of the biggest problem of camel, is the ease to misuse it. The best way to use it correctly is to think in terms of EIP : one of the main goals of camel, is to implement EIP in its DSL.
Here is a list of EIP
Now think about it. You want the request and the response at the end, for what use ? Logging, Aggregation, ... ? For logging, a correlationId should suffice, so I presume you need it to create a response, based on both request and the proxied-response. If that's what you want, you could do something like
from("direct:receiveRequest")
.enrich("direct:proxyResponse", new RequestAndResponseAggregationStrategy())
You will have the opportunity to merge your Request (in oldExchange) and your Response (in newExchange).
With all the due respect I have for Christian Schneider, I do think the idea of putting the request in a property that could be reused later is a bad design. By doing it, you create side-effect between your routes. If your route is a subroute for another, you'll maybe erase their property. If you store it to put it back later, maybe you should do something like
from("direct:receiveRequest")
.enrich("direct:subRouteToIgnoreResponse", AggregationStrategies.useOriginal())
A really really bad design that I have done too many time myself is to do :
from("direct:receiveRequest")
.to("direct:subroute")
from("direct:subroute")
.setProperty("originalBody", body())
.to("direct:handling")
.transform(property("originalBody")
This will lead to "properties/headers hell", and to routes that are just a successive call of processors.
And if you can't think of a solution of your problem with EIP, you should maybe use camel only to access their components. For example, something like :
from("http://api.com/services")
.to(new SomeNotTranslatableToEIPProcessor())
.to("ftp://destination")
But don't forget that those components has their own goals : creating a common abstraction between similar behaviour (e.g, time based polling consumer). If you have a very specific need, trying to bend a camel component to this specific need can lead to huge chunk of code not easily maintainable.
Don't let Camel become your Golden Hammer anti-pattern
I often use an aggregation strategy, which preserves the old exchange and puts the result of the enrich into a header:
import org.apache.camel.Exchange;
import org.apache.camel.processor.aggregate.AggregationStrategy;
public class SetBodyToHeaderAggregationStrategy implements AggregationStrategy {
private String headerName = "newBody";
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
oldExchange.getIn().setHeader(headerName, newExchange.getIn().getBody());
return oldExchange;
}
#SuppressWarnings("unused")
public void setHeaderName(String headerName) {
this.headerName = headerName;
}
}
Now you can use it like this:
<enrich strategyRef="setBodyToHeaderAggregationStrategy">
<constant>dosomething</constant>
</enrich>
<log loggingLevel="INFO" message="Result body from enrich: ${header.newBody}. Original body: ${body}" loggerRef="log"/>
Related
Requisite disclaimer about being new to Camel--and, frankly, new to developing generally. I'd like to have a string generated as the output of some function be the source of my camel route which then gets written to some file. It's the first part that seems challenging: I have a string, how do I turn it into a message? I can't write it into a file nor can I use JMS. I feel like it should be easy and obvious, but I'm having a hard time finding a simple guide to help.
Some pseudo-code using the Java DSL:
def DesiredString() {return "MyString";}
// A camel route to be implemented elsewhere; I want something like:
class MyRoute() extends RouteBuilder {
source(DesiredString())
.to("file://C:/out/?fileName=MyFileFromString.txt");
}
I vaguely understand using the bean component, but I'm not sure that solves the problem: I can execute my method that generates the string, but how do I turn that into a message? The "vague" is doing a lot of work there: I could be missing something there.
Thanks!
Not sure if I understand your problem. There is a bit of confusion about what the String should be become: the route source or the message body.
However, I guess that you want to write the String returned by your method into a File through a Camel route.
If this is correct, I have to clarify first the route source. A Camel Route normally starts with
from(component:address)
So if you want to receive requests from remote via HTTP it could be
from("http4:localhost:8080")
This creates an HTTP server that listens on port 8080 for messages.
In your case I don't know if the method that returns the String is in the same application as the Camel route. If it is, you can use the Direct component for "method-like" calls in the same process.
from(direct:input)
.to("file:...");
input is a name you can freely choose. You can then route messages to this route from another Camel route or with a ProducerTemplate
ProducerTemplate template = camelContext.createProducerTemplate();
template.sendBody("direct:input", "This is my string");
The sendBody method takes the endpoint where to send the message and the message body. But there are much more variants of sendBody with different signatures depending on what you want to send it (headers etc).
If you want to dive into Camel get a copy of Camel in Action 2nd edition. It contains everything you need to know about Camel.
Example:Sending String(as a body content)to store in file using camel Java DSL:
CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
public void configure() {
from("timer:StringSentToFile?period=2000")
.setBody(simple(DesiredString()))
.to("file:file://C:/out/?fileName=MyFileFromString.txt&noop=true")
.log("completed route");
}
});
ProducerTemplate template = context.createProducerTemplate();
context.start();
I have a class to run my route; The input comes from a queue (which is filled by a route that does a query and inserts the rows as messages on the queue)
These messages each contain a few headers:
- pdu_id, basically a prefetch on the filename.
- pad: the path the files reside in
What is to happen: I want the files in the path named by their "pdu_id".* in a tar; After that a REST call is to be done to remove the documents source.
I know a route has a from; but basically I need a route with a dynamic "from", and as below code example shows, queueing froms doesn't do the trick.
The question is what to use instead; I could not find a similar thing, but it can be I didn't use the right google search; in which case I'm deeply sorry.
public class ToDeleteTarAndDeleteRoute extends RouteBuilder {
#Override
public void configure() throws Exception
{
from("broker1:todelete.message_ids.queue")
.from("file:///?fileName=${in.header.pad}${in.header.pdu_id}.*")
.aggregate(new TarAggregationStrategy())
.constant(true)
.completionFromBatchConsumer()
.eagerCheckCompletion()
.to("file:///?fileName=${in.header.pad}${in.header.pdu_id}.tar")
.log("${header.pdu_id} tarred")
.setHeader(Exchange.HTTP_METHOD, constant("DELETE"))
.setHeader("Connection", constant("Close"))
.enrich()
.simple("http:127.0.0.1/restfuldb${header.pdu_id}?httpClient.authenticationPreemptive=true")
.log("${header.pdu_id} tarred and deleted.");
}
}
Yes. Poll enrich can help you in doing it. You should use it something like this:
from("broker1:todelete.message_ids.queue")
.aggregationStrategy(new TarAggregationStrategy())
.pollEnrich()
.simple("file:///?fileName=${in.header.pad}/${in.header.pdu_id}.*")
.unmarshal().string()
.to("file:///?fileName=${in.header.pad}/${in.header.pdu_id}.tar")
.log("${header.pdu_id} tarred")
.setHeader(Exchange.HTTP_METHOD, constant("DELETE"))
.setHeader("Connection", constant("Close"))
.enrich()
.simple("http:127.0.0.1/restfuldb${header.pdu_id}?httpClient.authenticationPreemptive=true")
.log("${header.pdu_id} tarred and deleted.");
Currently the solution to the problem consisted of a few changes based on what #daBigBug answered.
pollEnrich's simple expression uses antInclude rather than fileName;
aggregate is put after pollenrich; as each batch is the set of files, rather than the input from the queue. The input from the queue only provides meta information based on which actions are to be taken.
aggregationStrategy() is not possible in a RouteBuilder; I used aggregate() instead.
I removed the unmarshal(); I don't see why this would be needed; the files can contain binary content.
from("broker1:todelete.message_ids.queue")
.pollEnrich()
.simple("file:${in.header.pad}?antInclude=${in.header.pdu_id}.*")
.aggregate(new TarAggregationStrategy())
.constant(true)
.completionFromBatchConsumer()
.eagerCheckCompletion()
.log("tarring to: ${header.pad}${header.pdu_id}.tar")
.setHeader(Exchange.FILE_NAME, simple("${header.pdu_id}.tar"))
.setHeader(Exchange.FILE_PATH, simple("${header.pad}"))
.to("file://ignored")
...(and the rest of the operations);
I now see the files are getting picked up and even placed in a tar; however, the filename of the tar is unexpected as is the location (it's placed in ./ignored); Also in the rest of the operation, it appears the exchange headers are lost.
If anyone can help figure out how to preserve the headers in a safe way... I'm much obliged. Should I use a new question for that, or should I rephrase the question.
I'm trying to send a message from an Activiti Camel task to an ActiveMQ queue, which should resume the activity when it is received by Camel. As I understand it, when the message is received from the queue lacks the properties that would enable it to be identified by Camel in order to be routed to the correct activity task.
As such a Business key is Null Exception is raised and the route fails.
from("activiti:SampleProcess:myCamelTask")
.to("activemq:queue:myQueue");
As expected, if I hardcode either the PROCESS_ID_PROPERTY or the PROCESS_KEY_PROPERTY in the receiving route, the message is routed correctly (when the ID matches).
from("activemq:queue:myQueue")
.setBody(constant("test body"))
.setProperty(PROCESS_ID_PROPERTY, constant("50"))
// .setProperty(PROCESS_KEY_PROPERTY, constant("CUSTOM-KEY"))
.to("activiti:SampleProcess:receiveAsyncPing");
How can I get either property in the receiving route so I can set them accordingly?
Or is there a more recommended way to approach this?
A good question.
The way I handled this is to inject the PROCESS_KEY within the route using the setProperty() command:
See below where I set the process key (business key) to "bar":
from(startProcessEndpoint)
.log(LoggingLevel.INFO, logMsg3)
.setProperty("PROCESS_KEY_PROPERTY",foo)
.setBody(constant("bar"))
.to("activiti:testCamelTask:receive")
Now, if you dont want to use a constant, then you have access to the exchange object within the route and can use an Expression as shown below:
Expression foo = new Expression() {
#Override
public <T> T evaluate(Exchange exchange, Class<T> aClass) {
return (T) "foo";
}
};
Hope this helps,
Greg
I would like to write a Camel Route that gets in a URI (can be http, ftp, file, ...) and then fetches the data and stores it locally in a file.
This URI-String could be, for example:
"ftp://localhost/example.txt"
"file://tmp/example.txt"
"jms:queue:dataInputQueue"
...
Based on this string, the correct Camel Component should be used to access the data. Something like a case/switch in Java:
(1) Receive URI (from uri="vm:incomingUri")
(2) Chose "right" Camel Component
switch(URI)
case HTTP: use Camel HTTP component
case FTP: use Camel FTP component
case JMS: use Camel JMS component
...
(3) Read data from that URI, using the "right" Camel component
(4) Store file locally (to uri="file://...)
Example:
From "vm:incomingUri" I read a String "ftp://localhost/example.txt". That what finally needs to happen now should be equivalent to this:
<route>
<from uri="ftp://localhost/example.txt"/>
<to uri="file://tmpDir/example.txt"/>
</route>
How would this look like in Camel?
I believe one difficulty will be that, for the components you mention (HTTP, FTP, file, JMS), you may want to use either a producer or a consumer:
FTP, File: definitely a consumer to read a file.
HTTP (or HTTP4): definitely a producer, to send a request to the server (the server's reply will by the new message body)
JMS: depends on wether you want to read from a queue (consumer), or send a message to a queue with a ReplyTo header, then wait for the answer (producer).
Producers :
If you are using Camel 2.16+, you can use the new "dynamic to" syntax. It's basically the same as a regular "to", except that the endpoint uri can be evaluated dynamically using a simple expression (or, optionnaly, another type of expression). Alternatively, you can use the enrich flavor of the content-enricher pattern, wich also supports dynamic uris starting with Camel 2.16.
If you are using an older version of Camel, or if you need to dynamically route to several endpoints (not just one), you can use the recipient list pattern.
Here's an exemple. We will transform the message body by calling an endpoint; the uri for that endpoint will be found in a header named TargetUri and will be evaluated dynamically for each message.
// An instance of this class is registered as 'testbean' in the registry. Instead of
// sending to this bean, I could send to a FTP or HTTP endpoint, or whatever.
public class TestBean {
public String toUpperCase(final String str) {
return str.toUpperCase();
}
}
// This route sends a message to our example route for testing purpose. Of course, we
// could send any message as long as the 'TargetUri' header contains a valid endpoint uri
from("file:inbox?move=done&moveFailed=failed")
.setHeader("TargetUri").constant("bean:testbean?method=toUpperCase")
.setBody().constant("foo")
.to("direct:test");
// 1. The toD example :
from("direct:test")
.toD("${header.TargetUri}")
.to("log:myRoute");
// 2. The recipient list example :
from("direct:test")
.recipientList(header("TargetUri"))
.to("log:myRoute");
// 3. The enrich example :
from("direct:test")
.enrich().simple("${header.TargetUri}") // add an AggregationStrategy if necessary
.to("log:myRoute");
Consumers :
With Camel 2.16+, you can use the pollEnrich flavor of the content-enricher pattern.
For older versions of Camel, you can use a ConsumerTemplate in a processor.
// 4. The pollEnrich example (assuming the TargetUri header contains, e.g., a file
// or ftp uri) :
from("direct:test")
.pollEnrich().simple("${header.TargetUri}") // add an AggregationStrategy if necessary
.to("log:myRoute");
// 5. The ConsumerTemplate example (same assumption as above)
from("direct:test")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
String uri = exchange.getIn().getHeader("TargetUri", String.class);
ConsumerTemplate consumer = exchange.getContext().createConsumerTemplate();
final Object data = consumer.receiveBody(uri);
exchange.getIn().setBody(data);
}
})
.to("log:myRoute");
Producer or consumer?
Sadly, I can't think of any really elegant solution to handle both - I think you will have to route to two branches based on the uri and known components... Here's the sort of thing I might do (with Camel 2.16+), it's not very pretty:
// This example only handles http and ftp endpoints properly
from("direct:test")
.choice()
.when(header("TargetUri").startsWith("http"))
.enrich().simple("${header.TargetUri}")
.endChoice()
.when(header("TargetUri").startsWith("ftp"))
.pollEnrich().simple("${header.TargetUri}")
.endChoice()
.end()
.to("log:myRoute");
It is possible by using
<to uri="{{some.endpoint}}"/>
But you would require to add it in property .
<cm:property name="some.endpoint" value="SomeEndPoint"/>
And you can add any endpoint you want http, ftp, file, log, jms, vm etc.
Value of SomeEndPoint.
Log Component: log:mock
JMS Component: activemq:someQueueName
File Component: file://someFileShare
VMComponent: vm:toSomeRoute
Hope this doesn't sound ridiculous, but how can I discard a message in Camel on purpose?
Until now, I sent them to the Log-Component, but meanwhile I don't even want to log the withdrawal.
Is there a /dev/null Endpoint in Camel?
You can use the message filter eip to filter out unwanted messages.
http://camel.apache.org/message-filter
There is no dev/null, component.
Also there is a < stop /> you can use in the route, and when a message hit that, it will stop continue routing.
And the closest we got on a dev/null, is to route to a log, where you set logLeve=OFF as option.
With credit to my colleague (code name: cayha)...
You can use the Stub Component as a camel endpoint that is equivalent to /dev/null.
e.g.
activemq:route?abc=xyz
becomes
stub:activemq:route?abc=xyz
Although I am not aware of the inner workings of this component (and if there are dangers for memory leaks, etc), it works for me and I can see no drawbacks in doing it this way.
one can put uri/mock-uri to the config using property component
<camelContext ...>
<propertyPlaceholder id="properties" location="ref:myProperties"/>
</camelContext>
// properties
cool.end=mock:result
# cool.end=result
// route
from("direct:start").to("properties:{{cool.end}}");
I'm a little late to the party but you can set a flag on the exchange and use that flag to skip only that message (by calling stop) if it doesn't meet your conditions.
#Override
public void configure() throws Exception {
from()
.process(new Processor() {
#SuppressWarnings("unchecked")
#Override
public void process(Exchange exchange) throws Exception {
exchange.setProperty("skip", false);
byte[] messageBytes = exchange.getIn().getBody(byte[].class);
if (<shouldNotSkip>) {
} else { //skip
exchange.setProperty("skip", true);
}
}
}).choice()
.when(exchangeProperty("skip").isEqualTo(true))
.stop()
.otherwise()
.to();
}
I am using activemq route and needs to send reply in normal cases, so exchange pattern is InOut. When I configure a filter in the route I find that even it does not pass message to next step, the callback is executed(sending reply), just same as the behavior when calling stop(). And it will send the same message back to reply queue, which is not desirable.
What I do is to change the exchange pattern to InOnly conditionally and stop if I want to filter out the message, so reply is not sent. MAIN_ENDPOINT is a direct:main endpoint I defined to include normal business logic.
from("activemq:queue:myqueue" + "?replyToSameDestinationAllowed=true")
.log(LoggingLevel.INFO, "Correlation id is: ${header.JMSCorrelationID}; will ignore if not null")
.choice()
.when(simple("${header.JMSCorrelationID} == null"))
.to(MAIN_ENDPOINT)
.endChoice()
.otherwise()
.setExchangePattern(ExchangePattern.InOnly)
.stop()
.endChoice()
.end();
Note that this message is also consumed and not in the queue anymore. If you want to preserve the message in the queue(not consuming it), you may just stop() or just filter() so the callback(sending reply which is the original message) works, putting the message back to the queue.
Using only filter() would be much simpler:
from("activemq:queue:myqueue" + "?replyToSameDestinationAllowed=true")
.log(LoggingLevel.INFO, "Correlation id is: ${header.JMSCorrelationID}; will ignore if not null")
.filter(simple("${header.JMSCorrelationID} == null"))
.to(MAIN_ENDPOINT);