Author: ate Date: Wed Mar 26 23:21:17 2014 New Revision: 1582115 URL: http://svn.apache.org/r1582115 Log: SCXML-200: adding support for multiple <invoke> elements in a <state> or <parallel> instead of just one. Note: this is primarily a model update only. Semantically and process-wise the <invoke> handling is not (by far) in line with the specification yet.
Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCInstance.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Invoke.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionTarget.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionalState.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/semantics/SCXMLSemanticsImpl.java Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCInstance.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCInstance.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCInstance.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCInstance.java Wed Mar 26 23:21:17 2014 @@ -22,12 +22,14 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.apache.commons.scxml2.invoke.Invoker; import org.apache.commons.scxml2.invoke.InvokerException; import org.apache.commons.scxml2.model.Datamodel; import org.apache.commons.scxml2.model.EnterableState; import org.apache.commons.scxml2.model.History; +import org.apache.commons.scxml2.model.Invoke; import org.apache.commons.scxml2.model.TransitionTarget; import org.apache.commons.scxml2.model.TransitionalState; @@ -72,11 +74,12 @@ public class SCInstance implements Seria */ private final Map<String, Class<? extends Invoker>> invokerClasses; + private final Map<Invoke, String> invokeIds; /** * The <code>Map</code> of active <code>Invoker</code>s, keyed by - * (leaf) <code>State</code>s. + * their <code>invokeId</code>. */ - private final Map<TransitionalState, Invoker> invokers; + private final Map<String, Invoker> invokers; /** * The evaluator for expressions. @@ -108,7 +111,8 @@ public class SCInstance implements Seria this.contexts = Collections.synchronizedMap(new HashMap<EnterableState, Context>()); this.histories = Collections.synchronizedMap(new HashMap<History, Set<EnterableState>>()); this.invokerClasses = Collections.synchronizedMap(new HashMap<String, Class<? extends Invoker>>()); - this.invokers = Collections.synchronizedMap(new HashMap<TransitionalState, Invoker>()); + this.invokeIds = Collections.synchronizedMap(new HashMap<Invoke, String>()); + this.invokers = Collections.synchronizedMap(new HashMap<String, Invoker>()); this.completions = Collections.synchronizedMap(new HashMap<EnterableState, Boolean>()); this.evaluator = null; this.rootContext = null; @@ -348,36 +352,44 @@ public class SCInstance implements Seria } /** - * Get the {@link Invoker} for this {@link TransitionalState}. + * Get the {@link Invoker} for this {@link Invoke}. * May return <code>null</code>. A non-null {@link Invoker} will be - * returned if and only if the {@link TransitionalState} is - * currently active and contains an <invoke> child. + * returned if and only if the {@link Invoke} parent TransitionalState is + * currently active and contains the <invoke> child. * - * @param state The <code>TransitionalState</code>. + * @param invoke The <code>Invoke</code>. * @return The Invoker. */ - public Invoker getInvoker(final TransitionalState state) { - return invokers.get(state); + public Invoker getInvoker(final Invoke invoke) { + return invokers.get(invokeIds.get(invoke)); } /** - * Set the {@link Invoker} for this {@link TransitionalState}. + * Set the {@link Invoker} for a {@link Invoke} and returns the unique invokerId for the Invoker * - * @param state The TransitionalState. + * @param invoke The Invoke. * @param invoker The Invoker. + * @return The invokeId */ - public void setInvoker(final TransitionalState state, - final Invoker invoker) { - invokers.put(state, invoker); + public String setInvoker(final Invoke invoke, final Invoker invoker) { + String invokeId = invoke.getId(); + if (SCXMLHelper.isStringEmpty(invokeId)) { + invokeId = UUID.randomUUID().toString(); + } + invokeIds.put(invoke, invokeId); + invokers.put(invokeId, invoker); + return invokeId; + } + + public void removeInvoker(final Invoke invoke) { + invokers.remove(invokeIds.remove(invoke)); } /** - * Return the Map of {@link Invoker}s currently "active". - * - * @return The map of invokers. + * @return Returns the map of current active Invokes and their invokeId */ - public Map<TransitionalState, Invoker> getInvokers() { - return invokers; + public Map<Invoke, String> getInvokeIds() { + return invokeIds; } /** Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java Wed Mar 26 23:21:17 2014 @@ -250,9 +250,7 @@ final class ModelUpdater { updateTransition(transition, targets); } - // TODO: state must may have multiple invokes - Invoke inv = state.getInvoke(); - if (inv != null) { + for (Invoke inv : state.getInvokes()) { String type = inv.getType(); if (SCXMLHelper.isStringEmpty(type)) { logAndThrowModelError(ERR_INVOKE_NO_TYPE, Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java Wed Mar 26 23:21:17 2014 @@ -984,7 +984,9 @@ public final class SCXMLReader { configuration.parent.addTarget(child); readInExternalTargets(configuration.parent, child); } - s.setInvoke(include.getInvoke()); + for (Invoke invoke : include.getInvokes()) { + s.addInvoke(invoke); + } if (include.getInitial() != null) { s.setInitial(include.getInitial()); } @@ -1111,6 +1113,7 @@ public final class SCXMLReader { throws XMLStreamException, ModelException { Invoke invoke = new Invoke(); + invoke.setId(readAV(reader, ATTR_ID)); invoke.setSrc(readAV(reader, ATTR_SRC)); invoke.setSrcexpr(readAV(reader, ATTR_SRCEXPR)); invoke.setType(readAV(reader, ATTR_TYPE)); @@ -1150,7 +1153,7 @@ public final class SCXMLReader { } } - parent.setInvoke(invoke); + parent.addInvoke(invoke); } /** Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java Wed Mar 26 23:21:17 2014 @@ -533,8 +533,7 @@ public class SCXMLWriter { writeTransition(writer, t); } - Invoke inv = state.getInvoke(); - if (inv != null) { + for (Invoke inv : state.getInvokes()) { writeInvoke(writer, inv); } @@ -574,8 +573,7 @@ public class SCXMLWriter { writeTransition(writer, t); } - Invoke inv = parallel.getInvoke(); - if (inv != null) { + for (Invoke inv : parallel.getInvokes()) { writeInvoke(writer, inv); } @@ -733,6 +731,7 @@ public class SCXMLWriter { throws XMLStreamException { writer.writeStartElement(ELEM_INVOKE); + writeAV(writer, ATTR_ID, invoke.getId()); writeAV(writer, ATTR_SRC, invoke.getSrc()); writeAV(writer, ATTR_SRCEXPR, invoke.getSrcexpr()); writeAV(writer, ATTR_TYPE, invoke.getType()); Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Invoke.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Invoke.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Invoke.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Invoke.java Wed Mar 26 23:21:17 2014 @@ -38,6 +38,11 @@ public class Invoke implements Namespace private static final long serialVersionUID = 1L; /** + * Identifier for this Invoke. + * */ + private String id; + + /** * The type of target to be invoked. */ private String type; @@ -82,6 +87,24 @@ public class Invoke implements Namespace } /** + * Get the identifier for this invoke (may be null). + * + * @return Returns the id. + */ + public final String getId() { + return id; + } + + /** + * Set the identifier for this invoke. + * + * @param id The id to set. + */ + public final void setId(final String id) { + this.id = id; + } + + /** * Get the type for this <invoke> element. * * @return String Returns the type. @@ -209,5 +232,23 @@ public class Invoke implements Namespace this.namespaces = namespaces; } + /** + * Enforce identity equality only + * @param other other object to compare with + * @return this == other + */ + @Override + public final boolean equals(final Object other) { + return this == other; + } + + /** + * Enforce returning identity based hascode + * @return {@link System#identityHashCode(Object) System.identityHashCode(this)} + */ + @Override + public final int hashCode() { + return System.identityHashCode(this); + } } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionTarget.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionTarget.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionTarget.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionTarget.java Wed Mar 26 23:21:17 2014 @@ -26,6 +26,7 @@ import java.io.Serializable; public abstract class TransitionTarget implements Serializable, Observable { private static final EnterableState[] ZERO_ANCESTORS = new EnterableState[0]; + /** * Identifier for this transition target. Other parts of the SCXML * document may refer to this <state> using this ID. Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionalState.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionalState.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionalState.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/TransitionalState.java Wed Mar 26 23:21:17 2014 @@ -41,14 +41,13 @@ public abstract class TransitionalState private List<History> history; /** - * The Invoke child, which defines an external process that should + * The Invoke children, each which defines an external process that should * be invoked, immediately after the onentry executable content, * and the transitions become candidates after the invoked * process has completed its execution. - * May occur 0 or 1 times. Incompatible with the state or parallel - * property. + * May occur 0 or more times. */ - private Invoke invoke; + private List<Invoke> invokes; /** * The set of EnterableState children contained in this TransitionalState @@ -60,6 +59,7 @@ public abstract class TransitionalState transitions = new ArrayList<Transition>(); history = new ArrayList<History>(); children = new ArrayList<EnterableState>(); + invokes = new ArrayList<Invoke>(); } /** @@ -201,12 +201,12 @@ public abstract class TransitionalState } /** - * Get the Invoke child (may be null). + * Get the Invoke children (may be empty). * * @return Invoke Returns the invoke. */ - public final Invoke getInvoke() { - return invoke; + public final List<Invoke> getInvokes() { + return invokes; } /** @@ -215,14 +215,14 @@ public abstract class TransitionalState * @param invoke * The invoke to set. */ - public final void setInvoke(final Invoke invoke) { - this.invoke = invoke; + public final void addInvoke(final Invoke invoke) { + this.invokes.add(invoke); } /** * Get the set of child transition targets (may be empty). * - * @return Set Returns the children. + * @return Returns the children. * * @since 0.7 */ Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/semantics/SCXMLSemanticsImpl.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/semantics/SCXMLSemanticsImpl.java?rev=1582115&r1=1582114&r2=1582115&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/semantics/SCXMLSemanticsImpl.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/semantics/SCXMLSemanticsImpl.java Wed Mar 26 23:21:17 2014 @@ -218,23 +218,26 @@ public class SCXMLSemanticsImpl implemen final ErrorReporter errRep, final SCInstance scInstance) throws ModelException { NotificationRegistry nr = scInstance.getNotificationRegistry(); Collection<TriggerEvent> internalEvents = step.getAfterStatus().getEvents(); - Map<TransitionalState, Invoker> invokers = scInstance.getInvokers(); // ExecutePhaseActions / OnExit for (EnterableState es : step.getExitList()) { OnExit oe = es.getOnExit(); executeContent(oe, evtDispatcher, errRep, scInstance, internalEvents); - // check if invoke is active in this state - if (invokers.containsKey(es)) { - Invoker toCancel = invokers.get(es); - try { - toCancel.cancel(); - } catch (InvokerException ie) { - TriggerEvent te = new TriggerEvent(es.getId() - + ".invoke.cancel.failed", TriggerEvent.ERROR_EVENT); - internalEvents.add(te); + if (es instanceof TransitionalState) { + // check if invokers are active in this state + for (Invoke inv : ((TransitionalState)es).getInvokes()) { + Invoker toCancel = scInstance.getInvoker(inv); + if (toCancel != null) { + try { + toCancel.cancel(); + } catch (InvokerException ie) { + TriggerEvent te = new TriggerEvent(es.getId() + + ".invoke.cancel.failed", TriggerEvent.ERROR_EVENT); + internalEvents.add(te); + } + // done here, don't wait for cancel response + scInstance.removeInvoker(inv); + } } - // done here, don't wait for cancel response - invokers.remove(es); } nr.fireOnExit(es, es); nr.fireOnExit(stateMachine, es); @@ -437,10 +440,9 @@ public class SCXMLSemanticsImpl implemen allEvents.addAll(step.getBeforeStatus().getEvents()); allEvents.addAll(step.getExternalEvents()); // Finalize invokes, if applicable - for (TransitionTarget tt : scInstance.getInvokers().keySet()) { - State s = (State) tt; - if (finalizeMatch(s.getId(), allEvents)) { - Finalize fn = s.getInvoke().getFinalize(); + for (Map.Entry<Invoke, String> entry : scInstance.getInvokeIds().entrySet()) { + if (finalizeMatch(entry.getValue(), allEvents)) { + Finalize fn = entry.getKey().getFinalize(); if (fn != null) { executeContent(fn, evtDispatcher, errRep, scInstance, step.getAfterStatus().getEvents()); } @@ -768,10 +770,9 @@ public class SCXMLSemanticsImpl implemen throws ModelException { Set<TriggerEvent> allEvents = new HashSet<TriggerEvent>(); allEvents.addAll(Arrays.asList(events)); - for (Map.Entry<TransitionalState, Invoker> iEntry : scInstance.getInvokers().entrySet()) { - String parentId = iEntry.getKey().getId(); - if (!finalizeMatch(parentId, allEvents)) { // prevent cycles - Invoker inv = iEntry.getValue(); + for (Map.Entry<Invoke, String> entry : scInstance.getInvokeIds().entrySet()) { + if (!finalizeMatch(entry.getValue(), allEvents)) { // prevent cycles + Invoker inv = scInstance.getInvoker(entry.getKey()); try { inv.parentEvents(events); } catch (InvokerException ie) { @@ -800,82 +801,85 @@ public class SCXMLSemanticsImpl implemen if (es instanceof TransitionalState) { TransitionalState ts = (TransitionalState) es; Context ctx = scInstance.getContext(ts); - Invoke i = ts.getInvoke(); - if (i != null && scInstance.getInvoker(ts) == null) { - String src = i.getSrc(); - if (src == null) { - String srcexpr = i.getSrcexpr(); - Object srcObj; - try { - ctx.setLocal(NAMESPACES_KEY, i.getNamespaces()); - srcObj = eval.eval(ctx, srcexpr); - ctx.setLocal(NAMESPACES_KEY, null); - src = String.valueOf(srcObj); - } catch (SCXMLExpressionException see) { - errRep.onError(ErrorConstants.EXPRESSION_ERROR, - see.getMessage(), i); - } - } - String source = src; - PathResolver pr = i.getPathResolver(); - if (pr != null) { - source = i.getPathResolver().resolvePath(src); - } - String type = i.getType(); - Invoker inv; - try { - inv = scInstance.newInvoker(type); - } catch (InvokerException ie) { - TriggerEvent te = new TriggerEvent(ts.getId() - + ".invoke.failed", TriggerEvent.ERROR_EVENT); - internalEvents.add(te); - continue; - } - inv.setParentStateId(ts.getId()); - inv.setSCInstance(scInstance); - List<Param> params = i.params(); - Map<String, Object> args = new HashMap<String, Object>(); - for (Param p : params) { - String argExpr = p.getExpr(); - Object argValue = null; - ctx.setLocal(NAMESPACES_KEY, p.getNamespaces()); - // Do we have an "expr" attribute? - if (argExpr != null && argExpr.trim().length() > 0) { + for (Invoke i : ts.getInvokes()) { + if (i != null && scInstance.getInvoker(i) == null) { + String src = i.getSrc(); + if (src == null) { + String srcexpr = i.getSrcexpr(); + Object srcObj; try { - argValue = eval.eval(ctx, argExpr); + ctx.setLocal(NAMESPACES_KEY, i.getNamespaces()); + srcObj = eval.eval(ctx, srcexpr); + ctx.setLocal(NAMESPACES_KEY, null); + src = String.valueOf(srcObj); } catch (SCXMLExpressionException see) { errRep.onError(ErrorConstants.EXPRESSION_ERROR, see.getMessage(), i); } - } else { - // No. Does value of "name" attribute refer to a valid - // location in the data model? - try { - argValue = eval.evalLocation(ctx, p.getName()); - if (argValue == null) { + } + String source = src; + PathResolver pr = i.getPathResolver(); + if (pr != null) { + source = i.getPathResolver().resolvePath(src); + } + String type = i.getType(); + Invoker inv; + try { + inv = scInstance.newInvoker(type); + } catch (InvokerException ie) { + TriggerEvent te = new TriggerEvent(ts.getId() + + ".invoke.failed", TriggerEvent.ERROR_EVENT); + internalEvents.add(te); + continue; + } + List<Param> params = i.params(); + Map<String, Object> args = new HashMap<String, Object>(); + for (Param p : params) { + String argExpr = p.getExpr(); + Object argValue = null; + ctx.setLocal(NAMESPACES_KEY, p.getNamespaces()); + // Do we have an "expr" attribute? + if (argExpr != null && argExpr.trim().length() > 0) { + try { + argValue = eval.eval(ctx, argExpr); + } catch (SCXMLExpressionException see) { + errRep.onError(ErrorConstants.EXPRESSION_ERROR, + see.getMessage(), i); + } + } else { + // No. Does value of "name" attribute refer to a valid + // location in the data model? + try { + argValue = eval.evalLocation(ctx, p.getName()); + if (argValue == null) { // Generate error, 4.3.1 in WD-scxml-20080516 - TriggerEvent te = new TriggerEvent(ts.getId() - + ERR_ILLEGAL_ALLOC, - TriggerEvent.ERROR_EVENT); - internalEvents.add(te); + TriggerEvent te = new TriggerEvent(ts.getId() + + ERR_ILLEGAL_ALLOC, + TriggerEvent.ERROR_EVENT); + internalEvents.add(te); + } + } catch (SCXMLExpressionException see) { + errRep.onError(ErrorConstants.EXPRESSION_ERROR, + see.getMessage(), i); } - } catch (SCXMLExpressionException see) { - errRep.onError(ErrorConstants.EXPRESSION_ERROR, - see.getMessage(), i); } + ctx.setLocal(NAMESPACES_KEY, null); + args.put(p.getName(), argValue); + } + String invokeId = scInstance.setInvoker(i, inv); + // TODO: API should reflect this isn't the parent state ID anymore but the invokeId + inv.setParentStateId(invokeId); + inv.setSCInstance(scInstance); + try { + inv.invoke(source, args); + } catch (InvokerException ie) { + TriggerEvent te = new TriggerEvent(ts.getId() + + ".invoke.failed", TriggerEvent.ERROR_EVENT); + internalEvents.add(te); + scInstance.removeInvoker(i); + continue; } - ctx.setLocal(NAMESPACES_KEY, null); - args.put(p.getName(), argValue); - } - try { - inv.invoke(source, args); - } catch (InvokerException ie) { - TriggerEvent te = new TriggerEvent(ts.getId() - + ".invoke.failed", TriggerEvent.ERROR_EVENT); - internalEvents.add(te); - continue; } - scInstance.setInvoker(ts, inv); } } }