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