Using JBPM for workflow

JBPM terminology -> process-definition, node, state, transition, process instance, process instance id, actionhandler.

This example demostrates the use of JBPM as a workflow engine. I have used the jars in the package jbpm-jpdl-suite-3.2.3.zip available on the sourceforge site. In addition to this you will need the mysql jar containing the mysql jdbc driver.
I have used a standalone example to demonstrate the use of jbpm while in real practise it would require a full fledged GUI where state changes in ItemRequest would take place due to user actions. The various states that a RequestItem object can go into is shown in the diagram below and described in processdefinition.xml as a workflow.

processdefinition.xml



	
		
	
	
		
			
				Created
			
			
				Created
			
		
		
	
	
		
			
				Submitted
			
			
				Submitted
			
		
		
		
	
	
		
			
				Approved
			
			
				Approved
			
		
		
		
		
	
	
		
			
				Rejected
			
			
				Rejected
			
		
		
	
	
		
			
				Cancelled
			
			
				Cancelled
			
		
		
	
	
		
			
				Archived
			
			
				Archived
			
		
	

JBPM requires an initial setup in the database. JBPM tables are created in the mysql table when the following line(commented) in the main class is executed.

new JBPMTest().setupSchema();//run this only once for setup
This is a one time execution and should be run only once or whenever there is a change to processdefinition.xml.
Database jbpmappdb need to be created in mysql before running the above line.

The main class below:

package com.mattiz.jbpm;

import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.graph.def.ProcessDefinition;
import com.mattiz.jbpm.dto.ItemRequest;
import com.mattiz.jbpm.handler.CounterHandler;

public class JBPMTest {
	public static void main(String args[]) {
		// new JBPMTest().setupSchema();//run this only once for setup
		new JBPMTest().testHelloWorldProcess();
	}

	public void testHelloWorldProcess() {
		ItemRequestController ic = new ItemRequestController();
		ItemRequest itemRequest = new ItemRequest();
		itemRequest.setId("1");
		itemRequest.setName("My Item Request");
		itemRequest.setStatus("Submitted");
		ic.createItemRequest(itemRequest);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		String oldStatus = itemRequest.getStatus();
		itemRequest.setStatus("Approved");
		ic.updateItemRequest(itemRequest, oldStatus);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		oldStatus = itemRequest.getStatus();
		itemRequest.setStatus("Cancelled");
		ic.updateItemRequest(itemRequest, oldStatus);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		System.out.println("===================================");

		itemRequest = new ItemRequest();
		itemRequest.setId("2");
		itemRequest.setName("My Second Item Request");
		itemRequest.setStatus("Submitted");
		ic = new ItemRequestController();
		ic.createItemRequest(itemRequest);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		oldStatus = itemRequest.getStatus();
		itemRequest.setStatus("Approved");
		ic.updateItemRequest(itemRequest, oldStatus);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		oldStatus = itemRequest.getStatus();
		itemRequest.setStatus("Rejected");
		ic.updateItemRequest(itemRequest, oldStatus);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		System.out.println("===================================");

		itemRequest = new ItemRequest();
		itemRequest.setId("3");
		itemRequest.setName("My Third Item Request");
		itemRequest.setStatus("Submitted");
		ic = new ItemRequestController();
		ic.createItemRequest(itemRequest);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		oldStatus = itemRequest.getStatus();
		itemRequest.setStatus("Approved");
		ic.updateItemRequest(itemRequest, oldStatus);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		System.out.println("===================================");

		itemRequest = new ItemRequest();
		itemRequest.setId("4");
		itemRequest.setName("My Fourth Item Request");
		itemRequest.setStatus("Submitted");
		ic = new ItemRequestController();
		ic.createItemRequest(itemRequest);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		oldStatus = itemRequest.getStatus();
		itemRequest.setStatus("Approved");
		ic.updateItemRequest(itemRequest, oldStatus);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		oldStatus = itemRequest.getStatus();
		itemRequest.setStatus("Rejected");
		ic.updateItemRequest(itemRequest, oldStatus);
		System.out.println("PENDING  "
				+ CounterHandler.itemRequestsPendingCounter);
		System.out.println("CONFIRMED "
				+ CounterHandler.itemRequestsConfirmedCounter);
		System.out.println("===================================");
		System.out.println("===================================");
	}

	public void setupSchema() {
		JbpmConfiguration config = JbpmConfiguration
				.parseResource("jbpm.cfg.xml");
		ProcessDefinition processDefinition = ProcessDefinition
				.parseXmlResource("processdefinition.xml");
		// create schema.
		config.createSchema();
		JbpmContext context = config.createJbpmContext();
		// deploy process definition.
		try {
			context.deployProcessDefinition(processDefinition);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			context.close();
		}
	}
}

