Core features

Core features

The content of this page describes the features provided by the platform-camel-core component. These are the scripting layer, the module adapters and the DSL extensions. For an overview how platform-camel-core fits into its architectural context refer to the IPF architecture page.

Scripting layer

The IPF scripting layer not only brings the Groovy programming language to your route definitions it also allows you to define your own DSL extensions. It consists of the DSL extension mechanism and the predefined DSL extensions. All predefined DSL extensions provided by IPF have been built using the DSL extension mechanism. You can get an overview of these extensions in the IPF extensions index. For a guide how to write you own DSL extensions refer to the DSL extensions guide or continue reading this section to see a very simple example. In IPF, all platform-camel-* components contribute their extensions to the scripting layer i.e. the DSL extension mechanism supports modularization (DSL extensions provided by individual components are pluggable).

In addition to predefined DSL extensions applications can easily define their own DSL extensions, for example, to give a project-specific message processor or a message processing pattern a custom name. This is explained in detail in the DSL extensions guide. It makes message processing routes more readable and allows other projects to reuse these extensions. DSL extensions may also be based on other DSL extensions i.e. composition of DSL extensions is supported as well. There is also extensive support for Groovy closures in combination with existing Camel DSL elements and IPF DSL extensions.

DSL extension mechanism

The DSL extension mechanism allows for extending the Apache Camel DSL and the IPF DSL with custom language elements. The definition of extensions is done with Groovy meta-programming. Route definitions that use DSL extensions must therefore be written in Groovy. A Groovy meta-programming-based mechanism was chosen because Camel itself doesn't support DSL extensions on Java-level (at least at the time of writing where Camel 1.6 was out).

Example

DSL extensions guide and tutorials

The example presented here is explained in much more detail in the DSL extensions guide. Furthermore, all tutorials make use of the DSL extension mechanism and the predefined DSL extensions.

The easiest way to learn the DSL extension mechanism is to start with a simple example. Let's say we want to introduce a new translate DSL element that translates a message from one language into another. To keep the examle simple we assume that the translating magic is done inside a single Translator processor.

Translator.java
package example;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;

public class Translator implements Processor {

    public void process(Exchange exchange) throws Exception {
        // do language translation here ...
    }

}

Without using DSL extensions such a processor can be included into route definitions like in the following example.

MyRouteBuilder.groovy
package example

import org.apache.camel.spring.SpringRouteBuilder

class MyRouteBuilder extends SpringRouteBuilder {

    void configure() {
        from('direct:input')
        .process(new Translator())
        .to('direct:output')

    }

}

Our goal however is to introduce a translate DSL extension that makes use of this processor. The result should look like.

MyRouteBuilder.groovy
package example

import org.apache.camel.spring.SpringRouteBuilder

class MyRouteBuilder extends SpringRouteBuilder {

    void configure() {
        from('direct:input')
        .translate()
        .to('direct:output')

    }

}

To be able to use the translate() method in our route definitions we need to add that method into the corresponding DSL model class which is org.apache.camel.model.ProcessorType in our example. Adding the method is done via Groovy meta-programming inside an extensions block.

MyExtension.groovy
package example

import org.apache.camel.model.ProcessorType

class MyExtension {

    static extensions = {

        ProcessorType.metaClass.translate = {->
            delegate.process(new Translator())
        }

    }

}

You can add an extensions block to any class you like. To make the example work we have to wire the DSL extension, the route builder and the infrastructure components of the DSL extension mechanism together within a Spring application context.

context.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:camel="http://activemq.apache.org/camel/schema/spring"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://activemq.apache.org/camel/schema/spring 
http://activemq.apache.org/camel/schema/spring/camel-spring.xsd">

    <camel:camelContext id="camelContext" />
    
    <bean id="routeBuilder" depends-on="routeModelExtender"
        class="example.MyRouteBuilder">
    </bean>
    
    <bean id="exampleModelExtension"
        class="example.MyExtension">
    </bean>
    
    <bean id="coreModelExtension"
        class="org.openehealth.ipf.platform.camel.core.extend.CoreModelExtension">
    </bean>
    
    <bean id="routeModelExtender"
        class="org.openehealth.ipf.platform.camel.core.extend.DefaultModelExtender">
        <property name="routeModelExtensions">
            <list>
                <ref bean="coreModelExtension" />
                <ref bean="exampleModelExtension" />
            </list>
        </property>
    </bean>

</beans>

The above example also shows how to include the predefined DSL extensions provided by platform-camel-core. Please note that this is not required to make our example work. It's only for demonstration purposes how to combine predefined DSL extensions with custom DSL extensions.

Limitations

DSL extensions defined in Groovy are not available for Camel's Java DSL or for the Spring-based XML configuration. Defining extensions for multiple languages is currently under discussion for Camel 2.x. Also, Java IDE's cannot detect extensions introduced via Groovy meta-programming i.e. code completion for these extensions isn't supported at the moment. We currently investigate whether this is possible with IntelliJ IDEA.

Predefined DSL extensions

Predefined DSL extensions are provided by the following IPF components.

Component Extension class Documentation
platform-camel-core org.openehealth.ipf.platform.camel.core.extend.CoreModelExtension Core features DSL
platform-camel-flow org.openehealth.ipf.platform.camel.flow.extend.FlowModelExtension Flow management DSL
platform-camel-hl7 org.openehealth.ipf.platform.camel.hl7.extend.Hl7ModelExtension HL7 processing DSL
platform-camel-lbs org.openehealth.ipf.platform.camel.lbs.core.extend.LbsModelExtension LBS processing DSL
platform-camel-event org.openehealth.ipf.platform.camel.event.extend.EventModelExtension Event processing DSL

A description of these components is given on the IPF architecture page. For a detailed description of the extensions follow the links in the extension documentation column. The extension classes can be combined as shown in the application configuration section.

Summary of all predefined DSL extensions

A summary of all predefined DSL extensions provided by IPF is given in the DSL extensions index appendix.

Module adapters

