Skip to content

Commit

Permalink
Some more refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Schneider committed Jul 20, 2012
1 parent 9547899 commit c77f1b1
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 56 deletions.
147 changes: 147 additions & 0 deletions cameljpa/Karaf Tutorial Part 7.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
Apache Karaf Tutorial Part 7 - Camel JPA, JMS, transactions and error handling

{excerpt}
Practical Camel example that polls from a database table and sends the contents as XML to a jms queue. The route uses a JTA transaction to synchronize the DB and JMS transactions. An error case shows how you can handle problems.
{excerpt}

h2. Route and Overview

[[Diagram showing db, route and jms queue]]

{code}
from("jpa://net.lr.tutorial.karaf.camel.jpa2jms.model.Person").id("jpa2jms")
.transacted()
.marshal(df)
.to("jms:person");
{code}

The route starts with a jpa endpoint. It is configured with the fully qualified name of a JPA @Entity. From this entity camel knows the table to poll and how to read and remove the row. The jpa endpoint polls the table and creates a Person object for each row it finds. Then it calls the next step in the route with the Person object as body. The jpa component also needs to be set up separately as it needs an EntityManagerFactory

The next step transacted() marks the route as transactional it requires that a TransactedPolicy is setup in the camel context. It then makes sure all steps in the route have the chance to participate in a transaction. So if an error occurs all actions can be rolled back. In case of success all can be committed together.

The marshal(df) step converts the Person object to xml using JAXB. It references a dataformat df that sets up the JAXBContext. For brevity this setup is not shown here.

The last step to("jms:person") sends the xml representation of person to a jms queue. It requires that a JmsComponent named jms is setup in the camel context.

{code}
from("jms:person").id("jms2log")
.transacted()
.convertBodyTo(String.class)
.to("log:personreceived");
{code}

This second route simply listens on the person queue, reads and displays the content. In a production system this part would tpyically be in another module.

h2. DataSource and ConnectionFactory setup

