Ideally this should same as working with Hibernate-jBPM4. You create a session factory configuration and pass the jBPM-xxx.hbm.xml files to the session factory. A sample session factory may look like this:
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">...</property>
<property name="hibernate.format_sql">true</property>
.
.
.
<mapping />
<mapping resource="myResource.hbm.xml" />
<mapping resource="jbpm.repository.hbm.xml" />
<mapping resource="jbpm.execution.hbm.xml" />
<mapping resource="jbpm.history.hbm.xml" />
<mapping resource="jbpm.task.hbm.xml" />
<mapping resource="jbpm.identity.hbm.xml" />
</session-factory>
</hibernate-configuration>
Things should be similar with JPA as well:
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence" version="1.0">
<persistence-unit name="sample">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<mapping-file>jbpm.execution.hbm.xml</mapping-file>
<mapping-file>jbpm.repository.hbm.xml</mapping-file>
<mapping-file>jbpm.task.hbm.xml</mapping-file>
<mapping-file>jbpm.history.hbm.xml</mapping-file>
<class>org.santanu.MyDomainPojo</class>
<class>org.santanu.AnotherMyDomainPojo</class>
<properties>
...
</properties>
</persistence-unit>
</persistence>
- The class I loved the most when it comes to handle db access in jBPM4 is org.jbpm.pvm.internal.session.DbSession. By default we have an implementation of that interface, org.jbpm.pvm.internal.hibernate.DbSessionImpl. This implementation uses current hibernate session to do all database operations. But we want to use JPA APIs. So we need to have another implementation of DbSession that will use JPA APIs instead of Hibernate classes.
- Once we have the org.jbpm.pvm.internal.session.DbSession implementation we need configure jBPM to use this. So we need to replace <db-session/> with our own tag in jBPM configuration file.
- To support the tag in configuration we need to create binding and descriptor classes.
- We also need to configure entity manager instead of hibernate session. This involves creating the binding and descriptor classes.
- One more configuration is required for creating/accessing entity manager factory. But if the entity manager factory is created by some other component (if framework like Spring is there in our application then by all likelihood entity manager factory wil be created and managed by that) - then we may not need that. So if we assume Spring to be our container then we can give this configuration a skip.
JPA Implementation of DbSession - We can have a DbSession implementation org.jbpm.pvm.internal.jpa.JpaDbSessionImpl which somewhat follows org.jbpm.pvm.internal.hibernate.DbSessionImpl and have the same set of methods implemented using Entity manager instead of Hibernate Session.
Binding classes for JpaDbSession - We need a binding and a descriptor for JpaDbSession. This is just a part of our regular exercise to register a new configuration tag. Both the binding and descriptor for this can be fairly simple.
public class JpaDbSessionBinding extends WireDescriptorBinding {public static final String TAG = "jpa-session";public JpaDbSessionBinding() {super(TAG);}public Object parse(Element element, Parse parse, Parser parser) {JpaDbSessionDescriptor descriptor = new JpaDbSessionDescriptor();return descriptor;}}
public class JpaDbSessionDescriptor extends AbstractDescriptor {public Object construct(WireContext wireContext) {return new JpaDbSessionImpl();}
public void initialize(Object object, WireContext wireContext) {EntityManager entityManager = wireContext.get(EntityManager.class);if (entityManager == null) {throw new WireException("couldn't find entity manager "+ (entityManagerName != null ? "'" + entityManagerName + "'" + "by type ") + "to create db-session");}// inject the session((JpaDbSessionImpl) object).setEntityManager(entityManager);}
public Class<?> getType(WireDefinition wireDefinition) {return JpaDbSessionImpl.class;}}
The configuration we achieve here looks like this:
<jpa-session/>
package org.jbpm.pvm.internal.jpa;
import javax.persistence.EntityManager;import org.jbpm.api.cmd.Environment;
/*** @author Santanu*/public interface EntityManagerAccessor {/*** Implementing class should return the relevant entity manager.* @param environment* @return*/public EntityManager getEntityManager(Environment environment);}
package org.jbpm.pvm.internal.jpa;
import java.util.HashMap;import javax.persistence.EntityManager;import javax.persistence.EntityManagerFactory;import org.jbpm.api.cmd.Environment;import org.springframework.orm.jpa.EntityManagerFactoryUtils;
/*** @author Santanu*/public class SpringEntityManagerAccessor implements EntityManagerAccessor {@Override@SuppressWarnings("unchecked")public EntityManager getEntityManager(Environment environment) {EntityManagerFactory entityManagerFactory= environment.get(EntityManagerFactory.class);return EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory, new HashMap());}}
Rest of the binding and descriptor code are simple...
package org.jbpm.pvm.internal.wire.binding;
import org.jbpm.pvm.internal.wire.descriptor.JpaEntityManagerDescriptor;import org.jbpm.pvm.internal.xml.Parse;import org.jbpm.pvm.internal.xml.Parser;import org.w3c.dom.Element;
/*** @author Santanu*/public class JpaEntityManagerBinding extends WireDescriptorBinding {public static final String TAG = "entity-manager";public JpaEntityManagerBinding() {super(TAG);}
/* (non-Javadoc)* @see org.jbpm.pvm.internal.xml.Binding* #parse(org.w3c.dom.Element, org.jbpm.pvm.internal.xml.Parse,* org.jbpm.pvm.internal.xml.Parser)*/@Overridepublic Object parse(Element element, Parse parse, Parser parser) {JpaEntityManagerDescriptor descriptor= new JpaEntityManagerDescriptor();......if (element.hasAttribute("accessor-class")) {descriptor.setProviderClassName(element.getAttribute("accessor-class"));}if (element.hasAttribute("create-new")) {descriptor.setCreateNew(Boolean.valueOf(element.getAttribute("create-new")));}...return descriptor;}}
package org.jbpm.pvm.internal.wire.descriptor;
import javax.naming.InitialContext;import javax.naming.NamingException;import javax.persistence.EntityManager;import javax.persistence.EntityManagerFactory;import org.apache.commons.lang.StringUtils;import org.jbpm.pvm.internal.env.EnvironmentImpl;import org.jbpm.pvm.internal.jpa.EntityManagerAccessor;import org.jbpm.pvm.internal.wire.WireContext;import org.jbpm.pvm.internal.wire.WireDefinition;import org.jbpm.pvm.internal.wire.WireException;
/*** @author Santanu*/public class JpaEntityManagerDescriptor extends AbstractDescriptor {
...
/* (non-Javadoc)* @see org.jbpm.pvm.internal.wire.Descriptor* #construct(org.jbpm.pvm.internal.wire.WireContext)*/@SuppressWarnings("unchecked")@Overridepublic Object construct(WireContext wireContext) {EnvironmentImpl environment = EnvironmentImpl.getCurrent();if (environment==null) {throw new WireException("no environment");}EntityManagerFactory entityManagerFactory = null;if (StringUtils.isNotBlank(factoryName)) {entityManagerFactory= (EntityManagerFactory)wireContext.get(factoryName);} else {entityManagerFactory = environment.get(EntityManagerFactory.class);}if (entityManagerFactory == null) {throw new WireException("No entity manager factory found");}EntityManager entityManager = null;// here we fight to get the entity manager// entity manager can be obtained in multiple ways// it can be injected by the container// it can be a "resource local" entity manager, where onus is// on the application to manage entity manager// lets allow the user to plug in some codeto get the EntityManager
if ((entityManager == null) && StringUtils.isNotBlank(providerClassName)) {try {Class providerClass = Class.forName(providerClassName);EntityManagerAccessor entityManagerProvider= (EntityManagerAccessor)providerClass.newInstance();entityManager = entityManagerProvider.getEntityManager(environment);} catch (ClassNotFoundException e) {throw new WireException("Problem loading class " + providerClassName, e);} catch (InstantiationException e) {throw new WireException("Problem while creating object of type "+ providerClassName, e);} catch (IllegalAccessException e) {throw new WireException("Problem while creating object of type "+ providerClassName, e);}
// else if we are allowed to create an entity manager and have a factory// somewhere in the wire context then we can easily create oneif ((entityManager == null) && create) {entityManager = entityManagerFactory.createEntityManager();}...
...
return entityManager;
}
......public Class<?> getType(WireDefinition wireDefinition) {return EntityManager.class;}}
The last thing that is pending is the patch-up code we need to make the code dependent directly on hibernate session to work. We need to make sure that if the session is looked up we return the delegating session from the entity manager. For this we need a little change in the existing HibernateSessionDescriptor ans HibernateSessionBinding.We introduce a boolean to specify whether this is running in a JPA environment.
public class HibernateSessionBinding extends WireDescriptorBinding {
public HibernateSessionBinding() {super("hibernate-session");}
public Object parse(Element element, Parse parse, Parser parser) {HibernateSessionDescriptor descriptor = new HibernateSessionDescriptor();
if (element.hasAttribute("jpa")) {Boolean isJpa = XmlUtil.attributeBoolean(element, "jpa", false, parse);descriptor.setJpa(isJpa);//if its jpa then we need this muchif (isJpa) {return descriptor;}}......}......}public class HibernateSessionDescriptor extends AbstractDescriptor {......
//now we need some more if this class works to satisfy hibernate//session seekers//in a JPA environment - the strategy works only if the JPA//provider is hibernateprotected boolean jpa = false;public Object construct(WireContext wireContext) {...if (jpa) {return getSessionFromEntityManager(environment);}........}
private Session getSessionFromEntityManager(EnvironmentImpl environment) {EntityManager entityManager = environment.get(EntityManager.class);Session session = (Session)entityManager.getDelegate();return session;}......}
Putting these all together, the jBPM configuration with these changes look like this (only the modified parts are depicted here):
<transaction-context>...<!--db-session/--><jpa-session/>......<transaction type="spring" /><entity-manager provider-class="org.jbpm.pvm.internal.jpa.SpringEntityManagerProvider"/><hibernate-session jpa="true"/>...</transaction-context>