Appendix B - IPF tutorials
The tutorials presented here cover the extensions that the Open eHealth IPF makes to Apache Camel. For an overview of these extensions refer to the overview section of the IPF reference manual. The focus of the IPF tutorials will be on writing integration solutions based on the IPF Scripting Layer which leverages the Groovy programming language. Although you may continue to write integration solutions in Java you'll miss many features for HL7 message processing that are available in Groovy only like the HAPI DSL, for example. Here's a list of tutorials for IPF.
| Tutorial | Description |
|---|---|
| First steps | This tutorial is targeted at developers who want to get started with IPF but do not have a strong background on Camel and Groovy. |
| First details | This tutorial is targeted at developers who want to get started with IPF and who are already familiar with Camel and Groovy. |
| HL7 processing | Guides through some HL7 message processing examples. |
| Reference application | Guides through the IPF reference application. |
| XDS demo repository | Guides through the IPF XDS demo repository. |
| IPF extension mechanism tutorial | Usage examples for the IPF extension mechanism. |
First steps tutorial
| Prerequisites Before you start working on this tutorial make sure that you've read the IPF development pages for setting up the development environment. For this tutorial it is not necessary to checkout the IPF sources, all IPF dependencies are downloaded from the Open eHealth Maven repository. |
This tutorial is targeted at developers who want to get started with IPF and do not have a strong background on Camel and Groovy. The main goal is to have a very simple IPF application up and running very quickly. More technical background information is given in the First details tutorial.
| IPF 2.5 This tutorial refers to IPF version 2.5 or newer |
- We start by creating a project from an IPF archetype
- We see how integration logic looks like in IPF, using the IPF Scripting Layer
- We then extend integration logic in order to
- receive a message over HTTP
- transform that message
- write the result to a file.
- The messaging solution will be tested both automated and manually.
- The messaging solution will be packaged, installed and started.
Source code
The source code for this tutorial can be downloaded from here.
Project creation
We start by creating an IPF project via a Maven archetype. Navigate to a directory of your choice and create the project by entering the following on the command line (make sure to have the command on a single line):
mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-4:generate -DarchetypeGroupId=org.openehealth.ipf.archetypes -DarchetypeArtifactId=ipf-archetype-basic -DarchetypeVersion=2.5.0 -DgroupId=org.openehealth.tutorial -DartifactId=basic -Dversion=1.0-SNAPSHOT -DinteractiveMode=false
This will create a folder named basic in the current directory. Change to the basic folder and enter
mvn install
This will compile the project and install the project artifacts into your local Maven cache. Enter
mvn eclipse:eclipse
to generate the Eclipse project.
To import the project start Eclipse, navigate to File->Import->General->Existing Projects into Workspace and select the created basic folder as root directory. After having imported the project it should look similar to the following figure (exact display might vary depending on your workspace settings).

| It is assumed that you have installed the Groovy Eclipse plug-in as described in our development setup |
In addition to an IPF application skeleton the archetype also created some simple example files that can be used for initial experiments.
We'll mainly work with org.openehealth.tutorial.SampleRouteConfig.groovy in the src/main/resources path, which implements a trivial piece of message processing.
Route definition
SampleRouteBuilder.groovy defines two routes. A route is a logical unit of a sequence of message processing steps. The routes are implemented in the Groovy programming language, but we'll see that the syntax used is very close to Java.
- The first route receives a message from a direct:input1 endpoint, and transmogrifies (i.e. convert to something different) the message by duplicating it. The duplication is defined by a Groovy Closure, an elegant way to define a simple code block that is executed when a message arrives. The it-variable is the Groovy default parameter to the code block, which in our case contains the message we're processing. In Groovy you can use the * (multiply) operator on a string for repeating that string.
- The second route that receives a message from a direct:input2 endpoint, reverses the input message body and returns the result. The reverse() command is a tutorial-specific extension defined in SampleModelExtension.groovy and is just a shortcut notation for
.transmogrify { it.reverse() }
For the moment we can treat direct endpoints as internal labels of the Camel routes, that can e.g. be used from JUnit tests like the following:
This test sends a message to each endpoint and expects the messages to be duplicated or reversed, respectively.
Route extension
We now extend the example to make our integration logic accessible from the outside.
- We'll receive an HTTP request message from a jetty endpoint,
- transform it and
- send it to a file endpoint.
In addition we want to keep the direct endpoints so that we can still test the message transformation with the JUnit test shown above. Instead of only returning the processing result to the HTTP client we also write the message to a file. Here's the extended route definition:
- To receive messages over HTTP we have to create an HTTP endpoint, which will run a Jetty HTTP server listening on port 7799. The endpoint doesn't automatically convert the HTTP body from an InputStream to a String so we have to do that manually via convertBodyTo(String.class) as we expect a String body in later processing steps.
- After converting the message, we tell the file component how to name the output file. This is done via setFileHeaderFrom(java.lang.String). In this example the file name is taken from the message header 'destination'. setFileHeaderFrom is a custom extension defined by this tutorial and must be added to the SampleModelExtension.groovy file (see next box). You don't have to understand the exact details of this extension mechanism. For now it is sufficient to see that the route definition can also contain custom elements.
- The file endpoint finally writes the results to the target/output directory.
Before we can start the route we additionally have to define a dependency to the camel-jetty component in our pom.xml file.
Now that we have updated the dependencies, we also have to update the Eclipse classpath for this project. Enter
mvn eclipse:eclipse
to update the .classpath file.
Project testing
Unit test
When we run the unit test SampleRouteTest.java we should see a file default.txt in the target/output folder, because we didn't set a 'destination' message header. The unit test sent the message body abc so there should be a repeated abcabc contained in the output file.
You run the test inside Eclipse or using Maven by entering
mvn test
on the command line in the basic folder.
Server test
For testing the HTTP endpoint we first have to start the server. To do so, right-click on SampleServer.java and select Run As->Java Application. This will start a standalone route or integration server that listens on port 7799 for requests. As HTTP client we use a rest client. To prepare the test open the client and
- Enter http://localhost:7799/tutorial in the address field (top-left corner of the window)
- Select POST in the HTTP method tab (top-right corner of the window)
- Add as header key-value pair destination and custom in the Headers tab and click the green plus-Button
- Enter test in the Body tab
To submit the request press the green arrows at the top of the window. The result should look like:

The HTTP endpoint copies all HTTP headers onto Camel message headers, so we have the destination header present for creating a custom file name. In our example the destination header value is custom and the output file therefore has the name custom.txt. You should now see this file as well as the file generated during the unit test default.txt in the target/output folder. The content of the custom.txt file should be testtest because we sent a test HTTP request body.
Assembly and installation
We finally want to create a distribution package from our project, install (unzip) that package somewhere and start a standalone integration server (i.e. an integration server that runs outside Eclipse). Before continuing make sure that you stopped the SampleServer that you started within Eclipse, otherwise, you won't be able to start another server. To create the package enter
mvn assembly:assembly
on the command line. The created package basic-1.0-SNAPSHOT-bin.zip is written to the project's target folder. Copy the package to a new location and unzip it. This will create a folder named basic-1.0-SNAPSHOT with the following content:
Start server
To start the server run startup.bat. The console output should like like

