Defining completion actions

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:

  1. Call an initial operation that you intend to be part of a larger transaction
  2. Send the equivalent of a "commit" instruction to finalize it

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.

Getting ready

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.

How to do it...

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}");

How it works...

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>

Note

A failure is defined in by functionality as an Exception that is not handled by the processing thread within a doTry..doCatch..doFinally block or the onException block, and which propagates to the error handler.

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:

  • If no 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.
  • If an 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.
  • Only one 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.
    • If two onCompletion blocks are defined at the global level, only the one defined first is used.
    • If two onCompletion blocks are defined in a route, only the one defined last is used.
  • If a route defines an onCompletion block, and passes the exchange to another route that also defines an onCompletion, both will be executed.

There's more...

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.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.88.62