...
Designing a Service Oriented Architecture seems very obvious for most of us but implies that different parameters are taken into account :
- Identification of the layers of the application,
- Definition of the services,
- Granularity (what are the boundaries of a service, ...),
- Dependency with libraries,
- Testing and debugging strategies,
- Deployment procedure,
- Infrastructure
Some of the points mentioned are particular to SOA world like granularity and definition of service boundaries but others are mostly found in all IT projects. This is really important to keep them in your head because they will impact the project life cycle, quality of the deliverable, efficiency of the team and project duration/cost.
In this second part of the tutorial we will investigate some of the points mentioned and applied them to a real application. The application will be designed around different components (= bundles in the OSGI jargon) and deployed into ServiceMix Kernel. ServiceMix Kernel or SMXKNL is an OSGI platform created top of Apache Felix OSGI server, integrating Spring Dynamic Modules to facilitate the build of Spring application and PAX components who provide tools managing deployment of component and web support. For more information about the projects/frameworks mentioned, I recommend that you have a look on their respective web siteApache Felix Karaf (now part of Apache ServiceMix 4).
Apache Karaf is a small OSGi based runtime which provides a lightweight container onto which various components and applications can be deployed.
Here is a picture short list of the report incident application that this tutorial will cover :
To summarize, the application is listening for incidents coming from web service or files. According to the origin, the content (= incidents) are transformed into their corresponding objects using for the CSV file, a new camel component : camel-bindy and for the Web Service camel-cxf component. Each message transformed is placed in a queue handled by ActiveMQ engine. All the messages (containing the objects) are next processed by a Bean service who will (with the help of injection of dependency provided by Spring) save the incidents in a DB using Spring and Hibernate frameworks.
A small Apache Wicket web application running in Jetty Web server provide to the users a screen to consult the incidents created.
Remark : A bundle in the OSGI world represents component made by developer. For more info about OSGI, I recommend to have a look on OSGI web site
The project has been cut into the following components :
Maven project name = artifactId | Description | Is it a bundle ? |
---|---|---|
reportincident.activemq | configuration file of the ActiveMQ engine | yes |
reportincident.camelqueueservice | configuration file of the camel-activemq component | yes |
reportincident.db | generator of the script DB | no |
reportincident.features | features provisioning file containing our bundles dependencies | no |
reportincident.model | model layer | yes |
reportincident.persistence | hibernate persistence layer; bundle | yes |
reportincident.routing | camel routing | yes |
reportincident.service | spring service layer | yes |
reportincident.web | apache wicket module | yes |
reportincident.webservice | CXF web service generator | yes |
features supported by the Karaf:
- Hot deployment: Karaf supports hot deployment of OSGi bundles by monitoring jar files inside the home/deploy directory. Each time a jar is copied in this folder, it will be installed inside the runtime. You can then update or delete it and changes will be handled automatically. In addition, the Karaf also supports exploded bundles and custom deployers (blueprint and spring ones are included by default).
- Dynamic configuration: Services are usually configured through the ConfigurationAdmin OSGi service. Such configuration can be defined in Karaf using property files inside the home/etc directory. These configurations are monitored and changes on the properties files will be propagated to the services.
- Logging System: using a centralized logging back end supported by Log4J, Karaf supports a number of different APIs (JDK 1.4, JCL, SLF4J, Avalon, Tomcat, OSGi)
- Provisioning: Provisioning of libraries or applications can be done through a number of different ways, by which they will be downloaded locally, installed and started.
- Native OS integration: Karaf can be integrated into your own Operating System as a service so that the lifecycle will be bound to your Operating System.
- Extensible Shell console: Karaf features a nice text console where you can manage the services, install new applications or libraries and manage their state. This shell is easily extensible by deploying new commands dynamically along with new features or applications.
- Remote access: use any SSH client to connect to Karaf and issue commands in the console
- Security framework based on JAAS
- Managing instances: Karaf provides simple commands for managing multiple instances. You can easily create, delete, start and stop instances of Karaf through the console.
- Supports the latest OSGi 4.2 containers: Apache Felix Framework 2.0.0 and Eclipse Equinox 3.5
Here is a picture of the report incident application that this tutorial will cover :
To summarize, the application is listening for incidents coming from web service or files. According to the origin, the content (= incidents) are transformed into their corresponding objects using for the CSV file, a new camel component : camel-bindy and for the Web Service camel-cxf component. Each message transformed is placed in a queue handled by ActiveMQ engine. All the messages (containing the objects) are next processed by a Bean service who will (with the help of injection of dependency provided by Spring) save the incidents in a DB using Spring and Hibernate frameworks.
A small Apache Wicket web application running in Jetty Web server provide to the users a screen to consult the incidents created.
Remark : A bundle in the OSGI world represents component made by developer. For more info about OSGI, I recommend to have a look on OSGI web site
The project has been cut into the following components :
Maven project name = artifactId | Description | Is it a bundle ? |
---|---|---|
reportincident.activemq | configuration file of the ActiveMQ engine | yes |
reportincident.camelqueueservice | configuration file of the camel-activemq component | yes |
reportincident.db | generator of the script DB | no |
reportincident.features | features provisioning file containing our bundles dependencies | no |
reportincident.model | model layer | yes |
reportincident.persistence | hibernate persistence layer; bundle | yes |
reportincident.routing | camel routing | yes |
reportincident.service | spring service layer | yes |
reportincident.web | apache wicket module | yes |
reportincident.webservice | CXF web service generator | yes |
As you can see, some are considered as OSGI bundles and others no. An important point to mention here concerns the As you can see, some are considered as OSGI bundles and others no. An important point to mention here concerns the granularity : each layer of our application will be deployed as separate bundle. This will facilitate the maintenance and release management. Of course, you can argue that the granularity is too small. SOA is not an exact science and depending of the size of the application, the team in charge to develop, release management procedure this cutting will be redefined. You can imagine that the parameters used to configure Hibernate and Spring are bundles together instead inside the persistence project. Service bundle could be split into several bundles; one by service type, ... There are no rules of thumb except that the project must be manageable and maintainable.
Prerequisites
This tutorial uses:
- Maven 2.0.9 to setup the projects,
- Eclipse Ganymede 3.4.x,
- Maven eclipse plugin,
...
...
- 4.0,
- Dependencies (= jars) used by the tutorial will be downloaded (if not available locally) by Maven
Note: The sample project can be downloaded, see the resources section.
...
Different way exist to create maven project. For the basic project like db, we have used the archetype 'simple' with the command followed by mvn eclipse:eclipse
in the folder created :
Code Block |
---|
mvn archetype:creategenerate -DartifactIdDinteractiveMode=simplefalse -DgroupId=org.apache.camel.example -DartifactId=reportincident.model -Dversion=1.0-SNAPSHOT cd reportincident.db mvn -Dgoals=eclipse:eclipse |
For the OSGI bundles, different approaches are available depending on the tools that you prefer to use :
But for the purpose of this tutorial, we have used the PAX maven plugin. Why this choice, simply because PAX maven plugin offers a lot of advantages regarding to the one of Spring :
- pom.xml file generated is very simple to use and to understand,
- project can be designed with several modules,
- project can be tested with PAX Exam and launched using PAX runner
- generate all the folders required including also the META-INF,
- manifest file is generated automatically,
- can be imported easily in Eclipse
To create the tutorial projects, you can follow the procedure described here
...
Code Block |
---|
mvn org.ops4j:maven-pax-plugin:create-bundle -Dpackage=org.apache.camel.example.reportincident.model -DbundleGroupId=reportincident.model -DbundleName=reportincident.model -Dversion=1.0-SNAPSHOT |
...
Repeat this procedure for the projects :
- reportincident.activemq
- reportincident.camelqueueservice
- reportincident.persistence
- reportincident.routing
- reportincident.service
- reportincident.web
- reportincident.webservice
otherwise import the content of the unzipped file in your workspace. You will gain time.
...
It is time now to begin serious things. One of the most important part of a project (if not the most important) concerns the design of the model.
The reportincident model is really simple because it only contains one class that we will use :
- to map information with the database, CSV file,
- to transport information to web screens.
Here is the definition of the incident class that you can create in the reportincident.model project directory src/main/java/org/apache/camel/example/reportincident/model
or use the code imported
Code Block |
---|
import java.io.Serializable; public class Incident implements Serializable{ private static final long serialVersionUID = 1L; protectedprivate long incidentId; protectedprivate String incidentRef; protectedprivate Date incidentDate; protectedprivate String givenName; protectedprivate String familyName; protectedprivate String summary; protectedprivate String details; protectedprivate String email; protectedprivate String phone; protectedprivate String creationUser; protectedprivate Date creationDate; public long getIncidentId() { return incidentId; } public void setIncidentId(long incidentId) { this.incidentId = incidentId; } public String getIncidentRef() { return incidentRef; } public void setIncidentRef(String incidentRef) { this.incidentRef = incidentRef; } public Date getIncidentDate() { return incidentDate; } public void setIncidentDate(Date incidentDate) { this.incidentDate = incidentDate; } public String getGivenName() { return givenName; } public void setGivenName(String givenName) { this.givenName = givenName; } public String getFamilyName() { return familyName; } public void setFamilyName(String familyName) { this.familyName = familyName; } public String getSummary() { return summary; } public void setSummary(String summary) { this.summary = summary; } public String getDetails() { return details; } public void setDetails(String details) { this.details = details; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getCreationUser() { return creationUser; } public void setCreationUser(String creationUser) { this.creationUser = creationUser; } public Date getCreationDate() { return creationDate; } public void setCreationDate(Date creationDate) { this.creationDate = creationDate; } } |
...
To facilitate the work of the modeler, we will use the incident class not only to persist the information in the database but also to read or generate Comma Separate Value file (CSV). To map the content of the class with a CSV file, we have used a new Camel component : camel-bindy. Like its name suggests, camel-bindy is a binding framework (similar to JAXBJAXB2) to map non structured information with Java class using annotations. The current version supports CSV fields and key-value pairs (e.g. Financial FIX messages) but will be extended in the future to support Fixed Length format, ....
...
Code Block |
---|
import org.apache.camel.dataformat.bindy.annotation.CsvRecord; import org.apache.camel.dataformat.bindy.annotation.DataField; @CsvRecord(separator =",") public class Incident implements Serializable{ @DataField(pos = 01) protectedprivate String incidentRef; @DataField(pos = 12, pattern = "dd-mm-yyyy") protectedprivate Date incidentDate; @DataField(pos = 23) protectedprivate String givenName; @DataField(pos = 34) protectedprivate String familyName; @DataField(pos = 45) protectedprivate String summary; @DataField(pos = 56) protectedprivate String details; @DataField(pos = 67) protectedprivate String email; @DataField(pos = 78) protectedprivate String phone; ... } |
To build the project, simply execute the following maven command in the reportincident.model project
Code Block |
---|
mvn clean install
|
Step 4 : Map model layer with DB (Hibernate)
...
Code Block | ||||
---|---|---|---|---|
| ||||
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.apache.camel.example</groupId> <artifactId>reportincident.db</artifactId> <packaging>jar</packaging> <name>Report Incident DB </name> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> (1) <groupId>org.apache.camel.example</groupId> <artifactId>reportincident.model</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <!-- Hibernate plugin --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.2</version> <configuration> <components> <component> <name>hbm2ddl</name> </component> </components> <componentProperties> <drop>true</drop> <create>true</create> <format>true</format> <configurationfile>/src/config/hibernate.cfg.xml</configurationfile> <outputfilename>db_reportincident_create_hsqldb.sql</outputfilename> </componentProperties> </configuration> <dependencies> <dependency> (2) <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6<8</version> </dependency> </dependencies> <executions> <execution> <phase>process-classes</phase> <goals> <goal>hbm2ddl</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> |
...
Obviously, this file is not complete because the reference of the DAO class is not mentioned except the property name. Don't panic, we will come back later on when we will discuss Spring Blueprint services.
Step 8 : Webservice
This part has already been discussed in detail in the excellent tutorial : Report Incident - This tutorial introduces Camel steadily and is based on a real life integration problem. So we will only explain what we have done specifically for our project.
...
Code Block | ||||
---|---|---|---|---|
| ||||
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://reportincident.example.camel.apache.org"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://reportincident.example.camel.apache.org">
<!-- Type definitions for input- and output parameters for webservice -->
<wsdl:types>
<xs:schema targetNamespace="http://reportincident.example.camel.apache.org">
<xs:element name="inputReportIncident">
<xs:complexType name="inputReportIncident">
<xs:sequence>
<xs:element type="xs:string" name="incidentId"/>
<xs:element type="xs:string" name="incidentDate"/>
<xs:element type="xs:string" name="givenName"/>
<xs:element type="xs:string" name="familyName"/>
<xs:element type="xs:string" name="summary"/>
<xs:element type="xs:string" name="details"/>
<xs:element type="xs:string" name="email"/>
<xs:element type="xs:string" name="phone"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="outputReportIncident">
<xs:complexType name="outputReportIncident">
<xs:sequence>
<xs:element type="xs:string" name="code"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</wsdl:types>
<!-- Message definitions for input and output -->
<wsdl:message name="inputReportIncident">
<wsdl:part name="in" element="tns:inputReportIncident"/>
</wsdl:message>
<wsdl:message name="outputReportIncident">
<wsdl:part name="out" element="tns:outputReportIncident"/>
</wsdl:message>
<!-- Port (interface) definitions -->
<wsdl:portType name="ReportIncidentEndpoint">
<wsdl:operation name="ReportIncident">
<wsdl:input message="tns:inputReportIncident"/>
<wsdl:output message="tns:outputReportIncident"/>
</wsdl:operation>
</wsdl:portType>
<!-- Port bindings to transports and encoding - HTTP, document literal encoding is used -->
<wsdl:binding name="ReportIncidentBinding" type="tns:ReportIncidentEndpoint">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="ReportIncident">
<soap:operation
soapAction="http://reportincident.example.camel.apache.org/ReportIncident"
style="document"/>
<wsdl:input>
<soap:body parts="in" use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body parts="out" use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<!-- Service definition -->
<wsdl:service name="ReportIncidentEndpointService">
<wsdl:port name="ReportIncidentPort" binding="tns:ReportIncidentBinding">
<soap:address location="http://localhost:8080/camel-example/incident"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
The code will be generated thanks to a maven plugin : cxf-codegen-plugin.
Add the following line in your pom.xml
of the project reportincident.webservice
...
<!-- CXF wsdl2java generator, will plugin to the compile goal -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf-version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${basedir}/src/main/resources/META-INF/wsdl/report_incident.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
The code is generated using the maven command :
Code Block |
---|
mvn generate-sources
|
Remark : the code is generated in the directory target/src/main/java
Everything is in place to integrate the services together except the routing and OSGI stuffs. This is what we will cover in the following sections.
It is time now to have a break, to make some sport exercices, to drink a cup of good 'Java' coffee or to go outside of the building to take a walk with your favorite pets.
Routing/Mediation service
The routing/mediation between services/bundles will be created using Camel Spring DSL language. We will describe its creation/genesis step by step.
First, create the file camel-context.xml
in the directory src/main/resources/META-INF/spring
and add the lines :
...
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/spring"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xmlns:cxf="http://camel.apache.org/schema/cxf"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd
http://camel.apache.org/schema/osgi
http://camel.apache.org/schema/osgi/camel-osgi.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd
http://camel.apache.org/schema/cxf
http://camel.apache.org/schema/cxf/camel-cxf.xsd">
</beans>
Compare to a simple camel project, the spring beans tag has been enriched with new namespaces :
- spring-osgi (http://www.springframework.org/schema/osgi
) which is used by OSGI blueprint services
- camel-osgi (http://camel.apache.org/schema/osgi
) who will allow to integrate camel with OSGI world
- cxf (http://camel.apache.org/schema/cxf
) to integrate CXF architecture with Camel
Now, that the schema/namespaces are declared, we can start to add addtional stuffs like import resources, beans reference, ... that our routing engine will use.
1) Webservice infrastructure : CXF
We will use the CXF framework to deploy the reportincident webservice and run it into the OSGI platform.
To work with it, resources (= spring beans) declared in CXF project must be imported using the statement import resource
.
...
...
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-http.xml" />
<import resource="classpath:META-INF/cxf/osgi/cxf-extension-osgi.xml" />
...
These imports will be used by spring at the bundle startup to instantiate the beans defined in these files. These beans are responsible in fact to deploy the architecture of the CXF bus top of the OSGI server and to provide a servlet that we will use to communicate with webservices engine of CXF.
Remark : for the purpose of this tutorial, we have packaged this configuration into the camel-spring file but it could be defined in a separate xml file with by example the component bean that Camel will use to communicate with CXF bus. This allows you to separate routing from parameters to be provided to configure endpoints.
The camel CXF endpoint is configurated like this :
...
...
<!-- webservice endpoint -->
<cxf:cxfEndpoint id="reportIncident"
address="/camel-example/incident" (1)
serviceClass="org.apache.camel.example.reportincident.ReportIncidentEndpoint" (2)
xmlns:s="http://reportincident.example.camel.apache.org"> (3)
</cxf:cxfEndpoint>
...
Remarks :
(1) - the address corresponds to the URI address of the web services,
(2) - the serviceClass is the name of the class used work with the webservices and deployed in the bundle reportincident.webservice
(3) - xmlns:s is the namespace of the reportincident webservice (see reportincident.webservice)
2) Queuing engine
No matter if the incidents come from a webservice or a files but before to process and save them in the database, we will put
our messages in a queue. The queue manager used here is ActiveMQ.
Like CXF, we will use spring xml file to deploy ActiveMq into the server and configure it. This will be done in two steps
a) ActiveMQ
Like CXF, ActiveMq can be installed in the infrastructure using a spring.xml configuration file. So, create the file activemq-broker.xml
in the directory src/main/resources/META-INF/spring
and add the following lines.
...
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
The code will be generated thanks to a maven plugin : cxf-codegen-plugin.
Add the following line in your pom.xml
of the project reportincident.webservice
Code Block | ||||
---|---|---|---|---|
| ||||
<!-- CXF wsdl2java generator, will plugin to the compile goal -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf-version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${basedir}/src/main/resources/META-INF/wsdl/report_incident.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
|
The code is generated using the maven command :
Code Block |
---|
mvn generate-sources
|
Remark : the code is generated in the directory target/src/main/java
Conclusion
Everything is in place to integrate the services together except the routing and OSGI stuffs. This is what we will cover in the following sections.
It is time now to have a break, to make some sport exercices, to drink a cup of good 'Java' coffee or to go outside of the building to take a walk with your favorite pets.
Links
- Part 2 :
...
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd
http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<!-- Allows us to use system properties as variables in this configuration file -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="default" dataDirectory="${servicemix.base}/data/activemq/default" useShutdownHook="false">
<!-- Destination specific policies using destination names or wildcards -->
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">" memoryLimit="5mb"/>
<policyEntry topic=">" memoryLimit="5mb">
<subscriptionRecoveryPolicy>
<lastImageSubscriptionRecoveryPolicy/>
</subscriptionRecoveryPolicy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
<!-- Use the following to configure how ActiveMQ is exposed in JMX -->
<managementContext>
<managementContext createConnector="false"/>
</managementContext>
<!-- The store and forward broker networks ActiveMQ will listen to -->
<networkConnectors>
<!-- by default just auto discover the other brokers -->
<networkConnector name="default-nc" uri="multicast://default"/>
<!-- Example of a static configuration:
<networkConnector name="host1 and host2" uri="static://(tcp://host1:61616,tcp://host2:61616)"/>
-->
</networkConnectors>
<persistenceAdapter>
<amqPersistenceAdapter syncOnWrite="false" directory="${servicemix.base}/data/activemq/default" maxFileLength="20 mb"/>
</persistenceAdapter>
<!-- Use the following if you wish to configure the journal with JDBC -->
<!--
<persistenceAdapter>
<journaledJDBC dataDirectory="${activemq.base}/data" dataSource="#postgres-ds"/>
</persistenceAdapter>
-->
<!-- Or if you want to use pure JDBC without a journal -->
<!--
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#postgres-ds"/>
</persistenceAdapter>
-->
<!-- The maximum about of space the broker will use before slowing down producers -->
<systemUsage>
<systemUsage>
<memoryUsage>
<memoryUsage limit="20 mb"/>
</memoryUsage>
<storeUsage>
<storeUsage limit="1 gb" name="foo"/>
</storeUsage>
<tempUsage>
<tempUsage limit="100 mb"/>
</tempUsage>
</systemUsage>
</systemUsage>
<!-- The transport connectors ActiveMQ will listen to -->
<transportConnectors>
<transportConnector name="openwire" uri="tcp://localhost:61616" discoveryUri="multicast://default"/>
<transportConnector name="stomp" uri="stomp://localhost:61613"/>
</transportConnectors>
</broker>
<bean id="activemqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactoryBean">
<property name="maxConnections" value="8" />
<property name="maximumActive" value="500" />
<property name="transactionManager" ref="transactionManager" />
<property name="connectionFactory" ref="activemqConnectionFactory" />
<property name="resourceName" value="activemq.default" />
</bean>
<bean id="resourceManager" class="org.apache.activemq.pool.ActiveMQResourceManager" init-method="recoverResource">
<property name="transactionManager" ref="transactionManager" />
<property name="connectionFactory" ref="activemqConnectionFactory" />
<property name="resourceName" value="activemq.default" />
</bean>
<osgi:reference id="transactionManager" interface="javax.transaction.TransactionManager" />
<osgi:service ref="pooledConnectionFactory">
<osgi:interfaces>
<value>javax.jms.ConnectionFactory</value>
</osgi:interfaces>
<osgi:service-properties>
<entry key="name" value="default"/>
</osgi:service-properties>
</osgi:service>
</beans>
At the bundle startup, Spring will instantiate the beans declared and in consequence start the queuing engine. We haven't changed the content of the file corresponding to what is proposed in the ServiceMix distribution but you can use here the same technique described for the Datasource and add properties that you configure through by example a org.apache.activemq.config.etc
file.
...
...
<transportConnectors>
<transportConnector name="${name}" uri="${uri}" discoveryUri="${discoveryUri}"/>
</transportConnectors>
<!-- here is the list of values defined as default but can be overidded in the file org.apache.activemq.config.etc -->
<osgix:cm-properties id="confs" persistent-id="org.apache.activemq.config.etc">
<prop key="name">openwire</prop>
<prop key="uri">tcp://localhost:61616</prop>
<prop key="discoveryUri">multicast://default</prop>
</osgix:cm-properties>
...
Tip |
---|
The activeMq broker can also be integrated differently because the ServiceMix4 distribution (and not ServiceMix Kernel) proposes it in standard with additional commands that you can use from the console to : Available commands in activemq: |
The pom.xml file must be modified to add properties required by Spring blueprint. So add the following lines :
...
...
<instructions>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<DynamicImport-Package>*</DynamicImport-Package>
<Include-Resource>src/main/resources</Include-Resource> (1)
<Spring-Context>*;publish-context:=false;create-asynchronously:=true</Spring-Context> (2)
<Private-Package></Private-Package>
<Import-Package> (3)
javax.transaction,
org.apache.activemq,
org.apache.activemq.pool,
org.springframework.beans.factory.config,
*
</Import-Package>
</instructions>
...
Remarks :
(1) - Include-Resource will allow to add to the jar generated the spring files of the directory src/main/resources/META-INF/spring
(2) - The Spring-Context tag allows to provide specific information used by Spring blueprint service to load the application context and how to load it. The asterisk means that all the spring xml files will be processed
(3) - The list of classes to be used is dependent of the queue engine use as implementation. This list must be reviewed when switching from ActiveMq to IBM WebSphere MQ, Tibco, ...
b) Camel ActiveMq component
To makes Camel independent of the JMS queue engine deployed in the OSGI server, we will implement a proxy using blueprint service between Camel component and the queuing engine used (Apache ActiveMq, IBM Websphere MQ, Oracle Advance Queue, TIBCO, ...)
First, create a spring DSL file osgi-queuingservice.xml
in the directory {{src/main/resources/META-INF-spring}}directory containing the following information :
...
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="active-mq" class="org.apache.activemq.camel.component.ActiveMQComponent" /> (1)
<osgi:service id="osgiqueuingservice" ref="active-mq" interface="org.apache.camel.Component"/> (2)
</beans>
Remarks:
(1) Spring will instantiate the ActiveMqComponent to work with the ActiveMq server. If you would like to use another JMS component, then switch this class to org.apache.camel.component.jms.JmsComponent
(2) Our camel component will be exposed on the OSGI registry as an org.apache.camel.Component
and has a reference to the ActiveMQComponent, JMSComponent
Next adapt the POM.xml file like this to instruct the felix plugin how to generate the MANIFEST.MF file
...
...
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>${felix-version}</version>
<configuration>
<manifestLocation>META-INF</manifestLocation>
<instructions>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<Import-Package>
org.apache.activemq.camel.component,
org.apache.camel,
*</Import-Package>
<Include-Resource>src/main/resources</Include-Resource>
<Spring-Context>*;publish-context:=false;create-asynchronously:=true</Spring-Context>
<Private-Package></Private-Package>
</instructions>
</configuration>
</plugin>
...
Remark that we import here the org.apache.activemq.camel.component
class and not the ActiveMQComponent
java class.
All the infrastructure is in place, so we can start to describe the beans that we will use
3) Beans reference
5 beans will be used by our application :
- BindyCsvDataFormat : to generate the model, marshall (= parse a CSV file in to incidents objects) or unmarshall (= create a CSV file from incidents objects)
- IncidentSaver : proxy service who will extract from the body of the message the Incidents objects and call the reportinicident.Service bundle to save in the DB the incidents
- WebService : service who will receive messages from WebServices, extract them from the body message and transform them into Incident objects
- Feedback : simple class who will send a message back to the webservice
So, adapt the camel-context.xml
file :
...
<bean id="bindyDataformat" class="org.apache.camel.dataformat.bindy.csv.BindyCsvDataFormat">
<constructor-arg type="java.lang.String" value="org.apache.camel.example.reportincident.model" /> (1)
</bean>
<bean id="incidentSaver" class="org.apache.camel.example.reportincident.internal.IncidentSaver">
<property name="incidentService">
<osgi:reference interface="org.apache.camel.example.reportincident.service.IncidentService"/> (2)
</property>
</bean>
<bean id="webservice" class="org.apache.camel.example.reportincident.internal.WebService" />
<bean id="feedback" class="org.apache.camel.example.reportincident.internal.Feedback" />
<osgi:reference id="queuingservice" interface="org.apache.camel.Component" /> (3)
Remarks :
(1) - The name of the package containing the class of the model must be provided as parameter
(2) - We inject into our proxy service using OSGI service reference, the interface IncidentService
(3) - We need also an OSGI service reference to the queue engine using the interface org.apache.camel.Component
We will quickly review the three classes that we will created for our project in the directory org.apache.camel.example.reportincident.internal
:
a) IncidentSaver
Code Block |
---|
package org.apache.camel.example.reportincident.internal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.camel.Exchange;
import org.apache.camel.example.reportincident.model.Incident;
import org.apache.camel.example.reportincident.service.IncidentService;
public class IncidentSaver {
private static final transient Log LOG = LogFactory.getLog(IncidentSaver.class);
private IncidentService incidentService = null;
public void process(Exchange exchange) throws ParseException {
int count = 0;
List<Map<String, Object>> models = new ArrayList<Map<String, Object>>(); (1)
Map<String, Object> model = new HashMap<String, Object>();
// Get models from message
models = (List<Map<String, Object>>) exchange.getIn().getBody(); (2)
// Get Header origin from message
String origin = (String) exchange.getIn().getHeader("origin"); (3)
LOG.debug("Header origin : " + origin);
Iterator<Map<String, Object>> it = models.iterator();
// Specify current Date
DateFormat format = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
String currentDate = format.format( new Date() );
Date creationDate = format.parse( currentDate );
while (it.hasNext()) {
model = it.next();
LOG.debug("Model retrieved");
for (String key : model.keySet()) {
LOG.debug("Object retrieved : " + model.get(key).toString());
// Retrieve incident from model
Incident incident = (Incident) model.get(key); (4)
incident.setCreationDate(creationDate);
incident.setCreationUser(origin);
LOG.debug("Count : " + count + ", " + incident.toString());
// Save Incident
incidentService.saveIncident(incident); (5)
LOG.debug("Incident saved");
}
count++;
}
LOG.debug("Nber of CSV records received by the csv bean : " + count);
}
// Property used to inject service implementation (6)
public void setIncidentService(IncidentService incidentService) {
this.incidentService = incidentService;
}
}
|
Remarks :
(1) - We instantiate List and Map classes that we will use to extract objects of our incident model
(2) - Using the method exchange.getIn().getBody()
, we extract the objects from the message and put them in a List
(3) - We get the Header field ('Origin') to check the origin of the messages (file). This info will be persisted in the DB
(4) - The object incident is retrieved from the Map model. In our case, the key is unique because we have only created one model class
(5) - The incident is saved in the database by calling the OSGI service IncidentService.saveIncident
(6) - The field setIncidentService
is used by Spring to inject dependency with OSGI service
b) WebService
Code Block |
---|
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.example.reportincident.internal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.camel.Exchange;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.camel.example.reportincident.InputReportIncident;
import org.apache.camel.example.reportincident.model.Incident;
public class WebService {
private static final transient Log LOG = LogFactory.getLog(WebService.class);
public void process(Exchange exchange) throws ParseException {
InputReportIncident webincident = (InputReportIncident)exchange.getIn().getBody(); (1)
LOG.debug("Incident received from : " + webincident.getFamilyName() + ", " + webincident.getGivenName());
LOG.debug("Incident info : " + webincident.getIncidentId() + ", at : " + webincident.getIncidentDate());
LOG.debug("Incident details : " + webincident.getDetails() + ", summary : " + webincident.getSummary());
List<Map<String, Incident>> models = new ArrayList<Map<String, Incident>>();
Map<String, Incident> model = new HashMap<String, Incident>();
// Convert the InputReportIncident into an Incident
Incident incident = new Incident(); (2)
DateFormat format = new SimpleDateFormat( "dd-mm-yyyy" );
incident.setIncidentDate(format.parse(webincident.getIncidentDate()));
incident.setDetails(webincident.getDetails());
incident.setEmail(webincident.getEmail());
incident.setFamilyName(webincident.getFamilyName());
incident.setGivenName(webincident.getGivenName());
incident.setIncidentRef(webincident.getIncidentId());
incident.setPhone(webincident.getPhone());
incident.setSummary(webincident.getSummary());
// Get Header origin from message
String origin = (String) exchange.getIn().getHeader("origin"); (3)
// Specify current Date
format = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
String currentDate = format.format( new Date() );
Date creationDate = format.parse( currentDate );
incident.setCreationDate(creationDate);
incident.setCreationUser(origin);
LOG.debug("Incident created from web service : " + incident.toString());
// and place it in a model (cfr camel-bindy)
model.put(Incident.class.getName(), incident); (4)
models.add(model);
// replace with our input
exchange.getOut().setBody(models); (5)
// propagate the header
exchange.getOut().setHeader("origin", origin); (6)
}
}
|
Remarks :
(1) - The message coming from the WebService and copied in the InputReportIncident
class is retrieved from the body using the method exchange.getIn().getBody()
(2) - We create using the model a class Incident where we will put webservice's InputReportIncident
(3) - We get the Header field ('Origin') to check the origin of the messages (webservice). This info will be persisted in the DB
(4) - The model incident is added to the Map and List objects required by Camel Bindy
(5) - The model is added to the body object of the message that we send it OUT
(6) - Thbe header parameter is also propagated for the next endpoint
c) Feedback
Code Block |
---|
package org.apache.camel.example.reportincident.internal;
import org.apache.camel.example.reportincident.OutputReportIncident;
public class Feedback {
public OutputReportIncident setOk() { (3)
OutputReportIncident outputReportIncident = new OutputReportIncident(); (1)
outputReportIncident.setCode("0"); (2)
return outputReportIncident;
}
}
|
Remarks :
(1) - An OutputReportIncident object is created because it will be used to send the message back to the webservice
(2) - The field/property setCode is setted with the value ("OK")
(3) - The method setOk() will be called by Camel routing
4) Routing
Now that evverything is in place, we can create the three routes that we need to implement the architecture that we have presented in the introduction of this tutorial
From File(s) to queue
...
<camelContext trace="true" xmlns="http://camel.apache.org/schema/osgi"> (1)
<camel:route>
<camel:from uri="file://d:/temp/data/?move=d:/temp/done/${file:name}" /> (2)
<camel:setHeader headerName="origin"> (3)
<camel:constant>file</camel:constant>
</camel:setHeader>
<camel:unmarshal ref="bindyDataformat" /> (4)
<camel:to uri="queuingservice:queue:in" /> (5)
</camel:route>
...
Remarks :
(1) -
(2) -
(3) -
(4) -
(5) -
From Webservices to queue
...
...
<camel:route>
<camel:from uri="cxf:bean:reportIncident" />
<camel:setHeader headerName="origin">
<camel:constant>webservice</camel:constant>
</camel:setHeader>
<camel:convertBodyTo type="org.apache.camel.example.reportincident.InputReportIncident" />
<camel:to uri="bean:webservice" />
<camel:inOnly uri="queuingservice:queue:in" />
<camel:transform>
<camel:method bean="feedback" method="setOk" />
</camel:transform>
</camel:route>
...
Remarks :
(1) -
(2) -
(3) -
(4) -
(5) -
From queue to DB
...
...
<camel:route>
<camel:from uri="queuingservice:queue:in" />
<camel:to uri="bean:incidentSaver?method=process" />
</camel:route>
</camelContext>
Remarks :
(1) -
(2) -
(3) -
(4) -
(5) -
Once that you have finished to configure camel, created your classes, the pom.xml file must be modified :
...
...
<instructions>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<Import-Package> (1)
META-INF.cxf,
META-INF.cxf.osgi,
META-INF.wsdl,
org.apache.commons.logging,
org.apache.camel,
org.apache.camel.component,
org.apache.camel.component.cxf,
org.apache.camel.component.cxf.converter,
org.apache.camel.component.jms,
org.apache.camel.converter,
org.apache.camel.converter.jaxp,
org.apache.camel.converter.stream,
org.apache.camel.dataformat.bindy,
org.apache.camel.dataformat.bindy.csv,
org.apache.camel.example.reportincident,
org.apache.camel.example.reportincident.model,
org.apache.camel.example.reportincident.service,
org.apache.camel.processor,
org.apache.activemq.camel.component;${activemq.osgi.version},
org.apache.activemq.camel.converter;${activemq.osgi.version},
org.apache.activemq.pool,
org.apache.cxf,
org.apache.cxf.binding,
org.apache.cxf.binding.corba,
org.apache.cxf.binding.soap,
org.apache.cxf.binding.soap.spring,
org.apache.cxf.bus,
org.apache.cxf.bus.resource,
org.apache.cxf.bus.spring,
org.apache.cxf.buslifecycle,
org.apache.cxf.catalog,
org.apache.cxf.configuration,
org.apache.cxf.configuration.spring,
org.apache.cxf.endpoint,
org.apache.cxf.headers,
org.apache.cxf.management,
org.apache.cxf.management.jmx,
org.apache.cxf.phase,
org.apache.cxf.resource,
org.apache.cxf.transport,
org.apache.cxf.transport.http,
org.apache.cxf.transport.http.policy,
org.apache.cxf.transport.http_jetty,
org.apache.cxf.transport.jms,
org.apache.cxf.transports.http,
org.apache.cxf.workqueue,
org.apache.cxf.wsdl,
org.apache.cxf.wsdl11,
org.apache.servicemix.cxf.transport.http_osgi,
org.springframework.beans.factory.config,
*
</Import-Package>
<Private-Package>org.apache.camel.example.reportincident.internal</Private-Package> (2)
</instructions>
...
Remarks :
(1) - Classes required by Camel, CXF muste be imported.
(2) - Our internal classes are declared as private to avoid that they become available for another bundles of the OSGI server
Conclusion
TODO
Links
...
- real example, architecture, project setup, database creation
- Part
...
...
...
#Resources
Attachments patterns .*part2.zip