Finally, submit the HTTP request again that you've configured before with the Eclipse HTTP client. This should again create a file custom.txt in the newly created target/output folder.
Summary
In this tutorial we've seen
- how to create an integration project skeleton by using Maven archetypes
- how message processing routes are defined
- how message processing routes are tested
- how easy it is to add HTTP or File endpoints as external interfaces
- how an integration project is bundled and launched as a standalone application
| Continue with XML processing on IPF After this tutorial you might want to take a look at IPF's XML processing and transformation capabilities which are based on Groovy's XML support. |
First details tutorial
| Prerequisites Before you start working on this tutorial make sure that you've read the IPF development pages for setting up the development environment. For this tutorial it is not necessary to checkout the IPF sources, all IPF dependencies are downloaded from the Open eHealth Maven repository. |
This tutorial is targeted at developers who want to get started with IPF and who are already familiar with Camel and Groovy. It goes into some technical details for explaining how IPF applications internally work. If this is your first contact with IPF, we recommend working through the First steps tutorial first, which omits most of the technical explanations.
| IPF 2.5 This tutorial refers to IPF version 2.5 or newer |
- We start by creating a project from an IPF archetype
- The created project structure is explained in detail. You'll see
- how to write routes with the IPF scripting layer.
- how to extend the DSL with the DSL extension mechanism.
- how to configure the application components with Spring.
- how IPF uses Maven to build the project, manage dependencies and package distribution bundles.
- We then write a very simple messaging solution that
- receives a message over HTTP
- transforms that message
- writes the result to a file.
- The messaging solution will be tested both automated and manually.
- The messaging solution will be packaged, installed and started.
Source code
The source code for this tutorial can be downloaded from here.
Project creation
We start by creating an example IPF project by entering
mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-4:generate -DarchetypeGroupId=org.openehealth.ipf.archetypes -DarchetypeArtifactId=ipf-archetype-basic -DarchetypeVersion=2.5.0 -DgroupId=org.openehealth.tutorial -DartifactId=basic -Dversion=1.0-SNAPSHOT -DinteractiveMode=false
on the command line (make sure to have the command on a single line). This will create a folder named basic in the current directory. Change to the basic folder an enter
mvn install
This will compile the project and install the project artifacts into your local Maven cache. Enter
mvn eclipse:eclipse
to generate the Eclipse project. To import the project start Eclipse, navigate to File->Import->General->Existing Projects into Workspace and select the created basic folder as root directory. Refer to the archetype section of the IPF Development page for further details on the created project structure. After having imported the project into Eclipse it should look similar to the following figure.

| It is assumed that you have installed the Groovy Eclipse plug-in as described in our development setup |
In addition to an IPF application skeleton the archetype also created some simple example files that can be used for initial experiments. Here's an overview of the created project files.
| File | Package | Path | Description |
|---|---|---|---|
| SampleRouteBuilder.groovy | org.openehealth.tutorial | src/main/groovy | Implements the message processing route. |
| SampleModelExtensionModule.groovy | org.openehealth.tutorial | src/main/groovy | Implements tutorial-specific DSL extensions used within SampleRouteBuilder.groovy. |
| SampleRouteTest.java | org.openehealth.tutorial | src/test/java | A JUnit test for the route implemented by SampleRouteConfig.groovy. |
| SampleServer.java | org.openehealth.tutorial | src/main/java | A class for starting the route as standalone server. |
| context.xml | - | src/main/resources | The Spring application context wiring the individual platform and application components. |
| bin.xml | - | src/main/assembly | The Maven assembly descriptor for creating a distributable assembly zip file of the tutorial application. |
| pom.xml | - | . | The Maven project descriptor of the tutorial application. |
| startup.bat | - | . | The Windows startup file for a standalone server. This startup file can only be used in an installation of a distributable package. It cannot be used directly inside the original Eclipse/Maven project. |
| startup.sh | - | . | The Linux startup file for a standalone server. This startup file can only be used in an installation of a distributable package. It cannot be used directly inside the original Eclipse/Maven project. This file is currently empty. |
| .project | - | . | The project descriptor of the sample Eclipse project. |
| .classpath | - | . | The classpath definition of the sample Eclipse project. |
Route definition
SampleRouteBuilder.groovy defines two routes.
- A route that receives a message from a direct:input1 endpoint, multiplies the body of the input message by 2 and returns the result. In Groovy you can use the * (multiply) operator on a string for repeating that string. For transformation we use the transmogrify DSL extension provided by IPF and implement the transformation logic with a closure. Input to the closure is by default the body of the in-message. The result returned by the closure is set to the body of the result message. For a detailed description of the transmogrify DSL element refer to the DSL extensions for IPF module adapters section of the reference manual.
- A route that receives a message from a direct:input2 endpoint, reverses the input message body and returns the result. The reverse() DSL element is not provide by Camel or IPF, it is a tutorial-specific DSL extension defined in SampleModelExtension.groovy. This extension uses Groovy GDK's java.lang.String.reverse() method to reverse the character sequence of the input message string. For a detailed description of the DSL extension mechanism refer to the DSL extension mechanism section of the reference manual.
We use direct endpoints to access the Camel routes from JUnit tests. Behind the scenes communication with these endpoints is via Java method calls. The unit tests, as generated by the archetype, communicate with the route via a two-way (in-out), synchronous communication pattern. The corresponding in-out message exchange is implicitly created by the requestBody() method of the injected producerTemplate (an instance of type org.apache.camel.ProducerTemplate).
Using Enterprise Integration Pattern symbols this looks as follows:
Later we'll see how to route the message processing results to a final destination in addition to routing it back to the sender endpoint. The final destination will be a file endpoint that writes the message body to a file.
Extension definition
The second route makes use of the reverse() method, a custom-defined DSL extension *). Methods that make up the DSL are defined on route builders and model classes.
Because Camel doesn't provide a mechanism to extend the DSL on Java-level we use Groovy Extensions Modules to enhance existing model classes where needed (or even define our own model classes like IPF does). In our example, we enhance the org.apache.camel.model.ProcessorDefinition class with the reverse method.
We define a static method reverse that takes the extended class as first parameter (ProcessorDefinition). The method implementation is given by a closure that makes use of the transmogrify extension, another extension provided by IPF. The ProcessorDefinition instance on which the extension method gets called is available as delegate variable inside the method implementation closure. Argument to transmogrify is another closure that finally applies Groovy's java.lang.String.reverse() method on the message body (it). In our example we expect the message body to be a String. Using these simple mechanisms we extended the Camel DSL.
In order to active the extension, we define a extension descriptor in the META-INF/services directory called org.codehaus.groovy.runtime.ExtensionModule and reference our extension class SampleModelExtensionModule
Groovy will pick up descriptor automatically and extend the referenced class using metaclass programming.
*) For a complete reference of predefined DSL extensions provided by IPF refer to the IPF extensions index.
Application configuration
The following XML file shows a Spring configuration of an application that makes use of IPF's core features only. For examples how to configure support for flow management and HL7 message processing refer to the flow management tutorial and HL7 processing tutorial, respectivly.
The following table provides further information about the beans configured in the above application context.xml file.
| Bean | Description |
|---|---|
| camelContext | The Camel context. It represents a single Camel routing rulebase including a ProducerTemplate that is used in unit tests to send messages to endpoints. |
| routeBuilder | The sample route builder. Note that the camelContext refers to this bean to build the routes during startup. |
Project descriptor
Here is an excerpt of the Maven project descriptor.
The relevant entries are:
- <groupId> (defined at project-creation time).
- <artifactId> (defined at project-creation time).
- <version> (defined at project-creation time).
- Dependency to ipf-platform-camel-core. That's the only IPF dependency needed for simple IPF applications.
- Reference to the bin.xml assembly descriptor in the maven-assembly-plugin for creating binary distribution files.
Assembly descriptor
The assembly descriptor describes how to package the project into a distributable package.
In our example we define the package format (zip) and specify the filesets to be included into the package. We also define a <dependencySet> which causes the maven-assembly-plugin to include all runtime dependencies into the package under the lib directory. For further configuration details consult the Maven assembly plugin documentation. Section assembly and installation will explain how to create the distribution package.
Project customization
In this section we'll extend the example to receive an HTTP request message from a jetty endpoint, transform it and write send it to a file endpoint. Using Enterprise Integration Pattern symbols this looks as follows:
We want to keep the direct endpoint so that we can still test the message transformation with a JUnit test. To reuse that endpoint the additional jetty endpoint communicates with the transformer over the direct endpoint. Instead of only returning the processing result to the initiators (HTTP client or JUnit test) we also write the message to a final destination which is a file endpoint. This endpoint creates files containing the processing result. Here's the extended route definition:
In this example we only extended the first of the two routes that have been initially created. We leave the second route untouched. To receive messages over HTTP we have to create a jetty endpoint via the jetty:http://localhost:7799/tutorial. This will run a Jetty server listening on port 7799. The context path is tutorial. The jetty endpoint doesn't automatically convert the HTTP body from an InputStream to a String so we have to do that manually via convertBodyTo(String.class) (we expect a string body in later processing steps). Then the message received via HTTP is forwarded to the direct:input1 endpoint, the same that is used in our JUnit test. We again transform the original message body by repeating it (multiply it by 2) using transmogrify { it * 2 }. In the next step we tell the file component that the filename is determined via the message header destination. This is done with setFileHeaderFrom(java.lang.String). This is a custom DSL extension that is implemented as follows:
We define a new method setFileHeaderFrom on the ProcessorDefinition class. This makes setFileHeaderFrom available as a new DSL element in route definitions. This method derives the value of the Exchange.FILE_NAME header from the value of another user-defined header. The Exchange.FILE_NAME header is understood by the file component. This way we can influence the name of the file being written. The user-defined header is the argument to the setFileHeader extension method (sourceHeader parameter).
In our example, we derive the destination file name from the destination message header. The file endpoint defined in SampleRouteConfig.groovy finally writes the results to the target/output directory. Before we can start the route we have to define a dependency to the camel-jetty component in our pom.xml file.
You have to run mvn eclipse:eclipse in the project directory again to update Eclipse's .classpath file.
Project testing
Unit test
After running the unit tests we find a file default.txt in the target/output folder. The file default.txt was generated because we didn't set a destination header in the unit tests. The unit test sent the message body abc so there should be a repeated abcabc contained in the output file.
Server test
For testing the HTTP endpoint we first have to start the server. To do so, right-click on SampleServer.java and select Run As->Java Application. This will start a standalone route or integration server that listens on port 7799 for requests. As HTTP client we use a rest client. To prepare the test open the client and
- Enter http://localhost:7799/tutorial in the address field (top-left corner of the window)
- Select POST in the HTTP method tab (top-right corner of the window)
- Add as header key-value pair destination and custom in the Headers tab and click the green plus-Button
- Enter test in the Body tab
To submit the request press the green arrow at the top of the window. The result should look like:

