JBPM 3 web application example

User Rating: 4 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Inactive
 

An article about JBPM in a web application has been often requested in the forums and in this site too. Let's see how JBoss process engine fits in a web application.


Probably the thing which is most misunderstood by people approaching JBPM is -what is JBPM - ? a tool / framework / a server application ? when I approached a JBPM course in London 2 years ago our coach made this clear in the first 5 minutes of the course. JBPM is just a Java library wrapping -with Hibernate- a DB schema.

Of course in these classes there is the logic to interact with the DB schema (which contains all process data); however just being a set of Java classes you can deploy them  in every environment: EJB, web application. Even in another application server, which supports Hibernate.

Configuring JBPM in a web application

This said, let's see how to use JBPM in a web application. The only requirement is to make visible to the Web application the required files:
jbpm web application
As you can see at first you need to publish the hibernate configuration file: hibernate.cfg.xml. In the config folder of JBPM installation you can find hibernate configuration samples for every DB.

Then you need to add also the process definition file if you plan to deploy/redeploy your process from the web application.

Optionally you can add also jbpm.cfg.xml which contains additional configuration for JBPM (for example if you need to send mail from JBPM you should use this file to configure the mail server)

The second piece of information is the JBPM libraries which will be added in the "lib" folder of your application.

Dealing with the JBPM Context 

A JbpmContext separates jBPM from a sprecific environment. For each service that jBPM uses, there is an interface specified in the jBPM codebase.

A JbpmContext can also demarcate a transaction. When a PersistenceService is fetched from the JbpmContext, the default implementation for the persistence service will create a hibernate session and start a transaction. So that transactions can be configured in the hibernate configuration.

Last but not least, JbpmContext provides convenient access to the most common operations such as getTaskList(String), newProcessInstance(String) loadTaskInstanceForUpdate(long) and save(ProcessInstance). 

If you want to access the JBPM Context you must however follow the following paradigma:

jbpm web application
That is, you create the JBPM Context at every request, perform the necessary operation with JBPM, then close the JBPM Context when you have finished.

Don't worry: opening and closing the JBPM Context hasn't got any performance issue. What could a be an overkill is re-creating the JBPMConfiguration. Since this Object is Thread safe, you can create it once and store in a static field.

A sample JBPM web application

So here's a very simple web application made up of a Servlet Controller and a DAO component which interacts with JBPM. In this application the user is allowed to deploy a process, start new instances and see the process list.

package com.sample;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sample.dao.JBPMDao;

public class JBPMServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static String processName;

     public void init() throws ServletException {
           
            // Get the value of an initialization parameter
            processName = getServletConfig().getInitParameter("processName");                
     }

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = new PrintWriter(response.getOutputStream());

        JBPMDao dao = new JBPMDao();
        String html = null;
        String action = request.getParameter("action");
        
        if (action.equals("deploy")) {
            html = dao.redeployProcess("processdefinition.xml");
        } else if (action.equals("start")) {
            html = dao.startNewProcessInstance(processName);
        } else if (action.equals("list")) {
            html = dao.readProcessInstances(processName);
        } else if (action.equals("signal")) {
            html = dao.signalProcess(processName, request.getParameter("id"));
        }

        out.println(html);
        out.println("<br />");
        out.println("<a href='index.jsp'>Back</a>");
        out.close();
    }

}

The process definition file can be any process definition file you like, just insert into web.xml the processName parameter so it's available to the Servlet.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
 <display-name>JBPMWeb</display-name>
 <servlet>
  <servlet-name>JBPMServlet</servlet-name>
  <servlet-class>com.sample.JBPMServlet</servlet-class>
     <init-param>
        <param-name>processName</param-name>
        <param-value>simple</param-value>
     </init-param>
 </servlet>
 <servlet-mapping>
  <servlet-name>JBPMServlet</servlet-name>
  <url-pattern>/jbpmServlet</url-pattern>
 </servlet-mapping>

</web-app>

