JBPM Jpa Entegrasyonu

Bir süredir blog yazılarına ara vermiştim. Bu süre zarfında Business Process Management kabiliyetinin mevcut altyapımıza kazandırılması için çalışmalar yaptık. BPM için tercihimiz JBPM’den yana oldu. Bu ve devamındaki yazılarımda JBPM ile ilgili tecrübelerimizi, JBPM’in JPA, Spring WebFlow, Acegi Security gibi diğer frameworklerin de yer aldığı altyapımıza entegre edilmesi ile ilgili geliştirdiğimiz çözümleri, JBPM’in gömülü biçimde kurumsal web uygulamalarında kullanışını, yönetim arayüzünün mevcut uygulamalara nasıl entegre edilebileceğini anlatacağım.

İlk olarak JBPM JPA entegrasyonu ile konumuza başlayalım. JBPM persistence işlemleri için doğrudan Hibernate’i kullanmaktadır. Biz ise geliştirdiğimiz uygulamalarda JPA yı, JPA implementasyonu olarak da Hibernate’i kullanmaktayız. Bu durumda uygulamalarımızın gerçekleştirdiği persistence işlemleri ile JPBM’in gerçekleştirdiği persistence işlemlerinin farklı iki transaction context içerisinde çalışması durumu ortaya çıkmaktadır. Bu da doğal olarak veriler üzerinde tutarsızlığa yol açacaktır.

JPA kullanan bir uygulama ile JBPM in persistence işlemlerini aynı transaction context içinde gerçekleştirmelerini sağlamak için ilk akla gelen çözüm JTA’nın kullanılmasıdır. Aslında uygulamalarımızın ve JBPM’in aynı veri tabanını kullanmalarına rağmen JTA kullanma zorunluluğu tahmin ediyorum sizin de kulağınıza garip gelmiştir. Şu ana kadar lokal transaction’larla uygulama sunucularının herhangi bir servisine ihtiyaç duymadan standalone test edilebilen ve üretim hattında çalıştırılabilen uygulamalarımızın JTA kullanmaya başlamaları ile sahip olduğumuz esnekliği kaybetmek doğrusu benimde hiç içime sinmedi. Oysa her iki taraf da ortak bir veri tabanını, hatta aynı ORM implementasyonunu paylaşıyordu.

Aslında JPA implementasyonu olarak Hibernate’i kullanıyorsanız uygulama tarafında JPA API’si ile muhatap olsanız da arka tarafta persistence sürecini yine core Hibernate implementasyonu yönetmektedir. Yani uygulamanız bir EntityManagerFactory oluşturduğunda aslında Hibernate native SessionFactory nesnesini oluşturup kullanmaktadır. Aynı şekilde EntityManagerFactory üzerinden yeni bir EntityManager oluşturduğumuz vakit yine bu EntityManager nesnesi native bir Hibernate Session’ı üzerinden persistence işlemlerini gerçekleştirmektedir. Eğer JBPM’in persistence işlemleri için JPA’nın yönettiği SessionFactory ve Session nesnelerini kullanması mümkün olursa uygulamamız içerisinde gerçekleşen ve JBPM’in gerçekleştirdiği persistence işlemlerin JTA’ya gerek kalmadan aynı transaction context içerisinde yer alması sağlanabilir.

Bunun için yapılması gereken iki temel işlem söz konusudur. Birincisi JBPM’in ihtiyaç duyduğu SessionFactory’nin JPA EntityManagerFactory tarafından expose edilmesidir. Bu noktada ayrıca JBPM’in hibernate.cfg.xml içerisindeki mapping tanımlarının JPA persistence.xml içerisinde tanımlanması gerekir. JBPM’in mapping-file tanımlarını belirli bir sırada sağlamazsanız (jar-file tanımı kullanıldığı vakit bu durum ortaya çıkmaktadır) EntityManagerFactory/SessionFactory ayağa kaldırılırken problem oluşmaktadır.

EntityManagerFactory’nin native Hibernate SessionFactory nesnesini expose etmesi için basit bir Spring FactoryBean yazmamız yeterli oldu.

	public class EntityManagerFactoryToSessionFactoryBean extends AbstractFactoryBean {

	private EntityManagerFactory entityManagerFactory;

	public EntityManagerFactory getEntityManagerFactory() {
		return entityManagerFactory;
	}

	@Required
	public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
		this.entityManagerFactory = entityManagerFactory;
	}

	protected Object createInstance() throws Exception {
		return HibernateEntityManagerFactory)getEntityManagerFactory(?.getSessionFactory();
	}

	public Class getObjectType() {
		return SessionFactory.class;
	}
}
<bean id="sessionFactory" class="jbpm.jpa.integration.EntityManagerFactoryToSessionFactoryBean”>
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

Uygulamalarımızda JBPM konfigürasyonunu ayağa kaldırmak ve JBPM işlemlerini gerçekleştirmek için Spring Modules projesinden yararlandık. Spring Modules JBPM işlemleri için Spring’in klasikleşmiş XXXTemplate callback yapısına uygun JbpmTemplate sınıfını sunmaktadır. JbpmTemplate’in kullanılabilmesi için yapılması gereken bir JbpmConfiguration nesnesinin oluşturulmasıdır. Spring Modules’ün LocalJbpmConfigurationFactoryBean sınıfı bir JbpmConfiguration nesnesi oluşturmaktadır. Bu FactoryBean, JbpmConfiguration nesnesi üzerinden JBPM persistence servisinin (DbPersistenceService) kullandığı SessionFactory nesnesini EntityManagerFactory’den expose ettiğimiz SessionFactory nesnesi olarak set etmektedir.

<bean id="jbpmConfiguration" class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean">
    <property name="sessionFactory" ref="sessionFactory"/>
    <property name="configuration" value="classpath:jbpm.cfg.xml"/>
    <property name="createSchema" value="false"/>
</bean>

<bean id="jbpmTemplate" class="org.springmodules.workflow.jbpm31.JbpmTemplate">
    <constructor-arg index="0" ref="jbpmConfiguration"/>
    <property name="hibernateTemplate" ref="hibernateTemplate"/>
</bean>

Gelelim ikinci adımımıza. Bu adımda yapmamız gereken uygulamamızın persistence işlemler için kullandığı EntityManager’ın native Hibernate Session nesnesinin JBPM’in persistence işlemler için kullanmasını sağlamalıyız.

Hibernate 3.1’den itibaren “contextual session” kabiliyetini sunmaktadır. Bu sayede SessionFactory’ye Hibernate Session’ı nereden ve nasıl temin edeceğini pluggable biçimde belirtebilmekteyiz. Hibernate’in konfigürasyon parametrelerinden “hibernate.current_session_context_class “ bunun için tahsis edilmiştir. Bu prametreye Hibernate’in CurrentSessionContext interface’ini implement eden bir sınıfın full paket adını değer olarak girmemiz yeterlidir. CurrentSessionContext implementasyonu bizim için o anda mevcut olan thread bound EntityManager’ın native Hibernate Session’ını döndürecektir.

public class EntityManagerAwareCurrentSessionContext implements CurrentSessionContext {

	private EntityManagerFactory emf;

	private ThreadLocalSessionContext threadLocalSessionContext;


	public Session currentSession() throws HibernateException {
		EntityManager em = EntityManagerFactoryUtils.getTransactionalEntityManager(getEmf());
		if(em != null){
			return (Session)((HibernateEntityManager) em).getSession();
		} else {
			return threadLocalSessionContext.currentSession();
		}
	}

	public EntityManagerAwareCurrentSessionContext(SessionFactoryImplementor factory) {
		threadLocalSessionContext = new ThreadLocalSessionContext(factory);
	}


	private synchronized EntityManagerFactory getEmf() {
		if(emf == null) {
			emf = (EntityManagerFactory)SpringUtils.getBean("entityManagerFactory");
		}
		return emf;
	}
}

EntityManagerAwareCurrentSessionContext eğer thread bound bir EntityManager varsa bunun kullandığı Session’ı yoksa fallback olarak ThreadLocalSessionContext’i kullanarak yeni bir Sesion’ı döndürmektedir. Yukarıdaki CurrentSessionContext implementasyonunun aktif olması için persistence.xml dosyasının içerisinde

<properties>

...

    <property name="hibernate.current_session_context_class" value="jbpm.jpa.integration.EntityManagerAwareCurrentSessionContext" />
</properties>

şeklinde bir tanım yapmamız yeterli olacaktır.

JBPM’in DbPersistenceService sınıfına Hibernate SessionFactory’nin getCurrentSession() metodu ile o an mevcut Session’ı kullanması (isCurrentSessionEnabled) söylenebilir. Ancak bu değer default false olarak set edilidir. Bu property’de değişiklik yapmadan da DbPersistenceService’in mevcut Session’ı kullanması sağlanabilir. Nasıl mı? Okumaya devam…

Spring Modüles’ün JbpmTemplate sınıfı, bütün JBPM işlemlerini HibernateTemplate içinde yürütmektedir. JbpmTemplate herhangi bir operasyon gerçekleşmeden evvel HibernateTemplate’dan gelen Session’ı JbpmContext’e set etmektedir. Böylece DbPersistenceService’in yeni Session yaratmak yerine JbpmContext’deki mevcut Session’ı kullanması sağlanır. Bu arada HibernateTemplate’a da her seferinde yeni bir Session yaratmak yerine halihazırdaki SessionFactory’nin getCurrentSession() metodunu çağırarak mevcut Hibernate Session’ı kullanması (allowCreate=false) söylenmelidir.

<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
    <property name="sessionFactory">
        <ref local="sessionFactory"/>
    </property>
    <property name="allowCreate" value="false"/>
</bean>

Artık JBPM’in halihazırda thread bound bir EntityManager’dan expose edilmiş native Hibernate Session’ı kullanması sağlanmış olur. Bir sonraki yazımda JBPM ve Spring WebFlow’un entegrasyonu üzerinde duracağım.