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