The jetty endpoint copies all HTTP headers onto Camel message headers, so we have the destination header present for creating a custom file name. In our example the destination header value is custom and the output file therefore has the name custom.txt. You should now see this file as well as the file generated during the unit test default.txt in the target/output folder. The content of the custom.txt file should be testtest because we sent a test HTTP request body.
Assembly and installation
We finally want to create a distribution package from our project, install (unzip) that package somewhere and start a standalone integration server (i.e. an integration server that runs outside Eclipse). Before continuing make sure that you stopped the SampleServer that you started within Eclipse, otherwise, you won't be able to start another server. To create the package enter
mvn assembly:assembly
on the command line. The created package basic-1.0-SNAPSHOT-bin.zip is written to the project's target folder. Copy the package to a new location and unzip it. This will create a folder named basic-1.0-SNAPSHOT with the following content:
The lib folder contains the project jar file (basic-1.0-SNAPSHOT.jar) as well as all required runtime dependencies. The conf folder contains a log4j.xml configuration file. Startup scripts are located directly under the root folder. How the project is packaged can of course be customized by changing the assembly descriptor of the project in src/main/assembly/bin.xml. The script startup.sh is currently empty i.e. for testing-purposes you have to run the server on Windows using startup.bat.
Start server
To start the server run startup.bat. The console output should like like

Finally, submit the HTTP request again that you've configured before with the Eclipse HTTP client. This should again create a file custom.txt in the newly created target/output folder.
| Continue with XML processing on IPF After this tutorial you might want to take a look at IPF's XML processing and transformation capabilities which are based on Groovy's XML support. |
HL7 processing tutorial
Prerequisites
|
This tutorial guides you through an HL7 version 2 message processing example. Message processing is done via IPF's HL7 DSL and via HL7-specific extensions to the Camel DSL. We will create an IPF application that reads an HL7 version 2 message from an HTTP endpoint, validates and transforms the message, and writes the transformation result to an output file.
| HL7 features of Camel and IPF This tutorial currently doesn't use Camel's HL7 features. Camel's HL7 features and those provided by IPF are complementary to each other and can perfectly be combined. For example to receive HL7 version 2 messages via MLLP you can do so using Camel's HL7 component. |
Validation
Let's look at the message we will use in our example. The message is an ADT A01 message, version 2.2, and we want to enforce that
- all primitive type values have the correct format and
- the message itself contains a defined sequence of segments
The HL7 module of IPF comes with an HL7 validation DSL that makes the definition of validation rules very easy. Even better, IPF provides a complete set of rules that check whether the primitive type values meet the constraints defined in the HL7 specification.
Transformation
Now lets look the transformation we want to perform if we passed validation.
- The room number and bed number from the PV1[3] field shall be dropped. This field is a composite field and we want to drop the second and the third component.
- The birth date in PID[7] shall be reformatted by dropping the last 6 digits. This field is also a composite field with only a single component.
- The gender code in PID[8] shall be mapped to a code from another code system. We will use a simple code mapping service for that.
Finally, we will derive the destination file name from the MSH[4] field (sending facility).
Route design
Here's the message processing route using enterprise integration pattern symbols.
- An ADT A01 event message is received via a jetty endpoint (inbound HTTP endpoint).
- The message is validated as described above.
- The message is forwarded to a transformer that makes the changes to the HL7 message as described above.
- The transformation result is placed into different files whose names are derived from the MSH[4] field.
Source code
The source code for this tutorial can be downloaded from here.
Project creation
We start by creating an example IPF project by entering
mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-4:generate -DarchetypeGroupId=org.openehealth.ipf.archetypes -DarchetypeArtifactId=ipf-archetype-basic -DarchetypeVersion=2.5.0 -DgroupId=org.openehealth.tutorial -DartifactId=hl7 -Dversion=1.0-SNAPSHOT -DinteractiveMode=false
on the command line (make sure to have the command on a single line). This will create a folder named hl7 in the current directory. Change to the hl7 folder an enter
mvn install
This will compile the project and install the project artifacts into your local Maven cache. Enter
mvn eclipse:eclipse
to generate the Eclipse project. To import the project start Eclipse, navigate to File->Import->General->Existing Projects into Workspace and select the created hl7 folder as root directory. After having imported the project into Eclipse it should look like in the following figure.
| It is assumed that you have installed the Groovy Eclipse plug-in as described in our development setup |
You can find a detailed description of the created project structure in the project creation section of the first details tutorial.
Extend project descriptor
In order to enable HL7 processing and communication over HTTP we have to include further dependencies into the Maven project descriptor pom.xml. These are
| Dependency | Description |
|---|---|
| ipf-platform-camel-hl7 | IPF component that provides HL7-specific extensions to the Camel DSL. |
| ipf-modules-hl7dsl | IPF component that provides the HL7 DSL and is transitively included via ipf-platform-camel-hl7. |
| ipf-modules-hl7 | IPF component that provides the HAPI extensions and is transitively included via ipf-platform-camel-hl7. |
| camel-jetty | Camel component that enables inbound communication over HTTP. |
The pom.xml file is located directly under the project's root directory. Here's an excerpt of the extended project descriptor.
Now that we have updated the dependencies, we also have to update the Eclipse classpath for this project. Enter
mvn eclipse:eclipse
to update the .classpath file.
Extend application context
The components we included in the previous section must also be configured in the Spring application context.xml file. This file is located under src/main/resources. In addition to the default configuration created by the archetype we need to
- Register the HL7 DSL extensions provided by the ipf-platform-camel-hl7 component at the model extender
- Register the HAPI extensions provided by the ipf-modules-hl7 component at the model extender
- Configure a mapping service that we will use for code mappings
- Add a couple of beans needed for HL7 validation
Here's the complete application context.xml file.
The mapping table tutorial-hl7.map referenced by the mappingService bean is described in the code mapping section.
Route definition
Lets start with an overview of the complete route and then discuss it step by step. Before doing so open the SampleRouteBuilder.groovy file in Eclipse and replace the existing routes in the configure() method.
First, we define a jetty endpoint for receiving HL7 messages over HTTP.
...
from('jetty:http://0.0.0.0:7799/tutorial') // start HTTP server
.to('direct:input') // forward request
...
This will start an HTTP server listening on port 7799. The context path is tutorial. The message is then forwarded to a direct endpoint that we also use inside our JUnit tests for sending test messages. HL7 messages arrive as InputStream at the direct:input endpoint . We won't work on this representation of the HL7 message. Instead we create a so-called message adapter that implements the HL7 DSL. Creation of a message adapter from an InputStream is done via unmarshal().ghl7() (this also works if the message is a String).
...
from('direct:input') // receive HL7 message
.unmarshal().ghl7() // create message adapter (HL7 DSL support)
...
The next step in the HL7 message validation using the HL7 Validation framework.
...
.validate().ghl7()
.profile(lookup(ValidationContext.class)) // validate against custom validation context
...
The validation is executed against the HAPI message object. The profile DSL extension initializes the validator with the HL7 ValidationContext that has been injected into the route builder.
Now, we add our custom SampleRulesBuilder.groovy script, that defines the allowed sequence and cardinalities of HL7 groups and segments for this message. The file is added to the src/main/groovy directory in the package org.openehealth.tutorial:
The sequence and cardinality of groups and segments is defined in a syntax that is very closely related to the HL7 Abstract Message Syntax. The message is required to contain a MSH, EVN, PID and PV1 segment; it may contain any number of NK1 segments and it may contain a repeatable INSURANCE group. For detail, please read the documentation on HL7 validation.
As our test message matches this definition, we expect the message to pass validation.
The next step in the route is the HL7 message transformation using the HL7 DSL.
...
.transmogrify { msg ->
msg.PV1[3][2] = '' // clear room nr.
msg.PV1[3][3] = '' // clear bed nr.
msg.PID[7][1] = msg.PID[7][1].value.substring(0, 8) // format birth date
msg.PID[8] = msg.PID[8].mapGender() // map 'gender' code
msg // return result
...
}
We do the transformation with a transmogrifier closure. Inside the closure we use the HL7 DSL. The HL7 DSL allows us to manipulate HL7 messages on a very high level without dealing with low level HL7 API details. You immediately see how the message is processed by looking at the code. For accessing a message segment you directly use the segment name like msg.PV1 or msg.PID. Fields and sub-fields (components of a composite field) are accessed by indices starting from 1. For example, PV1[3][2] denotes the second component of the third PV1 field. For a complete reference of the HL7 DSL refer to the HL7 DSL section of the reference manual. Code mapping is done with the mapGender() method. This method translates the English F code (female) contained in PID[8] to a German W code (weiblich). This will be explained in more detail in the code mapping section.
The transformation result can now be written to a file. We derive the name of the file from the content of the MSH[4] field. For example, from HZL we write a file named HZL.hl7, from PKL a file named PKL.hl7 ... and so on. We achieve this by setting the Exchange.FILE_NAME message header which is understood by the file component.
...
.setHeader(Exchange.FILE_NAME) {exchange -> // set filename header to
exchange.in.body.MSH[4].value + '.hl7' // sending facility (MSH[4])
}
.marshal().ghl7() // convert to external representation
.to('file:target/output') // write external representation to file
...
For setting the message header we use the setHeader() method. Inside the setHeader closure we again use the HL7 DSL to access the MSH[4] field. Before the message can actually be written to a file we have to marshal the message adapter with marshal().ghl7(). This is the reverse operation to unmarshal().ghl7() at the beginning of the route. The file endpoint is configured in a way that it writes files to the target/output directory. Writing to an existing file will overwrite its content. Before we can start testing the route we have to setup the code mapping table.
Code mapping
In the extend application context section we've configured the code mapping service to use a mappingScript named tutorial-hl7.map. In our example, we only need a single gender mapping table with a single entry that maps F to W.
This format is valid Groovy syntax and is understood by the default bi-directional mapping service provided by IPF. For a complete reference refer to the mapping service section of the reference manual. You may also provide your own mapping service implementation by implementing the org.openehealth.ipf.modules.hl7.mappings.MappingService interface. To install the mapping table create a tutorial-hl7.map file under src/main/resources and copy the above mappings block into that file.
It is interesting to see how the gender table is selected for translating F to W. There is a correspondence between the name of the mapping method and the name of the selected code table. Using mapGender() instructs IPF to use the gender mapping table. If you write mapXyz() IPF would try to find a table named xyz ... and so on. You may also use the map*() methods on java.lang.String objects like in the following example.
assert 'W' == 'F'.mapGender()
This is achieved via dynamic Groovy language features.
Route testing
Automated test
For automated testing we will use the following HL7 message.
After processing we expect the following output.
Create two files, msg-01.hl7 and msg-01.hl7.expected, with the above content under the src/test/resources folder.
These files will be used inside our JUnit test. To implement the test open the SampleRouteTest.java file in Eclipse and replace its content with the following.
The testRoute method opens an InputStream on the input file and sends that stream in the in-message body to the direct:input endpoint. The processing result is loaded from the created HZL.hl7 file and compared with the expected processing result. For reading HL7 messages from a stream or a resource we use the MessageAdapters utility class. To execute the test right-click on SampleRouteTest.java and select Run As->JUnit Test from the context menu. The test should now successfully execute and you should also see an HZL.hl7 file in the target/output directory containing the processing result.
Just for fun, modify the validation part, so that the message does not validate against the rules. Modify the SampleRulesBuilder.groovy script and comment the EVN segment
.forVersion('2.2')
.message('ADT', 'A01').abstractSyntax(
'MSH',
// 'EVN',
'PID',
[ { 'NK1' } ],
If you run the test again, the console shows exceptions and the test fails:
... SEVERE: Invalid message ca.uhn.hl7v2.validation.ValidationException: The structure EVN appears in the message but not in the profile at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) ...
Before continuing, undo the change in SampleRulesBuilder.groovy by removing the comment again.
Manual test
As HTTP client we use a REST client to send HL7 messages to the jetty endpoint that we configured in SampleRouteBuilder.groovy. Before we can send messages over HTTP we have to start the route server. To do so, right-click on SampleServer.java and select Run As->Java Application. Now we have a server running that accepts HL7 messages on port 7799 under the tutorial context path. Then open the REST client and
- Enter http://localhost:7799/tutorial in the address field
- Select POST as HTTP method
- Copy the following HL7 message into the Body tab
This is the same message we used for the JUnit test but with a different MSH[4] value (PKL instead of HZL). To submit the request press the green arrow at the top of the window. The result should look like:

Because we derive the result filename from the MSH[4] field we should now see a PKL.hl7 file in the target/output directory.
Terminate the server that we started for the manual test.
Assembly and installation
We finally want to create a distribution package from our project, install (unzip) that package somewhere and start a standalone integration server (i.e. an integration server that runs outside Eclipse). Before continuing make sure that you stopped the SampleServer that you started within Eclipse, otherwise, you won't be able to start another server. To create the package enter
mvn assembly:assembly
on the command line. The created package hl7-1.0-SNAPSHOT-bin.zip is written to the project's target folder. Copy the package to a new location and unzip it. This will create a folder named hl7-1.0-SNAPSHOT with the following content:
The lib folder contains the project jar file (hl7-1.0-SNAPSHOT.jar) as well as all required runtime dependencies. The conf folder contains a log4j.xml configuration file. Startup scripts are located directly under the root folder. The way how the project is packaged can of course be customized by changing the project's src/main/assembly/bin.xml assembly descriptor. The script startup.sh is currently empty i.e. for testing-purposes you have to run the server on Windows using startup.bat.
Start server
To start the server run startup.bat. The console output should like like

Finally, submit the HTTP request again that you've configured before with the REST client. This should again create a file PKL.hl7 in the newly created target/output folder. The server can be stopped by pressing CTRL+C.
Tutorial for routing to a webservice via HTTP
| Deprecated Feature Large Binary Support has been deprecated in IPF 2.3.1 and has been removed in IPF 2.5 |
Prerequisites
|
This tutorial shows how to use the Large Binary Support (LBS) of the IPF to create a router for a webservice that exposes an HTTP-based protocol. It is targeted at developers that are familiar with the IPF, Camel and Groovy. A basic understanding of webservices based on a wsdl is also useful, although not essential.
The webservice is a simple image repository that allows uploading and downloading of images using standard HTTP POST and GET requests.
The steps of the tutorial are:
Source code
The source code for this tutorial can be downloaded from here. Unpack the zip and import the contained Eclipse project into your workspace. After importing it might be necessary to clean the project if errors are reported.
Create a basic project using the IPF and LBS
The Maven project is created via an IPF maven archetype. Find a suitable location on your disk and create the project using the following command (make sure that everything is on a single line):
Note: Depending on the version of the IPF you are using, you have to set the archetypeVersion setting.
Enter
mvn eclipse:eclipse
to generate the Eclipse project. Within an Eclipse workspace you can now import the project using File->Import->General->Existing Projects into Workspace. Select the router directory and click Finish. You should see the router project in the workspace.
The created project already contains a useful skeleton, but it must be configured to use the LBS and CXF. Open the pom.xml and add the following dependencies to the <project><dependencies> section:
Save the file and update Eclipse project with the dependencies to include the LBS jars. Enter
mvn eclipse:clean eclipse:eclipse
to clean and generate new Eclipse project.
The archetype project contains a sample configuration that is based on the core IPF. This must be changed in order to use the store and fetch processors. Open the file context.xml in src/main/resources. This is the configuration file of the Spring application context. It contains the extensions available for route definitions. The sample configuration includes two extensions: coreModelExtension for the core IPF features and sampleModelExtension for any custom extensions of our own project. Add the extension of the LBS by adding another bean:
All extension beans must be registered with a ModelExtender, in this case the routeModelExtender. Add the new bean to the list of extensions. The result should look like this:
Add beans for a ResourceFactory and a LargeBinaryStore. The factory creates resources for the images that are stored in the large binary store while they are being processed by the router:
Beginning with IPF 2.2.0 it is necessary to use a modified version of the Jetty component with the LBS. This component ensures that the LBS functionality has access to the pure InputStream despite various changes done in Camel 2.3.0. To enable the usage of this component you need to add the following bean:
Create the webservice
The webservice is created using a wsdl-first approach. It should allow uploading and downloading of images via two methods. The wsdl defines a service with these two methods and a type for the image. The upload method returns an ID for an image, that is used is subsequent calls to download the image again.
Create the following wsdl-file in the directory src/main/resources/wsdl and name it imagebin.wsdl:
Now add a build step to the pom.xml to generate the service-related classes from the wsdl file using wsdl2java. Open the pom.xml and add the build plugin to the <project><build><plugins> section:
To generate the java classes for the wsdl run Maven in the project root directory on the command line:
The generated sources are now inside target/generated/src/main/java. This directory has to be added to the source directories in Eclipse. Switch back to Eclipse and refresh the project tree. Right click on the project and choose Properties, then Java Build Path and select the Source tab. Press the button Add Folder and select target/generated/src/main/java in the folder selection. Exit the dialogs with OK. Now you should have a new source folder that contains the stubs for our ImageBin webservice.
Create a new class via File/New/Class. Put it in the package org.openehealth.tutorial.imagebin and call it ImageBinImpl. Also choose the interface org.openehealth.tutorial.imagebin.ImageBin and press Finish.
In the new class add a LargeBinaryStore from the IPF to store the uploaded images. Adding an image to the store is a simple matter of handing the store the input stream from the upload parameter. For downloading, create a DataSource that gets its input stream from the store.
The resulting implementation also configures the class to be used as a webservice using the @WebService annotation:
Please note: Exceptions are not properly handled in this code to keep it as small as possible.
Add a class called ImageBinServer to start and stop the CXF service. The class should be in the main java sources and in the org.openehealth.tutorial.imagebin package:
This should be tested by a simple test case that calls upload and download on the running webservice. Create a new class in the test directory src/test/java. Use the package org.openehealth.tutorial.imagebin and the name ImageBinServerTest. The test case could be something to this (using helper classes from the org.apache.commons.io package):
Running the test should work and leave a file in the router/target/store directory. You can open the file and see the uploaded content (TestImage).
Add the routing
The next step is to expose the webservice via an HTTP endpoint. To do this create a route via Camel. The route connects the HTTP endpoint with the CXF endpoint of the webservice. The first thing to do is to add a CXF endpoint. Open context.xml and add a bean for the endpoint, mapping it to the service.
This requires that a few CXF-related Camel resources are imported that define the cxf namespace. Make sure that the beans tag of the context.xml file looks like the following code:
Also add a list of resource handlers as a bean to context.xml that are used in the route for handling HTTP and CXF resources:
The router project already has a sample route written in Groovy. You can find it in src/main/groovy/org/openehealth/tutorial/SampleRouteBuilder.groovy. Open the file and remove the sample routes in the configure method. Now add a new route that accepts messages from a jetty endpoint and routes them to a CXF endpoint using the request methods of the HTTP requests:
Most of this route is handling the transformation between HTTP and CXF requests. Note how the store processor is used to enable support for large binaries without keeping the complete image in memory. In the upload part of the route the image is stored in the temporary store as soon as the HTTP POST message is identified. In the download part this is done after the webservice has been called. The two calls to the store processor use the resource handlers that are configured in context.xml.
To add a unit test for these routes, open SampleRouteTest.java in src/test/java/org/openehealth/tutorial. Remove the methods in the existing class. They test the sample route that was removed earlier. The Apache HttpClient is used to send a real test message to the route:
Now run the test. This shows the route in action with a started webservice. As a result you should again find the uploaded file in the target/store directory.
Reference application
The reference application is an IPF application that demonstrates how to use a broad range of IPF and Camel features. It is an example from the order processing domain. It consumes (over-simplified) order requests over HTTP and transforms these request messages into a different format depending on the order category. The transformation results are written to the file system. The reference application can be found under tutorials/ref in the IPF source tree. The Eclipse project name is org.openehealth.ipf.tutorials.ref.
In order to build the reference application, change into the project directory, run mvn assembly:assembly and unzip the
built ipf-tutorials-ref-<version>-bin.zip file.
To start a single instance of the reference application you need to start a Derby database server and an IPF instance. To start the database server, run start-flowdb.bat.
Thu Mar 21 08:54:36 CET 2013 : Apache Derby Network Server - 10.9.1.0 - (1344872) started and ready to accept connections on port 1527
To start the IPF instance select Run->Run Configurations... from the Eclipse menu, choose Java Application->TutorialServer1 in the dialog box and press the Run button. You should see the following output on the console (warnings omitted).
INFO - Registering new extension module CoreExtension defined in class org.openehealth.ipf.platform.camel.core.extend.CoreExtensionModule INFO - Registering new extension module FlowExtension defined in class org.openehealth.ipf.platform.camel.flow.extend.FlowExtensionModule INFO - Registering new extension module TutorialsRefExtension defined in class org.openehealth.ipf.tutorials.ref.extend.TutorialModelExtensionModule INFO - initialized sequence default INFO - Scheduler started: true INFO - Initialize flow purge jobs ... INFO - Initialization done. INFO - Using Persistence Adapter: KahaDBPersistenceAdapter[C:\dev\projects\ipf\tutorials\ref\target\ipf-tutorials-ref-2.5-SNAPSHOT\data\activemq1] INFO - Apache ActiveMQ 5.8.0 (broker1, ID:wdf-lap-0319-3679-1363853565821-0:1) is starting INFO - Apache ActiveMQ 5.8.0 (broker1, ID:wdf-lap-0319-3679-1363853565821-0:1) started INFO - For help or more information please see: http://activemq.apache.org INFO - PListStore:[C:\dev\projects\ipf\tutorials\ref\target\ipf-tutorials-ref-2.5-SNAPSHOT\data\activemq1\broker1\tmp_storage] started INFO - Registered replay strategy with identifier http at flow manager INFO - Registered replay strategy with identifier file at flow manager INFO - Connector vm://broker1 Started
Now the reference application is ready to accept order request messages via http://localhost:8081/tutorial. Here are two sample order request messages and their transformation results.
| Request message | Transformed message |
|---|---|
<order xmlns="http://www.openehealth.org/tutorial">
<customer>123</customer>
<category>animals</category>
<item>ozelot</item>
<count>3</count>
</order> |
Order ----- Customer: 123 Item: ozelot Count: 3 |
<order xmlns="http://www.openehealth.org/tutorial">
<customer>123</customer>
<category>books</category>
<item>eating ozelots</item>
<count>3</count>
</order> |
<?xml version="1.0" encoding="UTF-8"?>
<ns0:order xmlns:ns0="http://www.openehealth.org/tutorial" category="books">
<ns1:customer xmlns:ns1="http://www.openehealth.org/tutorial">123</ns1:customer>
<ns2:item xmlns:ns2="http://www.openehealth.org/tutorial">eating ozelots</ns2:item>
<ns3:count xmlns:ns3="http://www.openehealth.org/tutorial">3</ns3:count>
</ns0:order> |
The transformed messages are written to the order/out folder if processing was successful otherwise to the order/error-app or order/error-sys folder. For sending these messages you can use e.g. this REST client.
The reference application also implements the flow manager which can be accessed via a generic JMX client or the IPF Manager (Eclipse RCP application).
To connect to the flow management service with the standalone flow management client:
- Start the flow management client
- Create a connection on port 1801.
- Right-click on the connection and select Open Flow Manager from the context menu.
The flow management window opens. To get a list of all tracked message flows so far press the Search button. Depending on the number of order requests you've sent you'll see more or less flows in the result list of the Search view. For more information on how to use the flow management client refer to the IPF Manager documentation.
The reference application also implements the flow manager which can be accessed via a generic JMX client or the IPF Manager (Eclipse RCP application).
To connect to the flow management service with the JConsole client:
- Launch the start-console.bat script
- Connect to the TutorialServer process
- Navigate to the Flow Manager MBeans
To see the information about the recent requests:
- Under Operations click on setUpperTimeLimitToCurrentTime
- Search for findLastFlows operation, enter 15m (15 minutes) into the timespan input field and click the button.

As result you see a list window containing the two requests being recently processed

You can also select the ActiveMQ MBeans in the JConsole and check that each of the transformed-animals and transform-books message queues have processed 1 message, while the validate queue has processed 2 messages:

XDS demo repository
This tutorial is a guide to the XDS demo repository, a simplified implementation of an XDS registry and repository to store documents, folders, submission sets and associations. The demo is useful for everyone who wants to use the IPF XDS components. It shows how to:
- Use the XDS.b components to offer a registry and a repository service
- Transform and process registration, retrieval and query requests
- Configure and use ATNA logging
- Enable secure transport using HTTPS
The demo repository is non-persistent. A restart will therefore always start out with a blank respository/registry.
Overview
The XDS demo repository is implemented in Groovy. Most of the code deals with the query functionality. This is used for the ITI-18 transaction (registry stored query) and for the other transactions to perform checks of the input data. Within the project you can find the following source files, tests and configuration files.
Source files:
| DataStore | The actual storage of documents, document entries, folders, submission sets and associations. The store is non-persistent at the moment to keep things simple. Allows adding, retrieving and querying |
| Comparators | Basic comparison methods used by the query logic |
| ContentUtils | Helpers to calculate content related data, e.g. hash codes and size |
| Iti18RouteBuilder | Route for the stored query transaction |
| Iti4142RouteBuilder | Routes for the register document set transactions |
| Iti43RouteBuilder | Route for the retrieve document set transaction |
| QueryMatcher | Matching code for various stored query types used by ITI-18 |
| RegRepModelExtension | The DSL extension for the routes |
| SearchDefinition | The DSL element for creating a search query in a route |
| SearchProcessor | The processor that performs search queries using the data store |
| SearchResult | An enum that represents the type of results from a search query |
| Server | The main entry point of the demo repository that starts the server |
Test files:
| TestRepositoryAndRegistry | Basic tests that send individual requests and check their results |
| TestThreading | A multi-threading test to show the thread-safety of the repository and of the XDS components |
| Task | Base class for tasks used in the multi-threading test |
Configuration files:
| context.xml | Spring application context containing beans for Camel and IPF configuration as well as the data store |
| log4j.xml, logging.properties | logging configuration |
The repository can be started within Eclipse or from command line using the startup.bat after building an assembly. For this guide it is assumed that you have installed the Groovy Eclipse plugin as described in our development setup. To start the server within Eclipse, right click on Server.groovy and choose Run as/Groovy. The repository can be configured to use HTTPS by specifying the command line argument secure.
Running XDSToolKit tests against the demo repository
You can either implement your own XDS source and consumer or use the XDSToolKit to run tests against the repository. To use the toolkit you should first ensure that it runs fine against the public NIST repository that it is pre-configured with. Then you can change xdstest/actors.xml in the XDSToolKit installation to make it run against the demo repository. Add a new site to the existing <sites>:
The demo supports the XDS.b tests that are required for registry/repository implementations at the connectathon. Note that some of the XDS tests are checking the forwarding of registration requests to the public repository. At the moment the demo repository does not forward these requests. Instead the entries will be registered within its own registry. The following is the list of tests that were verified:
Stored Query Transaction:
12346, 11897, 11898, 11899, 11901, 11902, 11903, 11904, 11905, 11906, 11907, 11908, 11909, 12374, 12368, 11741
Provide and Register Document Set:
11966, 11979, 11983, 11981, 11986, 12369, 12086
Register Document Set:
11990, 11991, 11992, 12022, 11993, 11994, 11995, 11996, 11997, 11999, 12000, 12001, 12326, 12323, 12002, 12327, 12084, 12004, 12370, 12005
Retrieve Document Set Transaction:
12029, 12021, 12360, 12362, 12028
IPF XDS related code snippets
The main purpose of the demo repository is to demonstrate the features that the IPF XDS components offer. This section takes a closer look at such code pieces.
Basic configuration
Configuration of an XDS application is pretty similar to the standard configuration of an IPF application. The main difference is the configuration of CXF. Because the application runs within a Tomcat environment, CXF should not start its own Jetty instance. Here is the commented context.xml:
Exposing the endpoints
To allow clients to communicate with the registry/repository, a few routes are defined that automatically expose the SOAP-based endpoints. The SOAP related details that are required by the IHE profile are not defined directly. A from(...) is all it takes. You can find those in the route builders. Take a look at the following snippets:
Validating incoming messages
Once the endpoints have been exposed, clients can send in messages. These messages might or might not conform to the IHE specification. It is usually a good idea to validate incoming messages before processing them. The XDS components offer a simple validation. This is not meant to be complete, e.g. it cannot validate that a patient ID is actually known to the registry, but it performs a variety of checks that will simplify our route implementations. All routes of the demo repository perform this basic validation step right after logging the incoming request, e.g. in ITI-43:
A validation failure will throw an XDSMetaDataException. It is not necessary to put any onException handling in the route for this exception. The XDS components convert validation failures into an equivalent XDS response with the correct error code. Therefore, this exception can be thrown anywhere else in the routing. E.g. the demo repository contains code to check for a specific patient ID used by the XDSToolKit. Because the demo does not track patients yet, it simply throws an exception if this specific patient ID is found in a request:
A failure is reported by throwing an XDSMetaDataException via the fail DSL extension that is implemented in RegRepModelExtension.groovy. The code snippet below shows that this is simply a shortcut to throw the exception.
If any other exception is thrown in the route, the XDS components will report a general error in the failure response (either XDSRepositoryError or XDSRegistryError). Of course you can use standard exception handling from Camel to handle such cases.
Using the meta data classes
Once validated, the route starts processing the incoming message. The format of the data structure that is received in the message body is very important. By default these are instances of the raw ebXML classes. While these might be interesting for some use cases, it is often better to use classes that are closer to the XDS meta classes defined by the IHE specification. These meta classes serve two purposes: They ensure conformance with the XDS specification and they are much easier to use than the more generic ebXML classes. All route builders of the demo repository convert the ebXML bodies to the meta classes after validation. There are different ways to do this. One way is to simply use convertBodyTo which results in the body to be converted from the ebXML class to the meta data class. This is done in the ITI-43 route:
Another way is to retrieve the meta class instance via getBody. E.g. in the ITI-41 route builder, the input body is transformed into a map that contains the actual request object. This allows access to the request at any stage in the routing. To create the map, a transform processing is used:
In contrast to convertBodyTo, getBody does not replace the body of the message automatically. Check out the log step at the beginning of the route. It uses getBody to retrieve the meta class. The good thing about these classes is that they have meaningful equals, hashCode and toString implementations. The logging step converts the ebXML class on-the-fly and uses its toString method to get a nice textual representation. If convertBodyTo is used, the validation step will fail, because it expects an ebXML class in the message body.
Lets look at some typical use cases that require access to the meta classes.
Evaluating the query type
The next code snippet shows the dispatching of an ITI-18 message based on the stored query type. This uses content based routing via choice to call sub routes that perform the corresponding query logic. The queryType method is a simple shortcut to get the query type property from the request message. If a non-supported query type is found an exception is thrown using the fail processor. All query types that are defined by the IHE specification are listed in the enum org.openehealth.ipf.platform.camel.ihe.xds.commons.requests.query.QueryType.
Splitting for individual entry processing
Many XDS transactions work with sets of entries, e.g. upload and download are using multiple documents instead of just one. Using the splitter you can break down the request message into its individual entries and process them individually. In the demo repository this is done in many cases. The next snippet of the ITI-43 route shows how to retrieve a document set by retrieving each document from the store one at a time. Using split the actual splitting of the message is done by taking the list of documents contained in the meta class. The splitter aggregates a result list using the retrieved documents. This list is going to be in the message body after the splitting functionality has finished processing (indicated by end()). The entries of the list are the result of the processing of retrieve, which is a custom DSL element that calls DataStore.get() to get the contents of the document. Finally, the message is transformed, putting the aggregated list into the meta class for the response:
Secure transport
Using HTTPS instead of HTTP requires very little work. In fact, for a registry/repository it does not require anything related to IPF. Simply configure Tomcat to use secure transport for the webservices. With the embedded Tomcat class of the XDS test package, this is only a few lines of code:
The keystore provided with the test application is the one that is used in the XDSToolKit. Therefore, you can use the demo repository with secure transport enabled tests from the XDSToolKit. Note that those keystores are changed from time to time. You can replace the keystore of the demo repository by simply overwriting the keystore file in the project root directory.
The configuration of a complete Tomcat installation is well documented here. Of course your mileage may vary if you are using a different container. In any case, you will not require additional configuration steps within a registry/repository implementation. Note that clients (source and consumer) will need to configure the endpoint to use HTTPS. This is not part of this guide. You can find more details in the standard documentation.
Auditing
By default auditing is turned on by all endpoints. The configuration of the syslog server that receives auditing messages can be found in Server.groovy:
Auditing messages will always be send. Because they are send unreliably via the UDP protocol (this is the default), the XDS components "do not care" if there is actually a syslog server running at the specified host and port. If you want to see the audit messages that the demo repository logs, you can install a syslog server at localhost using the standard syslog port 514 or you can change the settings in Server.groovy to match your setup.
If you want to disable auditing you can do so by changing the endpoint configurations, e.g. for ITI-18:
IPF extension mechanism tutorial
| Preliminary content This section is work in progress. |
This tutorial is targeted at developers who want to learn more about the new IPF extension mechanism. Before going through this tutorial we recommend you first to read the IPF extension mechanism wiki section and then go through both First details tutorial and HL7 processing tutorial, where you can find most of the technical explanations needed to understand the content covered in this section.
Source code
The latest sources of the this tutorial are located at http://github.com/oehf/ipf/tree/master/tutorials/config. To check out the sources, clone the ipf git repository (see also Appendix A)
git clone git://github.com/oehf/ipf.git
Then change into the tutorials/config directory.
Project structure - base application
The concept of this tutorial is to present you how can be IPF extension mechanism applied to extend the functionality of one IPF based application (a base application) with some custom extensions. As a part of base application you can find the following resources inside the tutorial project:
- org.openehealth.ipf.tutorials.config.base.Base.java - the executable Main class of the base application
- org.openehealth.ipf.tutorials.config.base.route.SampleRouteBuilder.groovy - a route definition of the base application
- org.openehealth.ipf.tutorials.config.base.extend.SampleModelExtensionModule.groovy - a DSL extension of the base application
- config/base-context.xml - spring beans definition of the base module
- config/extender-context.xml - spring beans definition of the extension mechanism (configurers and post processor)
Route definition
The SampleRouteBuilder.groovy route builder defines 5 routes. Note that in order to be ready for customizations the SampleRouteBuilder extends the org.openehealth.ipf.platform.camel.core.config.CustomRouteBuilder:
Actually there are two separate routing sections. The input of the first one is defined over the first http endpoint (from('jetty:http://0.0.0.0:8800/reverse')), the EIP diagram is shown below:

It is a simple flow where the input is being converted to a string and then sent over the multicast to both direct endpoints (reverse-response and file-save). The file-save direct endpoint writes the content in a file whereas the reverse-response routes the exchange further to the transmogrifier which converts the input in reversed order and sends it back to the client.
The second routing section accepts the input messages over the other http endpoint (from('jetty:http://0.0.0.0:8800/map')) as shown on the EIP diagram below. This route basically deals with the HL7v2 extensions which are also available in IPF. The input is being converted to string and then unmarshaled to the message adapter. Afterwards the HL7v2 message will be validated and sent over the map direct endpoint to a marshaler which marshals a HL7 message adapter to an output stream. The next is the file endpoint which saves content to a file. Finally the last transmogrifier (responseTransmogrifier) creates the response and sends it back to the client.

Spring beans definition
The spring beans definition of the base application is separated in two context xml files.
- base-context.xml - bean definitions required for the base route
- extender-context.xml - defines all configurers and post processor as a part of the exension mechanism
First let's take a look at the base-context.xml. Note that you're not required to add the "baseRoute" bean to the "camelContext". Since it extends the CustomRouteBuilder the IPF extension mechanism will do that for you. Also the "baseExtension" bean will be activated by the IPF extension mechanism because it implements the org.openehealth.ipf.commons.core.extend.config.Extension marker interface therefore it doesn't require explicit adding to the "routeModelExtender".
The extender-context.xml defines all configurers potentially needed to extend the base application. Therefore activated are three configurers, one which activates the custom mappings, one for the custom DSL extensions and last one for the custom route builders. In addition defined is the "postProcessor" which actually processes all defined configurers. Each defined configurer must be explicitly added to the "postProcessor" bean:
Assembly and installation
To create a distribution package from our tutorial project please run from the command line:
mvn clean assembly:assembly
which should create a tutorial-config-<version>.zip file inside the projects target folder. If you unpack this archive you will get the structure like shown on the picture below:

The lib folder contains the project jar file (tutorial-config-<version>.jar) as well as all required runtime dependencies. Startup scripts are located directly under the root folder. The content of the conf folder will be analyzed later. That is all you need to run the base application. So let's start the provided startup.bat/sh script (depending on the operating system you running it on). This will start two routes accepting the requests on the port 8800. As HTTP client we use the Eclipse HTTP Client. To prepare the reverse-route test open the Eclipse HTTP client and
- Enter http://localhost:8800/reverse in the address field (top-left corner of the window)
- Select POST in the HTTP method field (top-right corner of the window)
- Enter some content of your choice in the Body field
To submit the request, press the green arrow at the top of the window. As a response you should get the reversed content with the 200 http response code, like shown on the picture below. Additionally you should get the original message content saved in the target/output/default.txt file:

Now let's prepare the map-route test again using the Eclipse HTTP client. For this test please download a sample hl7 message and:
- Enter http://localhost:8800/map in the address field
- Select POST in the HTTP method field
- Copy the sample hl7 message content in the Body field
Submit the request and as a response you should get the "map response ok!" with the 200 http response code. The original message content will be additionally saved in the target/output/default.txt file.
Extensions definition
Now here will be shown how we can extend/customize the functionalities of both routes with usage of the IPF extension mechanism. We will extend the reverse-route by adding an additional transmogrifier (htmlTransmogrifier) which transforms the content in html-format and saves it in the target/output/transmogrified-<timestamp>.html file. See the extension part on the EIP diagram below:

This custom logic is written in a separate route builder (also extends the CustomRouteBuilder), see the code snippet below. This CustomInterceptor should intercept all incoming exchanges to the file-save direct endpoint and additionally process it with the htmlTransmogrifier. The CustomInterceptor.groovy can be found under conf/config folder of the unzipped assembly archive:
Note the setDestinationHeader DSL extension. It is defined in the custom DSL extension and its being activated by the IPF extension mechanism:
The spring definition for these extensions you can found under conf/extension-context.xml. Here is the snippet of the CustomInterceptor, HtmlTransmogrifier and CustomModelExtension bean definition:
Note the intercepted property of the interceptorRoute bean. It tells the IPF extension mechanism to inject this custom route builder in the existing baseRoute custom route builder. If you don't want to inject your custom route builder in any of existing route builders but only to add it to the existing CamelContext, you should not define this property at all.
If you want to test this functionality please run the scripts startup.bat/sh with additional parameter:
startup.bat extension-context.xml
The org.openehealth.ipf.tutorials.config.base.Base class expects that first argument is the name of additional extensions spring context file and it tries to start this context along with the base-context.xml and extender-context.xml.
Now if you run the same test for the reverse-route with the HTTP client:
- Enter http://localhost:8800/reverse in the address field
- Select POST in the HTTP method field
- Enter some content of your choice in the Body field
After submitting the request you should get the reversed content with the 200 http response code, and additionally the original message content saved in the target/output/transmogrified-<timestamp>.html file similar to the screenshot below:

Next we will extend the mapping-route by adding a custom mapping and a custom transmogrifier which uses this mapping definition to transform the incoming HL7v2 message. Also it will be defined a custom exception handler which handles the exceptions of the type ca.uhn.hl7v2.HL7Exception.class. The exception handler customizes the response to the client (response 400) and saves the exception message to the targer/hl7-error/error-<timestamp>.txt file. See the extension parts on the EIP diagram below:

The CustomInterceptor.groovy intercepts all incoming exchanges to the map direct endpoint and additionally processes it with the "genderTransmogrifier". The CustomInterceptor.groovy can be found under conf/config folder of the unzipped assembly archive:
The "genderTransmogrifier" makes use of custom mapping definition (mapGender) to make the transformation.
Custom mapping is defined in separate mapping definition file under conf/gender.map:
In extension-context.xml you can found the bean definitions for these extensions as well. Here is the snippet of the "customInterceptor", "genderTransmogrifier" and "customMappings" bean definition:
You may have noticed when you run the base application, that if you tried to send some not-hl7v2 content to the "http://localhost:8800/map" endpoint, you would get an 500 http response code with the complete exception trace as a response content. Let's try to customize this behavior by adding the custom exception handler for the very specific ca.uhn.hl7v2.HL7Exception.class exception type, and do some custom handling when such exception occurs. We have defined this exception handler in a separate route builder CustomExceptionHandler.groovy which also extends the CustomRouteBuilder:
Details about exception handling in Camel can be found here. What we did here basically is extracted the exception message from the exchange and returned it back to the client with the 400 http response code. Additionally the exception message content will be saved in the /target/hl7-error/error-<timestamp>.txt file.
Notice the bean definition for this exception handler is similar to the "customInterceptor" bean. This bean also defines the intercepted property which means it will be also injected in the "baseRoute":
Now let's prepare the map-route test again using the Eclipse HTTP client. We will use the same sample hl7 message we used in the first test for this route:
- Enter http://localhost:8800/map in the address field
- Select POST in the HTTP method field
- Copy the sample hl7 message content in the Body field
Submit the request and as a response you should get the "map response ok!" with the 200 http response code. Message content transformed over the genderTransmogrifier will be additionally saved in the target/output/transmogrified-<timestamp>.html file. Note that the transformation was successful if you have in the message on the position marked at the screenshot the value of "W":

Let's try to send to the same http endpoint some not-hl7v2 content which should trigger the HL7Exception on the processor which tries to parse this content (unmarshal.ghl7). You should get this exception message as response content together with the 400 http response code. The exception message should be also saved in a target/hl7-error/error-<timestamp>.txt file.
