When developing integrations, you will at some point work with non-transactional resources that maintain some sort of state, such as web service endpoints. As an exchange is processed through your routes, it will trigger calls to these endpoints, modifying that state.
We have already seen how a compensating operation may be triggered when an exception is thrown in the Fine-grained error handling using doTry…doCatch recipe. As an example, if the original operation called was place order, you may have to call a cancel order operation to unwind it.
Camel's DSL contains an onCompletion
statement, which allows you to define actions taken when a message completes successfully or fails at the level of a route.
This allows multiple levels of failure compensation to be written cleanly without nesting doTry..doCatch..doFinally
blocks.
This mechanism can also be used to implement a two stage pessimistic transaction process, such as:
This pattern is frequently seen in SOA architectures where non-transactional services, such as those exposed via SOAP or REST, are composed into a pseudo-transaction that is coordinated by the caller.
This recipe will outline how to define completion actions to be performed based on whether an exchange was completed successfully or not, through Camel's onCompletion
statement.
The Java code for this recipe is located in the org.camelcookbook.error.oncompletion
package. The Spring XML files are located under src/main/resources/META-INF/spring
and prefixed with onCompletion
.
In the XML DSL, define an onCompletion
block within a route. The block may be defined at any point in a route—though by convention it is usually placed at the beginning. Within the block, define processing steps that ought to be executed when an exchange has been processed through the route.
<from uri="direct:in"/> <onCompletion> <log message="onCompletion triggered: ${threadName}"/> <to uri="mock:completed"/> </onCompletion> <log message="Processing message: ${threadName}"/>
In the Java DSL, the equivalent logic is expressed as follows:
from("direct:in") .onCompletion() .log("onCompletion triggered: ${threadName}") .to("mock:completed") .end() .log("Processing message: ${threadName}");
Once an exchange has been processed through a route, a copy of it will be handed off to a background thread pool that will then process it through the onCompletion
block. Any subsequent changes made to this copied exchange will not be visible to the caller of the route. The functionality is analogous to an implicit wireTap
statement at the end of a route (see Wire Tap – sending a copy of the message elsewhere, in Chapter 2, Message Routing). We can confirm this through the logged thread names in the preceding examples:
Processing message: Camel (camel-1) thread #0 - ProducerTemplate onCompletion triggered: Camel (camel-1) thread #1 - OnCompletion
The onCompletion
block will process both failed and successfully completed exchanges. You can specify which type you would like to handle, by applying the onCompleteOnly
or onFailureOnly
attributes in the XML DSL as follows:
<onCompletion onCompleteOnly="true"> <!-- ... --> </onCompletion> <onCompletion onFailureOnly="true"> <!-- ... --> </onCompletion>
The attributes onCompleteOnly
and onFailureOnly
affect the regular onCompletion
behavior only when set to true
, as setting them to false
would be ambiguous. They should be considered as markers that narrow the onCompletion
functionality.
This intent is expressed more clearly through the equivalent statements in the Java DSL:
.onCompletion().onCompleteOnly() // ... .onCompletion().onFailureOnly() // ...
An onCompletion
block can be global, that is, defined outside a route.
In the XML DSL, it will apply to all routes in the Camel context:
<camelContext> <onCompletion> <log message="global onCompletion thread: ${threadName}"/> <to uri="mock:global"/> </onCompletion> <!-- routes defined here --> </camelContext>
In the Java DSL, the meaning of the term "global" is different—the onCompletion
function will only apply to routes defined in the same RouteBuilder
implementation:
onCompletion() .log("global onCompletion thread: ${threadName}") .to("mock:global");
The following rules apply to onCompletion
usage:
onCompletion
is defined at the route level, and one is defined at the global level, then the global one is used. The combination of conditions (onCompleteOnly
/onFailureOnly
) is disregarded.onCompletion
is defined at the route level, then none of onCompletion
blocks defined at the global level will ever get triggered as a result of the processing of the exchange.onCompletion
block defined within a single scope will always be considered for execution. This applies even if one defines a failure action, and the other one for successful completion.onCompletion
blocks are defined at the global level, only the one defined first is used.onCompletion
blocks are defined in a route, only the one defined last is used.onCompletion
block, and passes the exchange to another route that also defines an onCompletion
, both will be executed.You can make the onCompletion
block conditional through the addition of an onWhen
statement.
In the XML DSL, this is written as:
<onCompletion onFailureOnly="true"> <onWhen> <simple> ${exception.class} =='java.lang.IllegalArgumentException' </simple> </onWhen> <!-- ... --> </onCompletion>
In the Java DSL, the equivalent would be written as follows:
.onCompletion().onFailureOnly() .onWhen( simple( "${exception.class} =='java.lang.IllegalArgumentException'" )) //...
Since only a single onCompletion
block can be applied to a route by the Camel runtime, the following Java DSL will result in only the onCompleteOnly
statement being executed:
.onCompletion().onFailureOnly() // handle failure .end() .onCompletion().onCompleteOnly() // handle everything going OK .end()
This is a minor inconvenience that can be worked around using the choice
block (see Content-based routing, in Chapter 2, Message Routing) in conjunction with a catch-all onCompletion
statement.
In the XML DSL, this is written as:
<onCompletion> <choice> <when> <simple>${exception} == null</simple> <to uri="mock:completed"/> </when> <otherwise> <to uri="mock:failed"/> </otherwise> </choice> </onCompletion>
In the Java DSL, due to an internal constraint of the DSL, we have to split out the choice
block into another route to achieve the same effect using the following code.
onCompletion() .to("direct:processCompletion") .end(); from("direct:processCompletion") .choice() .when(simple("${exception} == null")) .to("mock:completed") .otherwise() .to("mock:failed") .endChoice();
It is possible to define a custom thread pool to handle the procession of the exchange through the onCompletion
blocks.
In the XML DSL, this is written as:
<onCompletion executorServiceRef="customThreadPool"> <!-- ... --> </onCompletion>
In the Java DSL, the same thing is expressed as:
onCompletion().executorServiceRef("customThreadPool")
See Using custom thread pools in Chapter 6, Parallel Processing for more details.
onCompletion
: http://camel.apache.org/oncompletion.html18.116.15.161