Module adapters integrate components of the modules namespace into Camel routes. Components defined in this namespace usually implement interfaces defined in the org.openehealth.ipf.commons.core.modules.api package which is part of the commons-core component. These interfaces represent message processors like Validator, Aggregator or Transmogrifer (a transformer), to mention a few. The intention of these interfaces was to define message processors that are independent of integration infrastructures like Apache Camel or Apache ServiceMix in order to increase their reusability. The integration of these message processors into Camel routes is done via generic module adapters. They translate from Camel-specific interfaces to org.openehealth.ipf.commons.core.modules.api.* interfaces and are provided by the platform-camel-core component. The following UML diagram gives an overview (some of the relevant interfaces and adapters have been omitted)

Factory methods for module adapters are defined in the org.openehealth.ipf.platform.camel.core.builder.RouteHelper class. The adapters created by these factory methods implement Camel-specific interfaces like org.apache.camel.Processor or org.apache.camel.Predicate, for example. Use the factory methods directly if you want to write your route definitions in Java. If you want to use module adapters in Groovy you might want to have a look at the DSL extensions for IPF module adapters section.

Module adapter alternatives

An alternative to module adapters is Camel's bean integration mechanism.

Custom processors

Custom processors provided by platform-camel-core are described in the DSL extensions for custom IPF processors section. If you want to use these processors in Java-based route definitions you might want to have a look at the corresponding factory methods in the org.openehealth.ipf.platform.camel.core.builder.RouteHelper class. These factory methods and the processors they create are described in detail in the API docs.

DSL extensions

DSL extension for existing Camel features

Closure support

The following Camel core DSL elements have been extended to accept closures as arguments

  • process
  • intercept
  • filter
  • setHeader
  • setOutHeader
  • setFaultHeader
  • setProperty
  • setBody
  • transform
  • when
  • onWhen

Closure support for further DSL elements will follow in later IPF releases. These extensions are defined in the org.openehealth.ipf.platform.camel.core.extend.CoreModelExtension.groovy class. For example, to define a processor closure that reverses the input message body you would write:

'processor' closure
from('direct:input')
    .process {exchange ->
        exchange.in.body = exchange.in.body.reverse()
    }
    .to('mock:output')

The closure parameter is the org.apache.camel.Exchange to be processed. Defining an interceptor closure is similar:

'interceptor' closure
from('direct:input')
    .intercept {exchange, next ->
        // do some pre-processing work
        // ...

        // proceed with the next processor
        next.proceed(exchange)

        // do some post-processing work ...
        // ...
    }
    .to('mock:output')

The interceptor closure is passed a second argument (called next in our example) on which the proceed() method must be called. This method continues processing with the next processor and returns after down-stream processing has been done.

A filter closure is a closure that evaluates an org.apache.camel.Exchange and returns either true or false i.e. a filter closure is a predicate:

'filter' closure
from('direct:input')
    .filter {exchange ->
        exchange.in.body == 'blah'
    }
    .to('mock:output')

Here, the filter closure returns true if the in-message body equals 'blah'. If true is returned the filter forwards the message to the next processor, otherwise, the message is filtered out.


Closures can also be used for setting message headers. In the following example the foo-header is assigned the in-message's body.

'setHeader' closure
from('direct:input')
    .setHeader('foo') {exchange ->
        exchange.in.body
    }
    .to('mock:output')

The header name is given as first setHeader argument. The header value is the return value of the closure. You can also use closures with setOutHeader and setFaultHeader.


To set an exchange property you can use a setProperty-closure.

'setProperty' closure
from('direct:input')
    .setProperty('foo') {exchange ->
        exchange.in.body
    }
    .to('mock:output')


You can also set the in-message body with a closure. In the following example the in-message body is set to the value of the in-message foo-header.

'setBody' closure
from('direct:input')
    .setBody {exchange ->
        exchange.in.headers.foo
    }
    .to('mock:output')


The transform DSL element also supports expression closures. For example, to reverse a string contained in the in-message body and write the result to the out-message body use:

'transform' closure
from('direct:input')
    .transform {exchange ->
        exchange.in.body.reverse()
    }


Closure support was also added for content-based routing:

'when' closure
from('direct:input1')
    .choice()
    .when { it.in.body == 'a'}
        .to('mock:output1')
    .when { it.in.body == 'b'}
        .to('mock:output2')

when-closures implement routing decisions and return either true or false. In our example: If the in-message body equals a the message is forwarded to endpoint mock:endpoint1. If the in-message body equals b the message is forwarded to endpoint mock:endpoint2.


The onWhen DSL element allows for finer-grained exception handling. With IPF you can implement onWhen predicates with closures. The following example tries to match the in-message body with a regular expression.

'onWhen' closure
onException(Exception.class)
  .onWhen {it.in.body ==~ /u.w/}
  .to('direct:error')

Bean lookup


The process DSL element can also be used in combination with a bean name. The following route definition references a bean named sampleProcessor from the application context. The bean must implement the org.apache.camel.Processor interface.

Processor bean
from('direct:input1')
    .process('sampleProcessor')
    .to('mock:output')

Error handler


Another convenience DSL extension provided by IPF is the unhandled DSL element. It replaces the verbose errorHandler(noErrorHandler()) statement in route definitions i.e. it drops the error handler from a route.

'unhandled' extension
from('direct:input')
    .unhandled()
    // further processing here ...
    .to('mock:output')

The unhandled extension currently cannot be used globally for all routes i.e. a builder.unhandled() call is not supported yet.

DSL extensions for ExpressionClause

Exception objects and messages

The model class org.apache.camel.builder.ExpressionClause has been extended with expressions to access the exception object or the exception message of an exchange. The corresponding DSL extensions are exceptionMessage and exceptionObject. For example,

'exceptionMessage' extension
from('direct:input')
    .onException(ValidationException.class).setBody().exceptionMessage().to('mock:error').end()
    // ... ValidationException thrown here ...
    .to('mock:output')

sets the detail message of the ValidationException (obtained via ValidationException.getMessage) to the in-message body of the exchange. This extensions is particularly useful in exception routes because there you don't have access to the exception object via Exchange.getException(). In the next example we set the exception object on a custom header.

