Spring is in the air
icyjamie Feb 1, 2006 2:03 AMI guess there must be already some Spring integration initiatives. As we are heavily into Spring, we tried an integration as well. We have been using jBpm 3.1b2, because that looked like the easiest jBpm framework to "Springify" (the author has made it clear that the whole intention of 3.1 is more control over the configuration). I will not start any debate on strenghts and weaknesses of 3.1b2 and Spring, because "Unfortunately, I can't explain what jBpm 3.1b2 and Spring is, you have to see it for yourself".
The goal of the integration was purely project-based:
- features we will not use are not tested thoroughly, and are not injected/developed further (messaging, scheduler)
- customization are in the scope of the project for which we tend to use jBpm
However, we tried to made it non-intrusive: We subclassed were possible, we made factories and did a liiiiiittle bit of (Spring) AOP.
I will copy/paste my todo list I used without changes:
Create a custom jBpm ObjectFactory implementation which implements BeanFactoryAware also. Forward the getObject and hasObject to the BeanFactory methods
Add a resource hashmap to ObjectFactory, for use as a store of simple configuration parameters (String, Long).
Put hibernate configuration in Spring.
Inject factory services in services bean
Springify JpbmContext (singleton = FALSE !!). Inject services in it.
Create a descendant of the DBPersistenceService, overriding the getters to use special create methods, for creating the session objects, so we can have custom implementation of the session objects at hand. Warning: these session objects are NOT bean-aware !! (As all non-singletons are).
Create a custom PersistenceServiceFactory (override the one jBpm provides):
create a "createService", which will use spring look-up to create a PersistenceService
override openService, using the createService, which injects the session at runtime
Inject the jbpm hibernate sessionfactory
Inject the datasource
Add a jbpm.task.instance.factory bean
Add a jbpm.variable.resolver bean
Create a JbpmConfigurationFactoryBean which will create a jbpmConfiguration (by using the Configs innerclass to set the defaultObjectFactory and getInstance to create it)
Create a jbpmConfiguration Bean by using the JbpmConfigurationFactoryBean
You then get this (rather elaborated) spring configuration:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="jbpmSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" parent="common.sessionFactory"> <property name="dataSource"> <ref bean="common.datasource" /> </property> <property name="useTransactionAwareDataSource" value="true" /> <property name="mappingResources"> <list> <!-- graph.def mapping files --> <value>org/jbpm/graph/def/ProcessDefinition.hbm.xml</value> <value>org/jbpm/graph/def/Node.hbm.xml</value> <value>org/jbpm/graph/def/Transition.hbm.xml</value> <value>org/jbpm/graph/def/Event.hbm.xml</value> <value>org/jbpm/graph/def/Action.hbm.xml</value> <value>org/jbpm/graph/def/SuperState.hbm.xml</value> <value>org/jbpm/graph/def/ExceptionHandler.hbm.xml</value> <value>org/jbpm/instantiation/Delegation.hbm.xml</value> <!-- graph.node mapping files --> <value>org/jbpm/graph/node/StartState.hbm.xml</value> <value>org/jbpm/graph/node/EndState.hbm.xml</value> <value>org/jbpm/graph/node/ProcessState.hbm.xml</value> <value>org/jbpm/graph/node/Decision.hbm.xml</value> <value>org/jbpm/graph/node/Fork.hbm.xml</value> <value>org/jbpm/graph/node/Join.hbm.xml</value> <value>org/jbpm/graph/node/State.hbm.xml</value> <value>org/jbpm/graph/node/TaskNode.hbm.xml</value> <!-- context.def mapping files --> <value>org/jbpm/context/def/ContextDefinition.hbm.xml</value> <value>org/jbpm/context/def/VariableAccess.hbm.xml</value> <!-- taskmgmt.def mapping files --> <value>org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml</value> <value>org/jbpm/taskmgmt/def/Swimlane.hbm.xml</value> <value>org/jbpm/taskmgmt/def/Task.hbm.xml</value> <value>org/jbpm/taskmgmt/def/TaskController.hbm.xml</value> <!-- module.def mapping files --> <value>org/jbpm/module/def/ModuleDefinition.hbm.xml</value> <!-- bytes mapping files --> <value>org/jbpm/bytes/ByteArray.hbm.xml</value> <!-- file.def mapping files --> <value>org/jbpm/file/def/FileDefinition.hbm.xml</value> <!-- scheduler.def mapping files --> <value>org/jbpm/scheduler/def/CreateTimerAction.hbm.xml</value> <value>org/jbpm/scheduler/def/CancelTimerAction.hbm.xml</value> <!-- graph.exe mapping files --> <value>org/jbpm/graph/exe/Comment.hbm.xml</value> <value>org/jbpm/graph/exe/ProcessInstance.hbm.xml</value> <value>org/jbpm/graph/exe/Token.hbm.xml</value> <value>org/jbpm/graph/exe/RuntimeAction.hbm.xml</value> <!-- module.exe mapping files --> <value>org/jbpm/module/exe/ModuleInstance.hbm.xml</value> <!-- context.exe mapping files --> <value>org/jbpm/context/exe/ContextInstance.hbm.xml</value> <value>org/jbpm/context/exe/TokenVariableMap.hbm.xml</value> <value>org/jbpm/context/exe/VariableInstance.hbm.xml</value> <value>org/jbpm/context/exe/variableinstance/ByteArrayInstance.hbm.xml</value> <value>org/jbpm/context/exe/variableinstance/DateInstance.hbm.xml</value> <value>org/jbpm/context/exe/variableinstance/DoubleInstance.hbm.xml</value> <value>org/jbpm/context/exe/variableinstance/HibernateLongInstance.hbm.xml</value> <value>org/jbpm/context/exe/variableinstance/HibernateStringInstance.hbm.xml</value> <value>org/jbpm/context/exe/variableinstance/LongInstance.hbm.xml</value> <value>org/jbpm/context/exe/variableinstance/StringInstance.hbm.xml</value> <!-- msg.db mapping files --> <value>org/jbpm/msg/Message.hbm.xml</value> <value>org/jbpm/msg/db/TextMessage.hbm.xml</value> <value>org/jbpm/command/ExecuteActionCommand.hbm.xml</value> <value>org/jbpm/command/ExecuteNodeCommand.hbm.xml</value> <value>org/jbpm/command/SignalCommand.hbm.xml</value> <value>org/jbpm/command/TaskInstanceEndCommand.hbm.xml</value> <!-- taskmgmt.exe mapping files --> <value>org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml</value> <value>org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml</value> <value>org/jbpm/taskmgmt/exe/PooledActor.hbm.xml</value> <value>org/jbpm/taskmgmt/exe/SwimlaneInstance.hbm.xml</value> <!-- scheduler.exe mapping files --> <value>org/jbpm/scheduler/exe/Timer.hbm.xml</value> <!-- logging mapping files --> <value>org/jbpm/logging/log/ProcessLog.hbm.xml</value> <value>org/jbpm/logging/log/MessageLog.hbm.xml</value> <value>org/jbpm/logging/log/CompositeLog.hbm.xml</value> <value>org/jbpm/graph/log/ActionLog.hbm.xml</value> <value>org/jbpm/graph/log/NodeLog.hbm.xml</value> <value>org/jbpm/graph/log/ProcessInstanceCreateLog.hbm.xml</value> <value>org/jbpm/graph/log/ProcessInstanceEndLog.hbm.xml</value> <value>org/jbpm/graph/log/SignalLog.hbm.xml</value> <value>org/jbpm/graph/log/TokenCreateLog.hbm.xml</value> <value>org/jbpm/graph/log/TokenEndLog.hbm.xml</value> <value>org/jbpm/graph/log/TransitionLog.hbm.xml</value> <value>org/jbpm/context/log/VariableLog.hbm.xml</value> <value>org/jbpm/context/log/VariableCreateLog.hbm.xml</value> <value>org/jbpm/context/log/VariableDeleteLog.hbm.xml</value> <value>org/jbpm/context/log/VariableUpdateLog.hbm.xml</value> <value>org/jbpm/context/log/variableinstance/ByteArrayUpdateLog.hbm.xml</value> <value>org/jbpm/context/log/variableinstance/DateUpdateLog.hbm.xml</value> <value>org/jbpm/context/log/variableinstance/DoubleUpdateLog.hbm.xml</value> <value>org/jbpm/context/log/variableinstance/HibernateLongUpdateLog.hbm.xml</value> <value>org/jbpm/context/log/variableinstance/HibernateStringUpdateLog.hbm.xml</value> <value>org/jbpm/context/log/variableinstance/LongUpdateLog.hbm.xml</value> <value>org/jbpm/context/log/variableinstance/StringUpdateLog.hbm.xml</value> <value>org/jbpm/taskmgmt/log/TaskLog.hbm.xml</value> <value>org/jbpm/taskmgmt/log/TaskCreateLog.hbm.xml</value> <value>org/jbpm/taskmgmt/log/TaskAssignLog.hbm.xml</value> <value>org/jbpm/taskmgmt/log/TaskEndLog.hbm.xml</value> <value>org/jbpm/taskmgmt/log/SwimlaneLog.hbm.xml</value> <value>org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml</value> <value>org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml</value> </list> </property> </bean> <bean id="transactionManagerJbpm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref bean="common.datasource" /> </property> </bean> <bean id="jbpmObjectFactoryBeanFactoryAware" class="be.post.cits.ariane.jbpm.ObjectFactoryBeanFactoryAware"> <property name="resources"> <map> <entry key="resource.business.calendar"> <value type="java.lang.String">org/jbpm/calendar/jbpm.business.calendar.properties</value> </entry> <entry key="resource.default.modules"> <value type="java.lang.String">org/jbpm/graph/def/jbpm.default.modules.properties</value> </entry> <entry key="resource.converter"> <value type="java.lang.String">org/jbpm/db/hibernate/jbpm.converter.properties</value> </entry> <entry key="resource.action.types"> <value type="java.lang.String">org/jbpm/graph/action/action.types.xml</value> </entry> <entry key="resource.node.types"> <value type="java.lang.String">org/jbpm/graph/node/node.types.xml</value> </entry> <entry key="resource.parsers"> <value type="java.lang.String">org/jbpm/jpdl/par/jbpm.parsers.xml</value> </entry> <entry key="resource.varmapping"> <value type="java.lang.String">org/jbpm/context/exe/jbpm.varmapping.xml</value> </entry> <entry key="jbpm.msg.wait.timout"> <value type="java.lang.Long">5000</value> </entry> </map> </property> </bean> <bean id="dbPersistenceService" class="be.post.cits.ariane.jbpm.SpringDbPersistenceService" singleton="false"> <property name="contextSessionClass" value="org.jbpm.db.ContextSession" /> <property name="graphSessionClass" value="org.jbpm.db.GraphSession" /> <property name="loggingSessionClass" value="org.jbpm.db.LoggingSession" /> <property name="messagingSessionClass" value="org.jbpm.db.MessagingSession" /> <property name="schedulerSessionClass" value="org.jbpm.db.SchedulerSession" /> <property name="taskMgmtSessionClass" value="org.jbpm.db.TaskMgmtSession" /> <constructor-arg> <ref local="persistenceServiceFactory" /> </constructor-arg> </bean> <bean id="persistenceServiceFactory" class="be.post.cits.ariane.jbpm.SpringDbPersistenceServiceFactory"> <lookup-method name="createPersistenceService" bean="dbPersistenceService" /> <property name="dataSource"> <ref bean="common.datasource" /> </property> <property name="sessionFactory"> <ref local="jbpmSessionFactory" /> </property> <property name="localSessionFactoryBeanString"> <idref bean="jbpmSessionFactory" /> </property> </bean> <bean id="jbpmServices" class="org.jbpm.svc.Services"> <constructor-arg> <map> <entry key="persistence"> <ref local="persistenceServiceFactory"></ref> </entry> </map> </constructor-arg> </bean> <bean id="default.jbpm.context" singleton="false" class="org.jbpm.JbpmContext" name="jbpmContext"> <constructor-arg> <ref local="jbpmServices" /> </constructor-arg> <constructor-arg> <ref local="jbpmObjectFactoryBeanFactoryAware" /> </constructor-arg> </bean> <bean id="jbpm.task.instance.factory" class="org.jbpm.taskmgmt.impl.DefaultTaskInstanceFactoryImpl" /> <bean id="jbpm.variable.resolver" class="org.jbpm.jpdl.el.impl.JbpmVariableResolver" /> <bean id="jbpmConfiguration" class="be.post.cits.ariane.jbpm.JbpmConfigurationFactoryBean"> <property name="objectFactory"> <ref local="jbpmObjectFactoryBeanFactoryAware" /> </property> </bean> </beans>
Of course, the hibernate mappings and other configuration could reside in an external file as well. Spring can refer to this file also. This file depends on other (not shown) configuration, e.g. for having a common datasource.
Transactional behaviour is controlled by Spring or the application.
Word to the author:
- we need more setters :-) in favour of constructors.
- alternatively, we need more "protected" instead of "package" (otherwise we have to define the same package structure, and this could be a project constraint).
- we need more "design-by-interface". (We do some dangerous casting :-))