Tuesday, February 28, 2012

Jbpm and scheduler components.

Jbpm 4.x has inbuilt timer component that could be used for the standalone deployments. But when it come to production deployments on JEE servers like the websphere, weblogic etc, it is NOT prudent to use it as it uses custom threads that do not share container context. Due to this there could be issues related to transaction management, thread-safety, and database locks.
See issue related:
Un-managed threads
Issues with websphere

So there is need to customize the timer service to suite production deployments. One option is to use most popular open source scheduler library/framework Quartz

Alternatively, one could use websphere server provided scheduler service that is available on the jndi, is persistent, thread-safe, transaction-aware and easy to configure. Only problem is portability.

Quartz 1.8.x is most popular ones used. Recent versions 2.x are compatible with Java 1.5 threading mechanisms. But here is the approach to use quartz 1.8.x with Jbpm 4.x, along with Spring 3.x framework.

Assuming you have done spring integration with jbpm. Where ever you need timer functionality you use the custom state activity in jbpm. This activity will use Spring scheduler factory bean to schedule a quartz job with configured time at run time. The activity then goes to a wait state until the trigger happens or the job is canceled. Here is the program sample for using custom scheduler activity in jbpm.

public class CustomScheduler ExternalActivityBehaviour {

private static final long serialVersionUID = 1L;
private Scheduler scheduler;

public void execute(ActivityExecution execution) {
String executionId = execution.getId();

scheduler.scheduleJob(new TimerJob(executionId,timeInSecs,..);
execution.waitForSignal();
}

public void signal(ActivityExecution execution,String signalName,
Map parameters) {
execution.take(signalName);
}
}


This is simple mechanism to introduce custom timer. The drawback is that this needs improvement to provide better semantics and xml domain elements for ease of use. This could be improved by extending current implementation
of timer service and elements to use quartz framework

There is one important change needed in existing quartz 1.8.x framework to allow it to work with Spring framework and use
websphere provided container threads. The steps are:
1. Apply patch on 1.8.x code as given here
2. Configure Spring to use quartz along with websphere workmanager threads.
3. Use global transaction to create jobs
4. Provide non-XA datasource in scheduler factory configurations.

This should help you work with spring + quartz 1.8.x + Websphere 6.1 smoothly.
Here is the sample configuration:

<bean name="engineScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="schedulerName">
<value>MyScheduler</value>
</property>
<property name="dataSource">
<ref bean="dataBaseSource">
</ref>
<property name="nonTransactionalDataSource">
<ref bean="nonXADatasource">
</ref>
<property name="startupDelay">
<value>20</value>
</property>
<property name="waitForJobsToCompleteOnShutdown">
<value>true</value>
</property>
<property name="applicationContextSchedulerContextKey">
<value>applicationContext</value>
</property>
<property name="taskExecutor" ref="scheculerTaskExecutor">
<property name="transactionManager" ref="transactionManager">
<property name="quartzProperties">
<props>
<prop key="org.quartz.threadPool.class">org.springframework.scheduling.quartz.LocalTaskExecutorThreadPool</prop>
<prop key="org.quartz.scheduler.instanceId">AUTO</prop>
<prop key="org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer">true</prop>
<prop key="org.quartz.jobStore.class">org.springframework.scheduling.quartz.LocalDataSourceJobStore</prop>
<prop key="org.quartz.jobStore.isClustered">true</prop>
<prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.oracle.OracleDelegate</prop>
</props>
</property>
</property>

It is important to give non-XA datasouce as below:

<bean id="nonXADatasource" class="oracle.jdbc.pool.OracleDataSource" method="close">
<property name="connectionCachingEnabled" value="true"/>
<property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="user" value="username"/>
<property name="password" value="secret">
<property name="minPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
<property name="acquireIncrement" value="1"/>
<property name="connectionCacheProperties"/>
<props merge="default">
<prop key="MinLimit">3</prop>
<prop key="MaxLimit">20</prop>
</props>
</property>
</bean>