'exceptionObject' extension
from('direct:input')
   .onException(ValidationException.class).setHeader('foo').exceptionObject().to('mock:error').end()
    // ... ValidationException thrown here ...
    .to('mock:output')

Here, the in-message that arrives at mock:error has a foo header with the ValidationException object.

DSL extensions for custom IPF processors

IPF provides message processors that have been developed on top of Apache Camel. Initially, they were included into route definitions via the default extension points provided by Camel (e.g. the org.apache.camel.Processor or org.apache.camel.Predicate interfaces). With the IPF scripting layer, they are now available as first-class DSL elements. This section presents those DSL extensions that are independent of the IPF module adapters. The next section is about module-adapter-related DSL extensions.

Content enrichment

Content enricher contributed to Camel

The IPF content enricher has been contributed to Camel and is available there since version 2.0-m1. We will drop the content enricher from the IPF code base as soon as we upgraded to Camel 2.0. However, we will continue to support merge closures (see below). The contributed content enricher is also documented in the Camel Wiki.

The content enricher creates an additional message exchange from the original exchange for communicating with a so-called resource endpoint. This endpoint is used to obtain additional data to enrich the original exchange. The response from the resource endpoint is merged into the original message exchange using a configurable merge logic. In the following example the merge logic is defined by a closure:

'enrich' extension using a merge closure
from('direct:input')
    .enrich('direct:resource') {originalExchange, resourceExchange ->
        originalExchange.in.body += ':' + resourceExchange.out.body
        // return value is optional if originalExchange
        // shall be returned as merge result, otherwise
        // it must be returned explicitly.
        originalExchange
    }
    .to('mock:output')

In this example, the enricher obtains additional data from the direct:resource endpoint and passes the originalExchange and resourceExchange to a closure that merges data from the resourceExchange into originalExchange. The message exchange containing the merge result is returned by the closure. Returning an exchange is optional if the originalExchange object contains the merge result. Instead of providing merge logic via a closure you may also provide an org.apache.camel.processor.aggregate.AggregationStrategy instance.

'enrich' extension using an AggregationStrategy object
AggregationStrategy strategy = new MyCleverAggregationStrategy()

from('direct:input')
    .enrich('direct:resource', strategy)
    .to('mock:output')

Validation process

The validation DSL extension implements a simple validation process that delegates the actual message validation to a validator which can be an endpoint or an object that implements validation logic. If validation succeeds the message is forwarded to the next processor defined in the route, otherwise, the message is dropped. In both cases the response generated by the validator is returned to the sender of the original message exchange.


In the following example the validation process delegates message validation to the direct:validator endpoint. If validation succeeds the message is forwarded as in-only exchange to mock:output otherwise it is dropped. The response returned from direct:validator is returned to the direct:input endpoint.

'validation' extension using an endpoint
from('direct:input')
    .validation('direct:validator')
    .to('mock:output')

The validation process interprets a message exchange as failed if any of the following conditions is true:

  • an exception was thrown
  • the message exchange contains an exception
  • the message exchange contains a fault message


Instead of providing an endpoint you may also provide validator logic using a closure.

'validation' extension using a closure
from('direct:input1')
    // generate a 'success' validation response
    .validation {exchange -> exchange.out.body = 'success'}
    .to('mock:output')

from('direct:input2')
    // generate a 'failure' validation fault
    .validation {exchange -> exchange.fault.body = 'failure'}
    .to('mock:output')

from('direct:input3')
    // throw a validation exception
    .validation {throw new ValidationException('input sucks in any case')}
    .to('mock:output')


You may also provide an org.apache.camel.Processor instance for validation

'validation' extension using a processor
Processor validator = new MyFamousValidator()

from('direct:input')
    .validation(validator)
    .to('mock:output')

Splitter

The Splitter is used to split a message into multiple messages. You specify a split rule that defines the way the splitting is performed. Each message generated by the Splitter is send to the next processor defined in the route. The results of this processing are aggregated into the original message using a configurable strategy.


The following shows a String-based example of the Splitter that splits a comma-separated string coming from direct:input and passes the parts on to the mock:output endpoint. If you send the body "hello,world,!" to direct:input, the mock:output will receive three messages with "hello", "world" and "!" in the body.

'split' extension using a closure
from('direct:input')
    .split { Exchange exchange -> exchange.in.body.split(',') }
    .to('mock:output')


The split rule can also be defined via org.apache.camel.Expression:

'split' extension using an expression
Expression splitExpression = new MyCommaSplittingExpression()

from('direct:input')
    .split(splitExpression)
    .to('mock:output')


Finally, the split rule can be implemented as an org.apache.camel.Expression and referred to as a bean:

'split' extension using a bean
from('direct:input')
    .split("MySplitBean")
    .to('mock:output')

No matter how the split rule is defined, the aggregation of all results is by default performed using the UseLatestAggregationStrategy. This strategy simply uses the result of the last message generated by the split. In the above example, "!" is returned to the sender, because it is the last message that was split off.

You can specify a different strategy using aggregate after the split processor. The following example extends the previous one generating an output containing the parts reassembled and separated by a ':'. Therefore, the sender will get the result "hello:world:!".

'split' extension using an explicit aggregation strategy

from('direct:input')
    .split { Exchange exchange -> exchange.in.body.split(',') }
    .aggregate { oldExchange, newExchange ->
          String oldContent = oldExchange.in.body
          String newContent = newExchange.in.body
          Exchange aggregate = oldExchange.copy()
          aggregate.in.body = oldContent + ":" + newContent
          aggregate
    }
    .to('mock:output')

The aggregation strategy is called only if the split resulted in multiple messages. If no messages were generated, the result is the original message. If only one message was split off, this message is used as the result. In the above example, the aggregation strategy is called twice with the following messages as parameters:

First call: oldExchange = "hello", newExchange = "world" -> returns "hello:world"
Second call: oldExchange = "hello:world", newExchange = "!" -> returns "hello:world:!"

