Hello everyone,
 
This is my first post concerning Commons SCXML so please feel free to
provide suggestions on the semantics of my post, the question itself, or
the design I have chosen. Apologies in advance for the long e-mail!
 
Introduction
 
What I want to do is to have an SCXML document that at various points
calls out to a Java class to perform internal things. Now I expect these
SCXML documents to be exposed to my customers so the use of
AbstractStateMachine is a bit limiting because of its requirements to
have method names match state names and some additional weird behavior
with attempting to throw events from within those methods. So I have
chosen to use <invoke> with a custom target type of "java" and my own
Invoker class. This works very well for me because it allows me to have
a single invoker class that can then act as my entrance point to other
code which can perform arrange of different activities, based on which
state it is in.
 
Another function of my project is that the custom code that is executed
by the invoke sends out messages. I then have registered message handler
classes that are activated when I get a response message, but in
separate threads spawned by a message handling thread pool. It is this
message handler class that will then have to trigger SCXML events that
cause the state machine to progress. In other words, my program has one
thread that sets the state engine going, which enters the invoke and
runs the invoke method of my invoker, but that invoke cannot trigger an
event until I receive an asynchronous message. So the invoke method does
not trigger any events within it and the state machine sits and waits.
Then a separate thread activates the message handler, which then gets
access to the SCXMLExecutor class and triggers an event which causes the
special done event to be triggered for the invoke and the state machine
to continue. This actually works fine, but I am seeing some weird
behavior from within the Commons SCXML library that I am not sure
whether is an indication that I am doing something wrong or an issue
within the library.
 
I will include a small class and the SCXML that I am using as an example
of what I am doing. I will also include the output that I see and cannot
explain.
 
SCXML Document
 
<scxml xmlns="http://www.w3.org/2005/07/scxml"; version="1.0"
initialstate="step1">
    <state id="step1">
  <invoke targettype="java" src="foo"/>
        <transition event="step1.invoke.done" target="step2"/>
    </state>
    <state id="step2">
  <invoke targettype="java" src="bar"/>
        <transition event="step2.invoke.done" target="step3"/>
    </state>
    <state id="step3" final="true"/>
</scxml>
 
The idea here is that there are three steps in the document. The first
step invokes "foo", which simply returns the step1.invoke.done special
event immediately because it does what it needs to do at once. That
prompts the state engine to enter state "step2", which invokes "bar".
This time, the invoke method does not trigger an event. When the program
started I spawn a separate thread that sleeps for 5s to simulate the
message handler coming in asynchronously. When the thread wakes up, the
state machine should be waiting in step2. The asynchronous thread
triggers an event that is supposedto be monitored by the parentEvents
method of the invoker. I at this point the invoker triggers the
"step2.invoke.done" event to end that invoke. The state machine then
goes to state 3 and ends. I put print statements at various points in
the code to track what is being done and without the print statements
everything looks to work correctly, though with the print statements it
seems to be doing it in a convoluted way.
 
Java Code
 
import java.net.URL;
import java.util.Map;
 
import org.apache.commons.scxml.SCInstance;
import org.apache.commons.scxml.SCXMLExecutor;
import org.apache.commons.scxml.TriggerEvent;
import org.apache.commons.scxml.env.SimpleDispatcher;
import org.apache.commons.scxml.env.SimpleErrorHandler;
import org.apache.commons.scxml.env.SimpleErrorReporter;
import org.apache.commons.scxml.env.jexl.JexlContext;
import org.apache.commons.scxml.env.jexl.JexlEvaluator;
import org.apache.commons.scxml.invoke.Invoker;
import org.apache.commons.scxml.invoke.InvokerException;
import org.apache.commons.scxml.io.SCXMLDigester;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.model.SCXML;

public class SCXMLInvokeTest {
    SCXMLExecutor exec;
 
 public SCXMLInvokeTest() {
     URL invoke01;
     
        try {
         AsynchronousMessage mes = new AsynchronousMessage();
         mes.start();
         
         invoke01 =
this.getClass().getClassLoader().getResource("myscxml.xml");
            SCXML scxml = SCXMLDigester.digest(invoke01, new
SimpleErrorHandler());
            exec = new SCXMLExecutor(new JexlEvaluator(), new
SimpleDispatcher(), new SimpleErrorReporter());
            exec.setRootContext(new JexlContext());
            exec.setStateMachine(scxml);
            exec.registerInvokerClass("java", JavaInvoker.class);
            exec.go();
        } catch (Exception e) {
            e.printStackTrace();
        }
 }
    
 public static void main(String[] args) {
  new SCXMLInvokeTest();
 }
 
 public static class JavaInvoker implements Invoker {
     private String parentStateId;
     private SCInstance parentSCInstance;
     
