Access Keys:
Skip to content (Access Key - 0)

JBPM Integration

Labels

jbpm jbpm Delete
appfuse appfuse Delete
jsf jsf Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

HOWTO: JBPM process engine + appfuse jsf modular

required ressources:

  • appfuse 2.0.2 jsf modular
  • jbpm 3.2.2
  • spring-modules jbpm

integration:

database

use JBPM ddl according to your database. I recommend to put jbpm-db-objects to another schema than your domain-db-objects. Easier for updates.

hibernate

<hibernate-configuration>
    <session-factory name="java:/jbpmSessionFactory">
    	
   		<mapping class="org.appfuse.model.User"/>
   		<mapping class="org.appfuse.model.Role"/>
...
ALL JBPM *.hbm.xml

give your sessionFactory a name - so you can reference it in JBPM-configuration

Copy the *.hbm.xml files to your classpath. I recommend a new maven module for it.

jbpm.cfg.xml

to your classpath: - jbpm-config

<jbpm-configuration>

  <jbpm-context>
    <service name="persistence">
       <factory>
          <bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
             <!-- let us handle transactions by aop or @Transactional; so we have to turn jbpm tx off -->
             <field name="isTransactionEnabled"><false/></field> 
            <field name="sessionFactoryJndiName">
            <!-- same as name of your hibernate session factory -->
              <string value="java:/jbpmSessionFactory" />
            </field>
          </bean>
       </factory>
    </service>
    <service name="tx" factory="org.jbpm.tx.TxServiceFactory" />
    <service name="message" factory="org.jbpm.msg.db.DbMessageServiceFactory" />
    <service name="scheduler" factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory" />
    <service name="logging" factory="org.jbpm.logging.db.DbLoggingServiceFactory" />
    <service name="authentication" factory="org.jbpm.security.authentication.DefaultAuthenticationServiceFactory" />
   
  </jbpm-context>
  <!-- lets resolve JSF variables in process diagrams -->
  <bean name='jbpm.variable.resolver' class='at.iqsoft.jbpm.el.JsfJbpmVariableResolver' singleton='true' />
</jbpm-configuration>

JSF-EL + JBPM

make sure EL expressions from JSF can be used in JBPM process diagrams:

package at.iqsoft.jbpm.el;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.appfuse.webapp.util.FacesUtils;
import org.jbpm.jpdl.el.ELException;
import org.jbpm.jpdl.el.impl.JbpmVariableResolver;

/** jbpm can resolve JSF variables with this
use: have in view something like #{myForm.name}
than you can use this in your process diagram */
public class JsfJbpmVariableResolver extends JbpmVariableResolver {
	private static final Log log = LogFactory.getLog(JbpmVariableResolver.class);
	public Object resolveVariable(String name) throws ELException {
                //let jbpm first try to resolve the variable
		Object value = super.resolveVariable(name);
		if (value == null) {
                        //jbpm dont know the var, lets see whether jsf (implicitly) spring can resolve
			if (log.isDebugEnabled()) {
				log.debug(String.format("resolving %s to: %s", name, value));
			}
			value = FacesUtils.getManagedBean(name);		
		}
		return value;
	}
}

*Note: this class is referenced in the jbpm.cfg.xml

jbpm + spring (spring-modules)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
       default-lazy-init="true">

  <!-- helper for reading jBPM process definitions -->
  <bean id="process-todo" 
      class="org.springmodules.workflow.jbpm31.definition.ProcessDefinitionFactoryBean">
    <property name="definitionLocation" 
      value="classpath:process-todo/processdefinition.xml"/>
  </bean>

  <!-- jBPM configuration -->
  <bean id="jbpmConfiguration" 
      class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean">
    <property name="sessionFactory" ref="sessionFactory"/>
    <property name="configuration" value="classpath:jbpm.cfg.xml"/>
    <property name="processDefinitions">
     <list>
      <ref local="process-todo"/>
     </list>
    </property>
    <property name="createSchema" value="false"/>
  </bean>

 <!-- jBPM template -->
 <bean id="jbpmTemplate" class="org.springmodules.workflow.jbpm31.JbpmTemplate">
  <constructor-arg index="0" ref="jbpmConfiguration"/>
  <constructor-arg index="1" ref="process-todo"/>
  <property name="hibernateTemplate" ref="hibernateTemplate"/>
 </bean>
</beans>

Note: I referenced a specific process definition
classpath:process-todo/processdefinition.xml
you can use mine (see below) or define your own with jbpm-eclipse-plugin

hibernate session in view

Make sure you have some open session in view filter active. I usually dont use since i fetch what i need. (web.xml)

simple sample

process definition

a simple workflow

<?xml version="1.0" encoding="UTF-8"?>
<process-definition 
   name="todo"
   xmlns="urn:jbpm.org:jpdl-3.2"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="urn:jbpm.org:jpdl-3.2 http://jbpm.org/xsd/jpdl-3.2.xsd"
   >
   <start-state name="start">
      <transition to="todo"></transition>
   </start-state>

   <task-node name="todo">
      <task name="Todo" blocking="true">
         <description>
            #{todoList.description}
         </description>
         <assignment pooled-actors="admin"></assignment>
         <controller></controller>
      </task>
      <transition to="end"></transition>
   </task-node>

   <end-state name="end"></end-state>
</process-definition>

In simple words: On starting a new process instance, a new TASK is generated. This task will be shown to all appfuse-admins (role admin). Every admin can assign the TASK to himself. Then the task-assignee can finish the task. After that the process will end.

The expression

#{todoList.description}

will be taken from JSF.

view (facelets)

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://java.sun.com/jstl/core" 
      xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:ui="http://java.sun.com/jsf/facelets" 
      xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich"
      xmlns:jstl="http://java.sun.com/jsp/jstl/functions"
      >

<f:view> 
<f:loadBundle var="text" basename="#{basePage.bundleName}"/> 
    <head> 

    </head> 
<body id="bdy">


   <c:set var="taskInstancePriorityList" value="#{todoList.taskInstancePriorityList}"/>
   <a4j:form id="task_list_personal">
      <div>     
         <h:outputText value="There are no todo items." rendered="#{empty taskInstancePriorityList}"/>
         <h2><h:outputText value="todo items." rendered="#{not empty taskInstancePriorityList}"/></h2>
         <h:dataTable value="#{taskInstancePriorityList}" var="task" rendered="#{not empty taskInstancePriorityList}">
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Description"/>
                </f:facet>
                <h:inputText value="#{task.description}" style="width: 400"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Created"/>
                </f:facet>
                <h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
                </h:outputText>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Priority"/>
                </f:facet>
                <h:inputText value="#{task.priority}" style="width: 30"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Due Date"/>
                </f:facet>
                <h:inputText value="#{task.dueDate}" style="width: 100">
                </h:inputText>
            </h:column>
            <h:column>
                <a4j:commandLink action="#{todoList.done}" value="Done" reRender="task_list_personal">
                  <f:param name="taskId" value="#{task.id}"/>
                </a4j:commandLink>
            </h:column>
         </h:dataTable>
      </div>
   </a4j:form>
   

   
  <br/> 
   

   
   <c:set var="pooledTaskInstanceList" value="#{todoList.pooledTaskInstanceList}"/>
   <a4j:form id="task_list_pooled">
      <div>     
         <h:outputText value="There are no assignable items." rendered="#{empty pooledTaskInstanceList}"/>
         <h2><h:outputText value="assignable items." rendered="#{not empty pooledTaskInstanceList}"/></h2>
         <h:dataTable value="#{pooledTaskInstanceList}" var="task" rendered="#{not empty pooledTaskInstanceList}">
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Description"/>
                </f:facet>
                <h:inputText value="#{task.description}" style="width: 400"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Created"/>
                </f:facet>
                <h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
                </h:outputText>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Priority"/>
                </f:facet>
                <h:inputText value="#{task.priority}" style="width: 30"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Due Date"/>
                </f:facet>
                <h:inputText value="#{task.dueDate}" style="width: 100">
                </h:inputText>
            </h:column>
            <h:column>
            <h:column>
                <a4j:commandLink action="#{todoList.assign}" value="Assign" reRender="task_list_personal,task_list_pooled">
                  <f:param name="taskId" value="#{task.id}"/>
                </a4j:commandLink>
            </h:column>
            </h:column>
         </h:dataTable>
      </div>
   </a4j:form>
   

   
   
   <a4j:form id="task_new">
      <div>
         <h:inputText value="#{todoList.description}" style="width: 400"/>
         <a4j:commandButton value="Create New Item" action="#{todoList.createTodo}" reRender="task_list_pooled"/>
      </div>
   </a4j:form>


</body> 
</f:view> 
</html> 

jsf controller (spring way)

package at.iqsoft.templtest.webapp.list; 

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.taskmgmt.exe.TaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springmodules.workflow.jbpm31.JbpmTemplate;

import at.iqsoft.jbpm.util.JbpmAppfuseUtil;
import at.iqsoft.templtest.webapp.action.IqsBasePage;

