Version 6

    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);
            }
        }
    }