CMT
En J2EE hay más de una manera de llevar a cabo el control de transacciones:
- Podemos hacerlo a mano, ingresando el código encargado de demarcar, hacer commit y hacer rollback directamente en nuestro código, como si se tratara de una aplicación Java fuera de un servidor de aplicaciones J2EE. Esto se conoce como Bean Managed Transaction (BMT).
- Podemos dejar estas tareas al contenedor (container), es decir, el servidor de aplicaciones J2EE. Esto se conoce como Container Managed Transactions (CMT).
Es evidente que una de las ventajas de la modalidad CMT es que nos podemos olvidar de la complejidad del manejo de transacciones, concentrándonos únicamente en la lógica de nuestra aplicación. Este es un caso donde la publicidad (en este caso de J2EE) efectivamente tiene un efecto en la vida real.
Transacciones en jBPM
El motor de workflow jBPM utiliza internamente Hibernate para realizar el acceso a su base de datos. Si bien presenta una API de manejo de transacciones (no demasiado avanzada hasta donde hemos podido probar), jBPM se sostiene fuertemente sobre el manejo transaccional de Hibernate.
En más detalle, al crear un JbpmContext y realizar la primera llamada relacionada con base de datos, se crea una nueva sesión Hibernate, la cual finalmente es cerrada (con commit o rollback) al llamar a JbpmContext.close().
El problema con CMT
El esquema descrito en el punto anterior representa la forma “normal” de trabajar con JBPM en una aplicación Java. La situación cambia cuando llevamos esto a J2EE, y en particular a un Bean CMT.
¿Por qué? Fundamentalmente porque la especificación J2EE dice que en un Bean CMT no se puede hacer commit explícitamente en ningún recurso que vaya a ser manejado por el contenedor. Esto es más amplio que sólo hablar de las conexiones a bases de datos. Recursos en una transacción puede ser cualquier cosa con la interfaz de XA para JTA (como colas JMS, por ejemplo).
Sabiendo esto, podemos ver que cuando hacemos el JbpmContext.close() efectivamente estamos haciendo un commit por el lado Hibernate hacia el DataSource que es XA, lo que provoca un error y evita que podamos grabar.
Entonces, necesitamos alguna forma de decirle a JBPM que no tiene que realizar el commit explícitamente, y en cambio debe dejar todo en manos del contenedor.
La solución
Luego de mucho buscar, descubrimos que podemos indicar esto a JBPM en el archivojbpm.cfg.xml. Este archivo originalmente no lo estábamos ocupando, sino que al contrario utilizábamos sólo la versión por defecto (disponible en el starters kit).
Para hacer cambios sobre los valores por defecto, se debe crear el archivo jbpm.cfg.xml en la raiz del classpath de la aplicación (en términos prácticos, en la raiz del JAR). En este archivo debemos agregar sólo aquella información que cambia los valores por defecto. El archivo que estamos utilizando contiene el siguiente XML:
<jbpm-configuration> <jbpm-context> <service name="persistence"> <factory> <bean name="persistence.factory"> <field name="isTransactionEnabled"><false/></field> </bean> </factory> </service> </jbpm-context> </jbpm-configuration>
Puede apreciarse que este archivo es sólo un pedazo de la configuración total. En particular, nos interesa el cambio hecho sobre los parámetros del factory de persistencia que se utiliza por defecto (DbPersistenceServiceFactory). El texto en negrita es la clave:
<field name="isTransactionEnabled"><false/></field>
Este parámetro le dice al Factory que está instancia de JBPM no debe trabajar con transacciones, se debe olvidar por completo de ellas. Con esto, al momento de grabar JBPM no hará el commit de hibernate, permitiendo que esto sí se haga via el CMT.
Referencias
http://jira.jboss.com/jira/browse/JBPM-765;jsessionid=C923E367A2C36A29D73F24FF895BE745?page=worklog En esta entrada del JIRA de JBPM un tipo explica que el ejemplo que aparece en la documentación no es correcta, mostrando lo que efectivamente debe ir en el jbpm.cfg.xml. Este es el XML que utilizamos para solucionar el problema.