     public void setParentStateId(final String parentStateId) {
         this.parentStateId = parentStateId;
     }
     public void setSCInstance(final SCInstance scInstance) {
         this.parentSCInstance = scInstance;
     }
     public void invoke(final String source, final Map params) throws
InvokerException {
      System.out.println("Source = " + source);
         try {
       if (source.contains("foo")) {
        System.out.println("Invoking Foo. Sending special done event.");
           parentSCInstance.getExecutor().triggerEvent(new
TriggerEvent(parentStateId + ".invoke.done",
TriggerEvent.SIGNAL_EVENT));
       } else if (source.contains("bar")) {
        System.out.println("Invoking Bar, Part 1. We are not done so we
send back no events.");
       }
         } catch (ModelException me) {
             throw new InvokerException(me.getMessage(), me.getCause());
         }
      System.out.println("Done with Invoke");
     }
  public void parentEvents(TriggerEvent[] events) throws
InvokerException {
   System.out.println("In parentEvents for ID " + parentStateId + 
     ". There are " + events.length + " events. " +
     "First event name = " + events[0].getName());
   if (events[0].getName().equals("subsequent") &&
parentStateId.equals("step2")) {
          try {
        System.out.println("Invoking Bar, Part 2. We are now done so we
send the special done event.");
           parentSCInstance.getExecutor().triggerEvent(new
TriggerEvent(parentStateId + ".invoke.done",
TriggerEvent.SIGNAL_EVENT));
          } catch (ModelException me) {
              throw new InvokerException(me.getMessage(),
me.getCause());
          }
   }
  }
     public void cancel() throws InvokerException {
      System.out.println("In Cancel for ID " + parentStateId);
     }
 }
 
 private class AsynchronousMessage extends Thread {
  public void run() {
   try {
    Thread.sleep(5000);
    System.out.println("Triggering the event for Bar Part 2 from an
outside source.");
    TriggerEvent event = new TriggerEvent("subsequent",
TriggerEvent.SIGNAL_EVENT, null);
    exec.triggerEvent(event);
   } catch (Throwable t) {
    t.printStackTrace();
   }
  }
 }
}

Output that I see
 
Source =
file:/Users/elam/Development/Samba_ebirnbau/vob/scxmlTesting/bin/foo
Invoking Foo. Sending special done event.
Source =
file:/Users/elam/Development/Samba_ebirnbau/vob/scxmlTesting/bin/bar
Invoking Bar, Part 1. We are not done so we send back no events.
Done with Invoke.
Feb 2, 2007 5:12:31 PM org.apache.commons.scxml.SCXMLExecutor logState
INFO: Current States: [step2]
Done with Invoke.
Feb 2, 2007 5:12:31 PM org.apache.commons.scxml.SCXMLExecutor logState
INFO: Current States: [step2]
Triggering the event for Bar Part 2 from an outside source.
In parentEvents for ID step1. There are 1 events. First event name =
subsequent
In parentEvents for ID step2. There are 1 events. First event name =
subsequent
Invoking Bar, Part 2. We are now done so we send the special done event.
In parentEvents for ID step1. There are 1 events. First event name =
step2.invoke.done
In Cancel for ID step2
Feb 2, 2007 5:12:35 PM org.apache.commons.scxml.SCXMLExecutor logState
INFO: Current States: [step3]
Feb 2, 2007 5:12:35 PM org.apache.commons.scxml.SCXMLExecutor logState
INFO: Current States: [step3]
 
Comments
 
So if you look at the output you can see the first step works fine, the
invoke is entered and triggers the special done event. Without exiting
the first step's invoke method, the invoke for step 2 is then called,
which just prints out to the screen and returns. You can see that at
this point step2's invoke method ends, the current state is set to step2
(because it was not ended with the special done event), then step1's
invoke is done.
 
5 seconds later, the separate thread triggers an event with the name
"subsequent". This runs the parentEvents method as expected however you
can see it calls this method on both step1 and step2's invoker instance
even though step 1 should be complete by now. Since I added an if
statement in this method to activate the special done event only when in
step2's invoker, only when that method is called that the special done
event for step2 is triggered.
 
More weird behavior. The parentEvents method is called again, despite
the fact that this is the special done event that you would think should
not itself trigger this method (before I added the if statement in the
parentsEvents I got an infinite loop because of this!). What's even
weirder is that the event triggers the parentEvents method of step1, not
step2 (which is consistent with how it behaved before, where the special
done event does not trigger parentEvents on its own invoker instance) 
 
Then, finally, it calls the cancel method for step2 even though that
step properly triggered its done event and should have completed
normally.
 
In the end, the final state is the correct one, the invokes worked
correctly and despite all the extra triggers, the events were triggered
correctly and the transitions were followed correctly to step3, the
final state.
 
Questions
 
So after all this, there are some questions I have:
 
1) Is it correct behavior to have the parentEvents method triggered on
all invokes, even ones for states that have completed? Meaning I should
possibly add a payload to the event to identify which invoke it is meant
for and filter the handling within the parentEvent method? Or maybe is
there a different way to trigger an event on just a single state?
 
2) Should the special done event trigger the parentEvents method to be
called on either it or any other invoker instance or should this special
event simply be handled internally as one that signifies that the invoke
is done and to move on?
 
3) Could it be possible that #1 and #2 are related to a bug in Commons
SCXML that does not properly remove an invoker instance when the state
in which the invoker resides completes? If that was done, then the
situations described above would not happen, right? I have only been
looking at things for a couple of weeks so I don't know if I am missing
something in the design that requires things to be this way and I am
simply not using the library correctly...
 
4) Why would the cancel method be called in this scenario? Does this
mean that even though the transitions are followed correctly, the
history maintained by the system does not consider that step to have
been completed, or is this just a trigger of a method in the invoker and
just ignoring it causes no issues?
 
Apologies for the long e-mail, but this is a very specific situation
that I needed to provide a lot of introduction in order for my questions
to make sense!
 
Thanks,
 
Elam Birnbaum
Cisco

Reply via email to