In this tutorial we will explore some more features of jBPM 5 by deploying a Web application to JBoss application server 7 and configuring the persistence on a MySQL DB
You should read at first this introduction to Human Task management which introduces some basic concepts about the TaskServer and the TaskClient interfaces.
In the previous article we showed how to start and connect to the TaskServer using the org.jbpm.test.JBPMHelper utility class. This class is a good starting point for your first examples, however in real world projects you would need to use a custom database provider, such as MySQL or Oracle.
So as first step configure a MySQL datasource on JBoss AS 7 and bind it in the JNDI name java:jboss/datasources/jbpmDS
See how to configure a MySQL Datasource on JBoss AS 7: Configuring a Datasource with MySQL on WildFly
Ok, now create a Web application and add into the src/META-INF folder the persistence.xml file which configures the data persistence of jBPM5.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence version="1.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="org.jbpm.persistence.jpa" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:jboss/datasources/jbpmDS</jta-data-source> <mapping-file>META-INF/JBPMorm.xml</mapping-file> <mapping-file>META-INF/Taskorm.xml</mapping-file> <class>org.jbpm.persistence.processinstance.ProcessInstanceInfo</class> <class>org.drools.persistence.info.SessionInfo</class> <class>org.drools.persistence.info.WorkItemInfo</class> <class>org.drools.persistence.info.SessionInfo</class> <class>org.drools.persistence.info.WorkItemInfo</class> <class>org.jbpm.process.audit.ProcessInstanceLog</class> <class>org.jbpm.process.audit.NodeInstanceLog</class> <class>org.jbpm.process.audit.VariableInstanceLog</class> <class>org.jbpm.task.Attachment</class> <class>org.jbpm.task.Content</class> <class>org.jbpm.task.BooleanExpression</class> <class>org.jbpm.task.Comment</class> <class>org.jbpm.task.Deadline</class> <class>org.jbpm.task.Comment</class> <class>org.jbpm.task.Deadline</class> <class>org.jbpm.task.Delegation</class> <class>org.jbpm.task.Escalation</class> <class>org.jbpm.task.Group</class> <class>org.jbpm.task.I18NText</class> <class>org.jbpm.task.Notification</class> <class>org.jbpm.task.EmailNotification</class> <class>org.jbpm.task.EmailNotificationHeader</class> <class>org.jbpm.task.PeopleAssignments</class> <class>org.jbpm.task.Reassignment</class> <class>org.jbpm.task.Status</class> <class>org.jbpm.task.Task</class> <class>org.jbpm.task.TaskData</class> <class>org.jbpm.task.SubTasksStrategy</class> <class>org.jbpm.task.OnParentAbortAllSubTasksEndStrategy</class> <class>org.jbpm.task.OnAllSubTasksEndParentEndStrategy</class> <class>org.jbpm.task.User</class> <properties> <property name="hibernate.max_fetch_depth" value="3"/> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="false" /> <property name="hibernate.transaction.manager_lookup_class" value="org.jbpm.integration.console.JBPMTransactionManager" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> </properties> </persistence-unit> <persistence-unit name="org.jbpm.task"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <non-jta-data-source>java:jboss/datasources/jbpmDS</non-jta-data-source> <mapping-file>META-INF/Taskorm.xml</mapping-file> <class>org.jbpm.task.Attachment</class> <class>org.jbpm.task.Content</class> <class>org.jbpm.task.BooleanExpression</class> <class>org.jbpm.task.Comment</class> <class>org.jbpm.task.Deadline</class> <class>org.jbpm.task.Comment</class> <class>org.jbpm.task.Deadline</class> <class>org.jbpm.task.Delegation</class> <class>org.jbpm.task.Escalation</class> <class>org.jbpm.task.Group</class> <class>org.jbpm.task.I18NText</class> <class>org.jbpm.task.Notification</class> <class>org.jbpm.task.EmailNotification</class> <class>org.jbpm.task.EmailNotificationHeader</class> <class>org.jbpm.task.PeopleAssignments</class> <class>org.jbpm.task.Reassignment</class> <class>org.jbpm.task.Status</class> <class>org.jbpm.task.Task</class> <class>org.jbpm.task.TaskData</class> <class>org.jbpm.task.SubTasksStrategy</class> <class>org.jbpm.task.OnParentAbortAllSubTasksEndStrategy</class> <class>org.jbpm.task.OnAllSubTasksEndParentEndStrategy</class> <class>org.jbpm.task.User</class> <properties> <property name="hibernate.max_fetch_depth" value="3"/> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="false" /> <property name="hibernate.transaction.manager_lookup_class" value="org.jbpm.integration.console.JBPMTransactionManager" /> </properties> </persistence-unit> </persistence>
As you can see, two persistence units are defined: one JTA-enabled named org.jbpm.persistence.jpa which is used by the jBPM5 engine and one non-JTA named org.jbpm.task which is used by the HumanTaskServer. (The latter persistence unit is defined as non-JTA is due to the fact that the Task Server Api needs to demarcate transactions using UserTransaction API, such as begin(), commit() and thus JTA resources seem incompatible with this persistence unit)
Coding the Task Server
Ok, so we need to create at first a TaskServer where Human Task will be registered.
The Human Task service needs to know what all the possible valid user and group ids are (to make sure tasks are assigned to existing users and/or groups to avoid errors and tasks that end up assigned to non-existing users). You need to make sure to register all users and groups before tasks can be assigned to them.
Finally the MinaTaskServer is used to start the TaskServer on the default port.
Let’s code this TaskServer in a Startup Servlet so it will be started when you deploy your application:
package com.mastertheboss.jbpm5; import java.io.IOException; import javax.naming.InitialContext; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.transaction.TransactionManager; import org.drools.KnowledgeBaseFactory; import org.drools.SystemEventListenerFactory; import org.drools.base.MapGlobalResolver; import org.drools.runtime.Environment; import org.drools.runtime.EnvironmentName; import org.jbpm.task.User; import org.jbpm.task.service.TaskService; import org.jbpm.task.service.TaskServiceSession; import org.jbpm.task.service.mina.MinaTaskServer; /** * Servlet implementation class StartupServlet */ @WebServlet(value="/StartupServlet", loadOnStartup=1) public class StartupServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException { EntityManagerFactory emfTask = Persistence.createEntityManagerFactory( "org.jbpm.task" ); Environment env = KnowledgeBaseFactory.newEnvironment(); env.set( EnvironmentName.ENTITY_MANAGER_FACTORY, emfTask ); env.set( EnvironmentName.GLOBALS, new MapGlobalResolver() ); TaskService taskService = new TaskService(emfTask, SystemEventListenerFactory.getSystemEventListener()); TaskServiceSession taskSession = taskService.createSession(); taskSession.addUser(new User("Administrator")); taskSession.addUser(new User("frank")); taskSession.addUser(new User("mary")); MinaTaskServer server = new MinaTaskServer( taskService ); Thread thread = new Thread( server ); thread.start(); System.out.println("Started task service!"); } public StartupServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub } }
{loadposition adsensebasso}
Coding the Task client
Now we will code a simple Servlet which will be used to start a sample process containing two HumanTasks.
In this simple bpmn diagram two user Tasks are included: Task 1 which is in charge to the user “frank” and Task 2 which is in charge to the user “mary”.
Here is the Servlet which connects to the TaskClient and starts the process:
package com.mastertheboss.jbpm5; import java.io.IOException; import java.util.Properties; import javax.naming.InitialContext; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.transaction.TransactionManager; import org.drools.KnowledgeBase; import org.drools.KnowledgeBaseFactory; import org.drools.base.MapGlobalResolver; import org.drools.builder.KnowledgeBuilder; import org.drools.builder.KnowledgeBuilderFactory; import org.drools.builder.ResourceType; import org.drools.impl.EnvironmentFactory; import org.drools.io.ResourceFactory; import org.drools.persistence.jpa.JPAKnowledgeService; import org.drools.runtime.Environment; import org.drools.runtime.EnvironmentName; import org.drools.runtime.KnowledgeSessionConfiguration; import org.drools.runtime.StatefulKnowledgeSession; import org.jbpm.process.audit.JPAProcessInstanceDbLog; import org.jbpm.process.audit.JPAWorkingMemoryDbLogger; import org.jbpm.process.workitem.wsht.WSHumanTaskHandler; @WebServlet("/StartProcess") public class StartProcess extends HttpServlet { private static final long serialVersionUID = 1L; StatefulKnowledgeSession ksession = null; KnowledgeBase kbase = null; public StartProcess() { super(); // TODO Auto-generated constructor stub } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { /* * Reads the KnowledgeBase */ kbase = readKnowledgeBase(); } catch (Exception e) { e.printStackTrace(); } EntityManagerFactory emf = Persistence.createEntityManagerFactory("org.jbpm.persistence.jpa"); /* * Creates the knowledge session that uses JPA to persists runtime state */ StatefulKnowledgeSession ksession = createKnowledgeSession(kbase); /* * Registers the Human Task Service in the jBPM engine */ ksession.getWorkItemManager().registerWorkItemHandler("Human Task", new WSHumanTaskHandler()); ksession.startProcess("com.sample.bpmn.hello"); System.out.println("Process started successfully..."); } private static StatefulKnowledgeSession createKnowledgeSession(KnowledgeBase kbase) { EntityManagerFactory emf = Persistence.createEntityManagerFactory( "org.jbpm.persistence.jpa" ); Environment env = KnowledgeBaseFactory.newEnvironment(); env.set( EnvironmentName.ENTITY_MANAGER_FACTORY, emf ); env.set( EnvironmentName.TRANSACTION_MANAGER, getTransactionManager() ); env.set( EnvironmentName.GLOBALS, new MapGlobalResolver() ); Properties properties = new Properties(); properties.put("drools.processInstanceManagerFactory", "org.jbpm.persistence.processinstance.JPAProcessInstanceManagerFactory"); properties.put("drools.processSignalManagerFactory", "org.jbpm.persistence.processinstance.JPASignalManagerFactory"); KnowledgeSessionConfiguration config = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(properties); return JPAKnowledgeService.newStatefulKnowledgeSession(kbase, config, env); } private static KnowledgeBase readKnowledgeBase() throws Exception { KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add(ResourceFactory.newClassPathResource("sample.bpmn"), ResourceType.BPMN2); return kbuilder.newKnowledgeBase(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } public static TransactionManager getTransactionManager() { TransactionManager tm = null; try { InitialContext ic = new InitialContext(); tm = (TransactionManager) ic.lookup("java:jboss/TransactionManager"); if (tm == null) throw new IllegalStateException("Couldn't find the transaction mamager in JNDI"); }catch (Exception exc) { exc.printStackTrace(); } return tm; } }
As you can see this sample Servlet creates a new KnowledgeBase adding to the classpath the bpmn diagram.
Next the KnowledgeSession needs to be created. This in turn uses JPA to persists the process’s runtime state.
The persistence unit which is associated with this example is org.jbpm.persistence.jpa
Please notice that in order to create the StatefulKnowledgeSession some properties needs to be set in the jBPM environment, such as the TransactionManager which is, in our case, the AS 7 TransactionManager bound to the JNDI name java:jboss/TransactionManager
Finally, the human task service needs to be integrated with the jBPM engine by registering a work item handler that is responsible for translating the human task to a specific invocation of a service.
Setting up application libraries
You need a jBPM 5 runtime available (this can be obtained using the following ant command on the jbpm full installer distribution)
C:\jbpm-installer>ant install.jBPM.runtime
Buildfile: build.xml
download.jBPM.bin.check:
[echo] Checking jBPM binaries download …
download.jBPM.bin:
install.jBPM.runtime:
[mkdir] Created dir: C:\jbpm-installer\runtime
[unzip] Expanding: C:\jbpm-installer\lib\jbpm-5.2.0.Final-bin.zip into C:\jbpm-installer\runtime
BUILD SUCCESSFUL
Now link your application with jBPM libraries which are found in the “lib” folder of the jBPM runtime and its dependencies (which can be located in the lib subfolder).
09/12/2011 10.55 17.904 jbpm-bam-5.2.0.Final.jar
09/12/2011 10.55 176.271 jbpm-bpmn2-5.2.0.Final.jar
09/12/2011 10.55 324.927 jbpm-flow-5.2.0.Final.jar
09/12/2011 10.55 135.693 jbpm-flow-builder-5.2.0.Final.jar
09/12/2011 10.55 276.296 jbpm-human-task-5.2.0.Final.jar
09/12/2011 10.55 20.434 jbpm-persistence-jpa-5.2.0.Final.jar
09/12/2011 10.55 22.012 jbpm-test-5.2.0.Final.jar
09/12/2011 10.55 44.324 jbpm-workitems-5.2.0.Final.jar
22/01/2012 12.25 <DIR> lib
Any feedback, suggestion is highly welcome !