The hibernate configuration file hibernate.cfg.xml below:






	
		<!-- JDBC connection properties -->
		
			org.hibernate.dialect.MySQLDialect
		
		
			com.mysql.jdbc.Driver
		
		
			jdbc:mysql://localhost:3306/jbpmappdb
		
		admin
		admin
		<!-- JDBC connection properties (end) -->

		<!-- Simple memory-only cache -->
		
			org.hibernate.cache.HashtableCacheProvider
		

		<!-- logging properties -->
		true
		true

		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		

		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
	

The jbpm configuration file jbpm.cfg.xml below:


	
		
		<!-- Logging Service (begin) -->
		
		<!-- Logging Service (end) -->
		
		
		
		
	

	

	<!-- configuration resource files pointing to default configuration files
		in jbpm-{version}.jar -->
	
	
	
	
	
	
	

	<!-- make sure the block size matches the length in ByteArray.hbm.xml -->
	
	
	
	
	
	

	
		
			
		
		
			
		
		
			
		
		
			
		
		
			
		
		
			
		
		
			
		
		
			
		
		
			
		
	

The domain class is ItemRequest.java

package com.mattiz.jbpm.dto;

public class ItemRequest {
	private String id;
	private String name;
	private String status;
	/** JBPM mapping. */
	private Long processInstanceId;

	public Long getProcessInstanceId() {
		return processInstanceId;
	}

	public void setProcessInstanceId(Long processInstanceId) {
		this.processInstanceId = processInstanceId;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getStatus() {
		return status;
	}

	public void setStatus(String status) {
		this.status = status;
	}
}

Note that it defines a variable processInstanceId required for JBPM.

The class that makes JBPM work below:

package com.mattiz.jbpm.workflow;

import java.util.ArrayList;
import java.util.List;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.JbpmException;
import org.jbpm.graph.def.Transition;
import org.jbpm.graph.exe.ProcessInstance;
import com.mattiz.jbpm.dto.ItemRequest;

public class JBPMWorkFlow {
	JbpmConfiguration config = JbpmConfiguration.parseResource("jbpm.cfg.xml");

	public Long createItemRequestFlow(ItemRequest ItemRequest) {
		JbpmContext context = config.createJbpmContext();
		ProcessInstance pi = null;
		try {
			pi = context.newProcessInstance("Mattiz_Process_Flow");
			pi.getContextInstance().setVariable("ItemRequestId",
					ItemRequest.getId());
			pi.signal();
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			context.close();
		}
		return pi.getId();
	}

	public List makeTransition(Long processInstanceId,
            String transitionName) throws RuntimeException {
        JbpmContext context = config.createJbpmContext();
        List transitionNames = new ArrayList();
        try {
            ProcessInstance pi = context.getProcessInstance(processInstanceId);
            List leavingTransitions = pi.getRootToken().getNode()
                    .getLeavingTransitions();
            for (Transition t : leavingTransitions) {
                transitionNames.add(t.getName());
            }
            System.out.println("Choices are " + leavingTransitions);
            System.out.println("ACTION IS--->" + transitionName);
            pi.signal(transitionName);
        } catch (JbpmException e) {
            throw new RuntimeException(e);
        } finally {
            context.close();
        }
        return transitionNames;
    }

	public void makeCounterChanges(Long processInstanceId, String oldStatus)
			throws RuntimeException {
		JbpmContext context = config.createJbpmContext();
		try {
			ProcessInstance pi = context.getProcessInstance(processInstanceId);
			pi.getContextInstance().setVariable("oldStatus", oldStatus);
		} catch (JbpmException e) {
			throw new RuntimeException();
		} finally {
			context.close();
		}
	}
}

We define two JBPM handler classes.

CounterHandler keeps track of the number of ItemRequest objects in submitted state and in the approved state using two static counters whose values are printed out in the main class whenever a state change occurs.

package com.mattiz.jbpm.handler;

import java.util.HashMap;
import java.util.Map;
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;

public class CounterHandler implements ActionHandler {
	private static Map actionForStatusChange = new HashMap();
	public static int itemRequestsPendingCounter;
	public static int itemRequestsConfirmedCounter;
	// status is passed on implicitly from the processdefinition xml
	private String status;
	static {
		actionForStatusChange.put("NEW" + "_" + "Submitted",
				"addToItemRequestsPending");
		actionForStatusChange.put("Submitted" + "_" + "Approved",
				"addToItemRequestsConfirmedAndSubtractFromItemRequestsPending");
		actionForStatusChange.put("Approved" + "_" + "Rejected",
				"subtractFromItemRequestsConfirmed");
		actionForStatusChange.put("Submitted" + "_" + "Rejected",
				"subtractFromItemRequestsPending");
		actionForStatusChange.put("Approved" + "_" + "Cancelled",
				"subtractFromItemRequestsConfirmed");
	}

