Persistierung von Camel-Messages mit Hibernate

Was machen wir? Aus einem Verzeichnis sollen XML-Dateien von Camel gelesen und in eine Datenbank geschrieben werden. An sich ein sehr verbreitetes Verfahren der Datenorientierten Integrationsarchitektur, also genau so ein Szenario wie für das tolle Camel-Integration-Framework.

Die Datenbank, in die die XML-Datein persistiert werden sollen:
Kleines MySQL-DBMS aus dem Apache-Friends XAMPP-Paket mit phpMyAdmin-Oberfläche.
Das ganze läuft testweise auf localhost.
Name der DB: testdb
Username: root
Passwort: secret

und die zugehörige Tabelle auf der DB:

CREATE TABLE IF NOT EXISTS `messages` (
  `id` varchar(100) NOT NULL,
  `content` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Soweit so gut. Die Camel-Instanz lassen wir bequemerweise einfach auf einem lokal installierten ActiveMQ-Broker laufen. Nicht, weil wir den JMS-Broker brauchen, sondern weil dieser eine Runtime für Camel bereitstellt und wir nur noch die Route in der camel.xml konfigurieren müssen. Als Camel-Components stehen die integrierte camel-file-component und die im camel-extras-Projekt befindliche camel-hibernate-component zur Verfügung.

camel.xml

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

        <!-- used for the mapping from xml to value-object -->
	<bean id="xmlToVoBean" class="eu.christophburmeister.camel.processors.XmlToVoBean" />

        <!-- the context for the routes --> 
	<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
                <!-- take from directory, push to db -->
		<route id="route1">
			<from uri="file://D:/outbox" />		
			<to uri="log:incomingOrder_xml?level=INFO" />
			<bean ref="xmlToVoBean" method="toVO" />	
			<to uri="log:incomingOrder_vo?level=INFO" />	
			<to uri="hibernate:eu.christophburmeister.camel.processors.MessageVO" />
		</route>	
	</camelContext>	

        <!-- creating the hibernate-component with a sessionfactory -->
	<bean id="hibernate" class="org.apache.camel.component.hibernate.HibernateComponent">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>
        
        <!-- create a hibernate-template with a session -->
	<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>

        <!-- the session-factory with a datasource, mapping-resources and hibernate-properties -->
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="mappingResources">
			<list>
				<value>eu/christophburmeister/camel/processors/MessageVO.hbm.xml</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<value>
				hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
				hibernate.hbm2ddl.auto=update
				connection.pool_size=10
				cache.provider_class=org.hibernate.cache.NoCacheProvider
			</value>
		</property>
	</bean>

        <!-- the datasource for the underlying database -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>
		<property name="username" value="root"/>
		<property name="password" value="secret"/>
	</bean>

        <!-- the hibernate transaction-manager -->
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>

        <!-- the transaction-template -->
	<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
		<property name="transactionManager" ref="transactionManager"/>
	</bean>

</beans>

Was passiert da genau? Die From-Klausel gibt einen Speicherort (hier ein Verzeichnis auf Laufwerk D) an, in dem kontinuierlich nach neuen Dateien gesucht wird. Wird eine Datei gefunden, wird sie in die Route gepushed und nimmt ihren Lauf: Zuerst in ein Log, um nochmal auf der ActiveMQ-Kommandozeile (bzw. im Log) als XML-String zu erscheinen.

Anschließend wird sie dann in einen XML-ValueObject-Converter geworfen, um aus dem XML die notwendigen Daten zu extrahieren, dann nochmal ein Log, um zu zeigen, dass jetzt wirklich ein MessageVO rausgekommen ist und letztendlich wird dieses VO an den Hibernate-Endpoint übergeben. Der persistiert per ORM dann das Objekt in der bereitgestellten MySQL-Tabelle.

So sieht eine der besagten XML-Message aus:

<message>
	<param>
		<name>uuid</name>
		<value>23:42</value>
	</param>
	<param>
		<name>content</name> 
		<value>spongebob</value>
	</param>
</message>

Und unser XML-VO-Transformer-Bean sieht folgendermaßen aus:

package eu.christophburmeister.camel.processors;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.language.XPath;

public class XmlToVoBean {
	/**
         * transforms the xml-message in the camel-exchange into an value-object of the message
	 * @param exchange the camel-message-exchange
	 * @param uuid the via xpath extracted value for uuid
	 * @param content the via xpath extracted value for content
	 * @return the created vo
	 */
	public MessageVO toVO(			
			Exchange exchange, 
			@XPath("//param[name='uuid']/value/text()") String uuid,
			@XPath("//param[name='content']/value/text()") String content) {
		MessageVO messageVO = new MessageVO();
		messageVO.setUuid(uuid);
		messageVO.setContent(content);
		return messageVO;
	}
}

Das besagte MessageVO.java ist ein einfaches POJO und sieht so aus:

package eu.christophburmeister.camel.processors;
import java.io.Serializable;

public class MessageVO implements Serializable{
    String  uuid;
    String  content;
 
    /* Default-Ctor. */
    public MessageVO(){  }
 
    /* Getter */
    public String getUuid() { return uuid; } 
    public String getContent() { return content; }
 
    /* Setter */
    public void setUuid(String uuid) { this.uuid = uuid; }
    public void setContent(String content) { this.content = content; }
}

Und der zugehörige Hibernate-Mapper MessageVO.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  
<hibernate-mapping>
  <class name="eu.christophburmeister.camel.processors.MessageVO" table="messages">
    <id name="uuid" column="id" type="string" />
    <property name="content"   column="content"   type="string"/>
  </class>
</hibernate-mapping>

Das Maven-Projekt für das VO und den Transformer ist hier.

An sich kein Ding 🙂 Wären da nicht die Abhängigkeiten hinter der camel-hibernate-Komponente… und um das ganze zum Laufen zu bekommen, müssen noch ein paar Libraries zur ActiveMQ-Installation hinzugefügt werden:
– mysql-connector-java-5.1.18-bin.jar
– camel-hibernate-2.5.0.jar
– dom4j-1.6.jar
– hibernate-commons-annotations-3.2.0.Final.jar
– hibernate-core-3.6.7.Final.jar
– javassist-3.8.0.GA.jar
– javax.persistence.jar
– spring-jdbc-1.2.6.jar
– spring-orm-3.0.6.RELEASE.jar

Wirft man jetzt eine entsprechend konfigurierte Message-XML in das konfigurierte Verzeichnis, wird das wie von Zauberhand (Enterprise Integration Patterns sei Dank 😉 ) per ORM in die MySQL-DB geschrieben.