Author: ate Date: Sat Nov 15 03:07:58 2014 New Revision: 1639829 URL: http://svn.apache.org/r1639829 Log: SCXML-213: Fixing the datamodel handling and bringing it more in line with the SCXML specification This is a very large commit as so many of the needed changes turned out to be interdependent, and committing these in separate logical steps simply wouldn't work without complete intermediate breakage of the build. For background and more high-level details concerning these changes, see: https://issues.apache.org/jira/browse/SCXML-213
Important new features and changes: - complete replacing the JAXP xpath usage with Commons JXPath - re-implement the xpath Data() builtin function and introducing the xpath Location() builtin function - simplify (strip) the corresponding Builtin class and adding the XPathBuiltin to handle the xpath specific features separately - extend the Evaluator interface and corresponding language specific implementations to support all the SCXML spec <assign> xpath requirements using the XPathEvaluator - complete the <send> action implementation proper, and the default SimpleDispatcher implementation with it (dropping the SimpleScheduler) - implement the SCXML spec requirements for the _ioprocessors services lookup and proper handling of internal/external SCXML I/O Event Processors - also largely improve the <invoke> implementation and execution, although there remains several things TODO for it to be fully SCXML spec compliant Added: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/InvokerManager.java (with props) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/XPathBuiltin.java (with props) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java (with props) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Content.java (with props) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/ContentContainer.java (with props) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/NamelistHolder.java (with props) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/ParamsContainer.java (with props) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/PayloadProvider.java (with props) Removed: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleScheduler.java Modified: commons/proper/scxml/trunk/src/ (props changed) commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Builtin.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Context.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Evaluator.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EventDispatcher.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCInstance.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLIOProcessor.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlBuiltin.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathEvaluator.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathFunctions.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.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/Action.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Assign.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/Log.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Param.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/SCXML.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Send.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/ErrorConstants.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/semantics/SCXMLSemanticsImpl.java commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/test/StandaloneUtils.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/EventDataTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/WizardsTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/custom-hello-world-04-jexl.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/SimpleContextTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/GroovyContextTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluatorTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/StaticMethodTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/groovy-closure.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/microwave-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/microwave-02.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/microwave-03.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/microwave-04.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/groovy/microwave-05.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/javascript/JSEvaluatorTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/javascript/example-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/javascript/script-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/JexlContextTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/datamodel-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/datamodel-02.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/datamodel-03.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/datamodel-04.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/datamodel-05.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/eventdata-03.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/foreach.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/microwave-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/microwave-02.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/microwave-03.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/microwave-04.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/microwave-05.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/script-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/stateless-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/jexl/wizard-02.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/env/xpath/example-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/invoke/InvokeParamNameTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/invoke/invoker-04.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/io/SCXMLRequiredAttributesTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/model/ParallelTest.java commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/model/actions-initial-test.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/model/actions-parallel-test.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/model/actions-state-test.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/model/assign-test-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/model/assign-test-02.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/model/parallel-03.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/send-01.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/send-02.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/transitions-02.xml commons/proper/scxml/trunk/src/test/java/org/apache/commons/scxml2/transitions-with-cond-01.xml Propchange: commons/proper/scxml/trunk/src/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Sat Nov 15 03:07:58 2014 @@ -0,0 +1 @@ +w3c Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Builtin.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Builtin.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Builtin.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Builtin.java Sat Nov 15 03:07:58 2014 @@ -17,23 +17,12 @@ package org.apache.commons.scxml2; import java.io.Serializable; -import java.util.List; -import java.util.Map; import java.util.Set; -import org.apache.commons.jxpath.JXPathContext; -import org.apache.commons.jxpath.JXPathException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.commons.scxml2.model.TransitionTarget; -import org.w3c.dom.*; /** - * Implementations of builtin functions defined by the SCXML - * specification. - * - * The current version of the specification defines one builtin - * predicate In() + * Implementation of the SCXML specification required In() builtin predicate. */ public class Builtin implements Serializable { @@ -47,154 +36,38 @@ public class Builtin implements Serializ * name chosen is different since "in" is a reserved token * in some expression languages. * - * Does this state belong to the given Set of States. * Simple ID based comparator, assumes IDs are unique. * - * @param allStates The Set of State objects to look in + * @param ctx variable context * @param state The State ID to compare with - * @return Whether this State belongs to this Set - */ - public static boolean isMember(final Set<? extends TransitionTarget> allStates, - final String state) { - for (TransitionTarget tt : allStates) { - if (state.equals(tt.getId())) { - return true; - } - } - return false; - } - - /** - * Implements the Data() function for Commons SCXML documents, that - * can be used to obtain a node from one of the XML data trees. - * Manifests within "location" attribute of <assign> element, - * for Commons JEXL and Commons EL based documents. - * - * @param namespaces The current document namespaces map at XPath location - * @param data The context Node, though the method accepts an Object - * so error is reported by Commons SCXML, rather - * than the underlying expression language. - * @param path The XPath expression. - * @return The first node matching the path, or null if no nodes match. + * @return Whether this State is current active */ - public static Node dataNode(final Map<String, String> namespaces, final Object data, - final String path) { - if (data == null || !(data instanceof Node)) { - Log log = LogFactory.getLog(Builtin.class); - log.error("Data(): Cannot evaluate an XPath expression" - + " in the absence of a context Node, null returned"); - return null; - } - Node dataNode = (Node) data; - List result; - try { - JXPathContext context = JXPathContext.newContext(dataNode); - if (namespaces == null || namespaces.size() == 0) { - Log log = LogFactory.getLog(Builtin.class); - if (log.isDebugEnabled()) { - log.debug("Turning off namespaced XPath evaluation since " - + "no namespace information is available for path: " - + path); - } - } else { - for (String prefix : namespaces.keySet()) { - context.registerNamespace(prefix, namespaces.get(prefix)); - } - } - result = context.selectNodes(path); - } catch (JXPathException xee) { - Log log = LogFactory.getLog(Builtin.class); - log.error(xee.getMessage(), xee); - return null; - } - int length = result.size(); - if (length == 0) { - Log log = LogFactory.getLog(Builtin.class); - log.warn("Data(): No nodes matching the XPath expression \"" - + path + "\", returning null"); - return null; - } else { - if (length > 1) { - Log log = LogFactory.getLog(Builtin.class); - log.warn("Data(): Multiple (" + length + ") nodes matching XPath expression \"" - + path + "\", returning first"); - } - return (Node)result.get(0); - } + @SuppressWarnings("unchecked") + public static boolean isMember(final Context ctx, final String state) { + return isMember((Set<? extends TransitionTarget>)ctx.getSystemContext().get(SCXMLSystemContext.ALL_STATES_KEY), state); } /** - * A variant of the Data() function for Commons SCXML documents, - * coerced to a Double, a Long or a String, whichever succeeds, - * in that order. - * Manifests within rvalue expressions in the document, - * for Commons JEXL and Commons EL based documents.. + * Implements the In() predicate for SCXML documents. The method + * name chosen is different since "in" is a reserved token + * in some expression languages. * - * @param namespaces The current document namespaces map at XPath location - * @param data The context Node, though the method accepts an Object - * so error is reported by Commons SCXML, rather - * than the underlying expression language. - * @param path The XPath expression. - * @return The first node matching the path, coerced to a String, or null - * if no nodes match. - */ - public static Object data(final Map<String, String> namespaces, final Object data, final String path) { - Object retVal = null; - String strVal = getNodeValue(dataNode(namespaces, data, path)); - // try as a double - try { - double d = Double.parseDouble(strVal); - retVal = new Double(d); - } catch (NumberFormatException notADouble) { - // else as a long - try { - long l = Long.parseLong(strVal); - retVal = new Long(l); - } catch (NumberFormatException notALong) { - // fallback to string - retVal = strVal; - } - } - return retVal; - } - - /** - * Retrieve a DOM node value as a string depending on its type. + * Does this state belong to the given Set of States. + * Simple ID based comparator, assumes IDs are unique. * - * @param node A node to be retrieved - * @return The value as a string + * @param allStates The Set of State objects to look in + * @param state The State ID to compare with + * @return Whether this State belongs to this Set */ - private static String getNodeValue(final Node node) { - String result = ""; - if (node == null) { - return result; - } - switch(node.getNodeType()) { - case Node.ATTRIBUTE_NODE: - result = node.getNodeValue(); - break; - case Node.ELEMENT_NODE: - if (node.hasChildNodes()) { - Node child = node.getFirstChild(); - StringBuilder buf = new StringBuilder(); - while (child != null) { - if (child.getNodeType() == Node.TEXT_NODE) { - buf.append(((CharacterData) child).getData()); - } - child = child.getNextSibling(); - } - result = buf.toString(); + public static boolean isMember(final Set<? extends TransitionTarget> allStates, final String state) { + if (allStates != null) { + for (TransitionTarget tt : allStates) { + if (state.equals(tt.getId())) { + return true; } - break; - case Node.TEXT_NODE: - case Node.CDATA_SECTION_NODE: - result = ((CharacterData) node).getData(); - break; - default: - String err = "Trying to get value of a strange Node type: " + node.getNodeType(); - throw new IllegalArgumentException(err); + } } - return result.trim(); + return false; } } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Context.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Context.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Context.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Context.java Sat Nov 15 03:07:58 2014 @@ -94,4 +94,11 @@ public interface Context { */ Context getParent(); + /** + * Get the SCXMLSystemContext for this Context, should not be null unless this is the root Context + * + * @return The SCXMLSystemContext in a chained Context environment + */ + SCXMLSystemContext getSystemContext(); + } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Evaluator.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Evaluator.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Evaluator.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/Evaluator.java Sat Nov 15 03:07:58 2014 @@ -16,8 +16,6 @@ */ package org.apache.commons.scxml2; -import org.w3c.dom.Node; - /** * Interface for a component that may be used by the SCXML engines to * evaluate the expressions within the SCXML document. @@ -25,6 +23,52 @@ import org.w3c.dom.Node; */ public interface Evaluator { + /** SCXML 1.0 Null Data Model name **/ + String NULL_DATA_MODEL = "null"; + + /** SCXML 1.0 ECMAScript Data Model name **/ + String ECMASCRIPT_DATA_MODEL = "ecmascript"; + + /** SCXML 1.0 XPath Data Model name **/ + String XPATH_DATA_MODEL = "xpath"; + + /** Default Data Model name **/ + String DEFAULT_DATA_MODEL = ""; + + /** + * The allowable types of <assign/> including and in particular when using XPath + */ + enum AssignType { + + REPLACE_CHILDREN("replacechildren"), + FIRST_CHILD("firstchild"), + LAST_CHILD("lastchild"), + PREVIOUS_SIBLING("previoussibling"), + NEXT_SIBLING("nextsibling"), + REPLACE("replace"), + DELETE("delete"), + ADD_ATTRIBUTE("addattribute"); + + private final String value; + + private AssignType(String value) { + this.value = value; + } + + public String value() { + return value; + } + + public static AssignType fromValue(String value) { + for (AssignType type : AssignType.values()) { + if (type.value().equals(value)) { + return type; + } + } + return null; + } + } + /** * Get the datamodel type supported by this Evaluator * @return The supported datamodel type @@ -32,12 +76,12 @@ public interface Evaluator { String getSupportedDatamodel(); /** - * Evaluate an expression. + * Evaluate an expression returning a data value * * @param ctx variable context * @param expr expression - * @return a result of the evaluation - * @throws SCXMLExpressionException A malformed exception + * @return the result of the evaluation + * @throws SCXMLExpressionException A malformed expression exception */ Object eval(Context ctx, String expr) throws SCXMLExpressionException; @@ -50,24 +94,37 @@ public interface Evaluator { * @param ctx variable context * @param expr expression * @return true/false - * @throws SCXMLExpressionException A malformed exception + * @throws SCXMLExpressionException A malformed expression exception */ Boolean evalCond(Context ctx, String expr) throws SCXMLExpressionException; /** - * Evaluate a location that returns a Node within an XML data tree. + * Evaluate a location that returns a data assignable reference or list of references. * Manifests as "location" attributes of <assign> element. * * @param ctx variable context * @param expr expression - * @return The location node. - * @throws SCXMLExpressionException A malformed exception + * @return The location result. + * @throws SCXMLExpressionException A malformed expression exception */ - Node evalLocation(Context ctx, String expr) + Object evalLocation(Context ctx, String expr) throws SCXMLExpressionException; /** + * Assigns data to a location + * + * @param ctx variable context + * @param location location expression + * @param data the data to assign. + * @param type the type of assignment to perform, null assumes {@link AssignType#REPLACE_CHILDREN} + * @param attr the name of the attribute to add when using type {@link AssignType#ADD_ATTRIBUTE} + * @throws SCXMLExpressionException A malformed expression exception + */ + void evalAssign(Context ctx, String location, Object data, AssignType type, String attr) + throws SCXMLExpressionException; + + /** * Evaluate a script. * Manifests as <script> element. * Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java Sat Nov 15 03:07:58 2014 @@ -25,6 +25,7 @@ import org.apache.commons.scxml2.env.jex import org.apache.commons.scxml2.env.xpath.XPathEvaluator; import org.apache.commons.scxml2.model.ModelException; import org.apache.commons.scxml2.model.SCXML; +import static org.apache.commons.scxml2.Evaluator.DEFAULT_DATA_MODEL; /** * A static singleton factory for {@link EvaluatorProvider}s by supported SCXML datamodel type. @@ -35,7 +36,7 @@ import org.apache.commons.scxml2.model.S * <p> * The builtin supported providers are: * <ul> - * <li>no datamodel (default) or datamodel="jexl": {@link JexlEvaluator.JexlEvaluatorProvider}</li> + * <li>no or empty datamodel (default) or datamodel="jexl": {@link JexlEvaluator.JexlEvaluatorProvider}</li> * <li>datamodel="ecmascript": {@link JSEvaluator.JSEvaluatorProvider}</li> * <li>datamodel="groovy": {@link GroovyEvaluator.GroovyEvaluatorProvider}</li> * <li>datamodel="xpath": {@link XPathEvaluator.XPathEvaluatorProvider}</li> @@ -47,8 +48,8 @@ import org.apache.commons.scxml2.model.S * </p> * <p> * The default provider can be overridden using the {@link #setDefaultProvider(EvaluatorProvider)} which will - * register the provider under an empty ("") value for the datamodel.<br/> - * Note: this is <em>not</em> the same as datamodel="null" (which currently is not (yet) supported)! + * register the provider under the {@link Evaluator#DEFAULT_DATA_MODEL} ("") value for the datamodel.<br/> + * Note: this is <em>not</em> the same as datamodel="null"! * </p> */ public class EvaluatorFactory { @@ -58,45 +59,49 @@ public class EvaluatorFactory { private final Map<String, EvaluatorProvider> providers = new ConcurrentHashMap<String, EvaluatorProvider>(); private EvaluatorFactory() { - providers.put("xpath", new XPathEvaluator.XPathEvaluatorProvider()); - providers.put("ecmascript", new JSEvaluator.JSEvaluatorProvider()); - providers.put("groovy", new GroovyEvaluator.GroovyEvaluatorProvider()); - providers.put("jexl", new JexlEvaluator.JexlEvaluatorProvider()); - providers.put("", providers.get("jexl")); + providers.put(XPathEvaluator.SUPPORTED_DATA_MODEL, new XPathEvaluator.XPathEvaluatorProvider()); + providers.put(JSEvaluator.SUPPORTED_DATA_MODEL, new JSEvaluator.JSEvaluatorProvider()); + providers.put(GroovyEvaluator.SUPPORTED_DATA_MODEL, new GroovyEvaluator.GroovyEvaluatorProvider()); + providers.put(JexlEvaluator.SUPPORTED_DATA_MODEL, new JexlEvaluator.JexlEvaluatorProvider()); + providers.put(DEFAULT_DATA_MODEL, providers.get(JexlEvaluator.SUPPORTED_DATA_MODEL)); } public static void setDefaultProvider(EvaluatorProvider defaultProvider) { - INSTANCE.providers.put("", defaultProvider); + INSTANCE.providers.put(DEFAULT_DATA_MODEL, defaultProvider); } + @SuppressWarnings("unused") public static EvaluatorProvider getDefaultProvider() { - return INSTANCE.providers.get(""); + return INSTANCE.providers.get(DEFAULT_DATA_MODEL); } - public static EvaluatorProvider getEvaluatorProvider(String datamodelType) { - return INSTANCE.providers.get(datamodelType == null ? "" : datamodelType); + @SuppressWarnings("unused") + public static EvaluatorProvider getEvaluatorProvider(String datamodelName) { + return INSTANCE.providers.get(datamodelName == null ? DEFAULT_DATA_MODEL : datamodelName); } + @SuppressWarnings("unused") public static void registerEvaluatorProvider(EvaluatorProvider provider) { INSTANCE.providers.put(provider.getSupportedDatamodel(), provider); } - public static void unregisterEvaluatorProvider(String datamodelType) { - INSTANCE.providers.remove(datamodelType == null ? "" : datamodelType); + @SuppressWarnings("unused") + public static void unregisterEvaluatorProvider(String datamodelName) { + INSTANCE.providers.remove(datamodelName == null ? DEFAULT_DATA_MODEL : datamodelName); } /** - * Returns a dedicated Evaluator instance for a specific SCXML document its documentmodel type. + * Returns a dedicated Evaluator instance for a specific SCXML document its documentmodel. * <p>If no SCXML document is provided a default Evaluator will be returned.</p> * @param document The document to return a dedicated Evaluator for. May be null to retrieve the default Evaluator. * @return a new and not sharable Evaluator instance for the provided document, or a default Evaluator otherwise - * @throws ModelException If the SCXML document datamodel type is not supported. + * @throws ModelException If the SCXML document datamodel is not supported. */ public static Evaluator getEvaluator(SCXML document) throws ModelException { - String datamodelType = document != null ? document.getDatamodelType() : null; - EvaluatorProvider provider = INSTANCE.providers.get(datamodelType == null ? "" : datamodelType); + String datamodelName = document != null ? document.getDatamodelName() : null; + EvaluatorProvider provider = INSTANCE.providers.get(datamodelName == null ? DEFAULT_DATA_MODEL : datamodelName); if (provider == null) { - throw new ModelException("Unsupported SCXML document datamodel type \""+(datamodelType)+"\""); + throw new ModelException("Unsupported SCXML document datamodel \""+(datamodelName)+"\""); } return document != null ? provider.getEvaluator(document) : provider.getEvaluator(); } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EventDispatcher.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EventDispatcher.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EventDispatcher.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/EventDispatcher.java Sat Nov 15 03:07:58 2014 @@ -16,11 +16,8 @@ */ package org.apache.commons.scxml2; -import java.util.List; import java.util.Map; -import org.w3c.dom.Node; - /** * The event controller interface used to send messages containing * events or other information directly to another SCXML Interpreter, @@ -40,22 +37,19 @@ public interface EventDispatcher { /** * Send this message to the target. * + * @param ioProcessors the available SCXMLIOProcessors, the same map as the SCXML system variable _ioprocessors * @param id The ID of the send message * @param target An expression returning the target location of the event * @param type The type of the Event I/O Processor that the event should * be dispatched to * @param event The type of event being generated. - * @param params A list of zero or more whitespace separated variable - * names to be included with the event. + * @param data The event payload * @param hints The data containing information which may be * used by the implementing platform to configure the event processor * @param delay The event is dispatched after the delay interval elapses - * @param externalNodes The list of external nodes associated with - * the <send> element. */ - void send(String id, String target, String type, - String event, Map<String, Object> params, Object hints, - long delay, List<Node> externalNodes); + void send(Map<String, SCXMLIOProcessor> ioProcessors, String id, String target, String type, String event, + Object data, Object hints, long delay); } Added: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/InvokerManager.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/InvokerManager.java?rev=1639829&view=auto ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/InvokerManager.java (added) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/InvokerManager.java Sat Nov 15 03:07:58 2014 @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.scxml2; + +import org.apache.commons.scxml2.invoke.Invoker; +import org.apache.commons.scxml2.invoke.InvokerException; +import org.apache.commons.scxml2.model.Invoke; + +/** + * InvokerManager provides the ability to an Invoke action to + * create and register an active Invoker instance + */ +public interface InvokerManager { + + /** + * Create a new {@link Invoker} + * + * @param type The type of the target being invoked. + * @return An {@link Invoker} for the specified type, if an + * invoker class is registered against that type, + * <code>null</code> otherwise. + * @throws InvokerException When a suitable {@link Invoker} cannot be instantiated. + */ + Invoker newInvoker(final String type) throws InvokerException; + + /** + * Registers the active {@link Invoker} for an {@link Invoke} + * + * @param invoke The Invoke. + * @param invoker The Invoker. + * @throws InvokerException when the Invoker doesn't have an invokerId + */ + void registerInvoker(final Invoke invoke, final Invoker invoker) throws InvokerException; +} Propchange: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/InvokerManager.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/InvokerManager.java ------------------------------------------------------------------------------ svn:keywords = Id 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=1639829&r1=1639828&r2=1639829&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 Sat Nov 15 03:07:58 2014 @@ -25,6 +25,10 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.scxml2.env.SimpleContext; import org.apache.commons.scxml2.model.Data; import org.apache.commons.scxml2.model.Datamodel; import org.apache.commons.scxml2.model.EnterableState; @@ -33,6 +37,8 @@ import org.apache.commons.scxml2.model.M import org.apache.commons.scxml2.model.SCXML; import org.apache.commons.scxml2.model.TransitionalState; import org.apache.commons.scxml2.semantics.ErrorConstants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.Node; /** @@ -144,8 +150,8 @@ public class SCInstance implements Seria if (evaluator == null) { evaluator = EvaluatorFactory.getEvaluator(stateMachine); } - if (stateMachine.getDatamodelType() != null && !stateMachine.getDatamodelType().equals(evaluator.getSupportedDatamodel())) { - throw new ModelException("Incompatible SCXML document datamodel type \""+stateMachine.getDatamodelType()+"\"" + if (stateMachine.getDatamodelName() != null && !stateMachine.getDatamodelName().equals(evaluator.getSupportedDatamodel())) { + throw new ModelException("Incompatible SCXML document datamodel \""+stateMachine.getDatamodelName()+"\"" + " for evaluator "+evaluator.getClass().getName()+" supported datamodel \""+evaluator.getSupportedDatamodel()+"\""); } if (errorReporter == null) { @@ -170,13 +176,14 @@ public class SCInstance implements Seria * </p> */ protected void detach() { + this.internalIOProcessor = null; this.evaluator = null; this.errorReporter = null; } /** * Sets the I/O Processor for the internal event queue - * @param internalIOProcessor + * @param internalIOProcessor the I/O Processor */ protected void setInternalIOProcessor(SCXMLIOProcessor internalIOProcessor) { this.internalIOProcessor = internalIOProcessor; @@ -256,7 +263,7 @@ public class SCInstance implements Seria */ protected void cloneDatamodel(final Datamodel datamodel, final Context ctx, final Evaluator evaluator, final ErrorReporter errorReporter) { - if (datamodel == null) { + if (datamodel == null || Evaluator.NULL_DATA_MODEL.equals(evaluator.getSupportedDatamodel())) { return; } List<Data> data = datamodel.getData(); @@ -264,6 +271,10 @@ public class SCInstance implements Seria return; } for (Data datum : data) { + if (ctx.has(datum.getId())) { + // earlier or externally defined 'initial' value found: do not overwrite + continue; + } Node datumNode = datum.getNode(); Node valueNode = null; if (datumNode != null) { @@ -273,7 +284,7 @@ public class SCInstance implements Seria if (datum.getSrc() != null) { ctx.setLocal(datum.getId(), valueNode); } else if (datum.getExpr() != null) { - Object value = null; + Object value; try { ctx.setLocal(Context.NAMESPACES_KEY, datum.getNamespaces()); value = evaluator.eval(ctx, datum.getExpr()); @@ -283,8 +294,33 @@ public class SCInstance implements Seria internalIOProcessor.addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT)); } errorReporter.onError(ErrorConstants.EXPRESSION_ERROR, see.getMessage(), datum); + continue; + } + if (Evaluator.XPATH_DATA_MODEL.equals(evaluator.getSupportedDatamodel())) { + try { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + // TODO: should use SCXML namespace here? + Element dataNode = document.createElement("data"); + dataNode.setAttribute("id", datum.getId()); + ctx.setLocal(datum.getId(), dataNode); + evaluator.evalAssign(ctx, "$" + datum.getId(), value, Evaluator.AssignType.REPLACE_CHILDREN, null); + } + catch (ParserConfigurationException pce) { + if (internalIOProcessor != null) { + internalIOProcessor.addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT)); + } + errorReporter.onError(ErrorConstants.EXECUTION_ERROR, pce.getMessage(), datum); + } + catch (SCXMLExpressionException see) { + if (internalIOProcessor != null) { + internalIOProcessor.addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT)); + } + errorReporter.onError(ErrorConstants.EXPRESSION_ERROR, see.getMessage(), datum); + } + } + else { + ctx.setLocal(datum.getId(), value); } - ctx.setLocal(datum.getId(), value); } else { ctx.setLocal(datum.getId(), valueNode); } @@ -325,7 +361,8 @@ public class SCInstance implements Seria */ public Context getRootContext() { if (rootContext == null && evaluator != null) { - rootContext = evaluator.newContext(null); + rootContext = Evaluator.NULL_DATA_MODEL.equals(evaluator.getSupportedDatamodel()) + ? new SimpleContext() : evaluator.newContext(null); } return rootContext; } @@ -354,7 +391,9 @@ public class SCInstance implements Seria // force initialization of rootContext getRootContext(); if (rootContext != null) { - systemContext = new SCXMLSystemContext(evaluator.newContext(rootContext)); + Context internalContext = Evaluator.NULL_DATA_MODEL.equals(evaluator.getSupportedDatamodel()) ? + new SimpleContext(systemContext) : evaluator.newContext(rootContext); + systemContext = new SCXMLSystemContext(internalContext); systemContext.getContext().set(SCXMLSystemContext.SESSIONID_KEY, UUID.randomUUID().toString()); String _name = stateMachine != null && stateMachine.getName() != null ? stateMachine.getName() : ""; systemContext.getContext().set(SCXMLSystemContext.SCXML_NAME_KEY, _name); Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java Sat Nov 15 03:07:58 2014 @@ -17,11 +17,11 @@ package org.apache.commons.scxml2; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; -import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -37,7 +37,7 @@ import org.apache.commons.scxml2.model.S * SCXMLExecutionContext provides all the services and internal data used during the interpretation of an SCXML * statemachine across micro and macro steps */ -public class SCXMLExecutionContext implements SCXMLIOProcessor { +public class SCXMLExecutionContext implements SCXMLIOProcessor, InvokerManager { /** * SCXML Execution Logger for the application. @@ -100,6 +100,11 @@ public class SCXMLExecutionContext imple private final Map<String, Invoker> invokers = new HashMap<String, Invoker>(); /** + * The Map of the current ioProcessors + */ + private final Map<String, SCXMLIOProcessor> ioProcessors = new HashMap<String, SCXMLIOProcessor>(); + + /** * Constructor * * @param externalIOProcessor The external IO Processor @@ -117,6 +122,11 @@ public class SCXMLExecutionContext imple this.scInstance = new SCInstance(this, this.evaluator, this.errorReporter); this.actionExecutionContext = new ActionExecutionContext(this); + + ioProcessors.put(SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR, getExternalIOProcessor()); + ioProcessors.put(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR, getExternalIOProcessor()); + ioProcessors.put(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR, getInternalIOProcessor()); + initializeIOProcessors(); } public SCXMLIOProcessor getExternalIOProcessor() { @@ -162,6 +172,7 @@ public class SCXMLExecutionContext imple } internalEventQueue.clear(); scInstance.initialize(); + initializeIOProcessors(); scInstance.setRunning(true); } @@ -192,6 +203,16 @@ public class SCXMLExecutionContext imple scInstance.setStateMachine(stateMachine); // synchronize possible derived evaluator this.evaluator = scInstance.getEvaluator(); + initializeIOProcessors(); + } + + /** + * The SCXML specification section "C.1.1 _ioprocessors Value" states that the SCXMLEventProcessor <em>must</em> + * maintain a 'location' field inside its entry in the _ioprocessors environment variable. + * @return the 'location' of the SCXMLEventProcessor + */ + public String getLocation() { + return null; } /** @@ -221,6 +242,7 @@ public class SCXMLExecutionContext imple scInstance.setEvaluator(evaluator, false); // synchronize possible derived evaluator this.evaluator = scInstance.getEvaluator(); + initializeIOProcessors(); } /** @@ -269,6 +291,15 @@ public class SCXMLExecutionContext imple } /** + * Initialize the _ioprocessors environment variable, which only can be done when the evaluator is available + */ + protected void initializeIOProcessors() { + if (scInstance.getEvaluator() != null) { + getScInstance().getSystemContext().setLocal(SCXMLSystemContext.IOPROCESSORS_KEY, Collections.unmodifiableMap(ioProcessors)); + } + } + + /** * Detach the current SCInstance to allow external serialization. * <p> * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance @@ -278,6 +309,9 @@ public class SCXMLExecutionContext imple protected SCInstance detachInstance() { SCInstance instance = scInstance; scInstance.detach(); + Map<String, Object> systemVars = scInstance.getSystemContext().getVars(); + systemVars.remove(SCXMLSystemContext.IOPROCESSORS_KEY); + systemVars.remove(SCXMLSystemContext.EVENT_KEY); scInstance = null; return instance; } @@ -297,8 +331,10 @@ public class SCXMLExecutionContext imple if (scInstance != null) { scInstance.detach(); try { + scInstance.setInternalIOProcessor(this); scInstance.setEvaluator(evaluator, true); scInstance.setErrorReporter(errorReporter); + initializeIOProcessors(); } catch (ModelException me) { // should not happen @@ -362,20 +398,19 @@ public class SCXMLExecutionContext imple } /** - * Set the {@link Invoker} for a {@link Invoke} and returns the unique invokerId for the Invoker + * Register the active {@link Invoker} for a {@link Invoke} * * @param invoke The Invoke. * @param invoker The Invoker. - * @return The invokeId + * @throws InvokerException when the Invoker doesn't have an invokerId */ - public String setInvoker(final Invoke invoke, final Invoker invoker) { - String invokeId = invoke.getId(); + public void registerInvoker(final Invoke invoke, final Invoker invoker) throws InvokerException { + String invokeId = invoker.getInvokeId(); if (invokeId == null) { - invokeId = UUID.randomUUID().toString(); + throw new InvokerException("Registering an Invoker without invokerId"); } invokeIds.put(invoke, invokeId); invokers.put(invokeId, invoker); - return invokeId; } /** Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java Sat Nov 15 03:07:58 2014 @@ -44,11 +44,6 @@ import org.apache.commons.scxml2.semanti public class SCXMLExecutor implements SCXMLIOProcessor { /** - * SCXMLExecutor put into motion without setting a model (state machine). - */ - private static final String ERR_NO_STATE_MACHINE = "SCXMLExecutor: State machine not set"; - - /** * The Logger for the SCXMLExecutor. */ private Log log = LogFactory.getLog(SCXMLExecutor.class); Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLIOProcessor.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLIOProcessor.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLIOProcessor.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLIOProcessor.java Sat Nov 15 03:07:58 2014 @@ -23,6 +23,21 @@ package org.apache.commons.scxml2; public interface SCXMLIOProcessor { /** + * The name of the default SCXML I/O Event Processor + */ + String DEFAULT_EVENT_PROCESSOR = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; + + /** + * Default SCXML I/O Event Processor alias + */ + String SCXML_EVENT_PROCESSOR = "scxml"; + + /** + * The name of the internal Event Processor + */ + String INTERNAL_EVENT_PROCESSOR = "#_internal"; + + /** * Send an event into the SCXML processor queue * <p> * @param event the event to send Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java Sat Nov 15 03:07:58 2014 @@ -141,6 +141,11 @@ public class SCXMLSystemContext implemen return systemContext.getParent(); } + @Override + public SCXMLSystemContext getSystemContext() { + return this; + } + /** * @return Returns the wrapped (modifiable) system context */ Added: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/XPathBuiltin.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/XPathBuiltin.java?rev=1639829&view=auto ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/XPathBuiltin.java (added) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/XPathBuiltin.java Sat Nov 15 03:07:58 2014 @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.scxml2; + +import org.apache.commons.scxml2.env.xpath.XPathEvaluator; + +/** + * Implementation and support of Commons SCXML builtin predicates to support XPath based datamodel operations + * for non-XPath languages. + * + * These static builtin functions delegate to a static {@link }XPathEvaluator} instance. + */ +public class XPathBuiltin { + + private static XPathEvaluator evaluator = new XPathEvaluator(); + + /** + * Optional static setter to change and override the default {@link XPathEvaluator} + * @param evaluator A custom evaluator to be used + */ + public static void setEvaluator(XPathEvaluator evaluator) { + XPathBuiltin.evaluator = evaluator; + } + + /** + * Evaluate an xpath expression returning a data value + * + * @param ctx variable context + * @param expression xpath expression + * @return the result of the evaluation + * @throws SCXMLExpressionException A malformed expression exception + * @see Evaluator#eval(Context, String) + */ + public static Object eval(Context ctx, String expression) throws SCXMLExpressionException { + return evaluator.eval(ctx, expression); + } + + /** + * Evaluate an xpath location that returns a data assignable reference or list of references. + * Manifests as "location" attributes of <assign> element. + * + * @param ctx variable context + * @param expression expression + * @return The location result. + * @throws SCXMLExpressionException A malformed expression exception + * @see Evaluator#evalLocation(Context, String) + */ + public static Object evalLocation(Context ctx, String expression) throws SCXMLExpressionException { + return evaluator.evalLocation(ctx, expression); + } + + /** + * Determine if an {@link Evaluator#evalLocation(Context, String)} returned result represents an XPath location + * @param ctx variable context + * @param data result data from {@link Evaluator#evalLocation(Context, String)} + * @return true if the data represents an XPath location + * @see XPathEvaluator#isXPathLocation(Context, Object) + */ + public static boolean isXPathLocation(Context ctx, Object data) { + return evaluator.isXPathLocation(ctx, data); + } + + /** + * Assigns data to a location + * + * @param ctx variable context + * @param location location expression + * @param data the data to assign. + * @param type the type of assignment to perform, null assumes {@link Evaluator.AssignType#REPLACE_CHILDREN} + * @param attr the name of the attribute to add when using type {@link Evaluator.AssignType#ADD_ATTRIBUTE} + * @throws SCXMLExpressionException A malformed expression exception + * @see Evaluator#evalAssign(Context, String, Object, Evaluator.AssignType, String) + * @see XPathEvaluator#assign(Context, Object, Object, Evaluator.AssignType, String) + */ + public static void assign(Context ctx, Object location, Object data, Evaluator.AssignType type, String attr) + throws SCXMLExpressionException { + evaluator.assign(ctx, location, data, type, attr); + } +} Propchange: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/XPathBuiltin.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/XPathBuiltin.java ------------------------------------------------------------------------------ svn:keywords = Id Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java Sat Nov 15 03:07:58 2014 @@ -41,6 +41,8 @@ public class SimpleContext implements Co /** The Map of variables and their values in this Context. */ private Map<String, Object> vars; + protected final SCXMLSystemContext systemContext; + /** * Constructor. * @@ -57,14 +59,6 @@ public class SimpleContext implements Co public SimpleContext(final Context parent) { this(parent, null); } - /** - * Constructor. - * - * @param initialVars A pre-populated initial variables map - */ - public SimpleContext(final Map<String, Object> initialVars) { - this(null, initialVars); - } /** * Constructor. @@ -74,6 +68,8 @@ public class SimpleContext implements Co */ public SimpleContext(final Context parent, final Map<String, Object> initialVars) { this.parent = parent; + this.systemContext = parent instanceof SCXMLSystemContext ? + (SCXMLSystemContext) parent : parent != null ? parent.getSystemContext() : null; if (initialVars == null) { setVars(new HashMap<String, Object>()); } else { @@ -160,6 +156,15 @@ public class SimpleContext implements Co } /** + * Get the SCXMLSystemContext for this Context, should not be null unless this is the root Context + * + * @return The SCXMLSystemContext in a chained Context environment + */ + public final SCXMLSystemContext getSystemContext() { + return systemContext; + } + + /** * Assigns a new value to an existing variable or creates a new one. * The method allows to shaddow a variable of the same name up the * Context chain. Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java Sat Nov 15 03:07:58 2014 @@ -17,31 +17,125 @@ package org.apache.commons.scxml2.env; import java.io.Serializable; -import java.util.List; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.scxml2.EventDispatcher; -import org.w3c.dom.Node; +import org.apache.commons.scxml2.SCXMLIOProcessor; +import org.apache.commons.scxml2.TriggerEvent; /** - * Trivial EventDispatcher implementation. - * No remote eventing. + * <p>EventDispatcher implementation that can schedule <code>delay</code>ed + * <send> events for the "scxml" <code>type</code> + * attribute value (which is also the default). This implementation uses + * J2SE <code>Timer</code>s.</p> + * + * <p>No other <code>type</code>s are processed. Subclasses may support + * additional <code>type</code>s by overriding the + * <code>send(...)</code> and <code>cancel(...)</code> methods and + * delegating to their <code>super</code> counterparts for the + * "scxml" <code>type</code>.</p> * */ -public final class SimpleDispatcher implements EventDispatcher, Serializable { +public class SimpleDispatcher implements EventDispatcher, Serializable { /** Serial version UID. */ private static final long serialVersionUID = 1L; + + /** + * TimerTask implementation. + */ + class DelayedEventTask extends TimerTask { + + /** + * The ID of the <send> element. + */ + private String id; + + /** + * The event name. + */ + private String event; + + /** + * The event payload, if any. + */ + private Object payload; + + /** + * The target io processor + */ + private SCXMLIOProcessor target; + + /** + * Constructor for events with payload. + * + * @param id The ID of the send element. + * @param event The name of the event to be triggered. + * @param payload The event payload, if any. + * @param target The target io processor + */ + DelayedEventTask(final String id, final String event, final Object payload, SCXMLIOProcessor target) { + super(); + this.id = id; + this.event = event; + this.payload = payload; + this.target = target; + } + + /** + * What to do when timer expires. + */ + @Override + public void run() { + timers.remove(id); + target.addEvent(new TriggerEvent(event, TriggerEvent.SIGNAL_EVENT, payload)); + if (log.isDebugEnabled()) { + log.debug("Fired event '" + event + "' as scheduled by " + + "<send> with id '" + id + "'"); + } + } + } + /** Implementation independent log category. */ private Log log = LogFactory.getLog(EventDispatcher.class); /** - * Constructor. + * The <code>Map</code> of active <code>Timer</code>s, keyed by + * <send> element <code>id</code>s. + */ + private Map<String, Timer> timers = Collections.synchronizedMap(new HashMap<String, Timer>()); + + /** + * Get the log instance. + * + * @return The current log instance + */ + protected Log getLog() { + return log; + } + + /** + * Sets the log instance + * + * @param log the new log instance + */ + protected void setLog(Log log) { + this.log = log; + } + + /** + * Get the current timers. + * + * @return The currently scheduled timers */ - public SimpleDispatcher() { - super(); + protected Map<String, Timer> getTimers() { + return timers; } /** @@ -51,29 +145,94 @@ public final class SimpleDispatcher impl if (log.isInfoEnabled()) { log.info("cancel( sendId: " + sendId + ")"); } + if (!timers.containsKey(sendId)) { + return; // done, we don't track this one or its already expired + } + Timer timer = timers.get(sendId); + if (timer != null) { + timer.cancel(); + if (log.isDebugEnabled()) { + log.debug("Cancelled event scheduled by <send> with id '" + + sendId + "'"); + } + } + timers.remove(sendId); } /** - @see EventDispatcher#send(String,String,String,String,Map,Object,long,List) + @see EventDispatcher#send(java.util.Map, String, String, String, String, Object, Object, long) */ - public void send(final String id, final String target, - final String type, final String event, - final Map<String, Object> params, final Object hints, final long delay, - final List<Node> externalNodes) { + public void send(final Map<String, SCXMLIOProcessor> ioProcessors, final String id, final String target, + final String type, final String event, final Object data, final Object hints, final long delay) { if (log.isInfoEnabled()) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); buf.append("send ( id: ").append(id); buf.append(", target: ").append(target); buf.append(", type: ").append(type); buf.append(", event: ").append(event); - buf.append(", params: ").append(String.valueOf(params)); + buf.append(", data: ").append(String.valueOf(data)); buf.append(", hints: ").append(String.valueOf(hints)); buf.append(", delay: ").append(delay); buf.append(')'); log.info(buf.toString()); } - } + // We only handle the "scxml" type (which is the default too) and optionally the #_internal target + + if (type == null || type.equalsIgnoreCase(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR) || + type.equals(SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR)) { + + SCXMLIOProcessor ioProcessor; + + boolean internal = false; + if (target == null) { + ioProcessor = ioProcessors.get(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR); + } + else if (SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR.equals(target)) { + ioProcessor = ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR); + internal = true; + } + else { + // We know of no other target + if (log.isWarnEnabled()) { + log.warn("<send>: Unavailable target - " + target); + } + ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR). + addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT)); + return; // done + } + + if (event == null) { + if (log.isWarnEnabled()) { + log.warn("<send>: Cannot send without event name"); + } + ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR). + addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT)); + } + + else if (!internal && delay > 0L) { + // Need to schedule this one + Timer timer = new Timer(true); + timer.schedule(new DelayedEventTask(id, event, data, ioProcessor), delay); + timers.put(id, timer); + if (log.isDebugEnabled()) { + log.debug("Scheduled event '" + event + "' with delay " + + delay + "ms, as specified by <send> with id '" + + id + "'"); + } + } + else { + ioProcessor.addEvent(new TriggerEvent(event, TriggerEvent.SIGNAL_EVENT, data)); + } + } + else { + if (log.isWarnEnabled()) { + log.warn("<send>: Unsupported type - " + type); + } + ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR). + addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT)); + } + } } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java Sat Nov 15 03:07:58 2014 @@ -41,12 +41,6 @@ public class GroovyContext extends Simpl private static final Log log = LogFactory.getLog(GroovyContext.class); - /** - * Internal flag to indicate whether it is to evaluate a location - * that returns a Node within an XML data tree. - */ - private boolean evaluatingLocation = false; - private String scriptBaseClass; private GroovyEvaluator evaluator; private GroovyContextBinding binding; @@ -71,8 +65,8 @@ public class GroovyContext extends Simpl * * @param initialVars The initial set of variables. */ - public GroovyContext(final Map<String, Object> initialVars, GroovyEvaluator evaluator) { - super(initialVars); + public GroovyContext(final Context parent, final Map<String, Object> initialVars, GroovyEvaluator evaluator) { + super(parent, initialVars); this.evaluator = evaluator; } @@ -94,22 +88,6 @@ public class GroovyContext extends Simpl this.evaluator = evaluator; } - /** - * Returns the internal flag to indicate whether it is to evaluate a location - * that returns a Node within an XML data tree. - */ - public boolean isEvaluatingLocation() { - return evaluatingLocation; - } - - /** - * Sets the internal flag to indicate whether it is to evaluate a location - * that returns a Node within an XML data tree. - */ - public void setEvaluatingLocation(boolean evaluatingLocation) { - this.evaluatingLocation = evaluatingLocation; - } - @Override public Map<String, Object> getVars() { return vars; Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java Sat Nov 15 03:07:58 2014 @@ -22,6 +22,7 @@ import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -30,9 +31,9 @@ import org.apache.commons.scxml2.Evaluat import org.apache.commons.scxml2.EvaluatorProvider; import org.apache.commons.scxml2.SCXMLExpressionException; import org.apache.commons.scxml2.SCXMLSystemContext; +import org.apache.commons.scxml2.XPathBuiltin; import org.apache.commons.scxml2.env.EffectiveContextMap; import org.apache.commons.scxml2.model.SCXML; -import org.w3c.dom.Node; /** * Evaluator implementation enabling use of Groovy expressions in SCXML documents. @@ -45,13 +46,18 @@ public class GroovyEvaluator implements /** Serial version UID. */ private static final long serialVersionUID = 1L; - private static final String SUPPORTED_DATAMODEL = "groovy"; + /** + * Unique context variable name used for temporary reference to assign data (thus must be a valid variable name) + */ + private static final String ASSIGN_VARIABLE_NAME = "a"+UUID.randomUUID().toString().replace('-','x'); + + public static final String SUPPORTED_DATA_MODEL = "groovy"; public static class GroovyEvaluatorProvider implements EvaluatorProvider { @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } @Override @@ -153,7 +159,7 @@ public class GroovyEvaluator implements @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } /** @@ -175,13 +181,14 @@ public class GroovyEvaluator implements throw new SCXMLExpressionException(ERR_CTX_TYPE); } - GroovyContext groovyCtx = (GroovyContext) ctx; + final GroovyContext groovyCtx = (GroovyContext) ctx; if (groovyCtx.getGroovyEvaluator() == null) { groovyCtx.setGroovyEvaluator(this); } try { return getScript(getEffectiveContext(groovyCtx), groovyCtx.getScriptBaseClass(), expr).run(); - } catch (Exception e) { + } + catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("eval('" + expr + "'): " + exMessage, e); } @@ -200,12 +207,13 @@ public class GroovyEvaluator implements throw new SCXMLExpressionException(ERR_CTX_TYPE); } - GroovyContext groovyCtx = (GroovyContext) ctx; + final GroovyContext groovyCtx = (GroovyContext) ctx; if (groovyCtx.getGroovyEvaluator() == null) { groovyCtx.setGroovyEvaluator(this); } try { - return (Boolean)getScript(getEffectiveContext(groovyCtx), groovyCtx.getScriptBaseClass(), expr).run(); + final Object result = getScript(getEffectiveContext(groovyCtx), groovyCtx.getScriptBaseClass(), expr).run(); + return result == null ? Boolean.FALSE : (Boolean)result; } catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("evalCond('" + expr + "'): " + exMessage, e); @@ -216,10 +224,13 @@ public class GroovyEvaluator implements * @see Evaluator#evalLocation(Context, String) */ @Override - public Node evalLocation(final Context ctx, final String expr) throws SCXMLExpressionException { + public Object evalLocation(final Context ctx, final String expr) throws SCXMLExpressionException { if (expr == null) { return null; } + else if (ctx.has(expr)) { + return expr; + } if (!(ctx instanceof GroovyContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); @@ -231,8 +242,7 @@ public class GroovyEvaluator implements } try { final GroovyContext effective = getEffectiveContext(groovyCtx); - effective.setEvaluatingLocation(true); - return (Node)getScript(effective, groovyCtx.getScriptBaseClass(), expr).run(); + return getScript(effective, groovyCtx.getScriptBaseClass(), expr).run(); } catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("evalLocation('" + expr + "'): " + exMessage, e); @@ -240,6 +250,34 @@ public class GroovyEvaluator implements } /** + * @see Evaluator#evalAssign(Context, String, Object, AssignType, String) + */ + public void evalAssign(final Context ctx, final String location, final Object data, final AssignType type, + final String attr) throws SCXMLExpressionException { + + final Object loc = evalLocation(ctx, location); + if (loc != null) { + + if (XPathBuiltin.isXPathLocation(ctx, loc)) { + XPathBuiltin.assign(ctx, loc, data, type, attr); + } + else { + final StringBuilder sb = new StringBuilder(location).append("=").append(ASSIGN_VARIABLE_NAME); + try { + ctx.getVars().put(ASSIGN_VARIABLE_NAME, data); + eval(ctx, sb.toString()); + } + finally { + ctx.getVars().remove(ASSIGN_VARIABLE_NAME); + } + } + } + else { + throw new SCXMLExpressionException("evalAssign - cannot resolve location: '" + location + "'"); + } + } + + /** * @see Evaluator#evalScript(Context, String) */ @Override @@ -252,22 +290,21 @@ public class GroovyEvaluator implements throw new SCXMLExpressionException(ERR_CTX_TYPE); } - GroovyContext groovyCtx = (GroovyContext) ctx; + final GroovyContext groovyCtx = (GroovyContext) ctx; if (groovyCtx.getGroovyEvaluator() == null) { groovyCtx.setGroovyEvaluator(this); } try { final GroovyContext effective = getEffectiveContext(groovyCtx); - effective.setEvaluatingLocation(true); - boolean inGlobalContext = groovyCtx.getParent() instanceof SCXMLSystemContext; - Script script = getScript(effective, groovyCtx.getScriptBaseClass(), scriptSource); - Object result = script.run(); + final boolean inGlobalContext = groovyCtx.getParent() instanceof SCXMLSystemContext; + final Script script = getScript(effective, groovyCtx.getScriptBaseClass(), scriptSource); + final Object result = script.run(); if (inGlobalContext && useInitialScriptAsBaseScript) { groovyCtx.setScriptBaseClass(script.getClass().getName()); } return result; } catch (Exception e) { - String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); + final String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("evalScript('" + scriptSource + "'): " + exMessage, e); } } @@ -297,7 +334,7 @@ public class GroovyEvaluator implements * @return The effective GroovyContext for the path leading up to * document root. */ - private GroovyContext getEffectiveContext(final GroovyContext nodeCtx) { - return new GroovyContext(new EffectiveContextMap(nodeCtx), this); + protected GroovyContext getEffectiveContext(final GroovyContext nodeCtx) { + return new GroovyContext(nodeCtx, new EffectiveContextMap(nodeCtx), this); } } \ No newline at end of file Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java Sat Nov 15 03:07:58 2014 @@ -19,20 +19,19 @@ package org.apache.commons.scxml2.env.gr import java.lang.reflect.Array; import java.util.Collection; import java.util.Map; -import java.util.Set; import org.apache.commons.scxml2.Builtin; -import org.apache.commons.scxml2.Context; -import org.apache.commons.scxml2.SCXMLSystemContext; -import org.apache.commons.scxml2.model.EnterableState; +import org.apache.commons.scxml2.SCXMLExpressionException; +import org.apache.commons.scxml2.XPathBuiltin; import groovy.lang.Binding; import groovy.lang.MissingPropertyException; import groovy.lang.Script; /** - * Groovy {@link Script} base class for SCXML, providing the standard 'builtin' functions {@link #In(String)} and {@link #Data(Object, String)} - * as well as JEXL like convenience functions {@link #empty(Object)} and {@link #var(String)}. + * Groovy {@link Script} base class for SCXML, providing the standard 'builtin' functions {@link #In(String)}, + * {@link #Data(String)} and {@link #Location(String)}, as well as JEXL like convenience functions + * {@link #empty(Object)} and {@link #var(String)}. */ public abstract class GroovySCXMLScript extends Script { @@ -51,46 +50,30 @@ public abstract class GroovySCXMLScript } /** - * Gets the ALL_NAMESPACES map from context. - * @return the ALL_NAMESPACES map - */ - @SuppressWarnings("unchecked") - private Map<String, String> getNamespaces() { - return (Map<String, String>) context.get(Context.NAMESPACES_KEY); - } - - /** - * Gets the ALL_STATES set from context. - * @return the ALL_STATES set + * Implements the In() predicate for SCXML documents ( see Builtin#isMember ) + * @param state The State ID to compare with + * @return Whether this State belongs to this Set */ - @SuppressWarnings("unchecked") - private Set<EnterableState> getAllStates() { - return (Set<EnterableState>) context.get(SCXMLSystemContext.ALL_STATES_KEY); + public boolean In(final String state) { + return Builtin.isMember(context, state); } /** - * Implements the Data() predicate for SCXML documents ( see Builtin#data ). - * @param data the context node - * @param path the XPath expression - * @return the first node matching the path + * Implements the Data() predicate for SCXML documents. + * @param expression the XPath expression + * @return the data matching the expression */ - public Object Data(final Object data, final String path) { - // first call maps delegates to dataNode(), subsequent ones to data() - if (context.isEvaluatingLocation()) { - context.setEvaluatingLocation(false); - return Builtin.dataNode(getNamespaces(), data, path); - } else { - return Builtin.data(getNamespaces(), data, path); - } + public Object Data(final String expression) throws SCXMLExpressionException { + return XPathBuiltin.eval(context, expression); } /** - * Implements the In() predicate for SCXML documents ( see Builtin#isMember ) - * @param state The State ID to compare with - * @return Whether this State belongs to this Set + * Implements the Location() predicate for SCXML documents. + * @param location the XPath expression + * @return the location list for the location expression */ - public boolean In(final String state) { - return Builtin.isMember(getAllStates(), state); + public Object Location(final String location) throws SCXMLExpressionException { + return XPathBuiltin.evalLocation(context, location); } /**