SSJ整合jBPM4.3

来源:互联网 发布:java8 base64源码 编辑:程序博客网 时间:2024/05/13 08:22

原文地址

There have been a few discussion threads over using jBPM4 with JPA – with or without Spring. And a few “managed to work” kind of hacks came up. For example, prior to version 4.3, there was SpringConfiguration.setSessionFactory() where you inject the SessionFactory object obtained from the JPA entity manager factory. Another solution may be, you can have two sets of configurations – one for your regular business model and another for jBPM4. And you must admit none of these are really clean solutions.


  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:

<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/persistencehttp://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 assumption here is that your JPA provider is hibernate.


   So let me list the steps we need to do to achieve this:
  1. The class I loved the most when it comes to handle db access in jBPM4 isorg.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.
  2. 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.
  3. To support the tag in configuration we need to create binding and descriptor classes.
  4. We also need to configure entity manager instead of hibernate session. This involves creating the binding and descriptor classes.
  5. 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.
    Ideally with these changes we should be able to use JPA instead of using Hibernate (directly). But when was the last time life has been ideal?? So there has to be some twist in the tail.org.jbpm.pvm.internal.session.DbSession appeared to be a nice way to implement "DAO" pattern (http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html).  And all other classes will go through the DbSession implementation to execute the queries. But that is not the case. There are classes which gets the Hibernate Session directly from environment and uses that. In fact there are quite a few such cases (a search in Eclipse has shown 117 direct access to hibernate session in jBPM4 code). It looks like the idea of DbSession was lost in translation. Or may be I did not get the idea correct, but whatever it is, it helps to channelize all the db operation through one such class.


    Changing 100+ classes not to use hibernate session is quite a task. And that will be a decently wide spread change on existing jBPM code. Lets minimize the impact on the code. We will have the regular org.jbpm.pvm.internal.wire.descriptor.HibernateSessionDescriptor with the only modification that it gets the session from the entity manager if we are using JPA.


    JPA Implementation of DbSession - We can have a DbSession implementation org.jbpm.pvm.internal.jpa.JpaDbSessionImplwhich somewhat followsorg.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/>
    Binding classes for EntityManager -  We need to use JPA EntityManager instead of Hibernate Session. So we need to introduce another tag in the configuration file -
         <entity-manager/>
  
   This is supposed to replace hibernate session.
    But this one will be little more spicy than this. The primary reason is that there is no standard way to obtain the relevant EntityManager from the EntityManagerFactory. We miss methods like getCurrentSession() of Hibernate in JPA. We can use @PersistenceContext to get the EntityManager injected to a server managed component like Servlets or EJBs. The same technique works for Spring beans as well. So we need a Spring bean for Spring, some session bean or servlet for a JEE server managed application - in short for different environments we need different classes that can provide us the EntityManager.


    In such a scenario we can allow a class to be plugged in that can do the required lookup. Then we can evolve different possible lookup classes for different target environments and then automatically use the correct lookup class for the environment. We can focus on a Spring version of such EntityManager accessor class here.



    The interface for entity manager accessor looks like this:
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);
}
    And a spring implementation of the same may be this:
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)
     */
    @Override
    public 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")
    @Override
    public 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 one
        if ((entityManager == null) && create) {
           entityManager = entityManagerFactory.createEntityManager();
        }
        ...
        ...
        returnentityManager;
    }

    ...
    ...
    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 much
                  if (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 hibernate
          protected 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>

       
    I believe JPA wil be introduced in jBPM5. I just hope the point of smooth gel with hibernate in the process of moving to JPA is not ignored. Hibernate is stil and probably will by far remain the most popular and most mature ORM implementation in Java.

源代码提供

原创粉丝点击