Thursday, 16 June 2011

CombiningJBossESB/JMS/Spring/JTA/Hibernate

I had quite a lot of trouble getting all these frameworks to play well together in a way that I was happy with and would allow me to move forward. I did find this article by Quinton Anderson which is incredibly useful but it didn't work for me and I also wanted to make use of Spring 3 annotations more.


My requirements were to use the JBoss ESB to generate a web service, unmarshal the XML, then feed into a custom Spring action ready for persisting. Obviously the ESB itself can do the persisting but as I'm going to be building a Spring application I want to ensure I can get the data into Spring as in the future we'll be building business logic in there. Just to complicate things a little further and again for future scalability the message itself would be sent over JMS once received into the bus.


The appropriate snippets of my jboss-esb.xml as as follows :-
 <?xml version="1.0"?> <jbossesb parameterReloadSecs="5" xmlns="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas xml/jbossesb-1.3.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.3.0.xsd http://anonsvn.jboss.org/repos/labs/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.3.0.xsd">  
  <providers>  
 <jms-provider connection-factory="ConnectionFactory" name="ReceiveMarshalledDatex2Message">  
   <jms-bus busid="marshalledDatex2Channel">  
   <jms-message-filter dest-name="queue/SimpleJMSRequest" dest-type="QUEUE"/>  
   </jms-bus>  
  </jms-provider>  
  </providers>  
  <services>  
 <service category="WsServiceCategory" description="I receive messages and send over JMS" invmScope="GLOBAL" name="WsService">  
   <property name="maxThreads" value="100"/>  
   <actions inXsd="/request.xsd" mep="RequestResponse" outXsd="/response.xsd" responseLocation="RESPONSE" webservice="true">  
  <action class="org.jboss.soa.esb.actions.Notifier" name="NotifyAction2">  
    <property name="okMethod" value="notifyOK"/>  
    <property name="destinations">  
   <NotificationList type="ok">  
     <target class="NotifyQueues">  
     <queue jndiName="queue/SimpleJMSRequest"/>  
     </target>  
    </NotificationList>  
    </property>  
   </action>  
 </service>  
  <service category="Datex2_Service"  
   description="Processes a unmarshalled Datex2 Message" invmScope="GLOBAL"n ame="ProcessDatex2MarshalledService">  
   <property name="maxThreads" value="100"/>  
   <listeners>  
   <jms-listener busidref="marshalledDatex2Channel" is-gateway="true" maxThreads="100" name="ListenDatex2Marshall"/>  
   </listeners>  
   <actions>  
  <action class="com.thales.esbtest.ConvertDatex2ActionWs" name="UnmarshalXML"/>  
 <action class="com.thales.esbtest.MyTestSpringAction" name="MySpringAction">  
    <property name="springContextXml" value="/lib/applicationContext.xml"/>  
   </action>  
   </actions>  
 .....  

For the JMS you'll need to add a file (jbm-queue-service.xml) into your ESB archive to declare the JMS queue :
 <?xml version="1.0" encoding="UTF-8"?>  
 <server>  
  <mbean code="org.jboss.jms.server.destination.QueueService"  
   name="jboss.esb.quickstart.destination:service=Queue,name=SimpleJMSRequest"  
   xmbean-dd="xmdesc/Queue-xmbean.xml">  
   <depends optional-attribute-name="ServerPeer">jboss.messaging:service=ServerPeer</depends>  
     <depends>jboss.messaging:service=PostOffice</depends>  
  </mbean>  
 </server>  


In addition, I found I needed a deployment.xml file to ensure the queue is created before the ESB is processed :
 <?xml version="1.0" encoding="UTF-8"?>  
 <jbossesb-deployment>  
     <depends>jboss.esb.quickstart.destination:service=Queue,name=SimpleJMSRequest  
     </depends>  
 </jbossesb-deployment>s.messaging:service=PostOffice</depends>  
  </mbean>  
 </server>  

My unmarshall action looks like 
 public class ConvertDatex2ActionWs {  
   private JAXBContext jaxbContext;  
   public ConvertDatex2ActionWs() {  
     try {  
       jaxbContext = JAXBContext.newInstance("eu.datex2.schema._2_0rc2._2_0");  
     } catch (JAXBException e) {  
       // TODO Auto-generated catch block  
       e.printStackTrace();  
     }  
   }  
   @Process  
   public Object process(Message message) throws ActionProcessingException {  
     DeliverDatex2Request deliverDatex2Request = null;  
     try {  
       Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();  
       deliverDatex2Request = (DeliverDatex2Request) unmarshaller.unmarshal(new StreamSource(new StringReader(  
           (String) message.getBody().get())));  
     } catch (JAXBException e) {  
       // TODO Auto-generated catch block  
       e.printStackTrace();  
     }  
     if (deliverDatex2Request == null)  
       return null;  
     message.getBody().add("unmarshalled", deliverDatex2Request.getDatex2Message());  
     return message;  
   }  
   @OnSuccess  
   public Object processSuccess(Message message) {  
 ..  
   }  
   @OnException  
   public void manualRollback(Message message, Throwable theError) {  
     System.out.println("on exception called");  
     theError.printStackTrace();  
   }  
 }  