	public void execute(ExecutionContext executionContext) throws Exception {
		String oldStatus = (String) executionContext.getContextInstance()
				.getVariable("oldStatus");
		if (oldStatus == null) {
			oldStatus = "NEW";
		}
		String action = actionForStatusChange.get(oldStatus + "_" + status);
		if (null != action) {
			if (action.equals("addToItemRequestsPending")) {
				itemRequestsPendingCounter++;
			} else if (action.equals("addToItemRequestsConfirmed")) {
				itemRequestsConfirmedCounter++;
			} else if (action.equals("subtractFromItemRequestsConfirmed")) {
				itemRequestsConfirmedCounter--;
			} else if (action.equals("subtractFromItemRequestsPending")) {
				itemRequestsPendingCounter--;
			} else if (action
					.equals("addToItemRequestsConfirmedAndSubtractFromItemRequestsPending")) {
				itemRequestsPendingCounter--;
				itemRequestsConfirmedCounter++;
			} else if (action.equals("subtractFromItemRequestsPending")) {
				itemRequestsPendingCounter--;
			}
		}
	}
}

StatusHandler is also a listener which is triggered whenever status change occurs. In this app it does not do anything, but in a real app can be used to update the status of ItemRequest in the database.


package com.mattiz.jbpm.handler;

import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;

public class StatusHandler implements ActionHandler {
	private String status;

	public void execute(ExecutionContext executionContext) throws Exception {
		String itemRequestId = (String) executionContext.getContextInstance()
				.getVariable("ItemRequestId");
		System.out.println("Item " + itemRequestId);
		executionContext.getContextInstance().setVariable("status", status);
		// do something important like update the status in the database using
		// the itemRequestId
	}
}

ItemRequestController is the interface to the frontend and calls methods in the workflow service class:

package com.mattiz.jbpm;

import java.util.List;
import com.mattiz.jbpm.dto.ItemRequest;
import com.mattiz.jbpm.workflow.JBPMWorkFlow;

public class ItemRequestController {
    JBPMWorkFlow workflowService = new JBPMWorkFlow();

    public List createItemRequest(ItemRequest itemRequest) {
        Long processInstanceId = workflowService
                .createItemRequestFlow(itemRequest);
        itemRequest.setProcessInstanceId(processInstanceId);
        workflowService.makeCounterChanges(processInstanceId, null);
        List transitions = workflowService.makeTransition(
                itemRequest.getProcessInstanceId(), itemRequest.getStatus());
        return transitions;
    }

    public List updateItemRequest(ItemRequest itemRequest,
            String oldStatus) {
        Long processInstanceId = itemRequest.getProcessInstanceId();
        workflowService.makeCounterChanges(processInstanceId, oldStatus);
        List transitions = workflowService.makeTransition(
                processInstanceId, itemRequest.getStatus());
        return transitions;
    }
}

The output of the main class looks something like this:

Choices are [Transition(Submitted)]
ACTION IS--->Submitted
Item 1
PENDING 1
CONFIRMED 0
===================================
Choices are [Transition(Approved), Transition(Rejected)]
ACTION IS--->Approved
Item 1
PENDING 0
CONFIRMED 1
===================================
Choices are [Transition(Cancelled), Transition(Rejected), Transition(Archived)]
ACTION IS--->Cancelled
Item 1
PENDING 0
CONFIRMED 0
===================================
===================================
Choices are [Transition(Submitted)]
ACTION IS--->Submitted
Item 2
PENDING 1
CONFIRMED 0
===================================
Choices are [Transition(Approved), Transition(Rejected)]
ACTION IS--->Approved
Item 2
PENDING 0
CONFIRMED 1
===================================
Choices are [Transition(Cancelled), Transition(Rejected), Transition(Archived)]
ACTION IS--->Rejected
Item 2
PENDING 0
CONFIRMED 0
===================================
===================================
Choices are [Transition(Submitted)]
ACTION IS--->Submitted
Item 3
PENDING 1
CONFIRMED 0
===================================
Choices are [Transition(Approved), Transition(Rejected)]
ACTION IS--->Approved
Item 3
PENDING 0
CONFIRMED 1
===================================
===================================
Choices are [Transition(Submitted)]
ACTION IS--->Submitted
Item 4
PENDING 1
CONFIRMED 1
===================================
Choices are [Transition(Approved), Transition(Rejected)]
ACTION IS--->Approved
Item 4
PENDING 0
CONFIRMED 2
===================================
Choices are [Transition(Cancelled), Transition(Rejected), Transition(Archived)]
ACTION IS--->Rejected
Item 4
PENDING 0
CONFIRMED 1
===================================
===================================

Download the full app here

About cuppajavamattiz
Matty Jacob - Avid technical blogger with interests in J2EE, Web Application Servers, Web frameworks, Open source libraries, Relational Databases, Web Services, Source control repositories, ETL, IDE Tools and related technologies.

Comments are closed.

%d bloggers like this: