Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java Sat Nov 15 03:07:58 2014 @@ -17,6 +17,7 @@ package org.apache.commons.scxml2.env.javascript; +import java.util.UUID; import java.util.regex.Pattern; import javax.script.Bindings; @@ -24,14 +25,12 @@ import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; -import org.apache.commons.scxml2.Builtin; import org.apache.commons.scxml2.Context; import org.apache.commons.scxml2.Evaluator; 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.model.SCXML; -import org.w3c.dom.Node; /** * Embedded JavaScript expression evaluator for SCXML expressions. This @@ -47,15 +46,18 @@ import org.w3c.dom.Node; public class JSEvaluator implements Evaluator { - // CONSTANTS + /** + * 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'); - private static final String SUPPORTED_DATAMODEL = "ecmascript"; + public static final String SUPPORTED_DATA_MODEL = Evaluator.ECMASCRIPT_DATA_MODEL; public static class JSEvaluatorProvider implements EvaluatorProvider { @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } @Override @@ -73,6 +75,8 @@ public class JSEvaluator implements Eval private static final Pattern IN_FN = Pattern.compile("In\\("); /** Pattern for recognizing the Commons SCXML Data() builtin function. */ private static final Pattern DATA_FN = Pattern.compile("Data\\("); + /** Pattern for recognizing the Commons SCXML Location() builtin function. */ + private static final Pattern LOCATION_FN = Pattern.compile("Location\\("); // INSTANCE VARIABLES @@ -85,17 +89,15 @@ public class JSEvaluator implements Eval */ public JSEvaluator() { factory = new ScriptEngineManager(); - factory.put("_builtin", new Builtin()); } // INSTANCE METHODS @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } - /** * Creates a child context. * @@ -130,11 +132,14 @@ public class JSEvaluator implements Eval Bindings bindings = engine.getBindings (ScriptContext.ENGINE_SCOPE); // ... replace built-in functions - String jsExpression = IN_FN.matcher(expression).replaceAll("_builtin.isMember("+SCXMLSystemContext.ALL_STATES_KEY +", "); - jsExpression = DATA_FN.matcher(jsExpression).replaceAll("_builtin.data("+Context.NAMESPACES_KEY+", "); + String jsExpression = IN_FN.matcher(expression).replaceAll("_builtin.In("); + jsExpression = DATA_FN.matcher(jsExpression).replaceAll("_builtin.Data("); + jsExpression = LOCATION_FN.matcher(jsExpression).replaceAll("_builtin.Location("); // ... evaluate - return engine.eval(jsExpression,new JSBindings(context,bindings)); + JSBindings jsBindings = new JSBindings(context, bindings); + jsBindings.put("_builtin", new JSFunctions(context)); + return engine.eval(jsExpression,jsBindings); } catch (Exception x) { throw new SCXMLExpressionException("Error evaluating ['" + expression + "'] " + x); @@ -155,14 +160,14 @@ public class JSEvaluator implements Eval */ @Override public Boolean evalCond(Context context,String expression) throws SCXMLExpressionException { - Object object; + final Object result = eval(context,expression); - if ((object = eval(context,expression)) == null) { + if (result == null) { return Boolean.FALSE; } - if (object instanceof Boolean) { - return (Boolean) object; + if (result instanceof Boolean) { + return (Boolean)result; } throw new SCXMLExpressionException("Invalid boolean expression: " + expression); @@ -181,23 +186,41 @@ public class JSEvaluator implements Eval * @throws SCXMLExpressionException Thrown if the expression was invalid. */ @Override - public Node evalLocation(Context context,String expression) throws SCXMLExpressionException { - try { - - // ... initialize - ScriptEngine engine = factory.getEngineByName("JavaScript"); - Bindings bindings = engine.getBindings (ScriptContext.ENGINE_SCOPE); + public Object evalLocation(Context context,String expression) throws SCXMLExpressionException { + if (expression == null) { + return null; + } + else if (context.has(expression)) { + return expression; + } + return eval(context, expression); + } - // ... replace built-in functions - String jsExpression = IN_FN.matcher(expression).replaceAll("_builtin.isMember(_ALL_STATES, "); - jsExpression = DATA_FN.matcher(jsExpression).replaceFirst("_builtin.dataNode("+Context.NAMESPACES_KEY+", "); - jsExpression = DATA_FN.matcher(jsExpression).replaceAll("_builtin.data("+Context.NAMESPACES_KEY+", "); + /** + * @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 { - // ... evaluate - return (Node) engine.eval(jsExpression,new JSBindings(context,bindings)); + Object loc = evalLocation(ctx, location); + if (loc != null) { - } catch (Exception x) { - throw new SCXMLExpressionException("Error evaluating ['" + expression + "'] " + x); + if (XPathBuiltin.isXPathLocation(ctx, loc)) { + XPathBuiltin.assign(ctx, loc, data, type, attr); + } + else { + 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 + "'"); } } @@ -220,6 +243,4 @@ public class JSEvaluator implements Eval throws SCXMLExpressionException { return eval(ctx, script); } - } -
Added: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java?rev=1639829&view=auto ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java (added) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java Sat Nov 15 03:07:58 2014 @@ -0,0 +1,69 @@ +/* + * 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.env.javascript; + +import org.apache.commons.scxml2.Builtin; +import org.apache.commons.scxml2.Context; +import org.apache.commons.scxml2.SCXMLExpressionException; +import org.apache.commons.scxml2.XPathBuiltin; + +/** + * Custom Javascript engine function providing the SCXML In() predicate and the Commons SCXML extensions + * for Data() and Location() to support XPath datamodel access. + */ +public class JSFunctions { + + /** + * The context currently in use for evaluation. + */ + private Context ctx; + + /** + * Creates a new instance, wraps the context. + * @param ctx the context in use + */ + public JSFunctions(Context ctx) { + this.ctx = ctx; + } + + /** + * Provides the SCXML standard In() predicate for SCXML documents. + * @param state The State ID to compare with + * @return true if this state is currently active + */ + public boolean In(final String state) { + return Builtin.isMember(ctx, state); + } + + /** + * Provides the Commons SCXML Data() predicate extension for SCXML documents. + * @param expression the XPath expression + * @return the data matching the expression + */ + public Object Data(String expression) throws SCXMLExpressionException { + return XPathBuiltin.eval(ctx, expression); + } + + /** + * Provides the Commons SCXML Location() predicate extension for SCXML documents. + * @param expression the XPath expression + * @return the location matching the expression + */ + public Object Location(String expression) throws SCXMLExpressionException { + return XPathBuiltin.evalLocation(ctx, expression); + } +} Propchange: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java ------------------------------------------------------------------------------ svn:keywords = Id Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlBuiltin.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlBuiltin.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlBuiltin.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlBuiltin.java Sat Nov 15 03:07:58 2014 @@ -16,17 +16,13 @@ */ package org.apache.commons.scxml2.env.jexl; -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; /** - * Global JEXL namespace functor, implements Data() and In() operators. - * Cooperates with JexlContext. + * Global JEXL namespace functor, providing the standard SCXML In() operator and the Commons SCXML extensions + * for Data() and Location() to support XPath datamodel access. */ public final class JexlBuiltin { /** @@ -43,45 +39,29 @@ public final class JexlBuiltin { } /** - * 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 + * Provides the SCXML standard In() predicate for SCXML documents. + * @param state The State ID to compare with + * @return true if this state is currently active */ - @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 + * Provides the Commons SCXML Data() predicate extension 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 + * Provides the Commons SCXML Location() predicate extension for SCXML documents. + * @param expression the XPath expression + * @return the location matching the expression */ - public boolean In(final String state) { - return Builtin.isMember(getAllStates(), state); + public Object Location(final String expression) throws SCXMLExpressionException { + return XPathBuiltin.evalLocation(context, expression); } } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java Sat Nov 15 03:07:58 2014 @@ -32,12 +32,6 @@ public class JexlContext extends SimpleC private static final long serialVersionUID = 1L; /** - * Internal flag to indicate whether it is to evaluate a location - * that returns a Node within an XML data tree. - */ - private boolean evaluatingLocation = false; - - /** * Constructor. */ public JexlContext() { @@ -46,11 +40,11 @@ public class JexlContext extends SimpleC /** * Constructor with initial vars. - * + * @param parent The parent context * @param initialVars The initial set of variables. */ - public JexlContext(final Map<String, Object> initialVars) { - super(initialVars); + public JexlContext(final Context parent, final Map<String, Object> initialVars) { + super(parent, initialVars); } /** @@ -61,22 +55,5 @@ public class JexlContext extends SimpleC public JexlContext(final Context parent) { super(parent); } - - /** - * 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; - } - } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java Sat Nov 15 03:07:58 2014 @@ -19,6 +19,7 @@ package org.apache.commons.scxml2.env.je import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.apache.commons.jexl2.Expression; import org.apache.commons.jexl2.JexlEngine; @@ -27,9 +28,9 @@ import org.apache.commons.scxml2.Context import org.apache.commons.scxml2.Evaluator; import org.apache.commons.scxml2.EvaluatorProvider; import org.apache.commons.scxml2.SCXMLExpressionException; +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 JEXL expressions in @@ -44,13 +45,18 @@ public class JexlEvaluator implements Ev /** Serial version UID. */ private static final long serialVersionUID = 1L; - private static final String SUPPORTED_DATAMODEL = "jexl"; + /** + * 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 = "jexl"; public static class JexlEvaluatorProvider implements EvaluatorProvider { @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } @Override @@ -134,7 +140,7 @@ public class JexlEvaluator implements Ev @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } /** @@ -151,16 +157,12 @@ public class JexlEvaluator implements Ev if (expr == null) { return null; } - JexlContext jexlCtx = null; - if (ctx instanceof JexlContext) { - jexlCtx = (JexlContext) ctx; - } else { + if (!(ctx instanceof JexlContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } - Expression exp = null; try { - final JexlContext effective = getEffectiveContext(jexlCtx); - exp = getJexlEngine().createExpression(expr); + final JexlContext effective = getEffectiveContext((JexlContext)ctx); + Expression exp = getJexlEngine().createExpression(expr); return exp.evaluate(effective); } catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); @@ -176,17 +178,14 @@ public class JexlEvaluator implements Ev if (expr == null) { return null; } - JexlContext jexlCtx = null; - if (ctx instanceof JexlContext) { - jexlCtx = (JexlContext) ctx; - } else { + if (!(ctx instanceof JexlContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } - Expression exp = null; try { - final JexlContext effective = getEffectiveContext(jexlCtx); - exp = getJexlEngine().createExpression(expr); - return (Boolean) exp.evaluate(effective); + final JexlContext effective = getEffectiveContext((JexlContext)ctx); + Expression exp = getJexlEngine().createExpression(expr); + final Object result = exp.evaluate(effective); + 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); @@ -196,23 +195,22 @@ public class JexlEvaluator implements Ev /** * @see Evaluator#evalLocation(Context, String) */ - public Node evalLocation(final Context ctx, final String expr) + public Object evalLocation(final Context ctx, final String expr) throws SCXMLExpressionException { if (expr == null) { return null; } - JexlContext jexlCtx = null; - if (ctx instanceof JexlContext) { - jexlCtx = (JexlContext) ctx; - } else { + else if (ctx.has(expr)) { + return expr; + } + + if (!(ctx instanceof JexlContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } - Expression exp = null; try { - final JexlContext effective = getEffectiveContext(jexlCtx); - effective.setEvaluatingLocation(true); - exp = getJexlEngine().createExpression(expr); - return (Node) exp.evaluate(effective); + final JexlContext effective = getEffectiveContext((JexlContext)ctx); + Expression exp = getJexlEngine().createExpression(expr); + return exp.evaluate(effective); } catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("evalLocation('" + expr + "'): " + exMessage, e); @@ -220,6 +218,34 @@ public class JexlEvaluator implements Ev } /** + * @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 { + + Object loc = evalLocation(ctx, location); + if (loc != null) { + + if (XPathBuiltin.isXPathLocation(ctx, loc)) { + XPathBuiltin.assign(ctx, loc, data, type, attr); + } + else { + 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) */ public Object evalScript(final Context ctx, final String script) @@ -227,17 +253,12 @@ public class JexlEvaluator implements Ev if (script == null) { return null; } - JexlContext jexlCtx = null; - if (ctx instanceof JexlContext) { - jexlCtx = (JexlContext) ctx; - } else { + if (!(ctx instanceof JexlContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } - Script jexlScript = null; try { - final JexlContext effective = getEffectiveContext(jexlCtx); - effective.setEvaluatingLocation(true); - jexlScript = getJexlEngine().createScript(script); + final JexlContext effective = getEffectiveContext((JexlContext) ctx); + final Script jexlScript = getJexlEngine().createScript(script); return jexlScript.execute(effective); } catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); @@ -260,7 +281,7 @@ public class JexlEvaluator implements Ev * Create the internal JexlEngine member during the initialization. * This method can be overriden to specify more detailed options * into the JexlEngine. - * @return + * @return new JexlEngine instance */ protected JexlEngine createJexlEngine() { JexlEngine engine = new JexlEngine(); @@ -279,7 +300,7 @@ public class JexlEvaluator implements Ev * <P> * <EM>NOTE: The internal JexlEngine instance can be null when this is deserialized.</EM> * </P> - * @return + * @return the current JexlEngine */ private JexlEngine getJexlEngine() { JexlEngine engine = jexlEngine; @@ -305,8 +326,8 @@ public class JexlEvaluator implements Ev * @return The effective JexlContext for the path leading up to * document root. */ - private JexlContext getEffectiveContext(final JexlContext nodeCtx) { - return new JexlContext(new EffectiveContextMap(nodeCtx)); + protected JexlContext getEffectiveContext(final JexlContext nodeCtx) { + return new JexlContext(nodeCtx, new EffectiveContextMap(nodeCtx)); } } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathEvaluator.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathEvaluator.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathEvaluator.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathEvaluator.java Sat Nov 15 03:07:58 2014 @@ -17,6 +17,9 @@ package org.apache.commons.scxml2.env.xpath; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.Map; import org.apache.commons.jxpath.ClassFunctions; @@ -25,13 +28,19 @@ import org.apache.commons.jxpath.Functio import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.jxpath.JXPathException; import org.apache.commons.jxpath.PackageFunctions; +import org.apache.commons.jxpath.ri.model.NodePointer; +import org.apache.commons.jxpath.ri.model.VariablePointer; import org.apache.commons.scxml2.Context; import org.apache.commons.scxml2.Evaluator; import org.apache.commons.scxml2.EvaluatorProvider; import org.apache.commons.scxml2.SCXMLExpressionException; import org.apache.commons.scxml2.env.EffectiveContextMap; import org.apache.commons.scxml2.model.SCXML; +import org.w3c.dom.Attr; +import org.w3c.dom.CharacterData; +import org.w3c.dom.Element; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * <p>An {@link Evaluator} implementation for XPath environments.</p> @@ -44,13 +53,19 @@ public class XPathEvaluator implements E /** Serial version UID. */ private static final long serialVersionUID = -3578920670869493294L; - private static final String SUPPORTED_DATAMODEL = "xpath"; + public static final String SUPPORTED_DATA_MODEL = Evaluator.XPATH_DATA_MODEL; + + /** + * Internal 'marker' list used for collecting the NodePointer results of an {@link #evalLocation(Context, String)} + */ + private static class NodePointerList extends ArrayList<NodePointer> { + } public static class XPathEvaluatorProvider implements EvaluatorProvider { @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } @Override @@ -95,7 +110,7 @@ public class XPathEvaluator implements E @Override public String getSupportedDatamodel() { - return SUPPORTED_DATAMODEL; + return SUPPORTED_DATA_MODEL; } /** @@ -104,9 +119,15 @@ public class XPathEvaluator implements E @Override public Object eval(final Context ctx, final String expr) throws SCXMLExpressionException { - JXPathContext context = getContext(ctx); try { - return context.getValue(expr, String.class); + List list = getContext(ctx).selectNodes(expr); + if (list.isEmpty()) { + return null; + } + else if (list.size() == 1) { + return list.get(0); + } + return list; } catch (JXPathException xee) { throw new SCXMLExpressionException(xee.getMessage(), xee); } @@ -118,9 +139,8 @@ public class XPathEvaluator implements E @Override public Boolean evalCond(final Context ctx, final String expr) throws SCXMLExpressionException { - JXPathContext context = getContext(ctx); try { - return (Boolean)context.getValue(expr, Boolean.class); + return (Boolean)getContext(ctx).getValue(expr, Boolean.class); } catch (JXPathException xee) { throw new SCXMLExpressionException(xee.getMessage(), xee); } @@ -130,17 +150,43 @@ public class XPathEvaluator implements E * @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 { JXPathContext context = getContext(ctx); try { - return (Node)context.selectSingleNode(expr); + Iterator iterator = context.iteratePointers(expr); + Object pointer; + NodePointerList pointerList = null; + while (iterator.hasNext()) { + pointer = iterator.next(); + if (pointer != null && pointer instanceof NodePointer && ((NodePointer)pointer).getNode() != null) { + if (pointerList == null) { + pointerList = new NodePointerList(); + } + pointerList.add((NodePointer)pointer); + } + } + return pointerList; } catch (JXPathException xee) { throw new SCXMLExpressionException(xee.getMessage(), xee); } } /** + * @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 { + + Object loc = evalLocation(ctx, location); + if (isXPathLocation(ctx, loc)) { + assign(ctx, loc, data, type, attr); + } + else { + throw new SCXMLExpressionException("evalAssign - cannot resolve location: '" + location + "'"); + } + } + + /** * @see Evaluator#evalScript(Context, String) */ public Object evalScript(Context ctx, String script) @@ -156,9 +202,169 @@ public class XPathEvaluator implements E return new XPathContext(parent); } + /** + * 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 + */ + @SuppressWarnings("unused") + public boolean isXPathLocation(final Context ctx, Object data) { + return data instanceof NodePointerList; + } + + /** + * 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) + */ + public void assign(final Context ctx, final Object location, final Object data, final AssignType type, + final String attr) throws SCXMLExpressionException { + if (!isXPathLocation(ctx, location)) { + throw new SCXMLExpressionException("assign requires a NodePointerList as location but is of type: " + + (location==null ? "(null)" : location.getClass().getName())); + } + for (NodePointer pointer : (NodePointerList)location) { + Object node = pointer.getNode(); + if (node != null) { + if (node instanceof Node) { + assign(ctx, (Node)node, pointer.asPath(), data, type != null ? type : AssignType.REPLACE_CHILDREN, attr); + } + else if (pointer instanceof VariablePointer) { + if (type == AssignType.DELETE) { + pointer.remove(); + } + VariablePointer vp = (VariablePointer)pointer; + Object variable = vp.getNode(); + if (variable instanceof Node) { + assign(ctx, (Node)variable, pointer.asPath(), data, type != null ? type : AssignType.REPLACE_CHILDREN, attr); + } + else if (type == null || type == AssignType.REPLACE) { + String variableName = vp.getName().getName(); + if (data instanceof CharacterData) { + ctx.set(variableName, ((CharacterData)data).getNodeValue()); + } + else { + ctx.set(variableName, data); + } + } + else { + throw new SCXMLExpressionException("Unsupported assign type +" + + type.name()+" for XPath variable "+pointer.asPath()); + } + } + else { + throw new SCXMLExpressionException("Unsupported XPath location pointer " + + pointer.getClass().getName()+" for location "+pointer.asPath()); + } + } + // else: silent ignore - NodePointerList should not have pointers without node + } + } + + @SuppressWarnings("unused") + protected void assign(final Context ctx, final Node node, final String nodePath, final Object data, + final AssignType type, final String attr) throws SCXMLExpressionException { + + if (type == AssignType.DELETE) { + node.getParentNode().removeChild(node); + } + else if (node instanceof Element) { + Element element = (Element)node; + if (type == AssignType.ADD_ATTRIBUTE) { + if (attr == null) { + throw new SCXMLExpressionException("Missing required attribute name for adding attribute at " + + nodePath); + } + if (data == null) { + throw new SCXMLExpressionException("Missing required data value for adding attribute " + + attr + " to location " + nodePath); + } + element.setAttribute(attr, data.toString()); + } + else { + Node dataNode = null; + if (type != AssignType.REPLACE_CHILDREN) { + if (data == null) { + throw new SCXMLExpressionException("Missing required data value for assign type "+type.name()); + } + dataNode = data instanceof Node + ? element.getOwnerDocument().importNode((Node)data, true) + : element.getOwnerDocument().createTextNode(data.toString()); + } + switch (type) { + case REPLACE_CHILDREN: + // quick way to delete all children + element.setTextContent(null); + if (data instanceof Node) { + element.appendChild(element.getOwnerDocument().importNode((Node)data, true)); + } + else if (data instanceof List) { + for (Object dataElement : (List)data) { + if (dataElement instanceof Node) { + element.appendChild(element.getOwnerDocument().importNode((Node)dataElement, true)); + } + else if (dataElement != null) { + element.appendChild(element.getOwnerDocument().createTextNode(dataElement.toString())); + } + } + } + else if (data instanceof NodeList) { + NodeList list = (NodeList)data; + for (int i = 0, size = list.getLength(); i < size; i++) + element.appendChild(element.getOwnerDocument().importNode(list.item(i), true)); + } + else { + element.appendChild(element.getOwnerDocument().createTextNode(data.toString())); + } + // else if data == null: already taken care of above + break; + case FIRST_CHILD: + element.insertBefore(dataNode, element.getFirstChild()); + break; + case LAST_CHILD: + element.appendChild(dataNode); + break; + case PREVIOUS_SIBLING: + element.getParentNode().insertBefore(dataNode, element); + break; + case NEXT_SIBLING: + element.getParentNode().insertBefore(dataNode, element.getNextSibling()); + break; + case REPLACE: + element.getParentNode().replaceChild(dataNode, element); + break; + } + } + } + else if (node instanceof CharacterData) { + if (type != AssignType.REPLACE) { + throw new SCXMLExpressionException("Assign type "+ type.name() + + " not supported for character data node at " + nodePath); + } + ((CharacterData)node).setData(data.toString()); + } + else if (node instanceof Attr) { + if (type != AssignType.REPLACE) { + throw new SCXMLExpressionException("Assign type "+ type.name() + + " not supported for node attribute at " + nodePath); + } + ((Attr)node).setValue(data.toString()); + } + else { + throw new SCXMLExpressionException("Unsupported assign location Node type "+node.getNodeType()); + } + } + @SuppressWarnings("unchecked") - private JXPathContext getContext(final Context ctx) throws SCXMLExpressionException { + protected JXPathContext getContext(final Context ctx) throws SCXMLExpressionException { JXPathContext context = JXPathContext.newContext(jxpathContext, new EffectiveContextMap(ctx)); context.setVariables(new ContextVariables(ctx)); Map<String, String> namespaces = (Map<String, String>) ctx.get(Context.NAMESPACES_KEY); Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathFunctions.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathFunctions.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathFunctions.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/env/xpath/XPathFunctions.java Sat Nov 15 03:07:58 2014 @@ -25,14 +25,19 @@ import org.apache.commons.scxml2.SCXMLSy import org.apache.commons.scxml2.model.EnterableState; /** - * JXPath custom extension functions providing the SCXML In() function + * Commons JXPath custom extension function providing the SCXML In() predicate */ public class XPathFunctions { + /** + * Provides the SCXML standard In() predicate for SCXML documents. + * @param expressionContext The context currently in use for evaluation + * @param state The State ID to compare with + * @return true if this state is currently active + */ @SuppressWarnings("unchecked") public static boolean In(ExpressionContext expressionContext, String state) { Variables variables = expressionContext.getJXPathContext().getVariables(); - Set<EnterableState> allStates = (Set<EnterableState>) variables.getVariable(SCXMLSystemContext.ALL_STATES_KEY); - return Builtin.isMember(allStates, state); + return Builtin.isMember((Set<EnterableState>) variables.getVariable(SCXMLSystemContext.ALL_STATES_KEY), state); } } Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java Sat Nov 15 03:07:58 2014 @@ -68,6 +68,11 @@ import org.apache.commons.scxml2.Trigger public interface Invoker { /** + * @return get the invoke ID provided by the parent state machine executor + */ + String getInvokeId(); + + /** * Set the invoke ID provided by the parent state machine executor * Implementations must use this ID for constructing the event name for * the special "done" event (and optionally, for other event names Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java Sat Nov 15 03:07:58 2014 @@ -54,6 +54,14 @@ public class SimpleSCXMLInvoker implemen /** Cancellation status. */ private boolean cancelled; + + /** + * {@inheritDoc}. + */ + public String getInvokeId() { + return parentStateId; + } + /** * {@inheritDoc}. */ 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=1639829&r1=1639828&r2=1639829&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 Sat Nov 15 03:07:58 2014 @@ -109,20 +109,6 @@ final class ModelUpdater { + " belonging to {1}"; /** - * Error message when an <invoke> does not specify a "type" - * attribute. - */ - private static final String ERR_INVOKE_NO_TYPE = "{0} contains " - + "<invoke> with no \"type\" attribute specified."; - - /** - * Error message when an <invoke> does not specify a "src" - * or a "srcexpr" attribute. - */ - private static final String ERR_INVOKE_NO_SRC = "{0} contains " - + "<invoke> without a \"src\" or \"srcexpr\" attribute specified."; - - /** * Error message when an <invoke> specifies both "src" and "srcexpr" * attributes. */ @@ -290,12 +276,6 @@ final class ModelUpdater { } for (Invoke inv : state.getInvokes()) { - if (inv.getType() == null) { - logAndThrowModelError(ERR_INVOKE_NO_TYPE, new Object[] {getName(state)}); - } - if (inv.getSrc() == null && inv.getSrcexpr() == null) { - logAndThrowModelError(ERR_INVOKE_NO_SRC, new Object[] {getName(state)}); - } if (inv.getSrc() != null && inv.getSrcexpr() != null) { logAndThrowModelError(ERR_INVOKE_AMBIGUOUS_SRC, new Object[] {getName(state)}); } 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=1639829&r1=1639828&r2=1639829&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 Sat Nov 15 03:07:58 2014 @@ -47,6 +47,7 @@ import javax.xml.validation.SchemaFactor import javax.xml.validation.Validator; import org.apache.commons.logging.LogFactory; +import org.apache.commons.scxml2.Evaluator; import org.apache.commons.scxml2.PathResolver; import org.apache.commons.scxml2.env.SimpleErrorHandler; import org.apache.commons.scxml2.env.URLResolver; @@ -54,12 +55,15 @@ import org.apache.commons.scxml2.model.A import org.apache.commons.scxml2.model.ActionsContainer; import org.apache.commons.scxml2.model.Assign; import org.apache.commons.scxml2.model.Cancel; +import org.apache.commons.scxml2.model.Content; +import org.apache.commons.scxml2.model.ContentContainer; import org.apache.commons.scxml2.model.CustomAction; import org.apache.commons.scxml2.model.Data; import org.apache.commons.scxml2.model.Datamodel; import org.apache.commons.scxml2.model.Else; import org.apache.commons.scxml2.model.ElseIf; import org.apache.commons.scxml2.model.EnterableState; +import org.apache.commons.scxml2.model.ParamsContainer; import org.apache.commons.scxml2.model.TransitionalState; import org.apache.commons.scxml2.model.Raise; import org.apache.commons.scxml2.model.Executable; @@ -268,6 +272,7 @@ public final class SCXMLReader { //---- ATTRIBUTE NAMES ----// private static final String ATTR_ARRAY = "array"; + private static final String ATTR_ATTR = "attr"; private static final String ATTR_AUTOFORWARD = "autoforward"; private static final String ATTR_COND = "cond"; private static final String ATTR_DATAMODEL = "datamodel"; @@ -621,7 +626,7 @@ public final class SCXMLReader { private static void readSCXML(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml) throws IOException, ModelException, XMLStreamException { - scxml.setDatamodelType(readAV(reader, ATTR_DATAMODEL)); + scxml.setDatamodelName(readAV(reader, ATTR_DATAMODEL)); scxml.setExmode(readAV(reader, ATTR_EXMODE)); scxml.setInitial(readAV(reader, ATTR_INITIAL)); scxml.setName(readAV(reader, ATTR_NAME)); @@ -662,12 +667,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_SCXML.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -760,12 +760,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_STATE.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -850,12 +845,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_PARALLEL.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -913,12 +903,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_FINAL.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -952,7 +937,7 @@ public final class SCXMLReader { } // Parse external document - SCXML externalSCXML = null; + SCXML externalSCXML; try { externalSCXML = SCXMLReader.readInternal(configuration, new URL(location), null, null, null, null); } catch (Exception e) { @@ -1082,12 +1067,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_DATAMODEL.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -1115,7 +1095,7 @@ public final class SCXMLReader { datum.setId(readRequiredAV(reader, ELEM_DATA, ATTR_ID)); datum.setExpr(readAV(reader, ATTR_EXPR)); readNamespaces(configuration, datum); - datum.setNode(readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[] {"id"})); + datum.setNode(readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[]{"id"})); dm.addData(datum); } @@ -1156,7 +1136,7 @@ public final class SCXMLReader { } else if (ELEM_FINALIZE.equals(name)) { readFinalize(reader, configuration, parent, invoke); } else if (ELEM_CONTENT.equals(name)) { - readContent(reader, configuration, parent, invoke); + readContent(reader, configuration, invoke); } else { reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name); } @@ -1166,12 +1146,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_INVOKE.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -1184,19 +1159,36 @@ public final class SCXMLReader { * * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. * @param configuration The {@link Configuration} to use while parsing. - * @param parent The parent {@link Invoke} for this param. + * @param parent The parent {@link org.apache.commons.scxml2.model.ParamsContainer} for this param. * * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. */ private static void readParam(final XMLStreamReader reader, final Configuration configuration, - final Invoke parent) + final ParamsContainer parent) throws XMLStreamException, ModelException { Param param = new Param(); param.setName(readRequiredAV(reader, ELEM_PARAM, ATTR_NAME)); - param.setExpr(readAV(reader, ATTR_EXPR)); + String location = readAV(reader, ATTR_LOCATION); + String expr = readAV(reader, ATTR_EXPR); + if (expr != null) { + if (location != null) { + reportConflictingAttribute(reader, configuration, ELEM_PARAM, ATTR_LOCATION, ATTR_EXPR); + } + else { + param.setExpr(expr); + } + } + else if (location == null) { + // force error missing required location or expr: use location attr for this + param.setLocation(readRequiredAV(reader, ELEM_PARAM, ATTR_LOCATION)); + } + else { + param.setLocation(location); + } readNamespaces(configuration, param); - parent.addParam(param); + parent.getParams().add(param); + skipToEndElement(reader); } /** @@ -1226,16 +1218,32 @@ public final class SCXMLReader { * * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. * @param configuration The {@link Configuration} to use while parsing. - * @param state The {@link TransitionalState} which contains the parent {@link Invoke}. - * @param invoke The parent {@link Invoke} for this content. + * @param contentContainer The {@link ContentContainer} for this content. * * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. */ private static void readContent(final XMLStreamReader reader, final Configuration configuration, - final TransitionalState state, final Invoke invoke) + final ContentContainer contentContainer) throws XMLStreamException { - // TODO content support + Content content = new Content(); + content.setExpr(readAV(reader, ATTR_EXPR)); + if (content.getExpr() != null) { + skipToEndElement(reader); + } + else { + Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_CONTENT, new String[]{}); + if (body.hasChildNodes()) { + NodeList children = body.getChildNodes(); + if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) { + content.setBody(children.item(0).getNodeValue()); + } + else { + content.setBody(body); + } + } + } + contentContainer.setContent(content); } /** @@ -1274,12 +1282,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_INITIAL.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -1329,12 +1332,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && ELEM_HISTORY.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -1534,12 +1532,7 @@ public final class SCXMLReader { break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (XMLNS_SCXML.equals(nsURI) && end.equals(name)) { - break loop; - } - break; + break loop; default: } } @@ -1567,15 +1560,17 @@ public final class SCXMLReader { // In particular, the <send> and <raise> elements MUST NOT occur. reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_RAISE); } - - Raise raise = new Raise(); - raise.setEvent(readAV(reader, ATTR_EVENT)); - readNamespaces(configuration, raise); - raise.setParent(executable); - if (parent != null) { - parent.addAction(raise); - } else { - executable.addAction(raise); + else { + Raise raise = new Raise(); + raise.setEvent(readAV(reader, ATTR_EVENT)); + readNamespaces(configuration, raise); + raise.setParent(executable); + if (parent != null) { + parent.addAction(raise); + } else { + executable.addAction(raise); + } + skipToEndElement(reader); } } @@ -1626,6 +1621,7 @@ public final class SCXMLReader { readNamespaces(configuration, elseif); elseif.setParent(executable); iff.addAction(elseif); + skipToEndElement(reader); } /** @@ -1646,6 +1642,7 @@ public final class SCXMLReader { readNamespaces(configuration, els); els.setParent(executable); iff.addAction(els); + skipToEndElement(reader); } /** @@ -1702,6 +1699,7 @@ public final class SCXMLReader { } else { executable.addAction(log); } + skipToEndElement(reader); } /** @@ -1720,13 +1718,22 @@ public final class SCXMLReader { Assign assign = new Assign(); assign.setExpr(readAV(reader, ATTR_EXPR)); - assign.setName(readAV(reader, ATTR_NAME)); - if (assign.getName() != null && assign.getName().trim().length() > 0) { - // if 'non-standard' name attribute is defined, don't require location (as per the spec. 20130831) - assign.setLocation(readAV(reader, ATTR_LOCATION)); + assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION)); + String attrValue = readAV(reader, ATTR_TYPE); + if (attrValue != null) { + assign.setType(Evaluator.AssignType.fromValue(attrValue)); + if (assign.getType() == null) { + reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_TYPE, attrValue); + } } - else { - assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION)); + attrValue = readAV(reader, ATTR_ATTR); + if (attrValue != null) { + if (Evaluator.AssignType.ADD_ATTRIBUTE.equals(assign.getType())) { + assign.setAttr(attrValue); + } + else { + reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_ATTR, attrValue); + } } assign.setSrc(readAV(reader, ATTR_SRC)); assign.setPathResolver(configuration.pathResolver); @@ -1737,6 +1744,7 @@ public final class SCXMLReader { } else { executable.addAction(assign); } + skipToEndElement(reader); } /** @@ -1760,6 +1768,7 @@ public final class SCXMLReader { // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions. // In particular, the <send> and <raise> elements MUST NOT occur. reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_SEND); + return; } Send send = new Send(); @@ -1817,11 +1826,40 @@ public final class SCXMLReader { } readNamespaces(configuration, send); - Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_SEND, new String [] {}); - NodeList childNodes = body.getChildNodes(); - List<Node> externalNodes = send.getExternalNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - externalNodes.add(childNodes.item(i)); + loop : while (reader.hasNext()) { + String name, nsURI; + switch (reader.next()) { + case XMLStreamConstants.START_ELEMENT: + pushNamespaces(reader, configuration); + nsURI = reader.getNamespaceURI(); + name = reader.getLocalName(); + if (XMLNS_SCXML.equals(nsURI)) { + if (ELEM_PARAM.equals(name)) { + if (send.getContent() == null) { + readParam(reader, configuration, send); + } + else { + reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); + } + } else if (ELEM_CONTENT.equals(name)) { + if (send.getNamelist() == null && send.getParams().isEmpty()) { + readContent(reader, configuration, send); + } + else { + reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); + } + } else { + reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); + } + } else { + reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); + } + break; + case XMLStreamConstants.END_ELEMENT: + popNamespaces(reader, configuration); + break loop; + default: + } } send.setParent(executable); @@ -1854,6 +1892,7 @@ public final class SCXMLReader { } else { executable.addAction(cancel); } + skipToEndElement(reader); } /** @@ -1872,7 +1911,7 @@ public final class SCXMLReader { Script script = new Script(); readNamespaces(configuration, script); - script.setBody(readBody(reader, configuration, XMLNS_SCXML, ELEM_SCRIPT)); + script.setBody(readBody(reader)); script.setParent(executable); if (parent != null) { parent.addAction(script); @@ -1899,7 +1938,7 @@ public final class SCXMLReader { Script globalScript = new Script(); globalScript.setGlobalScript(true); readNamespaces(configuration, globalScript); - globalScript.setBody(readBody(reader, configuration, XMLNS_SCXML, ELEM_SCRIPT)); + globalScript.setBody(readBody(reader)); scxml.setGlobalScript(globalScript); } @@ -1927,6 +1966,7 @@ public final class SCXMLReader { } else { executable.addAction(var); } + skipToEndElement(reader); } /** @@ -1946,7 +1986,7 @@ public final class SCXMLReader { throws XMLStreamException { // Instantiate custom action - Object actionObject = null; + Object actionObject; String className = customAction.getActionClass().getName(); ClassLoader cl = configuration.customActionClassLoader; if (configuration.useContextClassLoaderForCustomActions) { @@ -1955,7 +1995,7 @@ public final class SCXMLReader { if (cl == null) { cl = SCXMLReader.class.getClassLoader(); } - Class<?> clazz = null; + Class<?> clazz; try { clazz = cl.loadClass(className); actionObject = clazz.newInstance(); @@ -1976,7 +2016,7 @@ public final class SCXMLReader { String name = reader.getAttributeLocalName(i); String value = reader.getAttributeValue(i); String setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); - Method method = null; + Method method; try { method = clazz.getMethod(setter, String.class); method.invoke(action, value); @@ -2002,6 +2042,9 @@ public final class SCXMLReader { externalNodes.add(childNodes.item(i)); } } + else { + skipToEndElement(reader); + } // Wire in the action and add to parent readNamespaces(configuration, action); @@ -2018,10 +2061,8 @@ public final class SCXMLReader { * * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. * @param configuration The {@link Configuration} to use while parsing. - * @param namespaceURI The namespace URI of the parent element (we will stop parsing into DOM nodes when we reach - * the corresponding end tag) - * @param localName The local name of the parent element (we will stop parsing into DOM nodes when we reach the - * corresponding end tag) + * @param namespaceURI The namespace URI of the parent element + * @param localName The local name of the parent element * @param attrs The attributes that will be read into the root DOM node. * * @return The parsed content as a DOM {@link Node}. @@ -2033,7 +2074,7 @@ public final class SCXMLReader { throws XMLStreamException { // Create a document in which to build the DOM node - Document document = null; + Document document; try { document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); } catch (ParserConfigurationException pce) { @@ -2042,18 +2083,15 @@ public final class SCXMLReader { // This root element will be returned, add any attributes as specified Element root = document.createElementNS(namespaceURI, localName); - for (int i = 0; i < attrs.length; i++) { - Attr attr = document.createAttributeNS(XMLNS_DEFAULT, attrs[i]); - attr.setValue(readAV(reader, attrs[i])); + for (final String attr1 : attrs) { + Attr attr = document.createAttributeNS(XMLNS_DEFAULT, attr1); + attr.setValue(readAV(reader, attr1)); root.setAttributeNodeNS(attr); } document.appendChild(root); - // <data> can have only one child element, flags to skip any text nodes on either side - boolean one = (XMLNS_SCXML.equals(namespaceURI) && ELEM_DATA.equals(localName)); - boolean text = false; + boolean children = false; Node parent = root; - String nodeNSURI = null, nodeName = null; // Convert stream to DOM node(s) while maintaining parent child relationships loop : while (reader.hasNext()) { @@ -2061,14 +2099,14 @@ public final class SCXMLReader { Node child = null; switch (reader.next()) { case XMLStreamConstants.START_ELEMENT: + if (!children && root.hasChildNodes()) { + // remove any children + root.setTextContent(null); + } + children = true; pushNamespaces(reader, configuration); nsURI = reader.getNamespaceURI(); name = reader.getLocalName(); - if (!text) { - nodeNSURI = nsURI; - nodeName = name; - text = true; - } Element elem = document.createElementNS(nsURI, name); for (int i = 0; i < reader.getAttributeCount(); i++) { nsURI = reader.getAttributeNamespace(i); @@ -2087,30 +2125,23 @@ public final class SCXMLReader { case XMLStreamConstants.SPACE: case XMLStreamConstants.CHARACTERS: case XMLStreamConstants.ENTITY_REFERENCE: - if (text || !one) { + if (!children || parent != root) { child = document.createTextNode(reader.getText()); } break; case XMLStreamConstants.CDATA: + children = true; child = document.createCDATASection(reader.getText()); break; case XMLStreamConstants.COMMENT: + children = true; child = document.createComment(reader.getText()); break; case XMLStreamConstants.END_ELEMENT: popNamespaces(reader, configuration); - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (namespaceURI.equals(nsURI) && localName.equals(name)) { - break loop; - } - if (((nodeNSURI == null && nsURI == null) || (nodeNSURI != null && nodeNSURI.equals(nsURI))) - && name.equals(nodeName)) { - text = false; - } parent = parent.getParentNode(); if (parent == document) { - parent = root; + break loop; } break; default: // rest is ignored @@ -2119,6 +2150,9 @@ public final class SCXMLReader { parent.appendChild(child); } } + if (!children && root.hasChildNodes()) { + root.setTextContent(root.getTextContent().trim()); + } return root; } @@ -2126,31 +2160,25 @@ public final class SCXMLReader { * Read the following body contents into a String. * * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. - * @param configuration The {@link Configuration} to use while parsing. - * @param namespaceURI The namespace URI of the parent element (we will stop reading content when we reach - * the corresponding end tag) - * @param localName The local name of the parent element (we will stop reading content when we reach the - * corresponding end tag) * * @return The body content read into a String. * * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. */ - private static String readBody(final XMLStreamReader reader, final Configuration configuration, - final String namespaceURI, final String localName) + private static String readBody(final XMLStreamReader reader) throws XMLStreamException { - StringBuffer body = new StringBuffer(); + StringBuilder body = new StringBuilder(); org.apache.commons.logging.Log log; - // Add all body content to StringBuffer + // Add all body content to StringBuilder loop : while (reader.hasNext()) { - String name, nsURI; switch (reader.next()) { case XMLStreamConstants.START_ELEMENT: log = LogFactory.getLog(SCXMLReader.class); - log.warn("Ignoring XML content in <script> element, encountered start tag with local name: " + log.warn("Ignoring XML content in <script> element, encountered element with local name: " + reader.getLocalName()); + skipToEndElement(reader); break; case XMLStreamConstants.SPACE: case XMLStreamConstants.CHARACTERS: @@ -2160,16 +2188,7 @@ public final class SCXMLReader { body.append(reader.getText()); break; case XMLStreamConstants.END_ELEMENT: - nsURI = reader.getNamespaceURI(); - name = reader.getLocalName(); - if (namespaceURI.equals(nsURI) && localName.equals(name)) { - popNamespaces(reader, configuration); - break loop; - } - log = LogFactory.getLog(SCXMLReader.class); - log.warn("Ignoring XML content in <script> element, encountered end tag with local name: " - + reader.getLocalName()); - break; + break loop; default: // rest is ignored } } @@ -2300,6 +2319,59 @@ public final class SCXMLReader { if (reporter != null) { reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); } + skipToEndElement(reader); + } + + /** + * Advances the XMLStreamReader until after the end of the current element: all children will be skipped as well + * @param reader the reader + * @throws XMLStreamException + */ + private static void skipToEndElement(final XMLStreamReader reader) throws XMLStreamException { + int elementsToSkip = 1; + while (elementsToSkip > 0 && reader.hasNext()) { + int next = reader.next(); + if (next == XMLStreamConstants.START_ELEMENT) { + elementsToSkip++; + } + else if (next == XMLStreamConstants.END_ELEMENT) { + elementsToSkip--; + } + } + } + + /** + * Report an ignored attribute via the {@link XMLReporter} if available and the class + * {@link org.apache.commons.logging.Log}. + * + * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. + * @param configuration The {@link Configuration} to use while parsing. + * @param element The element name. + * @param attr The attribute which is ignored. + * @param value The value of the attribute which is ignored. + * + * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. + * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes + * errors in the SCXML document that may not be identified by the schema). + */ + private static void reportIgnoredAttribute(final XMLStreamReader reader, final Configuration configuration, + final String element, final String attr, final String value) + throws XMLStreamException, ModelException { + + org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class); + StringBuilder sb = new StringBuilder(); + sb.append("Ignoring unknown or invalid <").append(element).append("> attribute ").append(attr) + .append("=\"").append(value).append("\" at ").append(reader.getLocation()); + if (!configuration.isSilent() && log.isWarnEnabled()) { + log.warn(sb.toString()); + } + if (configuration.isStrict()) { + throw new ModelException(sb.toString()); + } + XMLReporter reporter = configuration.reporter; + if (reporter != null) { + reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); + } } /** @@ -2434,7 +2506,7 @@ public final class SCXMLReader { URL scxmlSchema = new URL("TODO"); // TODO, point to appropriate location SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); - Schema schema = null; + Schema schema; try { schema = schemaFactory.newSchema(scxmlSchema); } catch (SAXException se) { 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=1639829&r1=1639828&r2=1639829&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 Sat Nov 15 03:07:58 2014 @@ -45,6 +45,7 @@ import org.apache.commons.logging.LogFac import org.apache.commons.scxml2.model.Action; import org.apache.commons.scxml2.model.Assign; import org.apache.commons.scxml2.model.Cancel; +import org.apache.commons.scxml2.model.Content; import org.apache.commons.scxml2.model.Data; import org.apache.commons.scxml2.model.Datamodel; import org.apache.commons.scxml2.model.Else; @@ -73,6 +74,7 @@ import org.apache.commons.scxml2.model.T import org.apache.commons.scxml2.model.TransitionTarget; import org.apache.commons.scxml2.model.Var; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * <p>Utility class for serializing the Commons SCXML Java object @@ -128,7 +130,7 @@ public class SCXMLWriter { //---- ELEMENT NAMES ----// private static final String ELEM_ASSIGN = "assign"; private static final String ELEM_CANCEL = "cancel"; - //private static final String ELEM_CONTENT = "content"; TODO + private static final String ELEM_CONTENT = "content"; private static final String ELEM_DATA = "data"; private static final String ELEM_DATAMODEL = "datamodel"; private static final String ELEM_ELSE = "else"; @@ -155,6 +157,7 @@ public class SCXMLWriter { //---- ATTRIBUTE NAMES ----// private static final String ATTR_ARRAY = "array"; + private static final String ATTR_ATTR = "attr"; private static final String ATTR_AUTOFORWARD = "autoforward"; private static final String ATTR_COND = "cond"; private static final String ATTR_DATAMODEL = "datamodel"; @@ -490,7 +493,7 @@ public class SCXMLWriter { // Attributes writeAV(writer, ATTR_VERSION, scxml.getVersion()); writeAV(writer, ATTR_INITIAL, scxml.getInitial()); - writeAV(writer, ATTR_DATAMODEL, scxml.getDatamodelType()); + writeAV(writer, ATTR_DATAMODEL, scxml.getDatamodelName()); writeAV(writer, ATTR_NAME, scxml.getName()); writeAV(writer, ATTR_PROFILE, scxml.getProfile()); writeAV(writer, ATTR_EXMODE, scxml.getExmode()); @@ -811,13 +814,15 @@ public class SCXMLWriter { writeAV(writer, ATTR_TYPE, invoke.getType()); writeAV(writer, ATTR_AUTOFORWARD, invoke.getAutoForward()); - for (Param p : invoke.params()) { + for (Param p : invoke.getParams()) { writer.writeStartElement(ELEM_PARAM); writeAV(writer, ATTR_NAME, p.getName()); + writeAV(writer, ATTR_LOCATION, p.getLocation()); writeAV(writer, ATTR_EXPR, escapeXML(p.getExpr())); writer.writeEndElement(); } writeFinalize(writer, invoke.getFinalize()); + writeContent(writer, invoke.getContent()); writer.writeEndElement(); } @@ -860,7 +865,10 @@ public class SCXMLWriter { Assign asn = (Assign) a; writer.writeStartElement(XMLNS_SCXML, ELEM_ASSIGN); writeAV(writer, ATTR_LOCATION, asn.getLocation()); - writeAV(writer, ATTR_NAME, asn.getName()); + if (asn.getType() != null) { + writeAV(writer, ATTR_TYPE, asn.getType().value()); + } + writeAV(writer, ATTR_ATTR, asn.getAttr()); writeAV(writer, ATTR_SRC, asn.getSrc()); writeAV(writer, ATTR_EXPR, escapeXML(asn.getExpr())); writer.writeEndElement(); @@ -935,7 +943,14 @@ public class SCXMLWriter { writeAV(writer, ATTR_NAMELIST, send.getNamelist()); writeAV(writer, ATTR_HINTS, send.getHints()); - writeExternalContent(writer, send); + for (Param p : send.getParams()) { + writer.writeStartElement(ELEM_PARAM); + writeAV(writer, ATTR_NAME, p.getName()); + writeAV(writer, ATTR_LOCATION, p.getLocation()); + writeAV(writer, ATTR_EXPR, escapeXML(p.getExpr())); + writer.writeEndElement(); + } + writeContent(writer, send.getContent()); writer.writeEndElement(); } @@ -977,6 +992,40 @@ public class SCXMLWriter { } /** + * Write the {@link Content} element. + * + * @param writer The {@link XMLStreamWriter} in use for the serialization. + * @param content The content element to write. + * + * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}. + */ + private static void writeContent(final XMLStreamWriter writer, final Content content) + throws XMLStreamException { + + if (content != null) { + writer.writeStartElement(ELEM_CONTENT); + writeAV(writer, ATTR_EXPR, content.getExpr()); + if (content.getBody() != null) { + if (content.getBody() instanceof Node) { + NodeList nodeList = ((Node)content.getBody()).getChildNodes(); + if (nodeList.getLength() > 0 && XFORMER == null) { + writer.writeComment("External content was not serialized"); + } + else { + for (int i = 0, size = nodeList.getLength(); i < size; i++) { + writeNode(writer, nodeList.item(i)); + } + } + } + else { + writer.writeCharacters(content.getBody().toString()); + } + } + writer.writeEndElement(); + } + } + + /** * Write the serialized body of this {@link ExternalContent} element. * * @param writer The {@link XMLStreamWriter} in use for the serialization. Modified: commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Action.java URL: http://svn.apache.org/viewvc/commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Action.java?rev=1639829&r1=1639828&r2=1639829&view=diff ============================================================================== --- commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Action.java (original) +++ commons/proper/scxml/trunk/src/main/java/org/apache/commons/scxml2/model/Action.java Sat Nov 15 03:07:58 2014 @@ -22,6 +22,7 @@ import java.util.Map; import org.apache.commons.scxml2.ActionExecutionContext; import org.apache.commons.scxml2.Context; import org.apache.commons.scxml2.SCXMLExpressionException; +import org.w3c.dom.Node; /** * An abstract base class for executable elements in SCXML, @@ -96,7 +97,7 @@ public abstract class Action implements * * @since 0.9 */ - public final EnterableState getParentEnterableState() + public EnterableState getParentEnterableState() throws ModelException { if (parent == null && this instanceof Script && ((Script)this).isGlobalScript()) { // global script doesn't have a EnterableState @@ -113,7 +114,7 @@ public abstract class Action implements return ((History)tt).getParent(); } else { throw new ModelException("Unknown TransitionTarget subclass:" - + tt.getClass().getName()); + + (tt != null ? tt.getClass().getName() : "(null)")); } } @@ -139,5 +140,17 @@ public abstract class Action implements return Context.NAMESPACES_KEY; } + /** + * Convenient method to convert a possible {@link Node} result from an expression evaluation to a String + * using its {@link Node#getTextContent()} method. + * @param result the result to convert + * @return its text content if the result is a {@link Node} otherwise the unmodified result itself + */ + protected Object getTextContentIfNodeResult(final Object result) { + if (result instanceof Node) { + return ((Node)result).getTextContent(); + } + return result; + } }