My custom Spring Action :
 public class MyTestSpringAction extends AbstractSpringAction {  
   public void process(final Message message) throws ActionProcessingException, ActionLifecycleException,  
       BeansException {  
     ...  
     AutService service = (AutService) getBeanFactory().getBean("AutService");  
     service.write(...);  
   }  
 }   

My AutService implementation :
 @Service("AutService")  
 public class AutServiceImpl implements AutService, InitializingBean {  
  @Autowired  
  private LocationDao locationDao;  
  public void write(...) {  
     Location entity = new Location();  
 ...  
     locationDao.save(entity);  
 }  

My implementation DAO class :
 @Repository  
 public class JpaLocationDao implements LocationDao {  
   private EntityManager entityManager;  
   @PersistenceContext  
   public void setEntityManager(EntityManager em) {  
     this.entityManager = em;  
   }  
   public void save(Location location) {  
       this.entityManager.merge(location);  
   }  
 }  

Now importantly! my persistence.xml :
 <persistence xmlns="http://java.sun.com/xml/ns/persistence"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"  
    version="2.0">  
    <persistence-unit name="AutJPA" transaction-type="JTA">  
          <!-- <jta-data-source>java:/XAOracleDS</jta-data-source> -->  
          <jta-data-source>java:/AutTransactedDB</jta-data-source>  
     <properties>  
      <!-- Auto-detect entity classes -->  
    <property name="hibernate.archive.autodetection" value="class, hbm"/>  
     <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>  
         <property name="jboss.entity.manager.factory.jndi.name" value="persistence-units/AutJPA"/>  
       <property name="hibernate.ejb.cfgfile" value="/lib/hibernate.cfg.xml" />  
        </properties>  
    </persistence-unit>  
 </persistence>  

My applicationContext.xml
 ...  
 <context:load-time-weaver weaver-class="org.jboss.instrument.classloading.JBoss5LoadTimeWeaver"/>  
     <!-- tell spring to use annotation based configurations -->  
     <!-- tell spring where to find the beans -->  
     <context:annotation-config />  
     <context:component-scan base-package="com.thales.esbtest"/>  
     <jee:jndi-lookup id="myEmf" jndi-name="persistence-units/AutJPA" />  
     <tx:annotation-driven mode="proxy" transaction-manager="txManager" />  
     <bean  
         class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />  
     <!-- Configure Transaction Support - Access the JTA transaction manager -->  
     <bean id="txManager"  
         class="org.springframework.transaction.jta.JtaTransactionManager">  
         <property name="transactionManagerName" value="java:/TransactionManager" />  
         <property name="userTransactionName" value="UserTransaction" />  
     </bean>  
     <!-- Use Spring AOP capabilities to manage transactions -->  
     <aop:config>  
         <aop:pointcut id="accountTransactions"  
             expression="execution(* com.thales.esbtest.MyTestSpringAction.*(..))" />  
         <aop:advisor pointcut-ref="accountTransactions"  
             advice-ref="txAdvice" />  
     </aop:config>  
     <tx:advice id="txAdvice" transaction-manager="txManager">  
         <tx:attributes>  
             <tx:method name="process" propagation="REQUIRED" />  
             <tx:method name="*" propagation="SUPPORTS" read-only="true" />  
         </tx:attributes>  
     </tx:advice>  
 </beans>  


Finally my hibernate.cfg.xml 
 <?xml version='1.0' encoding='UTF-8'?>  
 <!DOCTYPE hibernate-configuration PUBLIC  
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"  
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
 <hibernate-configuration>  
     <session-factory>  
          <property name="hbm2ddl.auto">create</property>  
         <property name="show_sql">true</property>  
         <property name="dialect">org.hibernatespatial.postgis.PostgisDialect</property>  
         <!-- <property name="dialect">org.hibernatespatial.oracle.OracleSpatial10gDialect</property> -->  
         <property name="current_session_context_class">thread</property>  
         <property name="hibernate.session_factory_name">SessionFactory</property>  
         <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>  
         <property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>  
         <property name="hibernate.validator.apply_to_ddl">false</property>  
         <property name="hibernate.validator.autoregister_listeners">false</property>  
         <mapping class="com.thales.esbtest.Location" />  
     </session-factory>  
 </hibernate-configuration>  

There's a few more enhancements needed. I still need to specify my entity classes in the hibernate.cfg.xml which I'd like to eliminate and have these be found automatically. Some other gotchas that I had to solve were :



1) You'll need to install the Snowdrop utility http://www.jboss.org/snowdrop Without this you'll get an I/O failure during classpath scanning; nested exception is java.util.zip.ZipException: error in opening zip file
I took the 3 Jar files and installed them in the JBoss lib directory.


2) You'll need to install the springsource aspectj weaver and tools libraries from http://ebr.springsource.com/repository/app/bundle otherwise you'll get a java.lang.NoClassDefFoundError: org/aspectj/weaver/reflect/ReflectionWorld$ReflectionWorldException


3) Finally I was getting security exception and I finally tracked this down to https://access.redhat.com/jbossnetwork/restricted/softwareDetail.html?softwareId=1012 where you need to replace cglib

I hope someone finds this useful as you can spend a long while scratching around trying to pull all this together. In a future blog I will show how easy this was using Gradle and Eclipse.

The versions I was using :
JBoss 5.1.0.GA (Enterprise Version)
Spring 3.0.5
Hibernate 3.3.2.GA (comes with JBoss 5.1.0.GA)