org.springmodules.workflow.jbpm30.LocalJbpmSessionFactoryBean Maven / Gradle / Ivy
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springmodules.workflow.jbpm30;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.jbpm.db.JbpmSession;
import org.jbpm.db.JbpmSessionFactory;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.jpdl.par.ProcessArchiveDeployer;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
/**
* FactoryBean that creates a local jBPM SessionFactory instance. Behaves like a SessionFactory instance when used as bean reference,
* e.g. for JbpmTemplate's "sessionFactory" property. Note that switching to JndiObjectFactoryBean is just a matter of configuration!
*
* Due to the nature of jbpm 3.0.x architecture and the lack of interfaces for core components, this factoryBean will create subclasses of jbpmSessionFactory
* and jbpmSession in order to allow Spring transaction and session management.
*
* @author Rob Harrop
* @author Costin Leau
*/
public class LocalJbpmSessionFactoryBean implements FactoryBean, InitializingBean, BeanFactoryAware,
BeanNameAware {
private static final Log logger = LogFactory.getLog(LocalJbpmSessionFactoryBean.class);
// internal factory locator
private JbpmFactoryLocator factoryLocator = new JbpmFactoryLocator();
private JbpmSessionFactory sessionFactory;
private SessionFactory hibernateSessionFactory;
private Configuration hibernateConfiguration;
private ProcessDefinition[] processDefinitions;
public void setHibernateSessionFactory(SessionFactory hibernateSessionFactory) {
this.hibernateSessionFactory = hibernateSessionFactory;
}
public void setHibernateConfiguration(Configuration hibernateConfiguration) {
this.hibernateConfiguration = hibernateConfiguration;
}
public void afterPropertiesSet() throws Exception {
if (this.hibernateConfiguration == null) {
throw new FatalBeanException("Property [hibernateConfiguration] of ["
+ LocalJbpmSessionFactoryBean.class
+ "] is required.");
}
if (this.hibernateSessionFactory == null) {
throw new FatalBeanException("Property [hibernateSessionFactory] of ["
+ LocalJbpmSessionFactoryBean.class
+ "] is required.");
}
this.sessionFactory = new SpringJbpmSessionFactory(this.hibernateConfiguration,
this.hibernateSessionFactory);
// deploy process definitions if there are any
if (processDefinitions != null && processDefinitions.length > 0) {
//TODO: replace with another faster utility
String toString = Arrays.asList(processDefinitions).toString();
logger.info("deploying process definitions:" + toString);
for (int i = 0; i < processDefinitions.length; i++) {
ProcessArchiveDeployer.deployProcessDefinition(processDefinitions[i], this.sessionFactory);
}
}
}
public Object getObject() throws Exception {
return this.sessionFactory;
}
public Class getObjectType() {
return (this.sessionFactory == null) ? JbpmSessionFactory.class : this.sessionFactory.getClass();
}
public boolean isSingleton() {
return true;
}
/**
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
factoryLocator.setBeanFactory(beanFactory);
}
/**
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
*/
public void setBeanName(String name) {
factoryLocator.setBeanName(name);
}
/**
* Subclass for JbpmSessionFactory that hooks the Spring jbpm session factory.
*
*/
private static class SpringJbpmSessionFactory extends JbpmSessionFactory {
private SessionFactory sessionFactory;
public SpringJbpmSessionFactory(Configuration configuration, SessionFactory sessionFactory) {
super(configuration, sessionFactory);
this.sessionFactory = sessionFactory;
}
public JbpmSession openJbpmSession(Connection jdbcConnection) {
if (jdbcConnection != null) {
throw new UnsupportedOperationException(
"Cannot start a new Hibernate Session using supplied JDBC connection");
}
// TODO: should this always be on?
Session session = SessionFactoryUtils.getSession(this.sessionFactory, true);
return new SpringJbpmSession(this, session, this.sessionFactory);
}
}
/**
* Extension of JbpmSession factory that hooks in Spring resource (transaction/session) management.
*
*/
private static class SpringJbpmSession extends JbpmSession {
private SessionFactory sessionFactory;
// determine the field at start time.
private static final Field sessionField;
private static final String SESSION_FIELD_NAME = "session";
private static final Field transactionField;
private static final String TRANSACTION_FIELD_NAME = "transaction";
static {
try {
// do the 'reflection' magic only once to avoid security checks
// on each close check
sessionField = JbpmSession.class.getDeclaredField(SESSION_FIELD_NAME);
sessionField.setAccessible(true);
transactionField = JbpmSession.class.getDeclaredField(TRANSACTION_FIELD_NAME);
transactionField.setAccessible(true);
}
catch (NoSuchFieldException e) {
throw new RuntimeException("can't find field "
+ SESSION_FIELD_NAME
+ "/"
+ TRANSACTION_FIELD_NAME
+ " for introspection on class "
+ JbpmSession.class, e);
}
}
public SpringJbpmSession(JbpmSessionFactory jbpmSessionFactory, Session session,
SessionFactory sessionFactory) {
super(jbpmSessionFactory, session);
this.sessionFactory = sessionFactory;
}
public void beginTransaction() {
if (isSpringManagedTransaction()) {
if (logger.isDebugEnabled())
logger.debug("begin transaction -> handled through Spring");
return;
}
if (logger.isDebugEnabled())
logger.debug("begin transaction -> handled through jBPM");
super.beginTransaction();
}
public void commitTransaction() {
if (isSpringManagedTransaction()) {
if (logger.isDebugEnabled())
logger.debug("commit transaction -> handled through Spring");
return;
}
if (logger.isDebugEnabled())
logger.debug("commit transaction -> handled through jBPM");
super.commitTransaction();
}
public void rollbackTransaction() {
if (isSpringManagedTransaction()) {
if (logger.isDebugEnabled())
logger.debug("rollback transaction -> handled through Spring");
return;
}
if (logger.isDebugEnabled())
logger.debug("rollback transaction -> handled through jBPM");
super.rollbackTransaction();
}
// TODO: thoroughly test these
// TODO: consider the effect of deferred close of these methods
// TODO: check out transaction management
public void commitTransactionAndClose() {
if (isSpringManagedTransaction()) {
if (logger.isDebugEnabled())
logger.debug("commit and close transaction -> handled through Spring");
nullifySuperField(transactionField);
close();
}
if (logger.isDebugEnabled())
logger.debug("commit and close transaction -> handled through jBPM");
super.commitTransactionAndClose();
}
public void rollbackTransactionAndClose() {
if (isSpringManagedTransaction()) {
if (logger.isDebugEnabled())
logger.debug("rollback and close transaction -> handled through Spring");
nullifySuperField(transactionField);
close();
}
if (logger.isDebugEnabled())
logger.debug("rollback and close transaction -> handled through jBPM");
super.rollbackTransactionAndClose();
}
public void close() {
if (logger.isDebugEnabled())
logger.debug("closing jbpm session");
// should remove the session reference in a nice way (let spring do the handling)
// a. first set through introspection the session to null to prevent any trouble or
// closing
if (isSpringManagedTransaction()) {
nullifySuperField(sessionField);
}
// no matter what let jbpm release its resources
super.close();
}
/**
* Utility method for nullified a super field.
* @param field
*/
protected void nullifySuperField(Field field) {
if (logger.isDebugEnabled())
logger.debug("nullfying field " + field.getName());
try {
field.set(this, null);
}
catch (IllegalAccessException e) {
throw new UnsupportedOperationException("can not set private field=" + field);
}
}
/**
* Check if the session is transaction (was binded to thread).
* @return
*/
private boolean isSpringManagedTransaction() {
return SessionFactoryUtils.isSessionTransactional(getSession(), this.sessionFactory);
}
}
/**
* @return Returns the processDefinitions.
*/
public ProcessDefinition[] getProcessDefinitions() {
return processDefinitions;
}
/**
* @param processDefinitions The processDefinitions to set.
*/
public void setProcessDefinitions(ProcessDefinition[] processDefinitions) {
this.processDefinitions = processDefinitions;
}
}