Part 4
Introduction
This section is about regular Camel. The examples presented here in this section is much more in common of all the examples we have in the Camel documentation.
...
Before we jump into it, we want to state that this tutorial is about Developers not loosing control. In my humble experience one of the key fears of developers is that they are forced into a tool/framework where they loose control and/or power, and the possible is now impossible. So in this part we stay clear with this vision and our starting point is as follows:
- We have generated the webservice source code using the CXF wsdl2java generator and we have our ReportIncidentEndpointImpl.java file where we as a Developer feels home and have the power.
So the starting point is:
...
As an end-user you usually use the RouteBuilder as of follows:
- create your own Route class that extends RouteBuilder
- implement your routing DSL in the configure method
So we create a new class ReportIncidentRoutes and implement the first part of the routing:
...
In the example above we have a very common routing, that can be distilled from pseudo verbs to actual code with:
- from A to B
- From Endpoint A To Endpoint B
- from
...
- ("endpointA").to("endpointB")
- from("direct:start").to("velocity:MailBody.vm");
from("direct:start") is the consumer that is kick-starting our routing flow. It will wait for messages to arrive on the direct queue and then dispatch the message.
to("velocity:MailBody.vm") is the producer that will receive a message and let Velocity generate the mail body response.
...
Notice that we get the producer template using the createProducerTemplate method on the CamelContext. Then we send the input parameters to the direct:start endpoint and it will route it to the velocity endpoint that will generate the mail body. Since we use direct as the consumer endpoint (=from) and its a synchronous exchange we will get the response back from the route. And the response is of course the output from the velocity endpoint.
Info | ||
---|---|---|
| ||
In the example above we create a new |
We have now completed this part of the picture:
Unit testing
Now is the time we would like to unit test what we got now. So we call for camel and its great test kit. For this to work we need to add it to the pom.xml
...
But hey we have added the file producer endpoint and thus a file should also be created as the backup file. If we look in the target/subfolder
we can see that something happened.
On my humble laptop it created this folder: target\subfolder\ID-claus-acer. So the file producer create a sub folder named ID-claus-acer
what is this? Well Camel auto generates an unique filename based on the unique message id if not given instructions to use a fixed filename. In fact it creates another sub folder and name the file as: target\subfolder\ID-claus-acer\3750-1219148558921\1-0 where 1-0 is the file with the mail body. What we want is to use our own filename instead of this auto generated filename. This is archived by adding a header to the message with the filename to use. So we need to add this to our route and compute the filename based on the message content.
We could do as in the previous parts where we send the computed filename as a message header when we "kick-start" the route. But we want to learn new stuff so we look for a different solution using some of Camels many languages. As OGNL is a favorite language of mine (used by WebWork) so we pick this baby for a Camel ride. For starters we must add it to our pom.xml:
Setting the filename
For starters we show the simple solution and build from there. We start by setting a constant filename, just to verify that we are on the right path, to instruct the file producer what filename to use. The file producer uses a special header FileComponent.HEADER_FILE_NAME
to set the filename.
What we do is to send the header when we "kick-start" the routing as the header will be propagated from the direct queue to the file producer. What we need to do is to use the ProducerTemplate.sendBodyAndHeader
method that takes both a body and a header. So we change out webservice code to include the filename also:
Code Block | ||||
---|---|---|---|---|
| ||||
Code Block | ||||
| ||||
public OutputReportIncident reportIncident(InputReportIncident parameters) <dependency>{ <groupId>org.apache.camel</groupId> <artifactId>camel-ognl</artifactId>// create the producer template to use for sending messages ProducerTemplate producer <version>${camel-version}</version>= context.createProducerTemplate(); </dependency> |
And remember to refresh your editor so you got the new .jars.
We want to construct the filename based on this syntax: mail-incident-#ID#.txt
where #ID# is the incident id from the input parameters. As OGNL is a language that can invoke methods on bean we can invoke the getIncidentId()
on the message body and then concat it with the fixed pre and postfix strings.
In OGNL glory this is done as:
...
"'mail-incident-' + in.body.incidentId + '.txt'"
// send the body and the filename defined with the special header key
Object mailBody = producer.sendBodyAndHeader("direct:start", parameters, FileComponent.HEADER_FILE_NAME, "incident.txt");
System.out.println("Body:" + mailBody);
// return an OK reply
OutputReportIncident out = new OutputReportIncident();
out.setCode("OK");
return out;
}
|
However we could also have used the route builder itself to configure the constant filename as shown below:
Code Block | ||||
---|---|---|---|---|
| ||||
public void configure() throws Exception {
from("direct:start")
.to("velocity:MailBody.vm")
// set the filename to a constant before the file producer receives the message
.setHeader(FileComponent.HEADER_FILE_NAME, constant("incident.txt"))
.to("file://target/subfolder");
}
|
But Camel can be smarter and we want to dynamic set the filename based on some of the input parameters, how can we do this?
Well the obvious solution is to compute and set the filename from the webservice implementation, but then the webservice implementation has such logic and we want this decoupled, so we could create our own POJO bean that has a method to compute the filename. We could then instruct the routing to invoke this method to get the computed filename. This is a string feature in Camel, its Bean binding. So lets show how this can be done:
Using Bean Language to compute the filename
First we create our plain java class that computes the filename, and it has 100% no dependencies to Camel what so ever.
Code Block | ||||
---|---|---|---|---|
| ||||
/**
* Plain java class to be used for filename generation based on the reported incident
*/
public class FilenameGenerator {
public String generateFilename(InputReportIncident input) {
// compute the filename
return "incident-" + input.getIncidentId() + ".txt";
}
}
|
The class is very simple and we could easily create unit tests for it to verify that it works as expected. So what we want now is to let Camel invoke this class and its generateFilename with the input parameters and use the output as the filename. Pheeeww is this really possible out-of-the-box in Camel? Yes it is. So lets get on with the show. We have the code that computes the filename, we just need to call it from our route using the Bean Language:
Code Block | ||||
---|---|---|---|---|
| ||||
public void configure() throws Exception {
from("direct:start")
// set the filename using the bean language and call the FilenameGenerator class.
// the 2nd null parameter is optional methodname, to be used to avoid ambiguity.
// if not provided Camel will try to figure out the best method to invoke, as we
// only have one method this is very simple
.setHeader(FileComponent.HEADER_FILE_NAME, BeanLanguage.bean(FilenameGenerator.class, null))
.to("velocity:MailBody.vm")
.to("file://target/subfolder");
}
|
Notice that we use the bean language where we supply the class with our bean to invoke. Camel will instantiate an instance of the class and invoke the suited method. For completeness and ease of code readability we add the method name as the 2nd parameter
Code Block | ||||
---|---|---|---|---|
| ||||
.setHeader(FileComponent.HEADER_FILE_NAME, BeanLanguage.bean(FilenameGenerator.class, "generateFilename"))
|
Then other developers can understand what the parameter is, instead of null
.
Now we have a nice solution, but as a sidetrack I want to demonstrate the Camel has other languages out-of-the-box, and that scripting language is a first class citizen in Camel where it etc. can be used in content based routing. However we want it to be used for the filename generation.
Note | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||||||||||||||||||||
Using a script language to set the filenameWe could do as in the previous parts where we send the computed filename as a message header when we "kick-start" the route. But we want to learn new stuff so we look for a different solution using some of Camels many Languages. As OGNL is a favorite language of mine (used by WebWork) so we pick this baby for a Camel ride. For starters we must add it to our pom.xml:
And remember to refresh your editor so you got the new .jars. In OGNL glory this is done as:
where
Now we got the expression to dynamic compute the filename on the fly we need to set it on our route so we turn back to our route, where we can add the OGNL expression:
And since we are on Java 1.5 we can use the static import of ognl so we have:
Notice the import static also applies for all the other languages, such as the Bean Language we used previously. |
Whatever worked for you we have now implemented the backup of the data files:
Sending the email
What we need to do before the solution is completed is to actually send the email with the mail body we generated and stored as a file. In the previous part we did this with a File consumer, that we manually added to the CamelContext. We can do this quite easily with the routing.
Code Block | ||||
---|---|---|---|---|
| ||||
import org.apache.camel.builder.RouteBuilder;
public class ReportIncidentRoutes extends RouteBuilder {
public void configure() throws Exception {
// first part from the webservice -> file backup
from("direct:start")
.setHeader(FileComponent.HEADER_FILE_NAME, bean(FilenameGenerator.class, "generateFilename"))
.to("velocity:MailBody.vm")
.to("file://target/subfolder");
// second part from the file backup -> send email |
where request.body.incidentId
computes to:
- in is a predefined object in the OGNLContext that Camel set. See the OGNL for other predefined objects available
- body is the body of the in message
- incidentId will invoke the
getIncidentId()
method on the body.
The rest is just more or less regular plain code where we can concat strings.
Now we got the expression to dynamic compute the filename on the fly we need to set it on our route so we turn back to our route, where we can add the OGNL expression:
Code Block | ||
---|---|---|
java | java | public void configure() throws Exception { from("direct:startfile://target/subfolder") // we need to set the filenamesubject andof uses OGNL for thisthe email .setHeader(FileComponent.HEADER_FILE_NAME, OgnlExpression.ognl("'mail-incident' + request.body.incidentId + '.txt'"subject", constant("new incident reported")) // using pipes-and-filters we send the outputemail from the previous to the next .to("smtp://someone@localhost?password=secret&to=incident@mycompany.com"); .pipeline("velocity:MailBody.vm", "} } |
The last 3 lines of code does all this. It adds a file consumer from("file://target/subfolder")
...
, sets the mail subject, and finally send it as an email.
The DSL is really powerful where you can express your routing integration logic.
So we completed the last piece in the picture puzzle with just 3 lines of code.
We have now completed the integration:
Conclusion
We have just briefly touched the routing in Camel and shown how to implement them using the fluent builder syntax in Java. There is much more to the routing in Camel than shown here, but we are learning step by step. We continue in part 5. See you there.
#Resources
...