We use an XADataSource for Derby (See https://github.com/cschneider/Karaf-Tutorial/blob/master/db/datasource/datasource-derby.xml).
As the default ConnectionDactory provided by ActiveMQ in Karaf is not XA ready we define the broker and ConnectionFactory definition by hand (See https://github.com/cschneider/Karaf-Tutorial/blob/master/cameljpa/jpa2jms/localhost-broker.xml).

Together with the Karaf transaction feature these provide the basis to have JTA transactions.

h2. JPAComponent, JMSComponent and transaction setup

An important part of this example is to use the jpa and jms components in a JTA transaction. This allows to roll back both in case of an error.
Below is the blueprint context we use. We setup the JMS component with a ConnectionFactory referenced as an OSGi service.
The JPAComponent is setup with an EntityManagerFactory using the jpa:unit config from Aries JPA. See [liquid:/2012/01/13/Apache Karaf Tutorial Part 6 - Database Access] for how this works.
The TransactionManager proviced by Aries transaction is referenced as an OSGi service, wrapped as a spring PlattformTransactionManager and injected into the JmsComponent and JPAComponent.

{code}
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xmlns:jpa="http://aries.apache.org/xmlns/jpa/v1.1.0"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://www.osgi.org/xmlns/blueprint-ext/v1.1.0 https://svn.apache.org/repos/asf/aries/tags/blueprint-0.3.1/blueprint-core/src/main/resources/org/apache/aries/blueprint/ext/blueprint-ext.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd
http://aries.apache.org/xmlns/jpa/v1.1.0 http://aries.apache.org/schemas/jpa/jpa_110.xsd
">

<reference id="connectionFactory" interface="javax.jms.ConnectionFactory" />

<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>

<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
<argument ref="jmsConfig"/>
</bean>

<reference id="jtaTransactionManager" interface="javax.transaction.TransactionManager"/>

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<argument ref="jtaTransactionManager"/>
</bean>

<bean id="jpa" class="org.apache.camel.component.jpa.JpaComponent">
<jpa:unit unitname="person2" property="entityManagerFactory"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>

<bean id="jpa2jmsRoute" class="net.lr.tutorial.karaf.camel.jpa2jms.Jpa2JmsRoute"/>

<bean id="PROPAGATION_REQUIRED" class="org.apache.camel.spring.spi.SpringTransactionPolicy">
<property name="transactionManager" ref="transactionManager"/>
</bean>

<camelContext id="jpa2jms" xmlns="http://camel.apache.org/schema/blueprint">
<routeBuilder ref="jpa2jmsRoute" />
</camelContext>

</blueprint>
{code}

h2. Running the Example

Follow the {Readme.txt|https://github.com/cschneider/Karaf-Tutorial/blob/master/cameljpa/jpa2jms/ReadMe.txt] to install the necessary Karaf features, bundles and configs.

Apart from fthis example we also install the dbexamplejpa. This allows us to use the person:add command defined there to populate the database table.
Open the Karaf console and type:

{code}
person:add "Christian Schneider" @schneider_chris
log:display
{code}

You should then see the following line in the log:

{code}
2012-07-19 10:27:31,133 | INFO | Consumer[person] | personreceived ...
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
<name>Christian Schneider</name>
<twitterName>@schneider_chris2</twitterName>
</person>
{code}

h2. So what happened

We used the person:add command to add a row to the person table. Our route picks up this record, reads and converts it to a Person object. Then it marshals it into xml and sends to the jms queue person.

Our second route then picks up the jms message and shows the xml in the log.

h2. Error handling

The route in the example also contains a small bean that reacts on the name of the person object and throws an exception if the name is "error".
It also contains some error handling so in case of an exception the xml is forwarded to an error directory.

{code}
onException(Exception.class).maximumRedeliveries(3).backOffMultiplier(2).handled(true).to("file:error");
{code}

So you can type the following in the Karaf:

{code}
person:add "Christian Schneider" @schneider_chris
log:display
{code}

This time the log should not show the xml. Instead it should appear as a file in the error directory below your karaf installation.

h2. Summary



Back to [liquid:Karaf Tutorials]
41 changes: 0 additions & 41 deletions cameljpa/jpa2jms/localhost-broker.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,6 @@

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${karaf.data}/activemq/localhost" useShutdownHook="false">

<!--
For better performances use VM cursor and small memory limit.
For more information, see:
http://activemq.apache.org/message-cursors.html
Also, if your producer is "hanging", it's probably due to producer flow control.
For more information, see:
http://activemq.apache.org/producer-flow-control.html
-->

<destinationPolicy>
<policyMap>
<policyEntries>
Expand All @@ -49,15 +38,6 @@
</pendingSubscriberPolicy>
</policyEntry>
<policyEntry queue=">" producerFlowControl="true" memoryLimit="1mb">
<!-- Use VM cursor for better latency
For more information, see:
http://activemq.apache.org/message-cursors.html
<pendingQueuePolicy>
<vmQueueCursor/>
</pendingQueuePolicy>
-->
</policyEntry>
</policyEntries>
</policyMap>
Expand All @@ -79,27 +59,6 @@
<kahaDB directory="${karaf.data}/activemq/localhost/kahadb"/>
</persistenceAdapter>

<!--
The systemUsage controls the maximum amount of space the broker will
use before slowing down producers. For more information, see:
http://activemq.apache.org/producer-flow-control.html
<systemUsage>
<systemUsage>
<memoryUsage>
<memoryUsage limit="20 mb"/>
</memoryUsage>
<storeUsage>
<storeUsage limit="1 gb" name="foo"/>
</storeUsage>
<tempUsage>
<tempUsage limit="100 mb"/>
</tempUsage>
</systemUsage>
</systemUsage>
-->

<shutdownHooks>
<bean xmlns="http://www.springframework.org/schema/beans" id="hook" class="org.apache.activemq.hooks.SpringContextHook" />
</shutdownHooks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import net.lr.tutorial.karaf.camel.jpa2jms.model.Person;

public class Mybean {
public class ExceptionDecider {
public void decideException(Person person) {
if ("error".equals(person.getName())) {
throw new RuntimeException("Exception to check if transactions and redelivery work");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ public class Jpa2JmsRoute extends RouteBuilder {
public void configure() throws Exception {
JaxbDataFormat df = createPersonJaxbDataFormat();

onException(Exception.class).maximumRedeliveries(3).backOffMultiplier(2).handled(true).to("file:error");
//onException(Exception.class).maximumRedeliveries(3).backOffMultiplier(2).handled(true).to("file:error");

from("jpa://net.lr.tutorial.karaf.camel.jpa2jms.model.Person").id("jpa2jms")
.transacted()
.marshal(df)
.bean(new Mybean())
.bean(new ExceptionDecider())
.to("jms:person");

from("jms:person").id("jms2log")
Expand Down
5 changes: 1 addition & 4 deletions cameljpa/jpa2jms/src/main/resources/META-INF/persistence.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,11 @@
version="1.0">

<persistence-unit name="person2" transaction-type="JTA">
<jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/derbyds)</jta-data-source>
<jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/derbydsxa)</jta-data-source>
<class>net.lr.tutorial.karaf.camel.jpa2jms.model.Person</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>

<properties>
<!-- <property name="openjpa.BrokerImpl" value="non-finalizing" /> -->
<!-- <property name="openjpa.Sequence" value="table(Table=OPENJPASEQ, Increment=100)"/> -->
<!-- <property name="openjpa.jdbc.UpdateManager" value="org.apache.openjpa.jdbc.kernel.BatchingConstraintUpdateManager"/> -->
<!-- These properties are creating the database on the fly. We are using them to avoid users having
to create a database to run the sample. This is not something that should be used in production.
See also the create=true line in the ariestrader-derby-ds blueprint meta data -->
Expand Down
5 changes: 5 additions & 0 deletions cxf/personservice/webui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Import-Package>
javax.servlet;version="[2.5,4)",
javax.servlet.http;version="[2.5,4)",
*
</Import-Package>
</instructions>
</configuration>
</plugin>
Expand Down
16 changes: 16 additions & 0 deletions cxf/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?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/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>net.lr.tutorial.karaf.cxf</groupId>
<artifactId>karaf-tutorial-cxf</artifactId>
<version>1.0</version>
<packaging>pom</packaging>

<modules>
<module>personservice</module>
</modules>

</project>

3 changes: 2 additions & 1 deletion tasklist/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ mvn clean install
h1. Installation

features:addurl mvn:net.lr.tasklist/tasklist-features/1.0.0-SNAPSHOT/xml
features:install example-tasklist-vaadin
features:install example-tasklist

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public TaskServiceImpl() {
taskMap = new HashMap<String, Task>();
Task task = new Task();
task.setId("1");
task.setTitle("Buy coffee");
task.setTitle("Buy 1kg coffee");
task.setDescription("Take the extra strong");
addTask(task);
task = new Task();
Expand Down
4 changes: 4 additions & 0 deletions tasklist/tasklist-ui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package></Export-Package>
<Import-Package>
*,
javax.servlet*;version="[2.5,4)"
</Import-Package>
</instructions>
</configuration>
</plugin>
Expand Down
2 changes: 1 addition & 1 deletion vaadin/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ h1. Overview

Example for the Karaf Tutorial Vaadin that implements Vaadin based UI on top of the tasklist example from the first tutorial.
The Vaadin UI reuses the original Tasklist model and persistence service. Below we deploy it alongside the original Servlet UI.
As both use the same persitence service the UIs keep in sync.
As both use the same persistence service the UIs keep in sync.

h1. Build

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,22 @@ class ExampleApplication extends Application {
public void init() {
final GridLayout layout = new GridLayout(1, 3);
layout.setWidth("100%");
layout.setMargin(false);
setMainWindow(new Window(this.title, layout));

final BeanContainer<String, Task> beans = new BeanContainer<String, Task>(Task.class);
beans.setBeanIdProperty("id");

final Form form = new Form();
form.setLocale(Locale.GERMAN);
final Table table = new Table("Tasks", beans);
final Table table = new Table(this.title, beans);
MenuBar menu = createMenuBar(beans, table);
layout.addComponent(menu);

table.setSelectable(true);
table.setImmediate(true);
table.setVisibleColumns(VISIBLE_COLUMNS);
table.addListener(new Property.ValueChangeListener() {

@Override
public void valueChange(ValueChangeEvent event) {
Object selectedId = table.getValue();
@SuppressWarnings("unchecked")
Expand All @@ -72,8 +71,6 @@ public void valueChange(ValueChangeEvent event) {
form.setVisibleItemProperties(VISIBLE_COLUMNS);
form.setImmediate(true);
form.addListener(new Property.ValueChangeListener() {

@Override
public void valueChange(ValueChangeEvent event) {
@SuppressWarnings("unchecked")
BeanItem<Task> item = (BeanItem<Task>) form.getItemDataSource();
Expand Down

0 comments on commit c77f1b1

Please sign in to comment.