Repository: commons-scxml Updated Branches: refs/heads/master 743eddbc2 -> 5de358aad
SCXML-265 Support <invoke> namelist attribute Project: http://git-wip-us.apache.org/repos/asf/commons-scxml/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-scxml/commit/5de358aa Tree: http://git-wip-us.apache.org/repos/asf/commons-scxml/tree/5de358aa Diff: http://git-wip-us.apache.org/repos/asf/commons-scxml/diff/5de358aa Branch: refs/heads/master Commit: 5de358aad3eac65822bdc4c62f50af24c36f2b8c Parents: 743eddb Author: Ate Douma <[email protected]> Authored: Sun Dec 10 17:55:39 2017 +0100 Committer: Ate Douma <[email protected]> Committed: Sun Dec 10 17:55:39 2017 +0100 ---------------------------------------------------------------------- src/changes/changes.xml | 13 +- .../apache/commons/scxml2/io/SCXMLReader.java | 1 + .../apache/commons/scxml2/io/SCXMLWriter.java | 1 + .../org/apache/commons/scxml2/model/Invoke.java | 45 +++++- .../commons/scxml2/model/NamelistHolder.java | 93 ------------ .../commons/scxml2/model/ParamsContainer.java | 57 +------ .../commons/scxml2/model/PayloadBuilder.java | 152 +++++++++++++++++++ .../org/apache/commons/scxml2/model/Send.java | 45 +++++- .../org/apache/commons/scxml2/w3c/tests.xml | 8 +- 9 files changed, 254 insertions(+), 161 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 363de8c..4c62ec4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -35,7 +35,12 @@ <release version="2.0" date="In Git master" description="Latest unreleased code"> - <action dev="ate" type="fix" issue="SCXML-264"> + <action dev="ate" type="add" issue="SCXML-265"> + [12-10-2017] Support <invoke> namelist attribute + </action> + + + <action dev="ate" type="add" issue="SCXML-264"> [12-10-2017] Support <invoke> with inline (<content> body) SCXML statemachine definition </action> @@ -55,11 +60,11 @@ [12-10-2017] <foreach> must fail on using illegal item definition </action> - <action dev="ate" type="update" issue="SCXML-259"> + <action dev="ate" type="add" issue="SCXML-259"> [12-10-2017] Complete implementation and handling of system variables _event and _ioprocessors </action> - <action dev="ate" type="update" issue="SCXML-258"> + <action dev="ate" type="add" issue="SCXML-258"> [12-10-2017] Add Evaluator strict mode (boolean) needed for Jexl to properly fail on incorrect expressions </action> @@ -67,7 +72,7 @@ [12-10-2017] <send> delay must support decimal values </action> - <action dev="ate" type="update" issue="SCXML-256"> + <action dev="ate" type="add" issue="SCXML-256"> [12-10-2017] Add SCXMLExecutor.run() ans SCXMLSemantics.initialize(...) methods and SCXML early/late binding </action> http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java b/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java index 9918196..c9b0349 100644 --- a/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java +++ b/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java @@ -1170,6 +1170,7 @@ public final class SCXMLReader { invoke.setSrcexpr(readAV(reader, ATTR_SRCEXPR)); invoke.setType(readAV(reader, ATTR_TYPE)); invoke.setAutoForward(readBooleanAV(reader, ELEM_INVOKE, ATTR_AUTOFORWARD)); + invoke.setNamelist(readAV(reader, ATTR_NAMELIST)); readNamespaces(configuration, invoke); loop : while (reader.hasNext()) { http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java b/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java index 82a6e48..5f0a8b1 100644 --- a/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java +++ b/src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java @@ -820,6 +820,7 @@ public class SCXMLWriter { writeAV(writer, ATTR_SRCEXPR, invoke.getSrcexpr()); writeAV(writer, ATTR_TYPE, invoke.getType()); writeAV(writer, ATTR_AUTOFORWARD, invoke.getAutoForward()); + writeAV(writer, ATTR_NAMELIST, invoke.getNamelist()); for (Param p : invoke.getParams()) { writer.writeStartElement(ELEM_PARAM); http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/main/java/org/apache/commons/scxml2/model/Invoke.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/model/Invoke.java b/src/main/java/org/apache/commons/scxml2/model/Invoke.java index 588dace..ef60123 100644 --- a/src/main/java/org/apache/commons/scxml2/model/Invoke.java +++ b/src/main/java/org/apache/commons/scxml2/model/Invoke.java @@ -16,7 +16,9 @@ */ package org.apache.commons.scxml2.model; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.xml.transform.TransformerException; @@ -41,7 +43,7 @@ import org.w3c.dom.Element; * <invoke> SCXML element. * */ -public class Invoke extends NamelistHolder implements ContentContainer { +public class Invoke extends Action implements ContentContainer, ParamsContainer { /** * Serial version UID. @@ -107,6 +109,16 @@ public class Invoke extends NamelistHolder implements ContentContainer { private int invokeIndex; /** + * The List of the params to be sent + */ + private final List<Param> paramsList = new ArrayList<>(); + + /** + * The namelist. + */ + private String namelist; + + /** * Get the identifier for this invoke (may be null). * * @return Returns the id. @@ -252,6 +264,33 @@ public class Invoke extends NamelistHolder implements ContentContainer { } /** + * Get the list of {@link Param}s. + * + * @return List The params list. + */ + public List<Param> getParams() { + return paramsList; + } + + /** + * Get the namelist. + * + * @return String Returns the namelist. + */ + public final String getNamelist() { + return namelist; + } + + /** + * Set the namelist. + * + * @param namelist The namelist to set. + */ + public final void setNamelist(final String namelist) { + this.namelist = namelist; + } + + /** * Enforce identity equality only * @param other other object to compare with * @return this == other @@ -395,8 +434,8 @@ public class Invoke extends NamelistHolder implements ContentContainer { ": no src and no content defined"); } Map<String, Object> payloadDataMap = new HashMap<>(); - addNamelistDataToPayload(axctx, payloadDataMap); - addParamsToPayload(axctx, payloadDataMap); + PayloadBuilder.addNamelistDataToPayload(parentState, ctx, eval, exctx.getErrorReporter(), namelist, payloadDataMap); + PayloadBuilder.addParamsToPayload(ctx, eval, paramsList, payloadDataMap); invoker.setParentSCXMLExecutor(exctx.getSCXMLExecutor()); if (src != null) { invoker.invoke(src, payloadDataMap); http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/main/java/org/apache/commons/scxml2/model/NamelistHolder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/model/NamelistHolder.java b/src/main/java/org/apache/commons/scxml2/model/NamelistHolder.java deleted file mode 100644 index 74324aa..0000000 --- a/src/main/java/org/apache/commons/scxml2/model/NamelistHolder.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.model; - -import java.util.Map; -import java.util.StringTokenizer; - -import org.apache.commons.scxml2.ActionExecutionContext; -import org.apache.commons.scxml2.Context; -import org.apache.commons.scxml2.Evaluator; -import org.apache.commons.scxml2.SCXMLExpressionException; -import org.apache.commons.scxml2.semantics.ErrorConstants; - -/** - * A <code>NamelistHolder</code> represents an element in the SCXML - * document that may have a namelist attribute to - * produce payload for events or external communication. - */ -public abstract class NamelistHolder extends ParamsContainer { - - /** - * The namelist. - */ - private String namelist; - - /** - * Get the namelist. - * - * @return String Returns the namelist. - */ - public final String getNamelist() { - return namelist; - } - - /** - * Set the namelist. - * - * @param namelist The namelist to set. - */ - public final void setNamelist(final String namelist) { - this.namelist = namelist; - } - - /** - * Adds data to the payload data map based on the namelist which names are location expressions - * (typically data ids or for example XPath variables). The names and the values they 'point' at - * are added to the payload data map. - * @param exctx The ActionExecutionContext - * @param payload the payload data map to be updated - * @throws ModelException if this action has not an EnterableState as parent - * @throws SCXMLExpressionException if a malformed or invalid expression is evaluated - * @see PayloadProvider#addToPayload(String, Object, java.util.Map) - */ - protected void addNamelistDataToPayload(ActionExecutionContext exctx, Map<String, Object> payload) - throws ModelException, SCXMLExpressionException { - if (namelist != null) { - EnterableState parentState = getParentEnterableState(); - Context ctx = exctx.getContext(parentState); - try { - ctx.setLocal(getNamespacesKey(), getNamespaces()); - Evaluator evaluator = exctx.getEvaluator(); - StringTokenizer tkn = new StringTokenizer(namelist); - while (tkn.hasMoreTokens()) { - String varName = tkn.nextToken(); - Object varObj = evaluator.eval(ctx, varName); - if (varObj == null) { - //considered as a warning here - exctx.getErrorReporter().onError(ErrorConstants.UNDEFINED_VARIABLE, - varName + " = null", parentState); - } - addToPayload(varName, evaluator.cloneData(varObj), payload); - } - } - finally { - ctx.setLocal(getNamespacesKey(), null); - } - } - } -} http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/main/java/org/apache/commons/scxml2/model/ParamsContainer.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/model/ParamsContainer.java b/src/main/java/org/apache/commons/scxml2/model/ParamsContainer.java index f37fabc..d750bbc 100644 --- a/src/main/java/org/apache/commons/scxml2/model/ParamsContainer.java +++ b/src/main/java/org/apache/commons/scxml2/model/ParamsContainer.java @@ -16,70 +16,19 @@ */ package org.apache.commons.scxml2.model; -import java.util.ArrayList; import java.util.List; -import java.util.Map; - -import org.apache.commons.scxml2.ActionExecutionContext; -import org.apache.commons.scxml2.Context; -import org.apache.commons.scxml2.Evaluator; -import org.apache.commons.scxml2.SCXMLExpressionException; /** - * A <code>ParamsContainer</code> represents an element in the SCXML + * A <code>ParamsContainer</code> is used for elements in the SCXML * document that may have one or more <param/> children which are used to * produce payload for events or external communication. */ -public abstract class ParamsContainer extends PayloadProvider { - - /** - * The List of the params to be sent - */ - private final List<Param> paramsList = new ArrayList<Param>(); +public interface ParamsContainer { /** * Get the list of {@link Param}s. * * @return List The params list. */ - public List<Param> getParams() { - return paramsList; - } - - /** - * Adds data to the payload data map based on the {@link Param}s of this {@link ParamsContainer} - * @param exctx The ActionExecutionContext - * @param payload the payload data map to be updated - * @throws ModelException if this action has not an EnterableState as parent - * @throws SCXMLExpressionException if a malformed or invalid expression is evaluated - * @see PayloadProvider#addToPayload(String, Object, java.util.Map) - */ - protected void addParamsToPayload(ActionExecutionContext exctx, Map<String, Object> payload) - throws ModelException, SCXMLExpressionException { - if (!paramsList.isEmpty()) { - EnterableState parentState = getParentEnterableState(); - Context ctx = exctx.getContext(parentState); - try { - ctx.setLocal(getNamespacesKey(), getNamespaces()); - Evaluator evaluator = exctx.getEvaluator(); - Object paramValue; - for (Param p : paramsList) { - if (p.getExpr() != null) { - paramValue = evaluator.eval(ctx, p.getExpr()); - } - else if (p.getLocation() != null) { - paramValue = evaluator.eval(ctx, p.getLocation()); - } - else { - // ignore invalid param definition - continue; - } - addToPayload(p.getName(), evaluator.cloneData(paramValue), payload); - } - } - finally { - ctx.setLocal(getNamespacesKey(), null); - } - } - } + List<Param> getParams(); } http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/main/java/org/apache/commons/scxml2/model/PayloadBuilder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/model/PayloadBuilder.java b/src/main/java/org/apache/commons/scxml2/model/PayloadBuilder.java new file mode 100644 index 0000000..dfd7c82 --- /dev/null +++ b/src/main/java/org/apache/commons/scxml2/model/PayloadBuilder.java @@ -0,0 +1,152 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.scxml2.Context; +import org.apache.commons.scxml2.ErrorReporter; +import org.apache.commons.scxml2.Evaluator; +import org.apache.commons.scxml2.SCXMLExpressionException; +import org.apache.commons.scxml2.semantics.ErrorConstants; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Utility class to build payload from {@link Param}s and/or a namelist + */ +public class PayloadBuilder { + + /** + * Payload data values wrapper list needed when multiple variable entries use the same names. + * The multiple values are then wrapped in a list. The PayloadBuilder uses this 'marker' list + * to distinguish between entry values which are a list themselves and the wrapper list. + */ + private static class DataValueList extends ArrayList { + } + + /** + * Adds an attribute and value to a payload data map. + * <p> + * As the SCXML specification allows for multiple payload attributes with the same name, this + * method takes care of merging multiple values for the same attribute in a list of values. + * </p> + * <p> + * Furthermore, as modifications of payload data on either the sender or receiver side should affect the + * the other side, attribute values (notably: {@link Node} value only for now) is cloned first before being added + * to the payload data map. This includes 'nested' values within a {@link NodeList}, {@link List} or {@link Map}. + * </p> + * @param attrName the name of the attribute to add + * @param attrValue the value of the attribute to add + * @param payload the payload data map to be updated + */ + @SuppressWarnings("unchecked") + public static void addToPayload(final String attrName, final Object attrValue, Map<String, Object> payload) { + DataValueList valueList = null; + Object value = payload.get(attrName); + if (value != null) { + if (value instanceof DataValueList) { + valueList = (DataValueList)value; + } + else { + valueList = new DataValueList(); + valueList.add(value); + payload.put(attrName, valueList); + } + } + value = attrValue; + if (value instanceof List) { + if (valueList == null) { + valueList = new DataValueList(); + payload.put(attrName, valueList); + } + valueList.addAll((List)value); + } + else if (valueList != null) { + valueList.add(value); + } + else { + payload.put(attrName, value); + } + } + + /** + * Adds data to the payload data map based on the {@link Param}s of this {@link ParamsContainer} + * @param ctx The Context to look up the data + * @param evaluator the evaluator to evaluate/lookup the data + * @param paramsList the list of params + * @param payload the payload data map to be updated + * @throws ModelException if this action has not an EnterableState as parent + * @throws SCXMLExpressionException if a malformed or invalid expression is evaluated + * @see PayloadProvider#addToPayload(String, Object, java.util.Map) + */ + public static void addParamsToPayload(final Context ctx, final Evaluator evaluator, final List<Param> paramsList, + Map<String, Object> payload) + throws ModelException, SCXMLExpressionException { + if (!paramsList.isEmpty()) { + Object paramValue; + for (Param p : paramsList) { + if (p.getExpr() != null) { + paramValue = evaluator.eval(ctx, p.getExpr()); + } + else if (p.getLocation() != null) { + paramValue = evaluator.eval(ctx, p.getLocation()); + } + else { + // ignore invalid param definition + continue; + } + addToPayload(p.getName(), evaluator.cloneData(paramValue), payload); + } + } + } + + /** + * Adds data to the payload data map based on the namelist which names are location expressions + * (typically data ids or for example XPath variables). The names and the values they 'point' at + * are added to the payload data map. + * @param parentState the enterable state in which the namelist holder is defined + * @param ctx the Context to look up the data + * @param evaluator the evaluator to evaluate/lookup the data + * @param errorReporter to report errors + * @param namelist the namelist + * @param payload the payload data map to be updated + * @throws ModelException if this action has not an EnterableState as parent + * @throws SCXMLExpressionException if a malformed or invalid expression is evaluated + * @see PayloadProvider#addToPayload(String, Object, java.util.Map) + */ + public static void addNamelistDataToPayload(final EnterableState parentState, final Context ctx, + final Evaluator evaluator, final ErrorReporter errorReporter, + final String namelist, Map<String, Object> payload) + throws ModelException, SCXMLExpressionException { + if (namelist != null) { + StringTokenizer tkn = new StringTokenizer(namelist); + while (tkn.hasMoreTokens()) { + String varName = tkn.nextToken(); + Object varObj = evaluator.eval(ctx, varName); + if (varObj == null) { + //considered as a warning here + errorReporter.onError(ErrorConstants.UNDEFINED_VARIABLE, varName + " = null", parentState); + } + addToPayload(varName, evaluator.cloneData(varObj), payload); + } + } + } +} http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/main/java/org/apache/commons/scxml2/model/Send.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/model/Send.java b/src/main/java/org/apache/commons/scxml2/model/Send.java index ae00889..3eb0cf4 100644 --- a/src/main/java/org/apache/commons/scxml2/model/Send.java +++ b/src/main/java/org/apache/commons/scxml2/model/Send.java @@ -16,7 +16,9 @@ */ package org.apache.commons.scxml2.model; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.apache.commons.scxml2.ActionExecutionContext; @@ -31,7 +33,7 @@ import org.apache.commons.scxml2.SCXMLSystemContext; * <send> SCXML element. * */ -public class Send extends NamelistHolder implements ContentContainer { +public class Send extends Action implements ContentContainer, ParamsContainer { /** * Serial version UID. @@ -125,6 +127,16 @@ public class Send extends NamelistHolder implements ContentContainer { private Content content; /** + * The List of the params to be sent + */ + private final List<Param> paramsList = new ArrayList<>(); + + /** + * The namelist. + */ + private String namelist; + + /** * Constructor. */ public Send() { @@ -338,6 +350,33 @@ public class Send extends NamelistHolder implements ContentContainer { } /** + * Get the list of {@link Param}s. + * + * @return List The params list. + */ + public List<Param> getParams() { + return paramsList; + } + + /** + * Get the namelist. + * + * @return String Returns the namelist. + */ + public final String getNamelist() { + return namelist; + } + + /** + * Set the namelist. + * + * @param namelist The namelist to set. + */ + public final void setNamelist(final String namelist) { + this.namelist = namelist; + } + + /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @@ -387,8 +426,8 @@ public class Send extends NamelistHolder implements ContentContainer { } Object payload = null; Map<String, Object> payloadDataMap = new LinkedHashMap<>(); - addNamelistDataToPayload(exctx, payloadDataMap); - addParamsToPayload(exctx, payloadDataMap); + PayloadBuilder.addNamelistDataToPayload(parentState, ctx, eval, exctx.getErrorReporter(), namelist, payloadDataMap); + PayloadBuilder.addParamsToPayload(ctx, eval, paramsList, payloadDataMap); if (!payloadDataMap.isEmpty()) { payload = payloadDataMap; } http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/5de358aa/src/test/java/org/apache/commons/scxml2/w3c/tests.xml ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/scxml2/w3c/tests.xml b/src/test/java/org/apache/commons/scxml2/w3c/tests.xml index 692df21..6efb288 100644 --- a/src/test/java/org/apache/commons/scxml2/w3c/tests.xml +++ b/src/test/java/org/apache/commons/scxml2/w3c/tests.xml @@ -138,18 +138,18 @@ <test id="236" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="237" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="239" mandatory="true" manual="false" jexl="true" ecma="true"/> - <test id="240" mandatory="true" manual="false" jexl="false" ecma="false"/> - <test id="241" mandatory="true" manual="false" jexl="false" ecma="false"/> + <test id="240" mandatory="true" manual="false" jexl="true" ecma="true"/> + <test id="241" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="242" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="243" mandatory="true" manual="false" jexl="true" ecma="true"/> - <test id="244" mandatory="true" manual="false" jexl="false" ecma="false"/> + <test id="244" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="245" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="247" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="250" mandatory="true" manual="true" jexl="true" ecma="true" finalState="final"/> <test id="252" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="253" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="530" mandatory="true" manual="false" jexl="false" ecma="false"/> - <test id="554" mandatory="true" manual="false" jexl="false" ecma="false"/> + <test id="554" mandatory="true" manual="false" jexl="true" ecma="true"/> <test id="436" mandatory="true" profile="minimal" manual="false" minimal="true"/> <test id="278" mandatory="false" profile="ecma" manual="false" ecma="true"/> <test id="444" mandatory="false" profile="ecma" manual="false" ecma="true"/>