DSL extensions for IPF module adapters

How this section is organized

This section is organized in the following way.

  • Detailed description of the transmogrify extension. Many concepts described here also apply to other module adapter extensions.
  • Overview of the remaining module adapter extensions. This overview only covers topics that haven't been discussed for the transmogrify extension.
  • Comprehensive summary of all module adapter extensions. A complete reference for module adapter extensions in tabular form (without detailed examples).

Transmogrifier

The easiest way to describe the DSL extensions for IPF module adapters is to start with an example. Let's use the org.openehealth.ipf.commons.core.modules.api.Transmogrifier interface for that purpose. Inspired by Calvin and Hobbes, a transmogrifier converts anything into whatever you like. Transmogrification is accompanied by a loud zap:

Transmogrifier.java
public interface Transmogrifier<S, T> {

    T zap(S object, Object... params);

}

Implementations of Transmogrifier are often used for message transformation. Transformation input is given by the object parameter and optionally some additional params. The transformation result is the return value of the zap method. To include a Transmogrifier instance into a Camel route we use the transmogrify DSL extension:

'transmogrify' extension using a transmogrifier object
org.openehealth.ipf.commons.core.modules.api.Transmogrifier transmogrifier = new MyTransmogrifier()
from('direct:input') 
    .transmogrify(transmogrifier)
    .to('mock:output')

Behind the scenes the transmogrify element creates an org.openehealth.ipf.platform.camel.core.adapter.TransmogrifierAdapter as described in the module adapters section. This adapter adapts the org.openehealth.ipf.commons.core.modules.api.Transmogrifier interface to an org.apache.camel.Processor interface. The adapter (processor) is included into the Camel route at the position where the transmogrify extension is used. During message processing the adapter accepts an org.apache.camel.Exchange, extracts the input from that exchange, delegates message processing to the adapted transmogrifier instance and populates the exchange with the transformation result.

Inclusion options

There are three different ways of including a transmogrifier into a Camel route.

  1. Pass a transmogrifier object as argument to the transmogrify() method. This has already been shown in the example route above.
  2. Pass the name of a transmogrifier bean as argument to the transmogrify method (see below). A bean with that name must exist in the Spring application context.
  3. Define a transmogrifier logic inline using a closure (see below). This is comparable to implement an anonymous Transmogrifier class.

'transmogrify' extension using a transmogrifier bean
from('direct:input') 
    .transmogrify('myTransmogrifierBean')
    .to('mock:output')

'transmogrify' extension using a closure
from('direct:input') 
    .transmogrify { body, headers ->
        def result = ... // create result from input body and headers
        return result    // return the transformation result
    }
    .to('mock:output')
Inclusion pattern

This is a pattern that also applies to all other DSL extensions for IPF module adapters: validate, parse, render, predicate and aggregationStrategy. The adapted object can either be included into a Camel route directly as object, indirectly via a Spring bean name or defined (inline) with a closure. Closures are not supported for parse and render.

Transmogrifier input

Default arguments to the Transmogrifier.zap(S object, Object... params) method are:

  • The in-message body for the object parameter.
  • The in-message headers for the params parameter.

A transmogrify closure may define a one or two parameters.

  • The first parameters corresponds to the object parameter.
  • The second parameter corresponds to the params parameter.

of the zap method.

  • The default argument to the first closure parameter is the in-message body.
  • The default argument to the second closure parameter are the in-message headers (a java.util.Map).

Input to the zap method as well as the transmogrify closure can be customized via the

  • input
  • params and
  • staticParams

DSL extensions. input and params either accept an org.apache.camel.Expression as argument or an expression closure. In both cases an org.apache.camel.Exchange is evaluated. The evaluation result will be used as transmogrifier input. The following snippet causes the transmogrifier's zap method to be called with the in-message's foo-header as first argument and the in-messages bar-header as the second argument

'transmogrify' parameterization with input and params
from('direct:input') 
    .transmogrify('myTransmogrifierBean')
    .input { exchange -> exchange.in.headers.foo }
    .params { exchange -> exchange.in.headers.bar }
    .to('mock:output')

The same rules apply for the transmogrify closure parameters.

'transmogrify' parameterization with input and params
from('direct:input') 
    .transmogrify { fooHeader, barHeader ->
        // ...
    }
    .input { it.in.headers.foo }
    .params { it.in.headers.bar }
    .to('mock:output')

The params DSL extension also supports predefined expressions. These are accessible by calling params with no arguments. The following predefined expressions are currently supported as part of the DSL.

Predefined expressions for 'params'
    ...
    .params().headers()           // in-message headers (default for transmogrifiers)
    ...
    .params().header('foo')       // in-message foo-header
    ...
    .params().builder()           // a Groovy XML builder
    ...
    .params().headersAndBuilder() // in-message headers and a Groovy XML builder (params array of length 2)
    ...

These predefined expressions are implemented by the org.openehealth.ipf.platform.camel.core.model.ParamsType model class. The builder and headersAndBuilder extensions are described in section DSL extensions for Groovy XML processing.

The staticParams extension can be used to pass constant values to the transmogrifier or transmogrifier closure. This extension method defines a variable argument parameter. For example to pass a String array with elements 'a', 'b' and 'c' via the params parameter or via the second closure parameter you could use

'transmogrify' parameterization with staticParams
from('direct:input') 
    .transmogrify('myTransmogrifierBean')
    .staticParams('a', 'b', 'c')
    .to('mock:output')
'transmogrify' parameterization with staticParams
from('direct:input') 
    .transmogrify { body, stringArray ->
        // ...
    }
    .staticParams('a', 'b', 'c')
    .to('mock:output')
Input customization for other adapter extensions
Transmogrifier output

The return value of the Transmogrifier.zap(S object, Object... params) method or the return value of the transmogrify closure is written to the org.apache.camel.Exchange object from which the input was taken. It depends on the exchange pattern to which exchange message the result is written. If the exchange is out-capable (i.e. exchange.getPattern().isOutCapable() returns true) then the result is written to the exchange's out-message body, otherwise, it is written to the in-message body. Furthermore, if the exchange is out-capable, the in-message is copied onto the out-message before the result is written (this is useful e.g. for preserving message headers along a precessing chain).

