At the moment, Superstates do not receive a NodeLog in the log table when they are exited (while other nodes of business interest do) (jBPM 3.1, JIRA issue). This is a work-a-round to ensure that your Superstates create a NodeLog with entered and exited Dates when they exit. To accomplish the work-a-round, we take advantage of the 'superstate-enter' and 'superstate-leave' events.
An example process definition:
<?xml version="1.0" encoding="UTF-8"?> <process-definition name="SuperState Test"> <!-- Configuration for these Superstate event types will apply to all Superstates, even if the event configuration is located within a particular <super-state> node. --> <event type='superstate-enter'> <!-- Record a MessageLog to mark entry into each Superstate. The only purpose of this entry is to provide a start time for the Superstate that will be needed when we compose the NodeLog later. --> <action name='logSSEnter' class='com.???.SuperstateEnterLogAH' ></action> </event> <event type='superstate-leave'> <!-- Record a NodeLog that will include enter and leave dates for each Superstate. This ActionHandler requires that an appropriate MessageLog was recorded when this Superstate was entered. --> <action name='logSSLeave' class='com.???.SuperstateNodeLogAH' ></action> </event> <start-state name='START' > <transition name='done' to='SS_1/N1.1'></transition> </start-state> <super-state name="SS_1"> <node name='N1.1'> <transition name='done' to='SS_1.2/N1.2'></transition> </node> <super-state name="SS_1.2"> <node name='N1.2'> <transition name='done' to='SS_1.3/N1.3'></transition> </node> <super-state name="SS_1.3"> <node name='N1.3'> <transition name='done' to='/N999'></transition> </node> </super-state> </super-state> </super-state> <state name="N999"> <transition name='done' to='END'></transition> </state> <end-state name="END" ></end-state> </process-definition>
Here are the ActionHandlers that do the work.
The super simple SuperstateEnterLogAH code:
/* * SuperstateEnterLogAH.java */ package com.???; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.Token; import org.jbpm.logging.log.MessageLog; /** * @author brittm */ public class SuperstateEnterLogAH implements ActionHandler{ public Log log = LogFactory.getLog(this.getClass()); /** Creates a new instance of SuperstateEnterLogAH */ public SuperstateEnterLogAH() { } /*Execute the action handler*/ public void execute(ExecutionContext executionContext){ try{ Token tok = executionContext.getToken(); // Create a MessageLog indicating that we have entered this Superstate String msg = "SuperstateEntry: " + executionContext.getEventSource().getName(); MessageLog ml = new MessageLog(msg); ml.setToken(tok); executionContext.getProcessInstance().getLoggingInstance().addLog(ml); }catch(java.lang.Exception e){ log.error(e.getMessage(), e); } } }
...and the slightly more involved SuperstateNodeLogAH code:
/* * SuperstateNodeLogAH.java */ package com.???; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.def.Node; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.Token; import org.jbpm.graph.log.NodeLog; import org.jbpm.logging.log.MessageLog; import org.hibernate.*; import java.util.Date; import java.util.List; /** * @author brittm */ public class SuperstateNodeLogAH implements ActionHandler{ public Log log = LogFactory.getLog(this.getClass()); /** Creates a new instance of SuperstateNodeLogAH */ public SuperstateNodeLogAH() { } /*Execute the action handler*/ public void execute(ExecutionContext executionContext){ try{ Token tok = executionContext.getToken(); // Get the node that generated this action (in this case, it will be a Superstate) org.jbpm.graph.def.Node ssNode = (org.jbpm.graph.def.Node)executionContext.getEventSource(); log.debug("this node's full name is: '" + ssNode.getFullyQualifiedName() + "'"); // Look for a message log that was recorded when this Superstate was entered MessageLog ml = null; String msgLogContent = "SuperstateEntry: " + ssNode.getName(); log.debug("looking for: '" + msgLogContent + "'"); // Check the database first. If the process has been saved since we entered the // Superstate, the MessageLog will be here. org.hibernate.Session hSession = executionContext.getJbpmContext().getSession(); String hql = "FROM " + MessageLog.class.getName() + " AS ml WHERE ml.token = :token AND ml.message = :message ORDER BY ml.index"; Query query = hSession.createQuery(hql); query.setEntity("token", tok); query.setString("message", "SuperstateEntry: " + ssNode.getName()); List instances = query.list(); if(instances.size() != 0) { log.debug("got a match from the database."); ml = (MessageLog)instances.get(instances.size() - 1); }else{ // If a matching MessageLog is not found in the log table, try looking in the // LoggingInstance (where new logs are held until they are persisted to the // log table during a jbpmContext.save(processInstance) ). List pendingLogs = tok.getProcessInstance().getLoggingInstance().getLogs(MessageLog.class); for(Object o : pendingLogs) { MessageLog potentialMl = (MessageLog)o; if(msgLogContent.equals(potentialMl.getMessage())) { log.debug("got a match from the LoggingInstance."); ml = potentialMl; } } // If no MessageLog was found either in the table or in the LoggingInstance, // something is wrong. Perhaps we didn't configure SuperstateEnterLogAH to run // on the superstate-enter event. if(ml == null) { throw new Exception("No MessageLog found documenting entry into Superstate '" + ssNode.getFullyQualifiedName() + "'"); } } // Create the new NodeLog NodeLog nl = new NodeLog(ssNode, ml.getDate(), new Date()); nl.setToken(tok); executionContext.getProcessInstance().getLoggingInstance().addLog(nl); }catch(java.lang.Exception e){ log.error(e.getMessage(), e); } } }
Comments