JBPMDao contains a set of methods to interact with JBPM and a Decorator Class to output the list of processes. ( This web application is much 90's programming style :-) however should give the idea how to work with JBPM in a web context)

package com.sample.dao;

import java.util.List;

import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;

import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.db.GraphSession;

public class JBPMDao {
    private static JbpmConfiguration jbpmConfiguration = JbpmConfiguration
            .getInstance();

    public String redeployProcess(String processName) {
        JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
        try {
            ProcessDefinition processDefinition = ProcessDefinition
                    .parseXmlResource(processName);
            jbpmContext.deployProcessDefinition(processDefinition);

            return "Process " + processDefinition.getName() + " redeployed.";
        } finally {
            jbpmContext.close();
        }
    }

    public String startNewProcessInstance(String processDefinitionName) {

        JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
        try {
            GraphSession graphSession = jbpmContext.getGraphSession();
            ProcessDefinition definition = graphSession
                    .findLatestProcessDefinition(processDefinitionName);
            ProcessInstance instance = definition.createProcessInstance();
            long id = instance.getId();
            jbpmContext.save(instance);

            return "Started process instance: " + id;
        } finally {
            jbpmContext.close();
        }
    }

    public String readProcessInstances(String processDefinitionName) {

        JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();

        try {
            GraphSession graphSession = jbpmContext.getGraphSession();
            ProcessDefinition processDefinition = graphSession
                    .findLatestProcessDefinition(processDefinitionName);

            List processInstances = jbpmContext.getGraphSession()
                    .findProcessInstances(processDefinition.getId());

            String html = ProcessDecorator.drawList(processInstances);

            return html;
        } finally {
            jbpmContext.close();
        }
    }

    public String signalProcess(String processDefinitionName, String id) {

        JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
        try {

            long processId = Long.parseLong(id);

            ProcessInstance instance = jbpmContext
                    .loadProcessInstance(processId);
            instance.signal();

            String node = instance.getRootToken().getNode().getName();
            StringBuffer sbHtml = new StringBuffer();
            sbHtml.append("Process ");
            sbHtml.append(processId);
            sbHtml.append(" just moved to Node " + node);
            jbpmContext.save(instance);
            return sbHtml.toString();
        } finally {
            jbpmContext.close();
        }
    }

    static class ProcessDecorator {
        private static final String SIGNAL = "jbpmServlet?action=signal";
        private static final String BR = "<br />";

        static String drawList(List processInstances) {
            StringBuffer sbHtml = new StringBuffer();
            sbHtml.append("Process instances");
            sbHtml.append("<hr>");

            for (int ii = 0; ii < processInstances.size(); ii++) {
                ProcessInstance processInstance = (ProcessInstance) processInstances
                        .get(ii);
                // We don't display ended processes
                if (processInstance.hasEnded())
                    continue;

                String nodeName = processInstance.getRootToken().getNode()
                        .getName();
                String processId = String.valueOf(processInstance.getId());

                sbHtml.append("Id              : " + processId);
                sbHtml.append("  Current Node  : <b>" + nodeName + "</b>");
                sbHtml.append("  Start time    : "
                                + processInstance.getStart());
                sbHtml.append("  "
                        + createLink(SIGNAL, "id", processId, "Signal") + BR);

            }
            sbHtml.append("<HR>");
            return sbHtml.toString();
        }

        private static String createLink(String link, String param,
                String value, String description) {
            return "<a href="/ + link + "&" + param + "=" + value + ">"
                    + description + "</a>";
        }
    }
}

This is the bare bones index.jsp which acts as start menu for this application

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<h1>JBPM web example</h1>
<body>
<a href="/jbpmServlet?action=deploy">Deploy  Process</a><br />
<a href="/jbpmServlet?action=start">Start new Process</a><br />
<a href="/jbpmServlet?action=list">Process List</a>
</body>
</html>

Once launched simply deploy the processdefinition file and start creating new process instances which can be advanced with the Signal link.

Download code for this article

 

Related articles available on mastertheboss.com

JBPM tutorial

JBoss jBPM 3 is a flexible, extensible framework for process lang

How do I fire an action every time a JBPM Node enters?

JBoss recipe of the day

JBoss Jbpm superstates

What is a superstate ? a Superstate is a group of nodes. Supersta

Jbpm Mail delivery

Almost every workflow needs a notification of the process activit

How do you embed Java code in your JPDL ?

  You can use a BeanShell expression to add a Java script in you

JBPM best practices

There's no perfect rule to model your workflow, it depends on the