Transmogrifier implementations

IPF provides two Transmogrifier implementations out of the box:

  • org.openehealth.ipf.commons.xml.XsltTransmogrifier for transforming XML documents
  • org.openehealth.ipf.commons.xml.SchematronTransmogrifier for creating Schematron validation reports from XML documents

These implementations are "by-products" for Schematron validation, but you can use them independently as well. Compared to Camel's xslt endpoint (http://camel.apache.org/xslt.html), the IPF counterpart

  • can use variable stylesheets
  • caches XSLT templates for better performance
  • accepts explicit XSLT parameters (not just as Camel message header)

The input is automatically converted into a StreamSource. By default, all Camel headers are added as parameters which are available in the stylesheet unless you define parameters by either using params(...) or staticParams(...).

Example:

IPF transmogrifiers
from('direct:input1') 
    .transmogrify().xslt().staticParams('path/to/stylesheet') // static stylesheet
    .to('mock:output')

from('direct:input1') 
    .transmogrify().xslt().staticParams('path/to/stylesheet', parameterMap) // static stylesheet with parameters
    .to('mock:output')

from('direct:input3')
    .setHeader('stylesheet', constant('path/to/stylesheet')) 
    .transmogrify().xslt().params().header('stylesheet') // dynamic stylesheet
    .to('mock:output')

// In most cases you will need the SchematronValidator, which scans the Schematron report
// for failed assertions. Use only if you require custom processing of the report in the
// route.

from('direct:input3') 
    .transmogrify().schematron().staticParams('path/to/rules', options ) // static rules
    .to('mock:output')

from('direct:input4') 
    .setHeader('rules', constant('path/to/rules')) 
    .transmogrify('schematron').params().header('rules') // dynamic rules
    .to('mock:output')

Please refer to Schematron validationfor description of the available Schematron options.

By default, XSLT transformations return a javax.xml.transform.Result object, which is, however, not very usaful for further processing. IPF's XSLT-related transmogrifiers therefore return a String by default. You can influence the returned type using a Class parameter to the xslt()/schematron() extensions or with a subsequent call to convertBodyTo(String.class):

IPF transmogrifiers with output type
from('direct:input1') 
    .transmogrify().xslt(InputStream.class).staticParams('path/to/stylesheet') 
    .to('mock:output')

from('direct:input2') 
    .transmogrify().xslt().staticParams('path/to/stylesheet') 
    .convertBodyTo(InputStream.class)
    .to('mock:output')

Validator

The modules API defines an org.openehealth.ipf.commons.core.modules.api.Validator interface for message validation.

Validator.java
public interface Validator<S, P> {

    void validate(final S message, final P profile);

}

It defines a single validate method that validates a message against a profile. If validation fails an org.openehealth.ipf.commons.core.modules.api.ValidationException is thrown. The validator is included into Camel routes via the validate DSL extension. The validate extension accepts either a validator object, a validator bean name or a validator closure as argument. If a closure is used, a failed validation is either indicated by throwing an org.openehealth.ipf.commons.core.modules.api.ValidationException or by returning false. If false is returned IPF internally generates a ValidationException. Here are some examples.

'validate' extension
// route 1
from('direct:input1') 
     .validate {body -> body == 'blah'}  
     .to('mock:output')

// route 2
from('direct:input2') 
     .validate {throw new ValidationException('always fail')}  
     .to('mock:output')

// route 3
from('direct:input3') 
     .validate {body, profile -> 
         body == profile
     }
     .staticProfile('blah')
     .to('mock:output')

// route 4
from('direct:input4') 
     .validate {fooHeader, profile -> 
         fooHeader == profile
     }
     .input {it.in.headers.foo}
     .staticProfile('abcd')
     .to('mock:output')

// route 5
from('direct:input5') 
     .validate(...)
     .input(...)
     .profile {exchange ->        
         exchange.in.headers.customProfile
     }                         
     .to('mock:output')

// you may also use validator objects
     ...
     .validate(new MyCustomValidator())
     ...

// you may also use validator beans
     ...
     .validate('myValidatorBean')
     ...
  • In route 1 validation will fail if the in-message body doesn't equal 'blah'. In this case false is returned which causes IPF to throw a ValidationException.
  • In route 2 validation will fail because a ValidationException is thrown directly (regardless of the message content).
  • In route 3 we define a closure with a second parameter for passing a validation profile. By default it is null but it can be customized via the staticProfile DSL extension *). As in route 1 the validation will fail if the in-message body doesn't equal 'blah'.
  • In route 4 we see how input is used to pass the in-message's foo-header as first argument to the validation closure. If the foo-header doesn't equal 'abcd' validation will fail.
  • In route 5 a validation profile is obtained from the in-messages's customProfile header using the profile() DSL extension and a closure. Instead of the closure one can also use an org.apache.camel.Expression instance.

*) In this example we could have hard-coded this profile directly inside the closure as well. Using the profile extension makes more sense when using validator objects or beans like in validate(myValidator) or validate('myValidatorBean').


As of version 1.7, IPF provides a Validator implementation that validates an XML Source against an W3C XML Schema.

'xsd' extension

from('direct:input1') 
     .validate().xsd().staticProfile('schema location')
     .to('mock:output')

The schema location value can be either a URL or a non-URL string, in the latter case the classpath is searched for the schema resource.


As of version 1.7, IPF also provides a Validator implementation that validates an XML Source against a set of Schematron rules.

'schematron' extension

import org.openehealth.ipf.commons.xml.SchematronProfile;
...
from('direct:input1') 
     .validate().schematron().staticProfile(new SchematronProfile('rules location', options))
     .to('mock:output')

Note that you have to provide an instance of SchematronProfile, not just the plain Schematron rules location. The rules location value can be either a URL or a non-URL string, in the latter case the classpath is searched for the schema resource.
The options parameter is optional. If present, it must be of type Map<String, Object> Its purpose is to configure Schematron's validation process. Please refer to the Schematron website http://www.schematron.com for more details.

key description values default
phase Select the phase for validation. Schematron allows for staged validation by assigning phases to validation rules. NMTOKEN | #ALL #ALL
allow-foreign Pass non-Schematron elements and rich markup to the generated stylesheet 'true' | 'false' 'false'
diagnose Add the diagnostics to the assertion test in reports 'true' | 'false' 'true'
property Experimental: Add properties to the assertion test in reports 'true' | 'false' 'true'
generate-paths Generate the @location attribute with XPaths 'true' | 'false' 'true'
sch.exslt.imports semi-colon delimited string of filenames for some EXSLT implementations string ''
optimize Use only when the schema has no attributes as the context nodes 'visit-no-attributes' ''
generate-fired-rule Generate fired-rule elements. Significantly increases report size 'true' | 'false' 'true'

Parser

The modules API org.openehealth.ipf.commons.core.modules.api.Parser interface declares methods for parsing an external representation of information into an internal model.

Parser.java
public interface Parser<S> {

    S parse(String message, Object... params);
    S parse(InputStream message, Object... params) throws IOException;
    S parse(Source source, Object... params) throws IOException;
    S parse(Reader reader, Object... params) throws IOException;

}

The external representation can be obtained from a java.io.InputStream, a java.io.Reader, a javax.xml.transform.Source or directly from a java.lang.String. These options are represented by the four parse methods. A parser is included into Camel routes via the parse DSL extension. IPF selects the appropriate method depending on the type of input data. The return value is the parse result and is written to the org.apache.camel.Exchange object from which the input was taken. It depends on the exchange pattern to which exchange message the result is written. If the exchange is out-capable (i.e. exchange.getPattern().isOutCapable() returns true) then the result is written to the exchange's out-message body, otherwise, it is written to the in-message body. Furthermore, if the exchange is out-capable, the in-message is copied onto the out-message before the result is written (this is useful e.g. for preserving message headers along a precessing chain). Here are some examples.

'parse' extension
// route 1
from('direct:input1')
    .parse(new MyParser())
    .to('mock:output')

// route 2
from('direct:input2')
    .parse('myParserBean')
    .input { it.in.headers.foo }
    .params { it.in.headers.bar }
    .to('mock:output')
  • In route 1 we directly include a MyParser object into the Camel route. Here, the parser input data are taken from the in-message body, the parser params are null.
  • In route 2 we include a Spring bean with name 'myParserBean' into the Camel route. Here, the parser's input data are taken from the in-message's foo-header, the parser params from the bar-header.

Closures for parse are currently not suppported.

Unmarshalling via Parser

IPF also provides an org.apache.camel.spi.DataFormat implementation that delegates unmarshal work to a parser.

'parse' extension for unmarshalling
from("direct:input1")
    .unmarshal().parse(new MyParser())
    ...

from("direct:input2")
    .unmarshal().parse('myParserBean')
    ...

However, using parse for unmarshalling currently doesn't allow input customization via input, params or staticParams.

Renderer

The modules API org.openehealth.ipf.commons.core.modules.api.Renderer interface declares methods for creating an external representation of an internal model.

Renderer.java
public interface Renderer<T> {

    Result render(final T model, Result result, final Object... params) throws IOException;
    OutputStream render(final T model, OutputStream result, final Object... params) throws IOException;
    Writer render(final T model, Writer result, final Object... params) throws IOException;
    String render(final T model, final Object... params);

}

Currently, only the last method i.e. the one that returns a java.lang.String is used by IPF. A renderer is included into Camel routes via the render DSL extension. IPF selects the appropriate method depending on the type of input data. The return value is the rendering result and is written to the org.apache.camel.Exchange object from which the input was taken. It depends on the exchange pattern to which exchange message the result is written. If the exchange is out-capable (i.e. exchange.getPattern().isOutCapable() returns true) then the result is written to the exchange's out-message body, otherwise, it is written to the in-message body. Furthermore, if the exchange is out-capable, the in-message is copied onto the out-message before the result is written (this is useful e.g. for preserving message headers along a precessing chain). Here are some examples.

'render' extension
// route 1
from('direct:input1')
    .render(new MyRenderer())
    .to('mock:output')

// route 2
from('direct:input2')
    .render('myRendererBean')
    .input { it.in.body[0] }
    .params { it.in.headers.bar }
    .to('mock:output')
  • In route 1 we directly include a MyRenderer object into the Camel route. Here, the renderer input data are taken from the in-message body, the renderer params are null.
  • In route 2 we include a Spring bean with name 'myRendererBean' into the Camel route. Here, the renderer's input data are taken from the first element of a list that is contained in the in-message's body. The renderer params are taken from the in-messages bar-header.

Closures for render are currently not suppported.

Marshalling via Renderer

IPF also provides an org.apache.camel.spi.DataFormat implementation that delegates marshal work to a renderer.

'render' extension for marshalling
from("direct:input1")
    .marshal().render(new MyRenderer())
    ...

from("direct:input2")
    .unmarshal().render('myRendererBean')
    ...

However, using render for marshalling currently doesn't allow input customization via input, params or staticParams.

Predicate

The org.openehealth.ipf.commons.core.modules.api.Predicate interface declares a matches method for evaluating a binary predicate on a source object.

'Predicate'
public interface Predicate<T> {

    boolean matches(T source, Object... params);

}


The predicate DSL extension can be used to include these predicates into Camel routes. It is implemented with the org.openehealth.ipf.platform.camel.core.adapter.PredicateAdapter that translates between org.openehealth.ipf.commons.core.modules.api.Predicate and org.apache.camel.Predicate. The predicate extension supports a predicate object, a bean name or a closure as argument. The created Camel predicate can then be used with e.g. filter or other DSL elements that expect an org.apache.camel.Predicate. The predicate extension is provided by an IPF extension to org.apache.camel.spring.SpringRouteBuilder. Here are some examples:

'predicate' extension
import org.apache.camel.spring.SpringRouteBuilder
import org.openehealth.ipf.commons.core.modules.api.Predicate
...

class MyRouteBuilder extends SpringRouteBuilder {
    void configure() {
        Predicate myPredicate = new MyPredicate()

        def predicate1 = predicate(myPredicate)
        def predicate2 = predicate('myPredicateBean') 
        def predicate3 = predicate { body -> body == 'test'}

        from('direct:input1').filter(predicate1).to('mock:output')
        from('direct:input2').filter(predicate2).to('mock:output')
        from('direct:input3').filter(predicate3).to('mock:output')
...

Input to the org.openehealth.ipf.commons.core.modules.api.Predicate.matches() method can be customized via the input, params or staticParams DSL extensions.

'predicate' extension with input customization
def predicate4 = 
    predicate { fooHeader, barHeader -> ... }
        .input { it.in.headers.foo }
        .params { it.in.headers.bar }

Aggregator

The org.openehealth.ipf.commons.core.modules.api.Aggregator interface is a transmogrifier that combines/aggregates a collection of input object into a result object. The result object is the return value of the zap method.

Aggregator.java
public interface Transmogrifier<S, T> {
    T zap(S object, Object... params);
}

public interface Aggregator<S, T> extends Transmogrifier<Collection<S>, T>

{anchor:aggregationStrategy}}
The aggregationStrategy DSL extension can be used to include an Aggregator into Camel routes. It is implemented with the org.openehealth.ipf.platform.camel.core.adapter.AggregatorAdapter that translates between org.openehealth.ipf.commons.core.modules.api.Aggregator and org.apache.camel.processor.aggregate.AggregationStrategy. The aggregationStrategy extension supports an aggregator object, a bean name or a closure as argument. The created Camel AggregationStrategy can then be used with e.g. enrich or other DSL elements that expect an org.apache.camel.processor.aggregate.AggregationStrategy. The aggregationStrategy extension is provided by an IPF extension of the org.apache.camel.spring.SpringRouteBuilder. Here are some examples:

'aggregationStrategy' extension
def aggregationStrategy1 = aggregationStrategy(new TestAggregator())
def aggregationStrategy2 = aggregationStrategy('sampleAggregator') 
def aggregationStrategy3 = aggregationStrategy {originalInBody, resourceOutBody -> 
    originalInBody + ':' + resourceOutBody
} 
def aggregationStrategy4 = aggregationStrategy {originalInBody, resourceOutBody, fooHeader -> 
    originalInBody + ':' + resourceOutBody  + ':' + fooHeader
}
.input            {originalExchange -> originalExchange.in.body}        // relates to 1st parameter
.aggregationInput {resourceExchange -> resourceExchange.out.body}       // relates to 2nd parameter
.params           {originalExchange -> originalExchange.in.headers.foo} // relates to 3rd parameter

from('direct:input1').enrich('direct:resource', aggregationStrategy1).to('mock:output')
from('direct:input2').enrich('direct:resource', aggregationStrategy2).to('mock:output')
from('direct:input3').enrich('direct:resource', aggregationStrategy3).to('mock:output')
from('direct:input4').enrich('direct:resource', aggregationStrategy4).to('mock:output')

The collection passed to the org.openehealth.ipf.commons.core.modules.api.Aggregator.zap method is a list of two objects - objects that have been derived from the arguments to the org.apache.camel.processor.aggregate.AggregationStrategy.aggregate method. If you use a closure you must define at least two parameters that correspond to the parameters of org.apache.camel.processor.aggregate.AggregationStrategy.aggregate. An optional third parameters corresponds to the params parameter that is common to all modules interfaces. Input can be customized via the input, params or staticParams DSL extensions. Input for the second object in the input list or the second closure parameter can be customized via the aggregationInput DSL extension (see aggregationStrategy4 in the example above).

Adapter extension summary

Relevant types
DSL extension Modules Interface *) Adapter class **) Extended model class
transmogrify Transmogrifier TransmogrifierAdapter org.apache.camel.model.ProcessorType
validate Validator ValidatorAdapter org.apache.camel.model.ProcessorType
parse Parser ParserAdapter org.apache.camel.model.ProcessorType
org.apache.camel.builder.DataFormatClause
render Renderer RendererAdapter org.apache.camel.model.ProcessorType
org.apache.camel.builder.DataFormatClause
predicate Predicate PredicateAdapter org.apache.camel.spring.SpringRouteBuilder
aggregationStrategy Aggregator AggregatorAdapter org.apache.camel.spring.SpringRouteBuilder

*) in package org.openehealth.ipf.commons.core.modules.api
**) in package org.openehealth.ipf.platform.camel.core.adapter

Parameters and input
DSL extension Parameter types Input customization
transmogrify
  • Transmogrifier
  • java.lang.String (bean name)
  • groovy.lang.Closure
  • input
  • params
  • staticParams
validate
  • Validator
  • java.lang.String (bean name)
  • groovy.lang.Closure
  • input
  • profile
parse
  • Parser
  • java.lang.String (bean name)
  • input
  • params
  • staticParams
render
  • Renderer
  • java.lang.String (bean name)
  • input
  • params
  • staticParams
predicate
  • Predicate
  • java.lang.String (bean name)
  • groovy.lang.Closure
  • input
  • params
  • staticParams
aggregationStrategy
  • Aggregator
  • java.lang.String (bean name)
  • groovy.lang.Closure
  • input
  • aggregationInput
  • params
  • staticParams
Closure profiles
DSL extension Parameter 1 Parameter 2 Parameter 3 Return value
transmogrify
  • Default value: in-message body
  • Customization via: input
  • Mandatory: true
  • Default value: in-message headers
  • Customization via: params, staticParams
  • Mandatory: false
- Transformation result (any type)
validate
  • Default value: in-message body
  • Customization via: input
  • Mandatory: true
  • Default value: null
  • Customization via: profile, staticProfile
  • Mandatory: false
- boolean or throws ValidationException
predicate
  • Default value: in-message body
  • Customization via: input
  • Mandatory: true
  • Default value: null
  • Customization via: params, staticParams
  • Mandatory: false
