LEGO® Java (II): Apache Camel Error Handling, Java Beans and Web Services
The first part of this series showed you how to start a Camel context, write a simple route, and then stop the context. You’re starting to see how to do some things in the “Camel way”. Let’s go for a second Camel ride and see how far we can go.
Error Handling in Camel
As promised, we’re going to add some error handling to our route and see how to deal with the usual problems. To see what I mean, go to the main class from the first article and substitute the URL of the W3C site (“http://www.w3.org”) with this alternate version: “http://www.w3c.org”. Run the application once again and now you should see the application failing to retrieve the page with a “HttpOperationFailedException” error. The problem is that the new URL is a redirect to the old one, but our route doesn’t know how to deal with this.
If you have a look at the “http” endpoint documentation, you will notice how flexible and configurable it is. You’ll also see that some HTTP response codes are thrown as exceptions by default. In plain Java, you can instruct a connection to follow redirections in a transparent way. With the “http” configuration options, we could probably do the same by configuring the “HttpClient” and modifying the “http” endpoint it uses internally. Instead of this, let’s learn some error handling strategies in Camel.
We want some sepcial logic for when an “HttpOperationFailedException” is thrown: we should grab the redirection location present in the exception and retry the request. For this, modify the code of the extractor route in the following way:
@Override
public void configure() throws Exception {
from("direct:page_extractor")
.onException(HttpOperationFailedException.class)
.log("Fetching URL failed: '${exception.message}', trying with relocation: '${exception.redirectLocation}'.")
.handled(true)
.setBody(simple("${exception.redirectLocation}"))
.to("direct:page_extractor")
.end()
.setHeader(Exchange.HTTP_URI, body())
.log("Extracting content from: '${body}'")
.to("http:extractor")
.unmarshal().tidyMarkup()
.log("Html from: '${body}'")
.split(xpath("//body//p/text()"), new SplitterAggregationStrategy("(?s).*[A-Za-z0-9].*"))
.log("Text chunk: '${body}'.")
.end();
}
If you re-run the application now, there is no exception in the console and a new log entry indicates the URL failed and fetching was retried using the redirection location.
What we introduced at the beginning of the route is an “onException” clause that, in case of an exception of the given type (or types) being thrown within this route, executes the steps contained in the clause. Please, notice the “end” method call separating what should be done in case of such an exception from the normal route steps.
In the error handling code, the first action we take is to log some information about what is about to happen. Then, we mark that the exception has already been handled and, after setting in the body the relocation URL, we send the message back to the endpoint to be retried.
The “onException” clause has a lot of options and the topic “error handling” is very well described in the chapter 5 from Camel in Action.
Fine grained logic with Java beans
If you need functionality that you cannot express with the Camel Java DSL, then using Java or any other languages supported in Camel is pretty easy. One option is to use the “process” method from the Java DSL. This method accepts a “Processor” parameter in through which you have access to the complete message (“Exchange” in Camel). While this seems pretty easy and practical, you will be tempted to pollute your routes with anonymous Java classes containing logic. You should also notice that, following this approach, you need to do almost the same mapping every time to access the body of the “in” exchange and set the result back into it. Another drawback of anonymous classes is that you cannot easily test the logic on them and, if you want to test them, you must create top-level classes that are both more complicated than simple Java beans and coupled to the Camel API through the “Exchange” class.
A better option is to use plain Java classes (“beans”) with the least possible logic, and let Camel do the default mapping using the signature of the methods invoked. If mapping the body of the exchange is not enough, you can use annotations to get access to some other parts of the exchange or even the whole exchange in your bean methods (this time relying on the Camel API because of the specific annotations).
To see the problem that we want to solve with this option, try now the application with the following URL: “http://www.w3.org/notfound”. It does not exist and you will notice that Camel retries the operation with an empty URL obtained from the redirect location property in the “HttpOperationFailedException” exception. The application fails with a “UnknowHostException” produced by the now empty URL.
Using the “bean” option, let’s see how to distinguish between any HTTP response code and a HTTP redirection code and avoid retrying the fetch unnecessarily. We can read this information by inspecting the “HttpOperationFailedException” properties. For this, create a class named “HttpErrorHelperBean” with the following content:
public class HttpErrorHelperBean {
public boolean isRedirectionError(Exception exception) {
return (exception instanceof HttpOperationFailedException) &&
(((HttpOperationFailedException) exception).getRedirectLocation() != null);
}
}
And modify the route introducing an “onWhen” clause after the “onException” in the following way:
...
.onException(HttpOperationFailedException.class)
.onWhen(bean(HttpErrorHelperBean.class).isEqualTo(true))
.log("Fetching URL failed: '${exception.message}', trying with relocation: '${exception.redirectLocation}'.")
...
If you try once again the non-existing URL, you will see that now the thrown exception is the initial “HttpOperationFailedException” with a response code 404. This exception does not match the condition in our bean and therefore the error handling has not been applied.
Did you also notice something strange in the “bean” method construction? We indicated no method name or bean instance, but Camel seems to have instantiated the class and called the method we expected. What Camel does is try to find an instance of the passed class in the “registry” or instantiate the class if it could not find an instance (for this, the class needs to have a default no arguments constructor). For guessing which method to call, Camel applies a matching algorithm based on the method signature and the message payload type. If you want to know how it exactly works, you can find a detailed description in the chapter 4.4 of Camel in Action.
Exposing our extractor service as a Web service
Now let’s expose our logic as a web service. But first let me clarify: the kind of Web service we expose here is a plain and simple HTTP&HTML service (not a REST or a SOAP based Web Service).
To implement a web service, create a class named “HtmlFormatterBean” with the following code:
public class HtmlFormatterBean {
public String asHtmlPage(List<String> contents) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("<html><body><h1>Extracted contents:</h1><ul>");
for (String content : contents) {
stringBuffer.append("<li>").append(content).append("</li>");
}
stringBuffer.append("</ul></body></html>");
return stringBuffer.toString();
}
}
Add a new dependency in the Maven pom file:
...
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jetty</artifactId>
<version>${camel.version}</version>
</dependency>
...
And modify the route builder adding this second route within the “configure” method:
from("jetty://http://0.0.0.0:8080/page_extractor")
.setBody(header("extractUrl"))
.to("direct:page_extractor")
.bean(HtmlFormatterBean.class);
Now, if you run the application and access the URL: “http://localhost:8080/page_extractor?extractUrl=http://www.w3c.org” from your web browser, then you should see a minimal HTML page containing a list with the extracted contents (you may need to increase the 10 seconds wait time before the context shuts down).
Wow! It was a short route but it had a big impact! By using the “jetty” endpoint in this way, Camel creates an embedded instance of the Jetty web server and starts listening to all the local IP interfaces, publishing the endpoint under the “page_extractor” web application context (“http://0.0.0.0:8080/page_extractor”). When you access this URL from a web browser adding the parameter “extractUrl=http://www.w3c.org”, the route creates a new message populating its headers with the different URL parameters and their values, and routes the message to the next step which happens to be the page extractor.
To send the request to our page extractor, we have to first set the value of the message body to the URL that we want to extract (which has been saved as a message header under the name of the HTTP parameter: “extractUrl”). To accomplish this, use the “setBody” method and, after this, the modified message is routed to the page extractor. When it comes back from the extractor, its body contains a list with the text chunks. The last step is to translate this payload into HTML, which is returned as a response to the HTTP request received in the “jetty” endpoint. If you do not like return HTML then instead of using a Java class you could also use the “velocity” Camel component.
This is the end of the second part of this series. In the next one, we introduce new functionality in a modular way and show how to test with the Camel testing framework.
I hope that you enjoyed the reading and that you come back for a new Camel ride in the next part.
Update: added source code for download. To run the application, just expand the file, change to the folder where the pom file is and execute: ‘mvn compile exec:java -Dexec.mainClass=”com.canoo.camel.Part2″‘











LEGO® Java (II): Apache Camel Error Handling, Java Beans and Web Services said,
March 16, 2011 @ 1:11 pm
[...] auto; } The first part of this series showed you how to start a Camel context, write a simple… [full post] alberto Rich Internet Applications (RIA) javaalbertocameleip 0 0 [...]
Rich Internet Applications (RIA) » Blog Archive » LEGO® Java: Apache Camel Context and Route Basics said,
March 16, 2011 @ 2:17 pm
[...] If you enjoyed the reading, I hope to see you in the second part. [...]
Claus Ibsen said,
March 17, 2011 @ 5:30 am
Hi
You guys are rocking. I enjoyed reading this 2nd part of the blog series. Keep it up.
I added a link to this blog from the Camel articles (link collection) web page
/Claus Ibsen
alberto said,
March 17, 2011 @ 10:53 am
@Claus: thanks Claus! Your book really rocks! Congrats!