Académique Documents
Professionnel Documents
Culture Documents
Legal Notices
Progress Software Corporation and/or its subsidiaries may have patents, patent applications, trademarks, copyrights, or other
intellectual property rights covering subject matter in this publication. Except as expressly provided in any written license agreement
from Progress Software Corporation, the furnishing of this publication does not give you any license to these patents, trademarks,
copyrights, or other intellectual property. Any rights not expressly granted herein are reserved.
Progress, IONA, IONA Technologies, the IONA logo, Orbix, High Performance Integration, Artix, FUSE, and Making Software
Work Together are trademarks or registered trademarks of Progress Software Corporation and/or its subsidiaries in the US and
other countries.
Java and J2EE are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
CORBA is a trademark or registered trademark of the Object Management Group, Inc. in the US and other countries. All other
trademarks that appear herein are the property of their respective owners.
While the information in this publication is believed to be accurate Progress Software Corporation makes no warranty of any kind
to this material including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose.
Progress Software Corporation shall not be liable for errors contained herein, or for incidental or consequential damages in
connection with the furnishing, performance or use of this material.
All products or services mentioned in this manual are covered by the trademarks, service marks, or product names as designated
by the companies who market those products.
No part of this publication may be reproduced, stored in a retrieval system or transmitted, in any form or by any means,
photocopying, recording or otherwise, without prior written consent of IONA Technologies PLC. No third-party intellectual property
right liability is assumed with respect to the use of the information contained herein. IONA Technologies PLC assumes no
responsibility for errors or omissions contained in this publication. This publication and features described herein are subject to
change without notice. Portions of this document may include Apache Foundation documentation, all rights reserved.
Table of Contents
Deploying Integration Patterns ................................................................................................. 11
What are the Enterprise Integration Patterns? ........................................................................ 12
Architecture ................................................................................................................. 14
Deploying an XML Route ................................................................................................. 17
Running the camel-osgi Demonstration ............................................................................... 27
Using the NMR Component .............................................................................................. 30
Running the camel-nmr Demonstration ............................................................................... 36
Defining Routes in Java DSL .................................................................................................... 39
Implementing a RouteBuilder Class .................................................................................... 40
Basic Java DSL Syntax .................................................................................................... 42
Processors ................................................................................................................... 46
Languages for Expressions and Predicates ............................................................................ 51
Transforming Message Content .......................................................................................... 56
Defining Routes in XML .......................................................................................................... 63
Using the Router Schema in an XML File ............................................................................. 64
Defining a Basic Route in XML .......................................................................................... 66
Processors ................................................................................................................... 67
Languages for Expressions and Predicates ............................................................................ 74
Transforming Message Content .......................................................................................... 76
3
4
List of Figures
1. FUSE Mediation Router Deployed into the OSGi Container ............. 14
2. Local Routing Rules .............................................................. 43
5
6
List of Tables
1. Properties for Simple Language ............................................... 52
2. Transformation Methods from the ProcessorType Class .................. 57
3. Methods from the Builder Class ............................................... 58
4. Modifier Methods from the ValueBuilder Class ............................. 59
5. Elements for Expression and Predicate Languages ........................ 74
7
8
List of Examples
1. Spring XML File Defining a Route ............................................. 18
2. Transformation Bean Example ................................................. 21
3. Manifest for the camel-osgi Demonstration ................................. 22
4. Configuration of Maven Bundle Plug-In in pom.xml File ................. 24
5. Spring XML Defining a Route with an NMR Endpoint .................... 32
6. Manifest for the camel-nmr Demonstration ................................. 34
7. Implementation of a RouteBuilder Class .................................... 40
8. Implementing a Custom Processor Class .................................... 50
9. Simple Transformation of Incoming Messages ............................. 56
10. Using Artix Data Services to Marshal and Unmarshal .................. 62
11. Specifying the Router Schema Location ................................... 64
12. Router Schema in a Spring Configuration File ............................ 65
13. Basic Route in XML ............................................................ 66
14. Using Artix Data Services to Marshal and Unmarshal .................. 78
9
10
Deploying Integration Patterns
This chapter explains how to get started with enterprise integration patterns in the context of FUSE ESB's OSGi
container. The fundamental task that you need to learn about is how to deploy a FUSE Mediation Router application
into the OSGi container (where FUSE Mediation Router provides the implementation of the enterprise integration
patterns.
11
Deploying Integration Patterns
12
What are the Enterprise Integration Patterns?
Example of an enterprise
To take a concrete example of an enterprise integration pattern, consider the
integration pattern
splitter pattern, which can be implemented by a simple route in Java, as
follows:
from("activemq:my.queue").split
ter(xpath("/*")).to("file://some/directory")
Where the route is written in FUSE Mediation Router's Java DSL (see Basic
Java DSL Syntax on page 42 for details). This route defines a consumer
endpoint, activemq:my.queue, using the ActiveMQ component (where
Apache ActiveMQ is a JMS -compliant messaging system). The consumer
endpoint is configured to receive messages from a queue called my.queue.
Messages pulled off the queue pass through the splitter processor,
splitter(xpath("/*")), which applies an XPath expression to the incoming
message (presumed to be in XML format) in order to split it into multiple
smaller messages. In this example, the effect is to split off every root-level
element into a separate message.
Further reading
To gain a fuller understanding of how to implement enterprise integration
patterns using FUSE Mediation Router, we recommend that you supplement
this introductory guide by reading one or more of the following documents
from the FUSE Mediation Router library:
13
Deploying Integration Patterns
Architecture
Overview
Figure 1 on page 14 gives an overview of the architecture that forms the
basis of FUSE Mediation Router applications.
OSGi container
The OSGi container provides the overall framework for your FUSE Mediation
Router applications. To start the OSGi container, open a command prompt,
change directory to the FUSE ESB install directory, and enter the following
command:
bin/servicemix
After a few moments, the container initializes itself and you should see a shell
prompt like the following:
ServiceMix>
14
Architecture
For example, to list all of the bundles currently deployed in the OSGi container,
enter the command osgi list.
OSGi bundles
All parts of the application, including the FUSE ESB kernel and the FUSE
Mediation Router core, are deployed as OSGi bundles. In OSGi, everything
is a bundle.
At its simplest, a bundle is simply a JAR file that has a few additional entries
in its manifest, META-INF/MANIFEST.MF. These additional entries are known
as bundle headers (for more details, see Example 3 on page 22).
NMR
The FUSE ESB NMR is a general-purpose message bus used for transmitting
messages between bundles in the OSGi container. It is modelled on the
Normalized Message Router (NMR) defined in the Java Business Integration
(JBI) specification [http://www.jcp.org/en/jsr/detail?id=208]. Hence, the
FUSE ESB NMR can be used to transmit XML messages, optionally augmented
with properties and attachments. Unlike the standard NMR, however, the
FUSE ESB NMR is not restricted to the JBI container. You can use the NMR
to transmit messages inside the OSGi container or, if the JBI container is also
deployed, to transmit messages between the two containers.
For your FUSE Mediation Router applications, you will always require the
FUSE Mediation Router core and possibly one or more FUSE Mediation Router
components as well.
When you start a bundle (for example, using the command, osgi start),
the Spring dynamic bundles extensor automatically checks to see whether
15
Deploying Integration Patterns
there are any Spring configuration files matching *.xml under the
META-INF/spring directory. If any configuration files are found there, Spring
automatically instantiates and initializes the beans defined in these files. By
exploiting this mechanism, you can effectively use Spring configuration files
to bootstrap your application. This mechanism is particularly convenient for
FUSE Mediation Router, because it is normal practice to use Spring
configuration files to initialize FUSE Mediation Router applications.
NMR component
The NMR component is of particular interest, because it enables FUSE
Mediation Router applications to communicate with each other (and with
other bundles) through the NMR. Additionally, the NMR makes it possible
to communicate with applications deployed into the JBI container.
For details about how to use the NMR component, see Using the NMR
Component on page 30.
NMR endpoints
If a FUSE Mediation Router application needs to communicate over the NMR,
it can create an NMR endpoint simply by specifying an NMR endpoint URI
as the source or destination of a route. For details of how this is done, see
Using the NMR Component on page 30.
16
Deploying an XML Route
camel-osgi demonstration
The camel-osgi demonstration is located in the following directory:
InstallDir/examples/camel-osgi
The demonstration shows how to define a route in XML, where the route is
built up from the following parts:
• At the end of the route is a log endpoint, which sends the transformed
message to Jakarta commons logger.
The route is deployed into the FUSE ESB container as an OSGi bundle.
When you start an application bundle in the FUSE ESB OSGi container, Spring
looks for any files matching *.xml located in META-INF/spring and processes
all of the matched files. In effect, the Spring XML files located in this directory
bootstrap the bundled application.
17
Deploying Integration Patterns
<beans xmlns="http://www.springframework.org/schema/beans" ❶
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
❷
xmlns:osgi="http://act
ivemq.apache.org/camel/schema/osgi" ❸
xmlns:osgix="http://www.springframework.org/schema/osgi-
compendium" ❹
xsi:schemaLocation="
http://www.springframework.org/schema/beans ht
tp://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/camel/schema/spring ht
tp://activemq.apache.org/camel/schema/spring/camel-spring.xsd
http://activemq.apache.org/camel/schema/osgi http://act
ivemq.apache.org/camel/schema/osgi/camel-osgi.xsd
http://www.springframework.org/schema/osgi-compendium
http://www.springframework.org/schema/osgi-compendium/spring-
osgi-compendium.xsd">
<osgi:camelContext xmlns="http://act
ivemq.apache.org/camel/schema/spring"> ❺
<route> ❻
<from uri="timer://myTimer?fixedRate=true&peri
od=2000"/>
<bean ref="myTransform" method="transform"/>
<to uri="log:ExampleRouter"/>
</route>
</osgi:camelContext>
<osgix:property-placeholder persistent-id="org.apache.servi
cemix.examples"> ❾
<osgix:default-properties>
<prop key="prefix">MyTransform</prop>
</osgix:default-properties>
</osgix:property-placeholder>
</beans>
18
Deploying an XML Route
19
Deploying Integration Patterns
For more details about how to define XML routes, see Defining Routes
in XML on page 63.
❼ The bean element instantiates the transformer bean using standard
Spring syntax. When Spring parses this bean element, it creates a new
instance of the class,
org.apache.servicemix.examples.camel.MyTransform, by calling
its default constructor. The property element is used to initialize the
value of a Java bean property. In this case, the effect is that Spring calls
the MyTransform.setPrefix() method, passing in the string,
MyTransform. Finally, Spring enters the newly instantiated bean in its
registry, using the ID value, myTransform. This enables other parts of
the configuration to access the bean (for example, using a tag like <bean
ref="IDValue" .../>).
For more details about how to instantiate Java classes using Spring, see
The IoC Container
[http://static.springframework.org/spring/docs/2.5.x/reference/beans.html]
chapter in the Spring documentation.
❽ The prefix value is specified using a property placeholder, ${prefix},
which enables you to source bean property values from the OSGi
Configuration Admin service.
Note
Using property placeholders is optional. You can specify the
literal value of the property here, if you prefer.
20
Deploying an XML Route
Definition of a transformation
Example 2 on page 21 shows the definition of a transformation bean class,
bean
org.apache.servicemix.examples.camel.MyTransform, which is
responsible for transforming the body of the In message in a route. The
MyTransform.transform() method from this class gets called by the
preceding XML route (see Example 1 on page 18).
import java.util.Date;
import org.apache.commons.logging.Log; ❶
import org.apache.commons.logging.LogFactory;
if (verbose) {
System.out.println(">>>> " + answer);
}
LOG.info(">>>> " + answer);
return answer; ❸
}
21
Deploying Integration Patterns
22
Deploying an XML Route
Import-Package: org.apache.camel.osgi,org.apache.commons.log
ging
Bnd-LastModified: 1222079507224
Bundle-Version: 4.0.0.fuse
Bundle-Name: Apache ServiceMix Example :: Camel OSGi
Bundle-Description: This pom provides project information that
is comm
on to all ServiceMix branches.
Build-Jdk: 1.5.0_08
Private-Package: org.apache.servicemix.examples.camel
Bundle-DocURL: http://www.apache.org/
Bundle-ManifestVersion: 2
Bundle-Vendor: The Apache Software Foundation
Bundle-SymbolicName: camel-osgi
Tool: Bnd-0.0.255
Bundle-Name:
A human-readable name for the current bundle. For example, when you
enter the command, osgi list, from the FUSE ESB console, the
installed bundles are listed with their bundle name.
Bundle-SymbolicName:
A unique name for the current bundle. The bundle symbolic name is the
primary way of identifying a bundle. For example, you would use the
bundle symbolic name to refer to a bundle from within a piece of code
or in a configuration file. The usual convention for a bundle symbolic
name is to use a reverse URL format (the sample value shown here is
unconventional in this respect).
Import-Package:
The value of this header is a comma-separated list of Java package
names for all of the packages needed by, but not included in this bundle.
For example, the org.apache.commons.logging package is listed here
because it is used by the transformation bean code.
23
Deploying Integration Patterns
Note
The syntax of the package list can be a bit more complicated
than a comma-separated list (for example, you can optionally
select particular package versions and so on). For full details of
the syntax, consult the OSGi specification
[http://www.osgi.org/Specifications/HomePage].
Export-Package:
(Not used in the example) The value of this header is a comma-separated
list of Java package names for packages contained in the current bundle
that you want to make available to other bundles. The Export-Package:
and Import-Package: headers are complementary: the Java packages
exported by one bundle using Export-Package: become available to
other bundles that import them using Import-Package:.
Creating the contents of these headers can be an annoying chore, if you have
to do it by hand. For example, a moderately complex piece of Java code might
need to import dozens of different packages using the Import-Package:
header. For larger applications, it would be convenient to have some way of
automatically generating bundle headers.
24
Deploying an XML Route
...
<dependencies>
<dependency> ❷
<groupId>org.apache.felix</groupId>
<artifactId>org.osgi.core</artifactId>
<version>1.0.0</version>
</dependency>
...
</dependencies>
...
<build>
<plugins>
<plugin> ❸
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Bundle-SymbolicName>${pom.artifact
Id}</Bundle-SymbolicName>
<Import-Pack
age>*,org.apache.camel.osgi</Import-Package>
<Private-Package>org.apache.service
mix.examples.camel</Private-Package>
</instructions>
</configuration>
</plugin>
</plugins>
...
</build>
...
</project>
Note
In the POM file for the camel-osgi demonstration, this
dependency does not appear explicitly. It is actually included
in the parent POM.
There is no need to install the Maven bundle plug-in explicitly. Because the
plug-in is archived in the default Maven2 repository (that is,
http://repo1.maven.org/maven2/), Maven will automatically locate and
25
Deploying Integration Patterns
download the plug-in the first time you build your application (provided that
the build machine is connected to the Internet). The preceding configuration
defines the following instructions for generating bundle headers:
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<Import-Package>*,org.apache.camel.osgi</Import-Package>
<Private-Package>org.apache.servicemix.examples.camel</Private-
Package>
Bundle-SymbolicName
Generates the Bundle-SymbolicName: manifest entry. In this example,
it is set to the project's artifact ID, which resolves to camel-osgi. If you
want to be sure to avoid a name clash, you could define the symbolic
name as follows:
<Bundle-SymbolicName>$(pom.groupId).${pom.artifact
Id}</Bundle-SymbolicName>
Import-Package
Generates the Import-Package: manifest entry. The wildcard character
, *, generates a list containing every referenced Java package not included
in the current Maven project. The org.apache.camel.osgi package
is listed explicitly, because it is not referenced in the source code, but it
is needed at run time.
Private-Package
Generates the Private-Package: manifest entry. This entry does not
correspond to a bundle header and is ignored by the OSGi container.
The private package list contains the names of all the Java packages
that are defined in the current bundle, but are not visible to other bundles.
It is provided for information purposes only.
Because the bundle plug-in generates sensible defaults for most of the bundle
headers, the preceding plug-in configuration is normally adequate to get you
started with most projects. For more details about configuring the Maven
bundle plug-in, see Maven bundle plug-in
[http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html].
26
Running the camel-osgi Demonstration
1. Install and start the camel-osgi bundle—at the FUSE ESB console, enter
the following command:
servicemix> features install examples-camel-osgi
This command has the effect of both installing and starting the camel-osgi
bundle. After entering this command, you should soon see output like the
following being logged to the console screen:
>>>> MyTransform set body: Mon Sep 22 11:43:42 BST 2008
>>>> MyTransform set body: Mon Sep 22 11:43:44 BST 2008
>>>> MyTransform set body: Mon Sep 22 11:43:46 BST 2008
2. Stop the camel-osgi bundle—to stop the camel-osgi bundle, you first
need to discover the relevant bundle number. To find the bundle number,
enter the following console command (this might look a bit confusing,
because the text you are typing will intermingle with the output that is
being logged to the screen):
servicemix> osgi list
At the end of the listing, you should see an entry like the following:
[ 126] [Active ] [ 50] Apache ServiceMix Example ::
Camel OSGi (4.0.0.2-fuse)
Where, in this example, the bundle number is 126. To stop this bundle,
enter the following console command:
servicemix> osgi stop 126
27
Deploying Integration Patterns
2. Install and start the camel-osgi bundle—at the FUSE ESB console, enter
the following command:
servicemix> osgi install -s file:InstallDir/examples/camel-
osgi/target/camel-osgi-4.0.0.2-fuse.jar
Where InstallDir is the directory where you installed FUSE ESB and
the -s flag directs the container to start the bundle right away. For example,
if your install directory is
C:\Programs\FUSE\apache-servicemix-4.0.0.2-fuse on a Windows
machine, you would enter the following command:
servicemix> osgi install -s file:C:/Programs/FUSE/apache-
servicemix-4.0.0.2-fuse/examples/camel-osgi/target/camel-
osgi-4.0.0-fuse-SNAPSHOT.jar
After entering this command, you should soon see output like the following
being logged to the console screen:
>>>> MyTransform set body: Mon Sep 22 11:43:42 BST 2008
>>>> MyTransform set body: Mon Sep 22 11:43:44 BST 2008
>>>> MyTransform set body: Mon Sep 22 11:43:46 BST 2008
Note
When specifying the location of a bundle to the osgi install
command, it is essential to use forward slashes, /, in the path
name, even on a Windows machine.
3. Stop the camel-osgi bundle—to stop the camel-osgi bundle, you first
need to discover the relevant bundle number. To find the bundle number,
enter the following console command (this might look a bit confusing,
28
Running the camel-osgi Demonstration
because the text you are typing will intermingle with the output that is
being logged to the screen):
servicemix> osgi list
At the end of the listing, you should see an entry like the following:
[ 126] [Active ] [ 50] Apache ServiceMix Example ::
Camel OSGi (4.0.0.2-fuse)
Where, in this example, the bundle number is 126. To stop this bundle,
enter the following console command:
servicemix> osgi stop 126
29
Deploying Integration Patterns
<beans xmlns="http://www.springframework.org/schema/beans"
... >
...
<import resource="classpath:org/apache/service
mix/camel/camel-nmr.xml" />
...
</beans>
30
Using the NMR Component
nmr:JBIAddressingURI
camel-nmr demonstration
The camel-nmr demonstration is located in the following directory:
InstallDir/examples/camel-nmr
The demonstration defines two routes in XML, where the routes are joined
together using an NMR endpoint. The first route is defined as follows:
• At the end of the route is an NMR endpoint, which transmits the messages
to the next route.
• At the start of the second route is an NMR endpoint, which receives the
messages sent by the first route.
• At the end of the route is a log endpoint, which sends the transformed
message to Jakarta commons logger.
The route is deployed into the FUSE ESB container as an OSGi bundle.
31
Deploying Integration Patterns
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://act
ivemq.apache.org/camel/schema/osgi"
xmlns:osgix="http://www.springframework.org/schema/osgi-
compendium"
xsi:schemaLocation="
http://www.springframework.org/schema/beans ht
tp://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/camel/schema/spring ht
tp://activemq.apache.org/camel/schema/spring/camel-spring.xsd
http://activemq.apache.org/camel/schema/osgi http://act
ivemq.apache.org/camel/schema/osgi/camel-osgi.xsd
http://www.springframework.org/schema/osgi-compendium
http://www.springframework.org/schema/osgi-compendium/spring-
osgi-compendium.xsd">
<import resource="classpath:org/apache/service
mix/camel/camel-nmr.xml" /> ❶
<osgi:camelContext xmlns="http://act
ivemq.apache.org/camel/schema/spring">
<!-- Route periodically sent events into the NMR -->
<route>
<from uri="timer://myTimer?fixedRate=true&peri
od=2000"/>
<to uri="nmr:ExampleRouter"/> ❷
</route>
<!-- Route exchange from the NMR endpoint to a log endpoint
-->
<route>
<from uri="nmr:ExampleRouter"/> ❸
<bean ref="myTransform" method="transform"/>
<to uri="log:ExampleRouter"/>
</route>
</osgi:camelContext>
</beans>
32
Using the NMR Component
The location of the XML snippet is specified using the classpath URI,
classpath:org/apache/servicemix/camel/camel-nmr.xml, which
indicates that the XML file can be found on the current CLASSPATH. As
a matter of fact, the camel-nmr.xml file is located in a separate bundle:
that is, it is part of the org.apache.servicemix.camel.component
bundle. Hence, it is necessary to import the
org.apache.servicemix.camel bundle, in order to make this XML
file accessible (see Example 6 on page 34 for details of how this is
done).
❷ At the end of the first route, messages are sent to the NMR endpoint,
nmr:ExampleRouter.
❸ When you specify an NMR endpoint in the uri attribute of the <from>
tag, a new NMR endpoint is created by the NMR component. In this
example, the <from> tag implicitly creates the NMR endpoint,
nmr:ExampleRouter, which is then capable of receiving the messages
sent by the first route.
Note
The NMR is a general purpose message bus that operates
independently of the JBI container. Hence, it is possible to
create NMR endpoints outside the JBI container, as is done in
this example.
Example 6 on page 34 shows the manifest for the camel-nmr bundle (for a
more detailed explanation of these manifest entries, see
Example 3 on page 22).
33
Deploying Integration Patterns
The important entries are the bundle headers that affect the interaction
between the bundle and the OSGi container and these are, as follows:
Bundle-Name:
A human-readable name for the current bundle.
Bundle-SymbolicName:
A unique name for the current bundle.
Import-Package:
A comma-separated list of Java package names for all of the packages
needed by, but not included in this bundle. The following packages are
imported into this bundle:
34
Using the NMR Component
35
Deploying Integration Patterns
1. Install and start the camel-nmr bundle—at the FUSE ESB console, enter
the following command:
servicemix> features install examples-camel-nmr
This command has the effect of both installing and starting the camel-nmr
bundle. After entering this command, you should soon see output like the
following being logged to the console screen:
>>>> MyTransform set body: Mon Sep 22 12:09:10 BST 2008
>>>> MyTransform set body: Mon Sep 22 12:09:12 BST 2008
>>>> MyTransform set body: Mon Sep 22 12:09:14 BST 2008
2. Stop the camel-nmr bundle—to stop the camel-nmr bundle, you first
need to discover the relevant bundle number. To find the bundle number,
enter the following console command (this might look a bit confusing,
because the text you are typing will intermingle with the output that is
being logged to the screen):
servicemix> osgi list
At the end of the listing, you should see an entry like the following:
[ 129] [Active ] [ 50] Apache ServiceMix Example ::
Camel NMR (4.0.0.2-fuse)
Where, in this example, the bundle number is 129. To stop this bundle,
enter the following console command:
servicemix> osgi stop 129
1. If you have not done so already, install the camel-nmr feature by entering
the following command at the FUSE ESB console:
36
Running the camel-nmr Demonstration
3. Install and start the camel-nmr bundle—at the FUSE ESB console, enter
the following command:
servicemix> osgi install -s file:InstallDir/examples/camel-
nmr/target/camel-nmr-4.0.0.2-fuse.jar
Where InstallDir is the directory where you installed FUSE ESB and
the -s flag directs the container to start the bundle right away. For example,
if your install directory is
C:\Programs\FUSE\apache-servicemix-4.0.0.2-fuse on a Windows
machine, you would enter the following command:
servicemix> osgi install -s file:C:/Programs/FUSE/apache-
servicemix-4.0.0.2-fuse/examples/camel-nmr/target/camel-
nmr-4.0.0-fuse-SNAPSHOT.jar
After entering this command, you should soon see output like the following
being logged to the console screen:
>>>> MyTransform set body: Mon Sep 22 12:09:10 BST 2008
>>>> MyTransform set body: Mon Sep 22 12:09:12 BST 2008
>>>> MyTransform set body: Mon Sep 22 12:09:14 BST 2008
Note
When specifying the location of a bundle to the osgi install
command, it is essential to use forward slashes, /, in the path
name, even on a Windows machine.
4. Stop the camel-nmr bundle—to stop the camel-nmr bundle, you first
need to discover the relevant bundle number. To find the bundle number,
37
Deploying Integration Patterns
enter the following console command (this might look a bit confusing,
because the text you are typing will intermingle with the output that is
being logged to the screen):
servicemix> osgi list
At the end of the listing, you should see an entry like the following:
[ 129] [Active ] [ 50] Apache ServiceMix Example ::
Camel NMR (4.0.0.2-fuse)
Where, in this example, the bundle number is 129. To stop this bundle,
enter the following console command:
servicemix> osgi stop 129
38
Defining Routes in Java DSL
You can define routing rules in Java, using a domain specific language (DSL). The routing rules represent the
core of a router application and Java DSL is currently the most flexible way to define them.
39
Defining Routes in Java DSL
RouteBuilder class
The org.apache.camel.builder.RouteBuilder class is the base class
for implementing your own route builder types. It defines an abstract method,
configure(), that you must override in your derived implementation class.
In addition, RouteBuilder also defines methods that are used to initiate the
routing rules (for example, from(), intercept(), and exception()).
Implementing a RouteBuilder
Example 7 on page 40 shows an example of a simpler RouteBuilder
implementation. You need only define a single method, configure(), which
contains a list of routing rules (one Java statement for each rule).
40
Implementing a RouteBuilder Class
}
}
Where the rule of the form from(URL1).to(URL2) instructs the router to read
messages from the file system located in directory, src/data, and send them
to files located in the directory, target/messages. The option, ?noop=true,
specifies that the source messages are not to be deleted from the src/data
directory.
41
Defining Routes in Java DSL
The syntax of the DSL is implicitly defined by the type system of the
specialized API. For example, the return type of a method determines which
methods can legally be invoked next (equivalent to the next command in the
DSL).
42
Basic Java DSL Syntax
Note
It is also possible to define a global routing rule, by starting the rule
with a special processor type (such as intercept(), exception(),
errorHandler(), and so on). This kind of rule lies outside the scope
of the Getting Started guide.
43
Defining Routes in Java DSL
Processors
A processor is a method that can access and modify the stream of messages
passing through a rule. If a message is part of a remote procedure call (InOut
call), the processor can potentially act on the messages flowing in both
directions: on the request messages, flowing from source to target, and on
the reply messages, flowing from target back to source (see Message
exchanges on page 44). Processors can take expression or predicate
arguments, that modify their behavior. For example, the rule shown in
Figure 2 on page 43 includes a filter() processor that takes an xpath()
predicate as its argument.
from("seda:a").filter(head
er("foo").isEqualTo("bar")).to("seda:b");
Message exchanges
When a router rule is activated, it can process messages passing in either
direction: that is, from source to target or from target back to source. For
example, if a router rule is mediating a remote procedure call (RPC), the rule
would process requests, replies, and faults. How do you manage message
correlation in this case? One of the most effective and straightforward ways
is to use a message exchange object as the basis for processing messages.
uses message exchange objects (of org.apache.camel.Exchange type) in
its API for processing router rules.
The basic idea of the message exchange is that, instead of accessing requests,
replies, and faults separately, you encapsulate the correlated messages inside
44
Basic Java DSL Syntax
a single object (an Exchange object). Message correlation now becomes trivial
from the perspective of a processor, because correlated messages are
encapsulated in a single Exchange object and processors gain access to
messages through the Exchange object.
• InOnly
• RobustInOnly
• InOut
• InOptionalOut
• OutOnly
• RobustOutOnly
• OutIn
• OutOptionalIn
45
Defining Routes in Java DSL
Processors
Overview
To enable the router to do something more interesting than simply connecting
a source endpoint to a target endpoint, you can add processors to your route.
A processor is a command you can insert into a routing rule in order to perform
arbitrary processing of the messages that flow through the rule. provides a
wide variety of different processors, as follows:
• Pipeline on page 47
Filter
The filter() processor can be used to prevent uninteresting messages from
reaching the target endpoint. It takes a single predicate argument: if the
predicate is true, the message exchange is allowed through to the target; if
the predicate is false, the message exchange is blocked. For example, the
following filter blocks a message exchange, unless the incoming message
contains a header, foo, with value equal to bar:
from("SourceURL").filter(header("foo").isEqualTo("bar")).to("Tar
getURL");
Choice
The choice() processor is a conditional statement that is used to route
incoming messages to alternative targets. The alternative targets are each
preceded by a when() method, which takes a predicate argument. If the
46
Processors
from("SourceURL").choice().when(Predicate1).to("Target1")
.when(Predicate2).to("Target2")
.otherwise().to("Target3");
Pipeline
The pipeline() processor is used to link together a chain of targets, where
the output of one target is fed into the input of the next target in the pipeline
(analogous to the UNIX pipe command). The pipeline() method takes an
arbitrary number of endpoint arguments, which specify the sequence of
endpoints in the pipeline. For example, to pass messages from SourceURL
to Target1 to Target2 to Target3 in a pipeline, you could use the following
rule:
from("SourceURL").pipeline("Target1","Target2","Target3");
Recipient list
If you want the messages from a source endpoint, SourceURL, to be sent to
more than one target, there are two alternative approaches you can use. One
approach is to invoke the to() method with multiple target endpoints (static
recipient list), for example:
from("SourceURL").to("Target1","Target2","Target3");
Splitter
The splitter() processor is used to split a message into parts, which are
then processed as separate messages. The splitter() method takes a list
47
Defining Routes in Java DSL
argument, where each item in the list represents a message part that is to be
re-sent as a separate message. For example, the following rule splits the body
of an incoming message into separate lines and then sends each line to the
target in a separate message:
from("SourceURL").splitter(bodyAs(String.class).token
ize("\n")).to("TargetURL");
Aggregator
The aggregator() processor is used to aggregate related incoming messages
into a single message. In order to distinguish which messages are eligible to
be aggregated together, you need to define a correlation key for the aggregator.
The correlation key is normally derived from a field in the message (for
example, a header field). Messages that have the same correlation key value
are eligible to be aggregated together. You can also optionally specify an
aggregation algorithm to the aggregator() processor (the default algorithm
is to pick the latest message with a given value of the correlation key and to
discard the older messages with that correlation key value).
For example, if you are monitoring a data stream that reports stock prices in
real time, you might only be interested in the latest price of each stock symbol.
In this case, you could configure an aggregator to transmit only the latest
price for a given stock and discard the older (out-of-date) price notifications.
The following rule implements this functionality, where the correlation key is
read from the stockSymbol header and the default aggregator algorithm is
used:
from("SourceURL").aggregator(header("stockSymbol")).to("Tar
getURL");
Resequencer
A resequencer() processor is used to re-arrange the order in which incoming
messages are transmitted. The resequencer() method takes a sequence
number as its argument (where the sequence number is calculated from the
contents of a field in the incoming message). Naturally, before you can start
re-ordering messages, you need to wait until a certain number of messages
have been received from the source. There are a couple of different ways to
specify how long the resequencer() processor should wait before attempting
to re-order the accumulated messages and forward them to the target, as
follows:
48
Processors
Throttler
The throttler() processor is used to ensure that a target endpoint does
not get overloaded. The throttler works by limiting the number of messages
that can pass through per second. If the incoming messages exceed the
specified rate, the throttler accumulates excess messages in a buffer and
transmits them more slowly to the target endpoint. For example, to limit the
rate of throughput to 100 messages per second, you can define the following
rule:
from("SourceURL").throttler(100).to("TargetURL");
Delayer
The delayer() processor is used to hold up messages for a specified length
of time. The delay can either be relative (wait a specified length of time after
receipt of the incoming message) or absolute (wait until a specific time). For
example, to add a delay of 2 seconds before transmitting received messages,
you can use the following rule:
49
Defining Routes in Java DSL
from("SourceURL").delayer(2000).to("TargetURL");
To wait until the absolute time specified in the processAfter header, you
can use the following rule:
from("SourceURL").delayer(header("processAfter").to("TargetURL");
Custom processor
If none of the standard processors described here provide the functionality
you need, you can always define your own custom processor. To create a
custom processor, define a class that implements the
org.apache.camel.Processor interface and override the process()
method in this class. For example, the following custom processor,
MyProcessor, removes the header named foo from incoming messages:
To insert the custom processor into a router rule, invoke the process()
method, which provides a generic mechanism for inserting processors into
rules. For example, the following rule invokes the processor defined in
Example 8 on page 50:
org.apache.camel.Processor myProc = new MyProcessor();
from("SourceURL").process(myProc).to("TargetURL");
50
Languages for Expressions and Predicates
Which implies that the languages associated with the ScriptBuilder class
can also be used as message processors (see Custom processor on page 50).
Simple
The simple language is a very limited expression language that is built into
the router core. This language can be useful, if you need to eliminate
dependancies on third-party libraries whilst testing. Otherwise, you should
use one of the other languages. To use the simple language in your application
code, include the following import statement in your Java source files:
import static org.apache.camel.language.simple.SimpleLan
guage.simple;
51
Defining Routes in Java DSL
XPath
The xpath() static method parses message content using the XPath language
(to learn about XPath, see the W3 Schools tutorial,
http://www.w3schools.com/xpath/default.asp). To use the XPath language in
your application code, include the following import statement in your Java
source files:
import static org.apache.camel.builder.xml.XPathBuilder.xpath;
You could choose which target endpoint to route the message to, based on
the content of the city element, using the following rule:
from("file:src/data?noop=true").
choice().
when(xpath("/person/city = 'London'")).to("file:tar
52
Languages for Expressions and Predicates
get/messages/uk").
otherwise().to("file:target/messages/others");
XQuery
The xquery() static method parses message content using the XQuery
language (to learn about XQuery, see the W3 Schools tutorial,
http://www.w3schools.com/xquery/default.asp). XQuery is a superset of the
XPath language; hence, any valid XPath expression is also a valid XQuery
expression. To use the XQuery language in your application code, include the
following import statement in your Java source files:
import static org.apache.camel.builder.saxon.XQueryBuild
er.xquery;
JoSQL
The sql() static method enables you to call on the JoSQL (SQL for Java
objects) language to evaluate predicates and expressions in . JoSQL employs
a SQL-like query syntax to perform selection and ordering operations on data
from in-memory Java objects—JoSQL is not a database, however. In the
JoSQL syntax, each Java object instance is treated like a table row and each
object method is treated like a column name. Using this syntax, it is possible
to construct powerful statements for extracting and compiling data from
collections of Java objects. For details, see http://josql.sourceforge.net/.
To use the JoSQL language in your application code, include the following
import statement in your Java source files:
53
Defining Routes in Java DSL
OGNL
The ognl() static method enables you to call on OGNL (Object Graph
Navigation Language) expressions, which can then be used as predicates and
expressions in a router rule. For details, see http://www.ognl.org/.
To use the OGNL language in your application code, include the following
import statement in your Java source files:
import static org.apache.camel.language.ognl.OgnlExpres
sion.ognl;
EL
The el() static method enables you to call on the Unified Expression
Language (EL) to construct predicates and expressions in a router rule. The
EL was originally specified as part of the JSP 2.1 standard (JSR-245), but
is now available as a standalone language. integrates with JUEL
(http://juel.sourceforge.net/), which is an open source implementation of the
EL language.
To use the EL language in your application code, include the following import
statement in your Java source files:
import static org.apache.camel.language.juel.JuelExpression.el;
Groovy
The groovy() static method enables you to call on the Groovy scripting
language to construct predicates and expressions in a route. To use the Groovy
language in your application code, include the following import statement in
your Java source files:
import static org.apache.camel.builder.camel.script.Script
Builder.*;
JavaScript
The javaScript() static method enables you to call on the JavaScript
scripting language to construct predicates and expressions in a route. To use
the JavaScript language in your application code, include the following import
statement in your Java source files:
54
Languages for Expressions and Predicates
PHP
The php() static method enables you to call on the PHP scripting language
to construct predicates and expressions in a route. To use the PHP language
in your application code, include the following import statement in your Java
source files:
import static org.apache.camel.builder.camel.script.Script
Builder.*;
Python
The python() static method enables you to call on the Python scripting
language to construct predicates and expressions in a route. To use the Python
language in your application code, include the following import statement in
your Java source files:
import static org.apache.camel.builder.camel.script.Script
Builder.*;
Ruby
The ruby() static method enables you to call on the Ruby scripting language
to construct predicates and expressions in a route. To use the Ruby language
in your application code, include the following import statement in your Java
source files:
import static org.apache.camel.builder.camel.script.Script
Builder.*;
Bean
You can also use Java beans to evaluate predicates and expressions. For
example, to evaluate the predicate on a filter using the isGoldCustomer()
method on the bean instance, myBean, you can use a rule like the following:
from("SourceURL")
.filter().method("myBean", "isGoldCustomer")
.to("TargetURL");
55
Defining Routes in Java DSL
Simple transformations
The Java DSL has a built-in API that enables you to perform simple
transformations on incoming and outgoing messages. For example, the rule
shown in Example 9 on page 56 would append the text, World!, to the
end of the incoming message body.
• org.apache.camel.model.ProcessorType
• org.apache.camel.builder.Builder
• org.apache.camel.builder.ValueBuilder
ProcessorType class
The org.apache.camel.model.ProcessorType class defines the DSL
commands you can insert directly into a router rule—for example, the
setBody() command in Example 9 on page 56. Table 2 on page 57 shows
the ProcessorType methods that are relevant to transforming message
content:
56
Transforming Message Content
Method Description
Type convertBodyTo(Class type) Converts the IN message body to the specified type.
Type convertFaultBodyTo(Class type) Converts the FAULT message body to the specified type.
Type convertOutBodyTo(Class type) Converts the OUT message body to the specified type.
Type removeFaultHeader(String name) Adds a processor which removes the header on the
FAULT message.
Type removeHeader(String name) Adds a processor which removes the header on the IN
message.
Type removeOutHeader(String name) Adds a processor which removes the header on the OUT
message.
Type removeProperty(String name) Adds a processor which removes the exchange property.
Type setFaultBody(Expression expression) Adds a processor which sets the body on the FAULT
message.
Type setFaultHeader(String name, Expression Adds a processor which sets the header on the FAULT
expression) message.
Type setHeader(String name, Expression Adds a processor which sets the header on the IN
expression) message.
Type setOutBody(Expression expression) Adds a processor which sets the body on the OUT
message.
ExpressionClause<ProcessorType<Type>> Adds a processor which sets the header on the OUT
setOutHeader(String name) message.
57
Defining Routes in Java DSL
Method Description
Type setOutHeader(String name, Expression Adds a processor which sets the header on the OUT
expression) message.
Type setProperty(String name, Expression Adds a processor which sets the exchange property.
expression)
Builder class
The org.apache.camel.builder.Builder class provides access to message
content in contexts where expressions or predicates are expected. In other
words, Builder methods are typically invoked in the arguments of DSL
commands—for example, the body() command in Example 9 on page 56.
Table 3 on page 58 summarizes the static methods available in the Builder
class.
Method Description
static <E extends Exchange> ValueBuilder<E> Returns a predicate and value builder for the inbound
body() body on an exchange.
static <E extends Exchange,T> ValueBuilder<E> Returns a predicate and value builder for the inbound
bodyAs(Class<T> type) message body as a specific type.
static <E extends Exchange> ValueBuilder<E> Returns a predicate and value builder for the fault body
faultBody() on an exchange.
static <E extends Exchange,T> ValueBuilder<E> Returns a predicate and value builder for the fault
faultBodyAs(Class<T> type) message body as a specific type.
static <E extends Exchange> ValueBuilder<E> Returns a predicate and value builder for headers on an
header(String name) exchange.
58
Transforming Message Content
Method Description
static <E extends Exchange> ValueBuilder<E> Returns a predicate and value builder for the outbound
outBody() body on an exchange.
static <E extends Exchange> ValueBuilder<E> Returns a predicate and value builder for the outbound
outBody() message body as a specific type.
static <E extends Exchange> ValueBuilder<E> Returns an expression for the given system property.
systemProperty(String name)
static <E extends Exchange> ValueBuilder<E> Returns an expression for the given system property.
systemProperty(String name, String
defaultValue)
ValueBuilder class
The org.apache.camel.builder.ValueBuilder class enables you to
modify values returned by the Builder methods. In other words, the methods
in ValueBuilder provide a simple way of modifying message content.
Table 4 on page 59 summarizes the methods available in the ValueBuilder
class. That is, the table shows only the methods that are used to modify the
value they are invoked on (for full details, see the API Reference
documentation).
Method Description
ValueBuilder<E> append(Object value) Appends the string evaluation of this expression with the
given value.
ValueBuilder<E> convertTo(Class type) Converts the current value to the given type using the
registered type converters.
ValueBuilder<E> convertToString() Converts the current value a String using the registered
type converters.
ValueBuilder<E> regexReplaceAll(String regex, Replaces all occurrencies of the regular expression with
Expression<E> replacement) the given replacement.
ValueBuilder<E> regexReplaceAll(String regex, Replaces all occurrencies of the regular expression with
String replacement) the given replacement.
59
Defining Routes in Java DSL
Method Description
ValueBuilder<E> regexTokenize(String regex) Tokenizes the string conversion of this expression using
the given regular expression.
ValueBuilder<E> tokenize()
ValueBuilder<E> tokenize(String token) Tokenizes the string conversion of this expression using
the given token separator.
from("SourceURL").unmarshal(jaxb)
.<FurtherProcessing>.to("TargetURL");
60
Transforming Message Content
• Artix Data Services— also integrates with Artix Data Services, enabling
you to integrate stub code generated by Artix Data Services. See Artix Data
Services on page 61 for details.
61
Defining Routes in Java DSL
Note
Artix Data Services is licensed separately from .
62
Defining Routes in XML
You can define routing rules in XML. This approach is not as flexible as Java DSL, but has the advantage that it
is easy to reconfigure the routing rules at runtime.
63
Defining Routes in XML
64
Using the Router Schema in an XML File
http://activemq.apache.org/camel/schema/spring ht
tp://activemq.apache.org/camel/schema/spring/camel-spring.xsd">
65
Defining Routes in XML
66
Processors
Processors
Overview
To enable the router to something more interesting than simply connecting a
source endpoint to a target endpoint, you can add processors to your route.
A processor is a command you can insert into a routing rule in order to perform
arbitrary processing of the messages that flow through the rule. provides a
wide variety of different processors, as follows:
Filter
The filter processor can be used to prevent uninteresting messages from
reaching the target endpoint. It takes a single predicate argument: if the
predicate is true, the message exchange is allowed through to the target; if
the predicate is false, the message exchange is blocked. For example, the
following filter blocks a message exchange, unless the incoming message
contains a header, foo, with value equal to bar:
67
Defining Routes in XML
</route>
</camelContext>
Choice
The choice processor is a conditional statement that is used to route incoming
messages to alternative targets. The alternative targets are each enclosed in
a when element, which takes a predicate argument. If the predicate is true,
the current target is selected, otherwise processing proceeds to the next when
element in the rule. For example, the following choice() processor directs
incoming messages to either Target1, Target2, or Target3, depending on
the values of the predicates:
<camelContext id="buildSimpleRouteWithChoice" xmlns="http://act
ivemq.apache.org/camel/schema/spring">
<route>
<from uri="SourceURL"/>
<choice>
<when>
<!-- First predicate -->
<simple>header.foo = 'bar'</simple>
<to uri="Target1"/>
</when>
<when>
<!-- Second predicate -->
<simple>header.foo = 'manchu'</simple>
<to uri="Target2"/>
</when>
<otherwise>
<to uri="Target3"/>
</otherwise>
</choice>
</route>
</camelContext>
Recipient list
If you want the messages from a source endpoint, SourceURL, to be sent to
more than one target, there are two alternative approaches you can use. One
approach is to include multiple to elements in the route, for example:
68
Processors
<to uri="Target3"/>
</route>
</camelContext>
Splitter
The splitter processor is used to split a message into parts, which are then
processed as separate messages. The splitter element must contain an
expression that returns a list, where each item in the list represents a message
part that is to be re-sent as a separate message. For example, the following
rule splits the body of an incoming message into separate sections (represented
by a top-level section element) and then sends each section to the target
in a separate message:
<camelContext id="splitterRoute" xmlns="http://act
ivemq.apache.org/camel/schema/spring">
<route>
<from uri="seda:a"/>
<splitter>
<xpath>/section</xpath>
<to uri="seda:b"/>
</splitter>
69
Defining Routes in XML
</route>
</camelContext>
Aggregator
The aggregator processor is used to aggregate related incoming messages
into a single message. In order to distinguish which messages are eligible to
be aggregated together, you need to define a correlation key for the aggregator.
The correlation key is normally derived from a field in the message (for
example, a header field). Messages that have the same correlation key value
are eligible to be aggregated together. You can also optionally specify an
aggregation algorithm to the aggregator processor (the default algorithm is
to pck the latest message with a given value of the correlation key and to
discard the older messages with that correlation key value).
For example, if you are monitoring a data stream that reports stock prices in
real time, you might only be interested in the latest price of each stock symbol.
In this case, you could configure an aggregator to transmit only the latest
price for a given stock and discard the older (out-of-date) price notifications.
The following rule implements this functionality, where the correlation key is
read from the stockSymbol header and the default aggregator algorithm is
used:
<camelContext id="aggregatorRoute" xmlns="http://act
ivemq.apache.org/camel/schema/spring">
<route>
<from uri="SourceURL"/>
<aggregator>
<simple>header.stockSymbol</simple>
<to uri="TargetURL"/>
</aggregator>
</route>
</camelContext>
Resequencer
A resequencer processor is used to re-arrange the order in which incoming
messages are transmitted. The resequencer element needs to be provided
with a sequence number (where the sequence number is calculated from the
contents of a field in the incoming message). Naturally, before you can start
re-ordering messages, you need to wait until a certain number of messages
have been received from the source. There are a couple of different ways to
specify how long the resequencer processor should wait before attempting
to re-order the accumulated messages and forward them to the target, as
follows:
70
Processors
71
Defining Routes in XML
</route>
</camelContext>
Throttler
The throttler processor is used to ensure that a target endpoint does not
get overloaded. The throttler works by limiting the number of messages that
can pass through per second. If the incoming messages exceed the specified
rate, the throttler accumulates excess messages in a buffer and transmits
them more slowly to the target endpoint. For example, to limit the rate of
throughput to 100 messages per second, you can define the following rule:
<camelContext id="throttlerRoute" xmlns="http://act
ivemq.apache.org/camel/schema/spring">
<route>
<from uri="SourceURL"/>
<throttler maximumRequestsPerPeriod="100" timePeriodMil
lis="1000">
<to uri="TargetURL"/>
</throttler>
</route>
</camelContext>
Delayer
The delayer processor is used to hold up messages for a specified length of
time. The delay can either be relative (wait a specified length of time after
receipt of the incoming message) or absolute (wait until a specific time). For
example, to add a delay of 2 seconds before transmitting received messages,
you can use the following rule:
<camelContext id="delayerRelative" xmlns="http://act
ivemq.apache.org/camel/schema/spring">
<route>
<from uri="SourceURL"/>
<delayer>
<delay>2000</delay>
<to uri="TargetURL"/>
</delayer>
</route>
</camelContext>
To wait until the absolute time specified in the processAfter header, you
can use the following rule:
<camelContext id="delayerRelative" xmlns="http://act
ivemq.apache.org/camel/schema/spring">
72
Processors
<route>
<from uri="SourceURL"/>
<delayer>
<simple>header.processAfter</simple>
<to uri="TargetURL"/>
</delayer>
</route>
</camelContext>
73
Defining Routes in XML
74
Languages for Expressions and Predicates
75
Defining Routes in XML
76
Transforming Message Content
<from uri="SourceURL"/>
<unmarshal>
<jaxb prettyPrint="true" contextPath="GeneratedPackage
Name"/>
</unmarshal>
<to uri="TargetURL"/>
</route>
</camelContext>
• Artix Data Services— also integrates with Artix Data Services, enabling
you to integrate stub code generated by Artix Data Services. See Artix Data
Services on page 77 for details.
77
Defining Routes in XML
Where the contentType attribute can be set to one of the following values:
TagValuePair, Sax, Xml, Java, Text, Binary, Auto, Default.
Note
Artix Data Services is licensed separately from .
78