@SuppressWarnings("unchecked")
@Controller //spring bean
public class TodoList extends IqsBasePage { //my custom basepage
	private static final long serialVersionUID = 1L;
	@Autowired private JbpmTemplate jbpm; //like pityful jsf injection mech. but better
	
	private List<TaskInstance> taskInstancePriorityList; //tasks for current user
	private List<TaskInstance> pooledTaskInstanceList; //pooled tasks not yet assigned, but assignable to current user
	private String description; //field in UI for new tasks
	
	public TodoList() {}
	
	@Transactional //ok this is very unlike the appfuse way - i just like seam too ;-)
	public String done() {
		Long id = getParameterId("taskId");
		TaskInstance task = (TaskInstance) jbpm.getHibernateTemplate().get(TaskInstance.class, id);
		task.start();
		task.end();
		jbpm.getHibernateTemplate().save(task);
		//trigger view refresh
		taskInstancePriorityList = null;
		pooledTaskInstanceList = null;
		return null;
	}
        /** assign given task to current user */
	@Transactional
	public String assign() {
		Long id = getParameterId("taskId");
		TaskInstance task = (TaskInstance) jbpm.getHibernateTemplate().get(TaskInstance.class, id);
		task.setActorId(getCurrentUsername());
		jbpm.getHibernateTemplate().save(task);
		log.info(String.format("assigned task: %s to user: %s", task.getId(), getCurrentUsername()));
		//trigger view refresh
		taskInstancePriorityList = null;
		pooledTaskInstanceList = null;
		return null;
	}
	@Transactional
	public String createTodo() {
		ProcessDefinition pdef = jbpm.getProcessDefinition();
		Map<String, Object> params = new HashMap<String, Object>();
		Assert.isTrue(StringUtils.isNotEmpty(description), "no desc from ui");
		params.put("#{todoList.description}", description);
		ProcessInstance p = pdef.createProcessInstance(params);
		p.signal();
		jbpm.getHibernateTemplate().save(p); //
		log.info(String.format("new process %s started by user %s", pdef.getName(), getCurrentUsername()));
		//trigger view refresh
		taskInstancePriorityList = null;
		pooledTaskInstanceList = null;
		return null;
	}
	@Transactional
	public List<TaskInstance> getTaskInstancePriorityList() {
		if (taskInstancePriorityList == null) {
			//init
			taskInstancePriorityList = jbpm.findTaskInstances(getCurrentUsername());
		}
		return taskInstancePriorityList;
	}
	public void setTaskInstancePriorityList(
			List<TaskInstance> taskInstancePriorityList) {
		this.taskInstancePriorityList = taskInstancePriorityList;
	}
	@Transactional
	public List<TaskInstance> getPooledTaskInstanceList() {
		if (pooledTaskInstanceList == null) {
			pooledTaskInstanceList = jbpm.findPooledTaskInstances(JbpmAppfuseUtil.getActorAndGroupIds());
		}
		return pooledTaskInstanceList;
	}
	public void setPooledTaskInstanceList(List<TaskInstance> pooledTaskInstanceList) {
		this.pooledTaskInstanceList = pooledTaskInstanceList;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
}

some pity helper class to get all known usernames + roles for pooled actors

package at.iqsoft.jbpm.util;

import java.util.ArrayList;
import java.util.List;

import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.context.SecurityContextHolder;

public class JbpmAppfuseUtil {
	public static List<String> getActorAndGroupIds() {
		List<String> result = new ArrayList<String>();
		Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
		result.add(currentUser.getName());
		GrantedAuthority[] auths = currentUser.getAuthorities();
		for (int i = 0; i < auths.length; i++) {
			GrantedAuthority auth = auths[i];
			result.add(auth.getAuthority());
		}
		return result;
	}
}

thoughts about transaction + db object separation

Usually you have some domain tables + some jbpm tables.

  • I dont want to intermix them
  • I want transactional behaviour between jbpm + application-domain

possible solutions:

put jbpm tables to different schema in same database. example with postgres:

I put my domain-tables into public-schema and jbpm-tables to jbpm schema.
Then I alter all jbpm *.hbm.xml files with SEARCH&REPLACE over files (jedit) and prefix all table names with "jbpm." Same goes for Foreign Key references.

jbpm tables to different database than domain-app-tables

use 2PC and XA with some nice jta-application server like jboss. Or tomcat/jetty + jotm/... (jta-impl for "lean" app-servers)
I posted some working configuration for seam: http://www.seamframework.org/Community/TransactionQuestionInJbpmContextNoTransation

Adaptavist Theme Builder (4.0.0-M8) Powered by Atlassian Confluence 3.1, the Enterprise Wiki.