- boolean
aggregationStrategy
  • Default value: in-message body of oldExchange
  • Customization via: input
  • Mandatory: true
  • Default value: out-message body of newExchange
  • Customization via: aggregationInput
  • Mandatory: true
  • Default value: null
  • Customization via: params, staticParams
  • Mandatory: false
aggregation result (any type)

DSL extensions for Groovy XML processing

IPF provides support for Groovy XML processing within Camel routes. Here's a summary of features

XML Unmarshalling with Groovy XmlParser

To unmarshal an XML stream or string using a groovy.util.XmlParser use Camel's unmarshal method with IPF's gnode extension.

'gnode' extension for unmarshalling (namespace-aware)
from('direct:input1')
    .unmarshal().gnode()
    .transmogrify { node ->
        // process XML ...
    }

This puts the parser result into the message body which can then be used in subsequent processors. By default, gnode is namespace-aware. To disable namespace-awareness use gnode(false).

'gnode' extension for unmarshalling (namespace-unaware)
from('direct:input1')
    .unmarshal().gnode(false)
    .transmogrify { node ->
        // process XML ...
    }

As of IPF 1.7, in order to add XML schema validation, simply add a schema location parameter. The value can be either a URL or a non-URL string, in the latter case the classpath is searched for the schema resource:

'gnode' extension for unmarshalling (namespace- and schema-aware)
from('direct:input1')
    // will throw an Exception if XML does not validate against the schema
    .unmarshal().gnode('path/to/schema.xsd', true) 
    .transmogrify { node ->
        // process XML ...
    }

XML Unmarshalling with Groovy XmlSlurper

To unmarshal an XML stream or string using a groovy.util.XmlSlurper use Camel's unmarshal method with IPF's gpath extension.

'gpath' extension for unmarshalling (namespace-aware)
from('direct:input1')
    .unmarshal().gpath()
    .transmogrify { gpathResult ->
        // process XML ...
    }

This puts the slurper result into the message body which can then be used in subsequent processors. By default, gpath is namespace-aware. To disable namespace-awareness use gpath(false).

'gpath' extension for unmarshalling (namespace-unaware)
from('direct:input1')
    .unmarshal().gpath(false)
    .transmogrify { gpathResult ->
        // process XML ...
    }

As of IPF 1.7, in order to add XML schema validation, simply add a schema location parameter. The value can be either a URL or a non-URL string, in the latter case the classpath is searched for the schema resource:

'gpath' extension for unmarshalling (namespace- and schema-aware)
from('direct:input1')
    // will throw an Exception if XML does not validate against the schema
    .unmarshal().gpath('path/to/schema.xsd', true) 
    .transmogrify { node ->
        // process XML ...
    }

XML Marshalling with Groovy XmlNodePrinter

The reverse operations to unmarshal().gnode() and unmarshal().gpath() are marshal().gnode() and marshal().gpath(), respectively. Currently only marshal().gnode() is supported. This writes a groovy.util.Node to an output stream using Groovy's groovy.util.XmlNodePrinter

'gnode' extension for marshalling (namespace-aware)
    ...
    .marshal().gnode()
    .to('mock:mock')

This makes the printer result available as byte array in the message body. By default gnode is namespace-aware. To disable namespace-awareness use gnode(false).

'gnode' extension for marshalling (namespace-unaware)
    ...
    .marshal().gnode(false)
    .to('mock:mock')

XML transmogrifiers

IPF provides an easy way to make Groovy XML markup builders available inside transmogrifiers or transmogrifier closures. Here's how it works for transmogrifier closures.

XML transmogrifier
from('direct:input1')
    .transmogrify { body, xmlBuilder ->
        // use xmlBuilder to create XML
        ...
        // return xmlBuilder result
        xmlBuilder.result
    }
    .params().builder()

The params().builder() call makes the builder available via the second closure parameter. A new instance of the builder is passed with every call, so using that builder is thread-safe. If you want to have both, the in-message headers and the builder, for the second closure parameter then use the predefined headersAndBuilder() extension. This extension creates an array of length 2 where the first array element is the in-message headers and the second element is the XML builder.

XML transmogrifier
from('direct:input1')
    .transmogrify { body, params ->
        def msgHeaders = params[0]
        def xmlBuilder = params[1]
        // use builder to create XML
        ...
        // return builder result
        xmlBuilder.result
    }
    .params().headersAndBuilder()

The xmlBuilder.result property was added by a class that subclasses groovy.xml.MarkupBuilder. It contains the result XML document as String.

XML message transmformations

By combining params().builder() with unmarshal().gnode() or unmarshal().gpath() transmogrifiers can implement XML message transformations based on Groovy's XML support. Transformation logic usually extracts content from an XML source document and includes that content into a result document. Navigation and content extraction from source documents is done via GPath expressions. Creation of result documents is done with Groovy's XML markup builder. The following example shows how an XML transformation route can be set up.

from(...)
    .unmarshal().gpath()
    .transmogrify { gpathResult, xmlBuilder ->
        // use gpathResult to read from source XML
        // use xmlBuilder to create result XML
        ...
        // return builder result
        xmlBuilder.result
    }
    .params().builder()
    .to(...)
{code}

In most cases creating a result document using an XML builder goes over a large number of lines of code and you probably want to factor that code out to a Transmogrifier that is implemented in Groovy. Here's an example.

MyTransmogrifier.groovy
public class MyTransmogrifier implements Transmogrifier {

     Object zap(Object input, Object... params) {
         // obtain builder from params
         def builder = params[0]
         // create XML document using builder
         ...
         // return result
         builder.result
     }

}

Such a Transmogrifier implementation can then be included into Camel routes either directly as transmogrifier object or via a Spring bean name.

XML transmogrifier object
from('direct:input1')
    .transmogrify(new MyTransmogrifier())
    .params().builder()
XML transmogrifier bean
from('direct:input1')
    .transmogrify('myTransmogrifierBean')
    .params().builder()
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.