SCXML-274 Cleanup and fix namespace handling and writing, specifically (and only...
authorAte Douma <ate@apache.org>
Sun, 17 Dec 2017 22:51:27 +0000 (23:51 +0100)
committerAte Douma <ate@apache.org>
Sun, 17 Dec 2017 22:51:27 +0000 (23:51 +0100)
Namespaces now only are recorded on the root SCXML element and for custom Actions.

For the latter a new CustomActionWrapper is introduced and the (currently only) Var Commons SCXML custom action
is now (during read) wrapped by it.
This now (also) allows using a namespace prefix other than the default 'cs'.

For Java based construction of a SCXML model using the Var action, a convenient CommonsSCXML class is added
which automatically registers the Commons SCXML namespace xmlns:cs="http://commons.apache.org/scxml".
This allows using the Var custom action 'naked' without need to wrap it with CustomActionWrapper before writing
the document with SCXMLWriter.

26 files changed:
src/main/java/org/apache/commons/scxml2/Context.java
src/main/java/org/apache/commons/scxml2/SCInstance.java
src/main/java/org/apache/commons/scxml2/SCXMLConstants.java [new file with mode: 0644]
src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java
src/main/java/org/apache/commons/scxml2/io/SCXMLWriter.java
src/main/java/org/apache/commons/scxml2/model/Action.java
src/main/java/org/apache/commons/scxml2/model/ActionsContainer.java
src/main/java/org/apache/commons/scxml2/model/Assign.java
src/main/java/org/apache/commons/scxml2/model/Cancel.java
src/main/java/org/apache/commons/scxml2/model/CommonsSCXML.java [moved from src/main/java/org/apache/commons/scxml2/model/NamespacePrefixesHolder.java with 52% similarity]
src/main/java/org/apache/commons/scxml2/model/CustomActionWrapper.java [new file with mode: 0644]
src/main/java/org/apache/commons/scxml2/model/Data.java
src/main/java/org/apache/commons/scxml2/model/Foreach.java
src/main/java/org/apache/commons/scxml2/model/If.java
src/main/java/org/apache/commons/scxml2/model/Invoke.java
src/main/java/org/apache/commons/scxml2/model/Log.java
src/main/java/org/apache/commons/scxml2/model/Param.java
src/main/java/org/apache/commons/scxml2/model/SCXML.java
src/main/java/org/apache/commons/scxml2/model/Script.java
src/main/java/org/apache/commons/scxml2/model/Send.java
src/main/java/org/apache/commons/scxml2/model/SimpleTransition.java
src/main/java/org/apache/commons/scxml2/model/Var.java
src/main/java/org/apache/commons/scxml2/semantics/SCXMLSemanticsImpl.java
src/test/java/org/apache/commons/scxml2/env/javascript/JSEvaluatorTest.java
src/test/java/org/apache/commons/scxml2/io/SCXMLReaderTest.java
src/test/java/org/apache/commons/scxml2/io/SCXMLWriterTest.java

index 698f1cd..dfa0123 100644 (file)
@@ -25,11 +25,6 @@ import java.util.Map;
 public interface Context {
 
     /**
-     * Current namespaces are saved under this key in the context.
-     */
-    String NAMESPACES_KEY = "_ALL_NAMESPACES";
-
-    /**
      * Assigns a new value to an existing variable or creates a new one.
      * The method searches the chain of parent Contexts for variable
      * existence.
index cd2e778..51c0cf7 100644 (file)
@@ -356,7 +356,6 @@ public class SCInstance implements Serializable {
             }
             else if (datum.getExpr() != null) {
                 try {
-                    ctx.setLocal(Context.NAMESPACES_KEY, datum.getNamespaces());
                     value = evaluator.eval(ctx, datum.getExpr());
                     setValue = true;
                 } catch (SCXMLExpressionException see) {
@@ -364,8 +363,6 @@ public class SCInstance implements Serializable {
                         internalIOProcessor.addEvent(new EventBuilder(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT).build());
                     }
                     errorReporter.onError(ErrorConstants.EXPRESSION_ERROR, see.getMessage(), datum);
-                } finally {
-                    ctx.setLocal(Context.NAMESPACES_KEY, null);
                 }
             }
             else {
diff --git a/src/main/java/org/apache/commons/scxml2/SCXMLConstants.java b/src/main/java/org/apache/commons/scxml2/SCXMLConstants.java
new file mode 100644 (file)
index 0000000..9adf580
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+public final class SCXMLConstants {
+
+    /**
+     * The W3C SCXML namespace
+     */
+    public static final String XMLNS_SCXML = "http://www.w3.org/2005/07/scxml";
+
+    /**
+     * The Apache Commons SCXML namespace for custom actions defined by the Apache Commons SCXML implementation.
+     * Any document that intends to use these custom actions needs to ensure that they are in the correct namespace.
+     * Use of actions in this namespace makes the document non-portable across implementations.
+     */
+    public static final String XMLNS_COMMONS_SCXML = "http://commons.apache.org/scxml";
+
+    /**
+     * The default {@link #XMLNS_COMMONS_SCXML} prefix
+     */
+    public static final String XMLNS_COMMONS_SCXML_PREFIX = "cs";
+
+    // W3C SCXML XML Element names
+    public static final String ELEM_ASSIGN = "assign";
+    public static final String ELEM_CANCEL = "cancel";
+    public static final String ELEM_CONTENT = "content";
+    public static final String ELEM_DATA = "data";
+    public static final String ELEM_DATAMODEL = "datamodel";
+    public static final String ELEM_DONEDATA = "donedata";
+    public static final String ELEM_ELSE = "else";
+    public static final String ELEM_ELSEIF = "elseif";
+    public static final String ELEM_FINAL = "final";
+    public static final String ELEM_FINALIZE = "finalize";
+    public static final String ELEM_HISTORY = "history";
+    public static final String ELEM_IF = "if";
+    public static final String ELEM_INITIAL = "initial";
+    public static final String ELEM_INVOKE = "invoke";
+    public static final String ELEM_FOREACH = "foreach";
+    public static final String ELEM_LOG = "log";
+    public static final String ELEM_ONENTRY = "onentry";
+    public static final String ELEM_ONEXIT = "onexit";
+    public static final String ELEM_PARALLEL = "parallel";
+    public static final String ELEM_PARAM = "param";
+    public static final String ELEM_RAISE = "raise";
+    public static final String ELEM_SCRIPT = "script";
+    public static final String ELEM_SCXML = "scxml";
+    public static final String ELEM_SEND = "send";
+    public static final String ELEM_STATE = "state";
+    public static final String ELEM_TRANSITION = "transition";
+
+    // Commons SCXML XML Element names
+    public static final String ELEM_VAR = "var";
+
+    // W3C SCXML XML Attribute names
+    public static final String ATTR_ARRAY = "array";
+    public static final String ATTR_AUTOFORWARD = "autoforward";
+    public static final String ATTR_BINDING = "binding";
+    public static final String ATTR_BINDING_EARLY = "early";
+    public static final String ATTR_BINDING_LATE = "late";
+    public static final String ATTR_COND = "cond";
+    public static final String ATTR_DATAMODEL = "datamodel";
+    public static final String ATTR_DELAY = "delay";
+    public static final String ATTR_DELAYEXPR = "delayexpr";
+    public static final String ATTR_EVENT = "event";
+    public static final String ATTR_EVENTEXPR = "eventexpr";
+    public static final String ATTR_EXPR = "expr";
+    public static final String ATTR_ID = "id";
+    public static final String ATTR_IDLOCATION = "idlocation";
+    public static final String ATTR_INDEX = "index";
+    public static final String ATTR_INITIAL = "initial";
+    public static final String ATTR_ITEM = "item";
+    public static final String ATTR_LABEL = "label";
+    public static final String ATTR_LOCATION = "location";
+    public static final String ATTR_NAME = "name";
+    public static final String ATTR_NAMELIST = "namelist";
+    public static final String ATTR_PROFILE = "profile";
+    public static final String ATTR_SENDID = "sendid";
+    public static final String ATTR_SENDIDEXPR = "sendidexpr";
+    public static final String ATTR_SRC = "src";
+    public static final String ATTR_SRCEXPR = "srcexpr";
+    public static final String ATTR_TARGET = "target";
+    public static final String ATTR_TARGETEXPR = "targetexpr";
+    public static final String ATTR_TYPE = "type";
+    public static final String ATTR_TYPEEXPR = "typeexpr";
+    public static final String ATTR_VERSION = "version";
+
+    // Commons SCXML XML Attribute names
+    public static final String ATTR_EXMODE = "exmode";
+    public static final String ATTR_HINTS = "hints";
+}
index 0015320..235f1b5 100644 (file)
@@ -25,11 +25,10 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.EmptyStackException;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
 
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
@@ -51,6 +50,7 @@ import javax.xml.validation.Validator;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.scxml2.PathResolver;
+import org.apache.commons.scxml2.SCXMLConstants;
 import org.apache.commons.scxml2.env.SimpleErrorHandler;
 import org.apache.commons.scxml2.env.URLResolver;
 import org.apache.commons.scxml2.model.Action;
@@ -60,6 +60,7 @@ 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.CustomActionWrapper;
 import org.apache.commons.scxml2.model.Data;
 import org.apache.commons.scxml2.model.Datamodel;
 import org.apache.commons.scxml2.model.DoneData;
@@ -77,7 +78,6 @@ import org.apache.commons.scxml2.model.Initial;
 import org.apache.commons.scxml2.model.Invoke;
 import org.apache.commons.scxml2.model.Log;
 import org.apache.commons.scxml2.model.ModelException;
-import org.apache.commons.scxml2.model.NamespacePrefixesHolder;
 import org.apache.commons.scxml2.model.OnEntry;
 import org.apache.commons.scxml2.model.OnExit;
 import org.apache.commons.scxml2.model.Parallel;
@@ -114,26 +114,9 @@ import org.xml.sax.SAXException;
  */
 public final class SCXMLReader {
 
-    //---------------------- PRIVATE CONSTANTS ----------------------//
-    //---- NAMESPACES ----//
-    /**
-     * The SCXML namespace that this Reader is built for. Any document
-     * that is intended to be parsed by this reader <b>must</b>
-     * bind the SCXML elements to this namespace.
-     */
-    private static final String XMLNS_SCXML =
-            "http://www.w3.org/2005/07/scxml";
-
-    /**
-     * The namespace that defines any custom actions defined by the Commons
-     * SCXML implementation. Any document that intends to use these custom
-     * actions needs to ensure that they are in the correct namespace. Use
-     * of actions in this namespace makes the document non-portable across
-     * implementations.
-     */
-    private static final String XMLNS_COMMONS_SCXML =
-            "http://commons.apache.org/scxml";
+    private static final org.apache.commons.logging.Log logger = LogFactory.getLog(SCXMLReader.class);
 
+    //---------------------- PRIVATE CONSTANTS ----------------------//
     /**
      * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1
      */
@@ -244,72 +227,6 @@ public final class SCXMLReader {
     private static final String ERR_INVALID_VERSION = "The <scxml> element defines"
             +" an unsupported version \"{0}\", only version \"1.0\" is supported.";
 
-    //--------------------------- XML VOCABULARY ---------------------------//
-    //---- ELEMENT NAMES ----//
-    private static final String ELEM_ASSIGN = "assign";
-    private static final String ELEM_CANCEL = "cancel";
-    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";
-    private static final String ELEM_ELSEIF = "elseif";
-    private static final String ELEM_RAISE = "raise";
-    private static final String ELEM_FINAL = "final";
-    private static final String ELEM_FINALIZE = "finalize";
-    private static final String ELEM_HISTORY = "history";
-    private static final String ELEM_IF = "if";
-    private static final String ELEM_INITIAL = "initial";
-    private static final String ELEM_INVOKE = "invoke";
-    private static final String ELEM_FOREACH = "foreach";
-    private static final String ELEM_LOG = "log";
-    private static final String ELEM_ONENTRY = "onentry";
-    private static final String ELEM_ONEXIT = "onexit";
-    private static final String ELEM_PARALLEL = "parallel";
-    private static final String ELEM_PARAM = "param";
-    private static final String ELEM_SCRIPT = "script";
-    private static final String ELEM_SCXML = "scxml";
-    private static final String ELEM_SEND = "send";
-    private static final String ELEM_STATE = "state";
-    private static final String ELEM_TRANSITION = "transition";
-    private static final String ELEM_VAR = "var";
-    private static final String ELEM_DONEDATA = "donedata";
-
-    //---- ATTRIBUTE NAMES ----//
-    private static final String ATTR_ARRAY = "array";
-    private static final String ATTR_AUTOFORWARD = "autoforward";
-    static final String ATTR_BINDING = "binding";
-    private static final String ATTR_COND = "cond";
-    private static final String ATTR_DATAMODEL = "datamodel";
-    private static final String ATTR_DELAY = "delay";
-    private static final String ATTR_DELAYEXPR = "delayexpr";
-    private static final String ATTR_EVENT = "event";
-    private static final String ATTR_EVENTEXPR = "eventexpr";
-    private static final String ATTR_EXMODE = "exmode";
-    private static final String ATTR_EXPR = "expr";
-    private static final String ATTR_HINTS = "hints";
-    private static final String ATTR_ID = "id";
-    private static final String ATTR_IDLOCATION = "idlocation";
-    private static final String ATTR_INDEX = "index";
-    private static final String ATTR_INITIAL = "initial";
-    private static final String ATTR_ITEM = "item";
-    private static final String ATTR_LABEL = "label";
-    private static final String ATTR_LOCATION = "location";
-    private static final String ATTR_NAME = "name";
-    private static final String ATTR_NAMELIST = "namelist";
-    private static final String ATTR_PROFILE = "profile";
-    private static final String ATTR_SENDID = "sendid";
-    private static final String ATTR_SENDIDEXPR = "sendidexpr";
-    private static final String ATTR_SRC = "src";
-    private static final String ATTR_SRCEXPR = "srcexpr";
-    private static final String ATTR_TARGET = "target";
-    private static final String ATTR_TARGETEXPR = "targetexpr";
-    private static final String ATTR_TYPE = "type";
-    private static final String ATTR_TYPEEXPR = "typeexpr";
-    private static final String ATTR_VERSION = "version";
-
-    static final String BINDING_LATE = "late";
-    static final String BINDING_EARLY = "early";
-
     //------------------------- PUBLIC API METHODS -------------------------//
     /*
      * Public methods
@@ -598,11 +515,10 @@ public final class SCXMLReader {
             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_SCXML.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_SCXML.equals(name)) {
                             readSCXML(reader, configuration, scxml);
                         } else {
                             reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
@@ -635,26 +551,26 @@ public final class SCXMLReader {
     private static void readSCXML(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml)
             throws IOException, ModelException, XMLStreamException {
 
-        scxml.setDatamodelName(readAV(reader, ATTR_DATAMODEL));
-        scxml.setExmode(readAV(reader, ATTR_EXMODE));
-        scxml.setInitial(readAV(reader, ATTR_INITIAL));
-        scxml.setName(readAV(reader, ATTR_NAME));
-        scxml.setProfile(readAV(reader, ATTR_PROFILE));
-        scxml.setVersion(readRequiredAV(reader, ELEM_SCXML, ATTR_VERSION));
-        String binding = readAV(reader, ATTR_BINDING);
+        scxml.setDatamodelName(readAV(reader, SCXMLConstants.ATTR_DATAMODEL));
+        scxml.setExmode(readAV(reader, SCXMLConstants.ATTR_EXMODE));
+        scxml.setInitial(readAV(reader, SCXMLConstants.ATTR_INITIAL));
+        scxml.setName(readAV(reader, SCXMLConstants.ATTR_NAME));
+        scxml.setProfile(readAV(reader, SCXMLConstants.ATTR_PROFILE));
+        scxml.setVersion(readRequiredAV(reader, SCXMLConstants.ELEM_SCXML, SCXMLConstants.ATTR_VERSION));
+        String binding = readAV(reader, SCXMLConstants.ATTR_BINDING);
         if (binding != null) {
-            if (BINDING_LATE.equals(binding)) {
+            if (SCXMLConstants.ATTR_BINDING_LATE.equals(binding)) {
                 scxml.setLateBinding(true);
-            } else if (BINDING_EARLY.equals(binding)) {
+            } else if (SCXMLConstants.ATTR_BINDING_EARLY.equals(binding)) {
                 scxml.setLateBinding(false);
             } else {
-                reportIgnoredAttribute(reader, configuration, ELEM_SCXML, ATTR_BINDING, binding);
+                reportIgnoredAttribute(reader, configuration, SCXMLConstants.ELEM_SCXML, SCXMLConstants.ATTR_BINDING, binding);
             }
         }
         if (!SCXML_REQUIRED_VERSION.equals(scxml.getVersion())) {
             throw new ModelException(new MessageFormat(ERR_INVALID_VERSION).format(new Object[] {scxml.getVersion()}));
         }
-        readNamespaces(configuration, scxml);
+        scxml.setNamespaces(readNamespaces(reader));
 
         boolean hasGlobalScript = false;
 
@@ -662,30 +578,28 @@ public final class SCXMLReader {
             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_STATE.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_STATE.equals(name)) {
                             readState(reader, configuration, scxml, null);
-                        } else if (ELEM_PARALLEL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_PARALLEL.equals(name)) {
                             readParallel(reader, configuration, scxml, null);
-                        } else if (ELEM_FINAL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_FINAL.equals(name)) {
                             readFinal(reader, configuration, scxml, null);
-                        } else if (ELEM_DATAMODEL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_DATAMODEL.equals(name)) {
                             readDatamodel(reader, configuration, scxml, null);
-                        } else if (ELEM_SCRIPT.equals(name) && !hasGlobalScript) {
+                        } else if (SCXMLConstants.ELEM_SCRIPT.equals(name) && !hasGlobalScript) {
                             readGlobalScript(reader, configuration, scxml);
                             hasGlobalScript = true;
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SCXML, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SCXML, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -710,12 +624,12 @@ public final class SCXMLReader {
             throws IOException, ModelException, XMLStreamException {
 
         State state = new State();
-        state.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_STATE));
-        String initial = readAV(reader, ATTR_INITIAL);
+        state.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_STATE));
+        String initial = readAV(reader, SCXMLConstants.ATTR_INITIAL);
         if (initial != null) {
             state.setFirst(initial);
         }
-        String src = readAV(reader, ATTR_SRC);
+        String src = readAV(reader, SCXMLConstants.ATTR_SRC);
         if (src != null) {
             String source = src;
             Configuration copy = new Configuration(configuration);
@@ -746,39 +660,37 @@ public final class SCXMLReader {
             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_TRANSITION.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
                             state.addTransition(readTransition(reader, configuration));
-                        } else if (ELEM_STATE.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_STATE.equals(name)) {
                             readState(reader, configuration, scxml, state);
-                        } else if (ELEM_INITIAL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_INITIAL.equals(name)) {
                             readInitial(reader, configuration, state);
-                        } else if (ELEM_FINAL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_FINAL.equals(name)) {
                             readFinal(reader, configuration, scxml, state);
-                        } else if (ELEM_ONENTRY.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
                             readOnEntry(reader, configuration, state);
-                        } else if (ELEM_ONEXIT.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
                             readOnExit(reader, configuration, state);
-                        } else if (ELEM_PARALLEL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_PARALLEL.equals(name)) {
                             readParallel(reader, configuration, scxml, state);
-                        } else if (ELEM_DATAMODEL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_DATAMODEL.equals(name)) {
                             readDatamodel(reader, configuration, null, state);
-                        } else if (ELEM_INVOKE.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_INVOKE.equals(name)) {
                             readInvoke(reader, configuration, state);
-                        } else if (ELEM_HISTORY.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_HISTORY.equals(name)) {
                             readHistory(reader, configuration, scxml, state);
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_STATE, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_STATE, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -803,8 +715,8 @@ public final class SCXMLReader {
             throws IOException, ModelException, XMLStreamException {
 
         Parallel parallel = new Parallel();
-        parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_PARALLEL));
-        String src = readAV(reader, ATTR_SRC);
+        parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_PARALLEL));
+        String src = readAV(reader, SCXMLConstants.ATTR_SRC);
         if (src != null) {
             String source = src;
             Configuration copy = new Configuration(configuration);
@@ -835,35 +747,33 @@ public final class SCXMLReader {
             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_TRANSITION.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
                             parallel.addTransition(readTransition(reader, configuration));
-                        } else if (ELEM_STATE.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_STATE.equals(name)) {
                             readState(reader, configuration, scxml, parallel);
-                        } else if (ELEM_PARALLEL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_PARALLEL.equals(name)) {
                             readParallel(reader, configuration, scxml, parallel);
-                        } else if (ELEM_ONENTRY.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
                             readOnEntry(reader, configuration, parallel);
-                        } else if (ELEM_ONEXIT.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
                             readOnExit(reader, configuration, parallel);
-                        } else if (ELEM_DATAMODEL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_DATAMODEL.equals(name)) {
                             readDatamodel(reader, configuration, null, parallel);
-                        } else if (ELEM_INVOKE.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_INVOKE.equals(name)) {
                             readInvoke(reader, configuration, parallel);
-                        } else if (ELEM_HISTORY.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_HISTORY.equals(name)) {
                             readHistory(reader, configuration, scxml, parallel);
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_PARALLEL, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_PARALLEL, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -888,7 +798,7 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException, IOException {
 
         Final end = new Final();
-        end.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_FINAL));
+        end.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_FINAL));
 
         if (parent == null) {
             scxml.addChild(end);
@@ -905,25 +815,23 @@ public final class SCXMLReader {
             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_ONENTRY.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
                             readOnEntry(reader, configuration, end);
-                        } else if (ELEM_ONEXIT.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
                             readOnExit(reader, configuration, end);
-                        } else if (ELEM_DONEDATA.equals(name) && end.getDoneData() == null) {
+                        } else if (SCXMLConstants.ELEM_DONEDATA.equals(name) && end.getDoneData() == null) {
                             readDoneData(reader, configuration, end);
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINAL, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINAL, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -952,33 +860,31 @@ public final class SCXMLReader {
             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 (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_PARAM.equals(name)) {
                             if (doneData.getContent() == null) {
                                 readParam(reader, configuration, doneData);
                             }
                             else {
-                                reportIgnoredElement(reader, configuration, ELEM_DONEDATA, nsURI, name);
+                                reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
                             }
-                        } else if (ELEM_CONTENT.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
                             if (doneData.getParams().isEmpty()) {
-                                readContent(reader, configuration, doneData);
+                                readContent(reader, doneData);
                             }
                             else {
-                                reportIgnoredElement(reader, configuration, ELEM_DONEDATA, nsURI, name);
+                                reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
                             }
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_DONEDATA, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_DONEDATA, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -1128,21 +1034,19 @@ public final class SCXMLReader {
             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_DATA.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_DATA.equals(name)) {
                             readData(reader, configuration, dm);
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DATAMODEL, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DATAMODEL, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -1168,13 +1072,13 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Data datum = new Data();
-        datum.setId(readRequiredAV(reader, ELEM_DATA, ATTR_ID));
-        final String expr = readAV(reader, ATTR_EXPR);
-        final String src = readAV(reader, ATTR_SRC);
+        datum.setId(readRequiredAV(reader, SCXMLConstants.ELEM_DATA, SCXMLConstants.ATTR_ID));
+        final String expr = readAV(reader, SCXMLConstants.ATTR_EXPR);
+        final String src = readAV(reader, SCXMLConstants.ATTR_SRC);
 
         if (expr != null) {
             if (src != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_DATA, ATTR_EXPR, ATTR_SRC);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_DATA, SCXMLConstants.ATTR_EXPR, SCXMLConstants.ATTR_SRC);
             }
             datum.setExpr(expr);
         } else {
@@ -1183,8 +1087,7 @@ public final class SCXMLReader {
             }
         }
 
-        readNamespaces(configuration, datum);
-        Node node = readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[]{"id"});
+        Element node = readElement(reader);
         datum.setNode(node);
         if (node.hasChildNodes()) {
             NodeList children = node.getChildNodes();
@@ -1224,38 +1127,35 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Invoke invoke = new Invoke();
-        invoke.setId(readAV(reader, ATTR_ID));
-        invoke.setIdlocation(readAV(reader, ATTR_IDLOCATION));
-        invoke.setSrc(readAV(reader, ATTR_SRC));
-        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);
+        invoke.setId(readAV(reader, SCXMLConstants.ATTR_ID));
+        invoke.setIdlocation(readAV(reader, SCXMLConstants.ATTR_IDLOCATION));
+        invoke.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
+        invoke.setSrcexpr(readAV(reader, SCXMLConstants.ATTR_SRCEXPR));
+        invoke.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
+        invoke.setAutoForward(readBooleanAV(reader, SCXMLConstants.ELEM_INVOKE, SCXMLConstants.ATTR_AUTOFORWARD));
+        invoke.setNamelist(readAV(reader, SCXMLConstants.ATTR_NAMELIST));
 
         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 (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_PARAM.equals(name)) {
                             readParam(reader, configuration, invoke);
-                        } else if (ELEM_FINALIZE.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_FINALIZE.equals(name)) {
                             readFinalize(reader, configuration, parent, invoke);
-                        } else if (ELEM_CONTENT.equals(name)) {
-                            readContent(reader, configuration, invoke);
+                        } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
+                            readContent(reader, invoke);
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -1278,12 +1178,12 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Param param = new Param();
-        param.setName(readRequiredAV(reader, ELEM_PARAM, ATTR_NAME));
-        String location = readAV(reader, ATTR_LOCATION);
-        String expr = readAV(reader, ATTR_EXPR);
+        param.setName(readRequiredAV(reader, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_NAME));
+        String location = readAV(reader, SCXMLConstants.ATTR_LOCATION);
+        String expr = readAV(reader, SCXMLConstants.ATTR_EXPR);
         if (expr != null) {
             if (location != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_PARAM, ATTR_LOCATION, ATTR_EXPR);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_LOCATION, SCXMLConstants.ATTR_EXPR);
             }
             else {
                 param.setExpr(expr);
@@ -1291,12 +1191,11 @@ public final class SCXMLReader {
         }
         else if (location == null) {
             // force error missing required location or expr: use location attr for this
-            param.setLocation(readRequiredAV(reader, ELEM_PARAM, ATTR_LOCATION));
+            param.setLocation(readRequiredAV(reader, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_LOCATION));
         }
         else {
             param.setLocation(location);
         }
-        readNamespaces(configuration, param);
         parent.getParams().add(param);
         skipToEndElement(reader);
     }
@@ -1327,22 +1226,20 @@ public final class SCXMLReader {
      * Read the contents of this &lt;content&gt; element.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
-     * @param configuration The {@link Configuration} to use while parsing.
      * @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 ContentContainer contentContainer)
+    private static void readContent(final XMLStreamReader reader, final ContentContainer contentContainer)
             throws XMLStreamException {
 
         Content content = new Content();
-        content.setExpr(readAV(reader, ATTR_EXPR));
+        content.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
         if (content.getExpr() != null) {
             skipToEndElement(reader);
         }
         else {
-            Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_CONTENT, new String[]{});
+            Element body = readElement(reader);
             if (body.hasChildNodes()) {
                 content.setBody(body);
                 NodeList children = body.getChildNodes();
@@ -1400,21 +1297,19 @@ public final class SCXMLReader {
             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_TRANSITION.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
                             initial.setTransition(readSimpleTransition(reader, configuration));
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INITIAL, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INITIAL, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -1440,8 +1335,8 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         History history = new History();
-        history.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_HISTORY));
-        history.setType(readAV(reader, ATTR_TYPE));
+        history.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_HISTORY));
+        history.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
 
         ts.addHistory(history);
         scxml.addTarget(history);
@@ -1450,21 +1345,19 @@ public final class SCXMLReader {
             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_TRANSITION.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
                             history.setTransition(readTransition(reader, configuration));
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_HISTORY, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_HISTORY, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -1487,7 +1380,7 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         OnEntry onentry = new OnEntry();
-        onentry.setRaiseEvent(readBooleanAV(reader, ELEM_ONENTRY, ATTR_EVENT));
+        onentry.setRaiseEvent(readBooleanAV(reader, SCXMLConstants.ELEM_ONENTRY, SCXMLConstants.ATTR_EVENT));
         readExecutableContext(reader, configuration, onentry, null);
         es.addOnEntry(onentry);
     }
@@ -1508,7 +1401,7 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         OnExit onexit = new OnExit();
-        onexit.setRaiseEvent(readBooleanAV(reader, ELEM_ONEXIT, ATTR_EVENT));
+        onexit.setRaiseEvent(readBooleanAV(reader, SCXMLConstants.ELEM_ONEXIT, SCXMLConstants.ATTR_EVENT));
         readExecutableContext(reader, configuration, onexit, null);
         es.addOnExit(onexit);
     }
@@ -1527,8 +1420,8 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         SimpleTransition transition = new SimpleTransition();
-        transition.setNext(readAV(reader, ATTR_TARGET));
-        String type = readAV(reader, ATTR_TYPE);
+        transition.setNext(readAV(reader, SCXMLConstants.ATTR_TARGET));
+        String type = readAV(reader, SCXMLConstants.ATTR_TYPE);
         if (type != null) {
             try {
                 transition.setType(TransitionType.valueOf(type));
@@ -1540,7 +1433,6 @@ public final class SCXMLReader {
             }
         }
 
-        readNamespaces(configuration, transition);
         readExecutableContext(reader, configuration, transition, null);
 
         return transition;
@@ -1560,10 +1452,10 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Transition transition = new Transition();
-        transition.setCond(readAV(reader, ATTR_COND));
-        transition.setEvent(readAV(reader, ATTR_EVENT));
-        transition.setNext(readAV(reader, ATTR_TARGET));
-        String type = readAV(reader, ATTR_TYPE);
+        transition.setCond(readAV(reader, SCXMLConstants.ATTR_COND));
+        transition.setEvent(readAV(reader, SCXMLConstants.ATTR_EVENT));
+        transition.setNext(readAV(reader, SCXMLConstants.ATTR_TARGET));
+        String type = readAV(reader, SCXMLConstants.ATTR_TYPE);
         if (type != null) {
             try {
                 transition.setType(TransitionType.valueOf(type));
@@ -1575,7 +1467,6 @@ public final class SCXMLReader {
             }
         }
 
-        readNamespaces(configuration, transition);
         readExecutableContext(reader, configuration, transition, null);
 
         return transition;
@@ -1599,59 +1490,58 @@ public final class SCXMLReader {
 
         String end = "";
         if (parent != null) {
-            end = parent.getContainerElementName();
+            end = parent instanceof If ? SCXMLConstants.ELEM_IF : SCXMLConstants.ELEM_FOREACH;
         } else if (executable instanceof SimpleTransition) {
-            end = ELEM_TRANSITION;
+            end = SCXMLConstants.ELEM_TRANSITION;
         } else if (executable instanceof OnEntry) {
-            end = ELEM_ONENTRY;
+            end = SCXMLConstants.ELEM_ONENTRY;
         } else if (executable instanceof OnExit) {
-            end = ELEM_ONEXIT;
+            end = SCXMLConstants.ELEM_ONEXIT;
         } else if (executable instanceof Finalize) {
-            end = ELEM_FINALIZE;
+            end = SCXMLConstants.ELEM_FINALIZE;
         }
 
         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_RAISE.equals(name)) {
+                    if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_RAISE.equals(name)) {
                             if (executable instanceof Finalize) {
-                                reportIgnoredElement(reader, configuration, ELEM_FINALIZE, nsURI, name);
+                                reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, nsURI, name);
                             } else {
                                 readRaise(reader, configuration, executable, parent);
                             }
-                        } else if (ELEM_FOREACH.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_FOREACH.equals(name)) {
                             readForeach(reader, configuration, executable, parent);
-                        } else if (ELEM_IF.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_IF.equals(name)) {
                             readIf(reader, configuration, executable, parent);
-                        } else if (ELEM_LOG.equals(name)) {
-                            readLog(reader, configuration, executable, parent);
-                        } else if (ELEM_ASSIGN.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_LOG.equals(name)) {
+                            readLog(reader, executable, parent);
+                        } else if (SCXMLConstants.ELEM_ASSIGN.equals(name)) {
                             readAssign(reader, configuration, executable, parent);
-                        } else if (ELEM_SEND.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_SEND.equals(name)) {
                             if (executable instanceof Finalize) {
-                                reportIgnoredElement(reader, configuration, ELEM_FINALIZE, nsURI, name);
+                                reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, nsURI, name);
                             } else {
                                 readSend(reader, configuration, executable, parent);
                             }
-                        } else if (ELEM_CANCEL.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_CANCEL.equals(name)) {
                             readCancel(reader, configuration, executable, parent);
-                        } else if (ELEM_SCRIPT.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_SCRIPT.equals(name)) {
                             readScript(reader, configuration, executable, parent);
-                        } else if (ELEM_IF.equals(end) && ELEM_ELSEIF.equals(name)) {
-                            readElseIf(reader, configuration, executable, (If) parent);
-                        } else if (ELEM_IF.equals(end) && ELEM_ELSE.equals(name)) {
-                            readElse(reader, configuration, executable, (If)parent);
+                        } else if (SCXMLConstants.ELEM_IF.equals(end) && SCXMLConstants.ELEM_ELSEIF.equals(name)) {
+                            readElseIf(reader, executable, (If) parent);
+                        } else if (SCXMLConstants.ELEM_IF.equals(end) && SCXMLConstants.ELEM_ELSE.equals(name)) {
+                            readElse(reader, executable, (If)parent);
                         } else {
                             reportIgnoredElement(reader, configuration, end, nsURI, name);
                         }
-                    } else if (XMLNS_COMMONS_SCXML.equals(nsURI)) {
-                        if (ELEM_VAR.equals(name)) {
-                            readVar(reader, configuration, executable, parent);
+                    } else if (SCXMLConstants.XMLNS_COMMONS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_VAR.equals(name)) {
+                            readCustomAction(reader, configuration, Var.CUSTOM_ACTION, executable, parent);
                         } else {
                             reportIgnoredElement(reader, configuration, end, nsURI, name);
                         }
@@ -1672,7 +1562,6 @@ public final class SCXMLReader {
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -1699,12 +1588,11 @@ public final class SCXMLReader {
             // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize
             // [...] 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_RAISE);
+            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_RAISE);
         }
         else {
             Raise raise = new Raise();
-            raise.setEvent(readAV(reader, ATTR_EVENT));
-            readNamespaces(configuration, raise);
+            raise.setEvent(readAV(reader, SCXMLConstants.ATTR_EVENT));
             raise.setParent(executable);
             if (parent != null) {
                 parent.addAction(raise);
@@ -1732,8 +1620,7 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         If iff = new If();
-        iff.setCond(readRequiredAV(reader, ELEM_IF, ATTR_COND));
-        readNamespaces(configuration, iff);
+        iff.setCond(readRequiredAV(reader, SCXMLConstants.ELEM_IF, SCXMLConstants.ATTR_COND));
         iff.setParent(executable);
         if (parent != null) {
             parent.addAction(iff);
@@ -1747,19 +1634,16 @@ public final class SCXMLReader {
      * Read the contents of this &lt;elseif&gt; element.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
-     * @param configuration The {@link Configuration} to use while parsing.
      * @param executable The parent {@link Executable} for this action.
      * @param iff The parent {@link If} for this &lt;elseif&gt;.
      *
      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
      */
-    private static void readElseIf(final XMLStreamReader reader, final Configuration configuration,
-                                   final Executable executable, final If iff)
+    private static void readElseIf(final XMLStreamReader reader, final Executable executable, final If iff)
             throws XMLStreamException, ModelException {
 
         ElseIf elseif = new ElseIf();
-        elseif.setCond(readRequiredAV(reader, ELEM_ELSEIF, ATTR_COND));
-        readNamespaces(configuration, elseif);
+        elseif.setCond(readRequiredAV(reader, SCXMLConstants.ELEM_ELSEIF, SCXMLConstants.ATTR_COND));
         elseif.setParent(executable);
         iff.addAction(elseif);
         skipToEndElement(reader);
@@ -1769,18 +1653,15 @@ public final class SCXMLReader {
      * Read the contents of this &lt;else&gt; element.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
-     * @param configuration The {@link Configuration} to use while parsing.
      * @param executable The parent {@link Executable} for this action.
      * @param iff The parent {@link If} for this &lt;else&gt;.
      *
      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
      */
-    private static void readElse(final XMLStreamReader reader, final Configuration configuration,
-                                 final Executable executable, final If iff)
+    private static void readElse(final XMLStreamReader reader, final Executable executable, final If iff)
             throws XMLStreamException {
 
         Else els = new Else();
-        readNamespaces(configuration, els);
         els.setParent(executable);
         iff.addAction(els);
         skipToEndElement(reader);
@@ -1803,10 +1684,9 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Foreach fe = new Foreach();
-        fe.setArray(readRequiredAV(reader, ELEM_FOREACH, ATTR_ARRAY));
-        fe.setItem(readRequiredAV(reader, ELEM_FOREACH, ATTR_ITEM));
-        fe.setIndex(readAV(reader, ATTR_INDEX));
-        readNamespaces(configuration, fe);
+        fe.setArray(readRequiredAV(reader, SCXMLConstants.ELEM_FOREACH, SCXMLConstants.ATTR_ARRAY));
+        fe.setItem(readRequiredAV(reader, SCXMLConstants.ELEM_FOREACH, SCXMLConstants.ATTR_ITEM));
+        fe.setIndex(readAV(reader, SCXMLConstants.ATTR_INDEX));
         fe.setParent(executable);
         if (parent != null) {
             parent.addAction(fe);
@@ -1820,20 +1700,17 @@ public final class SCXMLReader {
      * Read the contents of this &lt;log&gt; element.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
-     * @param configuration The {@link Configuration} to use while parsing.
      * @param executable The parent {@link Executable} for this action.
      * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
      *
      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
      */
-    private static void readLog(final XMLStreamReader reader, final Configuration configuration,
-                                final Executable executable, final ActionsContainer parent)
+    private static void readLog(final XMLStreamReader reader, final Executable executable, final ActionsContainer parent)
             throws XMLStreamException {
 
         Log log = new Log();
-        log.setExpr(readAV(reader, ATTR_EXPR));
-        log.setLabel(readAV(reader, ATTR_LABEL));
-        readNamespaces(configuration, log);
+        log.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
+        log.setLabel(readAV(reader, SCXMLConstants.ATTR_LABEL));
         log.setParent(executable);
         if (parent != null) {
             parent.addAction(log);
@@ -1858,12 +1735,11 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Assign assign = new Assign();
-        assign.setExpr(readAV(reader, ATTR_EXPR));
-        assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION));
-        assign.setSrc(readAV(reader, ATTR_SRC));
-        readNamespaces(configuration, assign);
+        assign.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
+        assign.setLocation(readRequiredAV(reader, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_LOCATION));
+        assign.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
         if (assign.getExpr() != null && assign.getSrc() != null) {
-            reportConflictingAttribute(reader, configuration, ELEM_ASSIGN, ATTR_EXPR, ATTR_SRC);
+            reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_EXPR, SCXMLConstants.ATTR_SRC);
         }
         else if (assign.getExpr() != null) {
             skipToEndElement(reader);
@@ -1882,7 +1758,7 @@ public final class SCXMLReader {
         }
         else {
             Location location = reader.getLocation();
-            Node node = readNode(reader, configuration, XMLNS_SCXML, ELEM_ASSIGN, new String[]{ATTR_LOCATION});
+            Element node = readElement(reader);
             if (node.hasChildNodes()) {
                 assign.setNode(node);
                 NodeList children = node.getChildNodes();
@@ -1904,7 +1780,7 @@ public final class SCXMLReader {
                 }
             } else {
                 // report missing expression (as most common use-case)
-                reportMissingAttribute(location, ELEM_ASSIGN, ATTR_EXPR);
+                reportMissingAttribute(location, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_EXPR);
             }
         }
 
@@ -1936,96 +1812,93 @@ public final class SCXMLReader {
             // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize
             // [...] 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);
+            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_SEND);
             return;
         }
 
         Send send = new Send();
-        send.setId(readAV(reader, ATTR_ID));
-        String attrValue = readAV(reader, ATTR_IDLOCATION);
+        send.setId(readAV(reader, SCXMLConstants.ATTR_ID));
+        String attrValue = readAV(reader, SCXMLConstants.ATTR_IDLOCATION);
         if (attrValue != null) {
             if (send.getId() != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_ID, ATTR_IDLOCATION);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_ID, SCXMLConstants.ATTR_IDLOCATION);
             }
             else {
                 send.setIdlocation(attrValue);
             }
         }
-        send.setDelay(readAV(reader, ATTR_DELAY));
-        attrValue = readAV(reader, ATTR_DELAYEXPR);
+        send.setDelay(readAV(reader, SCXMLConstants.ATTR_DELAY));
+        attrValue = readAV(reader, SCXMLConstants.ATTR_DELAYEXPR);
         if (attrValue != null) {
             if (send.getDelay() != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_DELAY, ATTR_DELAYEXPR);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_DELAY, SCXMLConstants.ATTR_DELAYEXPR);
             }
             else {
                 send.setDelayexpr(attrValue);
             }
         }
-        send.setEvent(readAV(reader, ATTR_EVENT));
-        attrValue = readAV(reader, ATTR_EVENTEXPR);
+        send.setEvent(readAV(reader, SCXMLConstants.ATTR_EVENT));
+        attrValue = readAV(reader, SCXMLConstants.ATTR_EVENTEXPR);
         if (attrValue != null) {
             if (send.getEvent() != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_EVENT, ATTR_EVENTEXPR);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_EVENT, SCXMLConstants.ATTR_EVENTEXPR);
             }
             else {
                 send.setEventexpr(attrValue);
             }
         }
-        send.setHints(readAV(reader, ATTR_HINTS));
-        send.setNamelist(readAV(reader, ATTR_NAMELIST));
-        send.setTarget(readAV(reader, ATTR_TARGET));
-        attrValue = readAV(reader, ATTR_TARGETEXPR);
+        send.setHints(readAV(reader, SCXMLConstants.ATTR_HINTS));
+        send.setNamelist(readAV(reader, SCXMLConstants.ATTR_NAMELIST));
+        send.setTarget(readAV(reader, SCXMLConstants.ATTR_TARGET));
+        attrValue = readAV(reader, SCXMLConstants.ATTR_TARGETEXPR);
         if (attrValue != null) {
             if (send.getTarget() != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TARGET, ATTR_TARGETEXPR);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_TARGET, SCXMLConstants.ATTR_TARGETEXPR);
             }
             else {
                 send.setTargetexpr(attrValue);
             }
         }
-        send.setType(readAV(reader, ATTR_TYPE));
-        attrValue = readAV(reader, ATTR_TYPEEXPR);
+        send.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
+        attrValue = readAV(reader, SCXMLConstants.ATTR_TYPEEXPR);
         if (attrValue != null) {
             if (send.getType() != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TYPE, ATTR_TYPEEXPR);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_TYPE, SCXMLConstants.ATTR_TYPEEXPR);
             }
             else {
                 send.setTypeexpr(attrValue);
             }
         }
-        readNamespaces(configuration, send);
 
         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 (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+                        if (SCXMLConstants.ELEM_PARAM.equals(name)) {
                             if (send.getContent() == null) {
                                 readParam(reader, configuration, send);
                             }
                             else {
-                                reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
+                                reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
                             }
-                        } else if (ELEM_CONTENT.equals(name)) {
+                        } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
                             if (send.getNamelist() == null && send.getParams().isEmpty()) {
-                                readContent(reader, configuration, send);
+                                readContent(reader, send);
                             }
                             else {
-                                reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
+                                reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
                             }
                         } else {
-                            reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
+                            reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
                         }
                     } else {
-                        reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
+                        reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     break loop;
                 default:
             }
@@ -2054,17 +1927,16 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Cancel cancel = new Cancel();
-        cancel.setSendid(readAV(reader, ATTR_SENDID));
-        String attrValue = readAV(reader, ATTR_SENDIDEXPR);
+        cancel.setSendid(readAV(reader, SCXMLConstants.ATTR_SENDID));
+        String attrValue = readAV(reader, SCXMLConstants.ATTR_SENDIDEXPR);
         if (attrValue != null) {
             if (cancel.getSendid() != null) {
-                reportConflictingAttribute(reader, configuration, ELEM_CANCEL, ATTR_SENDID, ATTR_SENDIDEXPR);
+                reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_CANCEL, SCXMLConstants.ATTR_SENDID, SCXMLConstants.ATTR_SENDIDEXPR);
             }
             else {
                 cancel.setSendidexpr(attrValue);
             }
         }
-        readNamespaces(configuration, cancel);
         cancel.setParent(executable);
         if (parent != null) {
             parent.addAction(cancel);
@@ -2129,8 +2001,7 @@ public final class SCXMLReader {
             throws XMLStreamException, ModelException {
 
         Script script = new Script();
-        readNamespaces(configuration, script);
-        script.setSrc(readAV(reader, ATTR_SRC));
+        script.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
         if (script.getSrc() != null) {
             String resolvedSrc = script.getSrc();
             if (configuration.pathResolver != null) {
@@ -2151,33 +2022,6 @@ public final class SCXMLReader {
     }
 
     /**
-     * Read the contents of this &lt;var&gt; element.
-     *
-     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
-     * @param configuration The {@link Configuration} to use while parsing.
-     * @param executable The parent {@link Executable} for this action.
-     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
-     *
-     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
-     */
-    private static void readVar(final XMLStreamReader reader, final Configuration configuration,
-                                final Executable executable, final ActionsContainer parent)
-            throws XMLStreamException {
-
-        Var var = new Var();
-        var.setName(readAV(reader, ATTR_NAME));
-        var.setExpr(readAV(reader, ATTR_EXPR));
-        readNamespaces(configuration, var);
-        var.setParent(executable);
-        if (parent != null) {
-            parent.addAction(var);
-        } else {
-            executable.addAction(var);
-        }
-        skipToEndElement(reader);
-    }
-
-    /**
      * Read the contents of this custom action.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
@@ -2220,30 +2064,42 @@ public final class SCXMLReader {
 
         // Set the attribute values as properties
         Action action = (Action) actionObject;
+
+        CustomActionWrapper actionWrapper = new CustomActionWrapper();
+        actionWrapper.setAction(action);
+        actionWrapper.setPrefix(reader.getPrefix());
+        actionWrapper.setLocalName(reader.getLocalName());
+        Map<String, String> namespaces = readNamespaces(reader);
+        if (namespaces != null) {
+            actionWrapper.getNamespaces().putAll(namespaces);
+        }
+
+        Map<String, String> attributes = new HashMap<>();
         for (int i = 0; i < reader.getAttributeCount(); i++) {
             String name = reader.getAttributeLocalName(i);
+            String qname = createQualifiedName(reader.getAttributePrefix(i), name);
             String value = reader.getAttributeValue(i);
+            attributes.put(qname, value);
             String setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
             Method method;
             try {
                 method = clazz.getMethod(setter, String.class);
                 method.invoke(action, value);
             } catch (NoSuchMethodException nsme) {
-                throw new XMLStreamException("No setter in class:" + className + ", for string property:" + name,
-                        nsme);
+                logger.warn("No method: " + setter + "(String) found in custom action class: " + className+ " for "
+                        + qname + "=\"" + value + "\". Attribute ignored");
             } catch (InvocationTargetException ite) {
-                throw new XMLStreamException("Exception calling setter for string property:" + name + " in class:"
+                throw new XMLStreamException("Exception calling method:" + setter + "(String) in custom action class:"
                         + className, ite);
             } catch (IllegalAccessException iae) {
-                throw new XMLStreamException("Cannot access setter for string property:" + name + " in class:"
+                throw new XMLStreamException("Cannot access method: " + setter +"(String) in custom action class: "
                         + className, iae);
             }
         }
 
         // Add any body content if necessary
         if (action instanceof ExternalContent) {
-            Node body = readNode(reader, configuration, customAction.getNamespaceURI(),
-                    customAction.getLocalName(), new String [] {});
+            Element body = readElement(reader);
             NodeList childNodes = body.getChildNodes();
             List<Node> externalNodes = ((ExternalContent) action).getExternalNodes();
             for (int i = 0; i < childNodes.getLength(); i++) {
@@ -2255,30 +2111,24 @@ public final class SCXMLReader {
         }
 
         // Wire in the action and add to parent
-        readNamespaces(configuration, action);
-        action.setParent(executable);
+        actionWrapper.setParent(executable);
         if (parent != null) {
-            parent.addAction(action);
+            parent.addAction(actionWrapper);
         } else {
-            executable.addAction(action);
+            executable.addAction(actionWrapper);
         }
     }
 
     /**
-     * Read the following contents into a DOM {@link Node}.
+     * Read the current element into a DOM {@link Element}.
      *
      * @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
-     * @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}.
+     * @return The parsed content as a DOM {@link Element}.
      *
      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
      */
-    private static Node readNode(final XMLStreamReader reader, final Configuration configuration,
-                                 final String namespaceURI, final String localName, final String[] attrs)
+    private static Element readElement(final XMLStreamReader reader)
             throws XMLStreamException {
 
         // Create a document in which to build the DOM node
@@ -2290,12 +2140,7 @@ public final class SCXMLReader {
         }
 
         // This root element will be returned, add any attributes as specified
-        Element root = document.createElementNS(namespaceURI, localName);
-        for (final String attr1 : attrs) {
-            Attr attr = document.createAttributeNS(XMLNS_DEFAULT, attr1);
-            attr.setValue(readAV(reader, attr1));
-            root.setAttributeNodeNS(attr);
-        }
+        Element root = document.createElementNS(reader.getNamespaceURI(), createQualifiedName(reader.getPrefix(), reader.getLocalName()));
         document.appendChild(root);
 
         boolean children = false;
@@ -2303,7 +2148,6 @@ public final class SCXMLReader {
 
         // Convert stream to DOM node(s) while maintaining parent child relationships
         loop : while (reader.hasNext()) {
-            String name, nsURI;
             Node child = null;
             switch (reader.next()) {
                 case XMLStreamConstants.START_ELEMENT:
@@ -2312,18 +2156,11 @@ public final class SCXMLReader {
                         root.setTextContent(null);
                     }
                     children = true;
-                    pushNamespaces(reader, configuration);
-                    nsURI = reader.getNamespaceURI();
-                    name = reader.getLocalName();
-                    Element elem = document.createElementNS(nsURI, name);
+                    Element elem = document.createElementNS(reader.getNamespaceURI(),
+                            createQualifiedName(reader.getPrefix(), reader.getLocalName()));
                     for (int i = 0; i < reader.getAttributeCount(); i++) {
-                        nsURI = reader.getAttributeNamespace(i);
-                        name = reader.getAttributeLocalName(i);
-                        String prefix = reader.getAttributePrefix(i);
-                        if (prefix != null && prefix.length() > 0) {
-                            name = prefix + ":" + name;
-                        }
-                        Attr attr = document.createAttributeNS(nsURI, name);
+                        Attr attr = document.createAttributeNS(reader.getAttributeNamespace(i),
+                                createQualifiedName(reader.getAttributePrefix(i), reader.getAttributeLocalName(i)));
                         attr.setValue(reader.getAttributeValue(i));
                         elem.setAttributeNodeNS(attr);
                     }
@@ -2346,7 +2183,6 @@ public final class SCXMLReader {
                     child = document.createComment(reader.getText());
                     break;
                 case XMLStreamConstants.END_ELEMENT:
-                    popNamespaces(reader, configuration);
                     parent = parent.getParentNode();
                     if (parent == document) {
                         break loop;
@@ -2377,15 +2213,13 @@ public final class SCXMLReader {
             throws XMLStreamException {
 
         StringBuilder body = new StringBuilder();
-        org.apache.commons.logging.Log log;
 
         // Add all body content to StringBuilder
         loop : while (reader.hasNext()) {
             switch (reader.next()) {
                 case XMLStreamConstants.START_ELEMENT:
-                    log = LogFactory.getLog(SCXMLReader.class);
-                    log.warn("Ignoring XML content in <script> element, encountered element with local name: "
-                            + reader.getLocalName());
+                    logger.warn("Ignoring XML content in <script> element, encountered element: "
+                            + createQualifiedName(reader.getPrefix(), reader.getLocalName()));
                     skipToEndElement(reader);
                     break;
                 case XMLStreamConstants.SPACE:
@@ -2405,6 +2239,15 @@ public final class SCXMLReader {
     }
 
     /**
+     * @param prefix prefix
+     * @param localName localName
+     * @return a qualified name from a prefix and localName
+     */
+    private static String createQualifiedName(final String prefix, final String localName) {
+        return (prefix != null && prefix.length() > 0 ? prefix + ":" : "") +localName;
+    }
+
+    /**
      * @param input input string to check if null or empty after trim
      * @return null if input is null or empty after trim()
      */
@@ -2469,7 +2312,7 @@ public final class SCXMLReader {
     private static String readOrGeneratedTransitionTargetId(final XMLStreamReader reader, final SCXML scxml,
                                                             final String elementName)
             throws ModelException {
-        String id = readAV(reader, ATTR_ID);
+        String id = readAV(reader, SCXMLConstants.ATTR_ID);
         if (id == null) {
             id = scxml.generateTransitionTargetId();
         }
@@ -2482,14 +2325,20 @@ public final class SCXMLReader {
     }
 
     /**
-     * Read the current active namespace declarations into the namespace prefixes holder.
+     * Read the current active namespace declarations.
      *
-     * @param configuration The {@link Configuration} to use while parsing.
-     * @param holder The {@link NamespacePrefixesHolder} to populate.
+     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
+     * @return the map of active namespace declarations, null if none defined
      */
-    private static void readNamespaces(final Configuration configuration, final NamespacePrefixesHolder holder) {
-
-        holder.setNamespaces(configuration.getCurrentNamespaces());
+    private static Map<String, String> readNamespaces(final XMLStreamReader reader) {
+        Map<String, String> namespaces = null;
+        if (reader.getNamespaceCount() > 0) {
+            namespaces = new LinkedHashMap<>();
+            for (int i = 0; i < reader.getNamespaceCount(); i++) {
+                namespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
+            }
+        }
+        return namespaces;
     }
 
     /**
@@ -2526,14 +2375,13 @@ public final class SCXMLReader {
                                              final String parent, final String nsURI, final String name)
             throws XMLStreamException, ModelException {
 
-        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
         StringBuilder sb = new StringBuilder();
         sb.append("Ignoring unknown or invalid element <").append(name)
                 .append("> in namespace \"").append(nsURI)
                 .append("\" as child of <").append(parent)
                 .append("> at ").append(reader.getLocation());
-        if (!configuration.isSilent() && log.isWarnEnabled()) {
-            log.warn(sb.toString());
+        if (!configuration.isSilent() && logger.isWarnEnabled()) {
+            logger.warn(sb.toString());
         }
         if (configuration.isStrict()) {
             throw new ModelException(sb.toString());
@@ -2564,7 +2412,7 @@ public final class SCXMLReader {
     }
 
     /**
-     * Report an ignored attribute via the {@link XMLReporter} if available and the class
+     * Report an ignored attribute via the {@link XMLReporter} if available and
      * {@link org.apache.commons.logging.Log}.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
@@ -2581,12 +2429,11 @@ public final class SCXMLReader {
                                                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.isSilent() && logger.isWarnEnabled()) {
+            logger.warn(sb.toString());
         }
         if (configuration.isStrict()) {
             throw new ModelException(sb.toString());
@@ -2598,7 +2445,7 @@ public final class SCXMLReader {
     }
 
     /**
-     * Report a conflicting attribute via the {@link XMLReporter} if available and the class
+     * Report a conflicting attribute via the {@link XMLReporter} if available and
      * {@link org.apache.commons.logging.Log}.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
@@ -2615,13 +2462,12 @@ public final class SCXMLReader {
                                              final String element, final String attr, final String conflictingAttr)
             throws XMLStreamException, ModelException {
 
-        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
         StringBuilder sb = new StringBuilder();
         sb.append("Ignoring <").append(element).append("> attribute \"").append(conflictingAttr)
                 .append("\" which conflicts with already defined attribute \"").append(attr)
                 .append("\" at ").append(reader.getLocation());
-        if (!configuration.isSilent() && log.isWarnEnabled()) {
-            log.warn(sb.toString());
+        if (!configuration.isSilent() && logger.isWarnEnabled()) {
+            logger.warn(sb.toString());
         }
         if (configuration.isStrict()) {
             throw new ModelException(sb.toString());
@@ -2633,51 +2479,6 @@ public final class SCXMLReader {
     }
 
     /**
-     * Push any new namespace declarations on the configuration namespaces map.
-     *
-     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
-     * @param configuration The {@link Configuration} to use while parsing.
-     */
-    private static void pushNamespaces(final XMLStreamReader reader, final Configuration configuration) {
-
-        for (int i = 0; i < reader.getNamespaceCount(); i++) {
-            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
-            if (stack == null) {
-                stack = new Stack<String>();
-                configuration.namespaces.put(reader.getNamespacePrefix(i), stack);
-            }
-            stack.push(reader.getNamespaceURI(i));
-        }
-    }
-
-    /**
-     * Pop any expiring namespace declarations from the configuration namespaces map.
-     *
-     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
-     * @param configuration The {@link Configuration} to use while parsing.
-     *
-     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
-     */
-    private static void popNamespaces(final XMLStreamReader reader, final Configuration configuration)
-            throws XMLStreamException {
-
-        for (int i = 0; i < reader.getNamespaceCount(); i++) {
-            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
-            if (stack == null) {
-                throw new XMLStreamException("Configuration namespaces stack null");
-            }
-            try {
-                stack.pop();
-                if (stack.empty()) {
-                    configuration.namespaces.remove(reader.getNamespacePrefix(i));
-                }
-            } catch (EmptyStackException e) {
-                throw new XMLStreamException("Configuration namespaces stack popped too many times");
-            }
-        }
-    }
-
-    /**
      * Use the supplied {@link Configuration} to create an appropriate {@link XMLStreamReader} for this
      * {@link SCXMLReader}. Exactly one of the url, path, stream, reader or source parameters must be provided.
      *
@@ -2882,12 +2683,6 @@ public final class SCXMLReader {
          */
         final boolean useContextClassLoaderForCustomActions;
 
-        /**
-         * The map for bookkeeping the current active namespace declarations. The keys are prefixes and the values are
-         * {@link Stack}s containing the corresponding namespaceURIs, with the active one on top.
-         */
-        final Map<String, Stack<String>> namespaces;
-
         // Mutable Commons SCXML object model configuration properties.
         /**
          * The parent SCXML document if this document is src'ed in via the &lt;state&gt; or &lt;parallel&gt; element's
@@ -3083,7 +2878,6 @@ public final class SCXMLReader {
             this.customActions = (customActions == null ? new ArrayList<CustomAction>() : customActions);
             this.customActionClassLoader = customActionClassLoader;
             this.useContextClassLoaderForCustomActions = useContextClassLoaderForCustomActions;
-            this.namespaces = new HashMap<String, Stack<String>>();
             this.silent = silent;
             this.strict = strict;
             this.contentParser = new ContentParser();
@@ -3092,19 +2886,6 @@ public final class SCXMLReader {
         /*
          * Package access convenience methods
          */
-        /**
-         * Get the current namespaces at this point in the StAX reading.
-         *
-         * @return Map<String,String> The namespace map (keys are prefixes and values are the corresponding current
-         *                            namespace URIs).
-         */
-        Map<String, String> getCurrentNamespaces() {
-            Map<String, String> currentNamespaces = new HashMap<String, String>();
-            for (Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet()) {
-                currentNamespaces.put(nsEntry.getKey(), nsEntry.getValue().peek());
-            }
-            return currentNamespaces;
-        }
 
         /**
          * Returns true if it is set to read models silently without any model error warning logs.
index a91acf7..23f3016 100644 (file)
@@ -42,13 +42,14 @@ import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
 
 import org.apache.commons.logging.LogFactory;
+import org.apache.commons.scxml2.SCXMLConstants;
 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.CustomActionWrapper;
 import org.apache.commons.scxml2.model.Data;
 import org.apache.commons.scxml2.model.Datamodel;
-import org.apache.commons.scxml2.model.DoneData;
 import org.apache.commons.scxml2.model.Else;
 import org.apache.commons.scxml2.model.ElseIf;
 import org.apache.commons.scxml2.model.EnterableState;
@@ -100,16 +101,6 @@ import org.w3c.dom.NodeList;
 public class SCXMLWriter {
 
     //---------------------- PRIVATE CONSTANTS ----------------------//
-    //---- NAMESPACES ----//
-    /**
-     * The SCXML namespace.
-     */
-    private static final String XMLNS_SCXML = "http://www.w3.org/2005/07/scxml";
-
-    /**
-     * The Commons SCXML namespace.
-     */
-    private static final String XMLNS_COMMONS_SCXML = "http://commons.apache.org/scxml";
 
     //---- ERROR MESSAGES ----//
     /**
@@ -127,67 +118,6 @@ public class SCXMLWriter {
      */
     private static final String ERR_NULL_RES = "Cannot parse null Result";
 
-    //--------------------------- XML VOCABULARY ---------------------------//
-    //---- ELEMENT NAMES ----//
-    private static final String ELEM_ASSIGN = "assign";
-    private static final String ELEM_CANCEL = "cancel";
-    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";
-    private static final String ELEM_ELSEIF = "elseif";
-    private static final String ELEM_RAISE = "raise";
-    private static final String ELEM_FINAL = "final";
-    private static final String ELEM_FINALIZE = "finalize";
-    private static final String ELEM_HISTORY = "history";
-    private static final String ELEM_IF = "if";
-    private static final String ELEM_INITIAL = "initial";
-    private static final String ELEM_INVOKE = "invoke";
-    private static final String ELEM_FOREACH = "foreach";
-    private static final String ELEM_LOG = "log";
-    private static final String ELEM_ONENTRY = "onentry";
-    private static final String ELEM_ONEXIT = "onexit";
-    private static final String ELEM_PARALLEL = "parallel";
-    private static final String ELEM_PARAM = "param";
-    private static final String ELEM_SCRIPT = "script";
-    private static final String ELEM_SCXML = "scxml";
-    private static final String ELEM_SEND = "send";
-    private static final String ELEM_STATE = "state";
-    private static final String ELEM_TRANSITION = "transition";
-    private static final String ELEM_VAR = "var";
-    private static final String ELEM_DONEDATA = "donedata";
-
-    //---- ATTRIBUTE NAMES ----//
-    private static final String ATTR_ARRAY = "array";
-    private static final String ATTR_AUTOFORWARD = "autoforward";
-    private static final String ATTR_COND = "cond";
-    private static final String ATTR_DATAMODEL = "datamodel";
-    private static final String ATTR_DELAY = "delay";
-    private static final String ATTR_DELAYEXPR = "delayexpr";
-    private static final String ATTR_EVENT = "event";
-    private static final String ATTR_EVENTEXPR = "eventexpr";
-    private static final String ATTR_EXMODE = "exmode";
-    private static final String ATTR_EXPR = "expr";
-    private static final String ATTR_HINTS = "hints";
-    private static final String ATTR_ID = "id";
-    private static final String ATTR_IDLOCATION = "idlocation";
-    private static final String ATTR_INDEX = "index";
-    private static final String ATTR_INITIAL = "initial";
-    private static final String ATTR_ITEM = "item";
-    private static final String ATTR_LABEL = "label";
-    private static final String ATTR_LOCATION = "location";
-    private static final String ATTR_NAME = "name";
-    private static final String ATTR_NAMELIST = "namelist";
-    private static final String ATTR_PROFILE = "profile";
-    private static final String ATTR_SENDID = "sendid";
-    private static final String ATTR_SRC = "src";
-    private static final String ATTR_SRCEXPR = "srcexpr";
-    private static final String ATTR_TARGET = "target";
-    private static final String ATTR_TARGETEXPR = "targetexpr";
-    private static final String ATTR_TYPE = "type";
-    private static final String ATTR_TYPEEXPR = "typeexpr";
-    private static final String ATTR_VERSION = "version";
-
     //------------------------- STATIC MEMBERS -------------------------//
     /**
      * The JAXP transformer.
@@ -479,38 +409,40 @@ public class SCXMLWriter {
             throws XMLStreamException {
 
         // Start
-        writer.writeStartElement(ELEM_SCXML);
+        writer.writeStartElement(SCXMLConstants.ELEM_SCXML);
 
         // Namespaces
-        writer.writeNamespace(null, XMLNS_SCXML);
-        writer.writeNamespace("cs", XMLNS_COMMONS_SCXML);
-        for (Map.Entry<String, String> entry : scxml.getNamespaces().entrySet()) {
-            String key = entry.getKey();
-            if (key != null && key.trim().length() > 0 && !key.equals("cs")) { // TODO Remove reserved prefixes
-                writer.writeNamespace(key, entry.getValue());
+        writer.writeNamespace(null, SCXMLConstants.XMLNS_SCXML);
+//        writer.writeNamespace("cs", XMLNS_COMMONS_SCXML);
+        if (scxml.getNamespaces() != null) {
+            for (Map.Entry<String, String> entry : scxml.getNamespaces().entrySet()) {
+                String key = entry.getKey();
+                if (key != null && key.trim().length() > 0) {
+                    writer.writeNamespace(key, entry.getValue());
+                }
             }
         }
 
         // Attributes
-        writeAV(writer, ATTR_VERSION, scxml.getVersion());
-        writeAV(writer, ATTR_INITIAL, scxml.getInitial());
-        writeAV(writer, ATTR_DATAMODEL, scxml.getDatamodelName());
+        writeAV(writer, SCXMLConstants.ATTR_VERSION, scxml.getVersion());
+        writeAV(writer, SCXMLConstants.ATTR_INITIAL, scxml.getInitial());
+        writeAV(writer, SCXMLConstants.ATTR_DATAMODEL, scxml.getDatamodelName());
         if (scxml.isLateBinding() != null) {
-            writeAV(writer, SCXMLReader.ATTR_BINDING, scxml.isLateBinding() ? SCXMLReader.BINDING_LATE : SCXMLReader.BINDING_EARLY);
+            writeAV(writer, SCXMLConstants.ATTR_BINDING, scxml.isLateBinding() ? SCXMLConstants.ATTR_BINDING_LATE : SCXMLConstants.ATTR_BINDING_EARLY);
         }
-        writeAV(writer, ATTR_NAME, scxml.getName());
-        writeAV(writer, ATTR_PROFILE, scxml.getProfile());
-        writeAV(writer, ATTR_EXMODE, scxml.getExmode());
+        writeAV(writer, SCXMLConstants.ATTR_NAME, scxml.getName());
+        writeAV(writer, SCXMLConstants.ATTR_PROFILE, scxml.getProfile());
+        writeAV(writer, SCXMLConstants.ATTR_EXMODE, scxml.getExmode());
 
         // Marker to indicate generated document
-        writer.writeComment(XMLNS_COMMONS_SCXML);
+        writer.writeComment(SCXMLConstants.XMLNS_COMMONS_SCXML);
 
         // Write global script if defined
         if (scxml.getGlobalScript() != null) {
             Script s = scxml.getGlobalScript();
-            writer.writeStartElement(XMLNS_SCXML, ELEM_SCRIPT);
+            writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_SCRIPT);
             if (s.getSrc() != null) {
-                writeAV(writer, ATTR_SRC, s.getSrc());
+                writeAV(writer, SCXMLConstants.ATTR_SRC, s.getSrc());
             } else {
                 writer.writeCData(s.getScript());
             }
@@ -548,7 +480,7 @@ public class SCXMLWriter {
             return;
         }
 
-        writer.writeStartElement(ELEM_DATAMODEL);
+        writer.writeStartElement(SCXMLConstants.ELEM_DATAMODEL);
         if (datamodel.getData().size() > 0 && XFORMER == null) {
             writer.writeComment("Datamodel was not serialized");
         } else {
@@ -557,10 +489,10 @@ public class SCXMLWriter {
                 if (n != null) {
                     writeNode(writer, n);
                 } else {
-                    writer.writeStartElement(ELEM_DATA);
-                    writeAV(writer, ATTR_ID, d.getId());
-                    writeAV(writer, ATTR_SRC, escapeXML(d.getSrc()));
-                    writeAV(writer, ATTR_EXPR, escapeXML(d.getExpr()));
+                    writer.writeStartElement(SCXMLConstants.ELEM_DATA);
+                    writeAV(writer, SCXMLConstants.ATTR_ID, d.getId());
+                    writeAV(writer, SCXMLConstants.ATTR_SRC, escapeXML(d.getSrc()));
+                    writeAV(writer, SCXMLConstants.ATTR_EXPR, escapeXML(d.getExpr()));
                     writer.writeEndElement();
                 }
             }
@@ -577,7 +509,7 @@ public class SCXMLWriter {
     private static void writeTransitionTargetId(final XMLStreamWriter writer, final TransitionTarget tt)
             throws XMLStreamException {
         if (!tt.getId().startsWith(SCXML.GENERATED_TT_ID_PREFIX)) {
-            writeAV(writer, ATTR_ID, tt.getId());
+            writeAV(writer, SCXMLConstants.ATTR_ID, tt.getId());
         }
     }
 
@@ -592,9 +524,9 @@ public class SCXMLWriter {
     private static void writeState(final XMLStreamWriter writer, final State state)
             throws XMLStreamException {
 
-        writer.writeStartElement(ELEM_STATE);
+        writer.writeStartElement(SCXMLConstants.ELEM_STATE);
         writeTransitionTargetId(writer, state);
-        writeAV(writer, ATTR_INITIAL, state.getFirst());
+        writeAV(writer, SCXMLConstants.ATTR_INITIAL, state.getFirst());
         writeInitial(writer, state.getInitial());
         writeDatamodel(writer, state.getDatamodel());
         writeHistory(writer, state.getHistory());
@@ -637,7 +569,7 @@ public class SCXMLWriter {
     private static void writeParallel(final XMLStreamWriter writer, final Parallel parallel)
             throws XMLStreamException {
 
-        writer.writeStartElement(ELEM_PARALLEL);
+        writer.writeStartElement(SCXMLConstants.ELEM_PARALLEL);
         writeTransitionTargetId(writer, parallel);
 
         writeDatamodel(writer, parallel.getDatamodel());
@@ -681,7 +613,7 @@ public class SCXMLWriter {
     private static void writeFinal(final XMLStreamWriter writer, final Final end)
             throws XMLStreamException {
 
-        writer.writeStartElement(ELEM_FINAL);
+        writer.writeStartElement(SCXMLConstants.ELEM_FINAL);
         writeTransitionTargetId(writer, end);
         for (OnEntry onentry : end.getOnEntries()) {
             writeOnEntry(writer, onentry);
@@ -690,12 +622,12 @@ public class SCXMLWriter {
             writeOnExit(writer, onexit);
         }
         if (end.getDoneData() != null) {
-            writer.writeStartElement(ELEM_DONEDATA);
+            writer.writeStartElement(SCXMLConstants.ELEM_DONEDATA);
             for (Param p : end.getDoneData().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.writeStartElement(SCXMLConstants.ELEM_PARAM);
+                writeAV(writer, SCXMLConstants.ATTR_NAME, p.getName());
+                writeAV(writer, SCXMLConstants.ATTR_LOCATION, p.getLocation());
+                writeAV(writer, SCXMLConstants.ATTR_EXPR, escapeXML(p.getExpr()));
                 writer.writeEndElement();
             }
             writeContent(writer, end.getDoneData().getContent());
@@ -719,7 +651,7 @@ public class SCXMLWriter {
             return;
         }
 
-        writer.writeStartElement(ELEM_INITIAL);
+        writer.writeStartElement(SCXMLConstants.ELEM_INITIAL);
         writeTransition(writer, initial.getTransition());
         writer.writeEndElement();
     }
@@ -741,12 +673,12 @@ public class SCXMLWriter {
         }
 
         for (History h : history) {
-            writer.writeStartElement(ELEM_HISTORY);
+            writer.writeStartElement(SCXMLConstants.ELEM_HISTORY);
             writeTransitionTargetId(writer, h);
             if (h.isDeep()) {
-                writeAV(writer, ATTR_TYPE, "deep");
+                writeAV(writer, SCXMLConstants.ATTR_TYPE, "deep");
             } else {
-                writeAV(writer, ATTR_TYPE, "shallow");
+                writeAV(writer, SCXMLConstants.ATTR_TYPE, "shallow");
             }
             writeTransition(writer, h.getTransition());
             writer.writeEndElement();
@@ -765,8 +697,8 @@ public class SCXMLWriter {
             throws XMLStreamException {
 
         if (onentry != null && (onentry.isRaiseEvent() || onentry.getActions().size() > 0 )) {
-            writer.writeStartElement(ELEM_ONENTRY);
-            writeAV(writer, ATTR_EVENT, onentry.getRaiseEvent());
+            writer.writeStartElement(SCXMLConstants.ELEM_ONENTRY);
+            writeAV(writer, SCXMLConstants.ATTR_EVENT, onentry.getRaiseEvent());
             writeExecutableContent(writer, onentry.getActions());
             writer.writeEndElement();
         }
@@ -784,8 +716,8 @@ public class SCXMLWriter {
             throws XMLStreamException {
 
         if (onexit != null && (onexit.isRaiseEvent() || onexit.getActions().size() > 0)) {
-            writer.writeStartElement(ELEM_ONEXIT);
-            writeAV(writer, ATTR_EVENT, onexit.getRaiseEvent());
+            writer.writeStartElement(SCXMLConstants.ELEM_ONEXIT);
+            writeAV(writer, SCXMLConstants.ATTR_EVENT, onexit.getRaiseEvent());
             writeExecutableContent(writer, onexit.getActions());
             writer.writeEndElement();
         }
@@ -802,15 +734,15 @@ public class SCXMLWriter {
     private static void writeTransition(final XMLStreamWriter writer, final SimpleTransition transition)
             throws XMLStreamException {
 
-        writer.writeStartElement(ELEM_TRANSITION);
+        writer.writeStartElement(SCXMLConstants.ELEM_TRANSITION);
         if (transition instanceof Transition) {
-            writeAV(writer, ATTR_EVENT, ((Transition)transition).getEvent());
-            writeAV(writer, ATTR_COND, escapeXML(((Transition)transition).getCond()));
+            writeAV(writer, SCXMLConstants.ATTR_EVENT, ((Transition)transition).getEvent());
+            writeAV(writer, SCXMLConstants.ATTR_COND, escapeXML(((Transition)transition).getCond()));
         }
 
-        writeAV(writer, ATTR_TARGET, transition.getNext());
+        writeAV(writer, SCXMLConstants.ATTR_TARGET, transition.getNext());
         if (transition.getType() != null) {
-            writeAV(writer, ATTR_TYPE, transition.getType().name());
+            writeAV(writer, SCXMLConstants.ATTR_TYPE, transition.getType().name());
         }
         writeExecutableContent(writer, transition.getActions());
         writer.writeEndElement();
@@ -827,20 +759,20 @@ public class SCXMLWriter {
     private static void writeInvoke(final XMLStreamWriter writer, final Invoke invoke)
             throws XMLStreamException {
 
-        writer.writeStartElement(ELEM_INVOKE);
-        writeAV(writer, ATTR_ID, invoke.getId());
-        writeAV(writer, ATTR_IDLOCATION, invoke.getIdlocation());
-        writeAV(writer, ATTR_SRC, invoke.getSrc());
-        writeAV(writer, ATTR_SRCEXPR, invoke.getSrcexpr());
-        writeAV(writer, ATTR_TYPE, invoke.getType());
-        writeAV(writer, ATTR_AUTOFORWARD, invoke.getAutoForward());
-        writeAV(writer, ATTR_NAMELIST, invoke.getNamelist());
+        writer.writeStartElement(SCXMLConstants.ELEM_INVOKE);
+        writeAV(writer, SCXMLConstants.ATTR_ID, invoke.getId());
+        writeAV(writer, SCXMLConstants.ATTR_IDLOCATION, invoke.getIdlocation());
+        writeAV(writer, SCXMLConstants.ATTR_SRC, invoke.getSrc());
+        writeAV(writer, SCXMLConstants.ATTR_SRCEXPR, invoke.getSrcexpr());
+        writeAV(writer, SCXMLConstants.ATTR_TYPE, invoke.getType());
+        writeAV(writer, SCXMLConstants.ATTR_AUTOFORWARD, invoke.getAutoForward());
+        writeAV(writer, SCXMLConstants.ATTR_NAMELIST, invoke.getNamelist());
 
         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.writeStartElement(SCXMLConstants.ELEM_PARAM);
+            writeAV(writer, SCXMLConstants.ATTR_NAME, p.getName());
+            writeAV(writer, SCXMLConstants.ATTR_LOCATION, p.getLocation());
+            writeAV(writer, SCXMLConstants.ATTR_EXPR, escapeXML(p.getExpr()));
             writer.writeEndElement();
         }
         writeFinalize(writer, invoke.getFinalize());
@@ -861,7 +793,7 @@ public class SCXMLWriter {
             throws XMLStreamException {
 
         if (finalize != null && finalize.getActions().size() > 0) {
-            writer.writeStartElement(ELEM_FINALIZE);
+            writer.writeStartElement(SCXMLConstants.ELEM_FINALIZE);
             writeExecutableContent(writer, finalize.getActions());
             writer.writeEndElement();
         }
@@ -869,7 +801,7 @@ public class SCXMLWriter {
 
     /**
      * Write out this executable content (list of actions) into its serialization as the corresponding set of action
-     * elements. Custom actions aren't serialized.
+     * elements.
      *
      * @param writer The {@link XMLStreamWriter} in use for the serialization.
      * @param actions The list of actions to serialize.
@@ -888,37 +820,37 @@ public class SCXMLWriter {
                 if (asn.getNode() != null) {
                     writeNode(writer, asn.getNode());
                 } else {
-                    writer.writeStartElement(XMLNS_SCXML, ELEM_ASSIGN);
-                    writeAV(writer, ATTR_LOCATION, asn.getLocation());
-                    writeAV(writer, ATTR_SRC, asn.getSrc());
-                    writeAV(writer, ATTR_EXPR, escapeXML(asn.getExpr()));
+                    writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_ASSIGN);
+                    writeAV(writer, SCXMLConstants.ATTR_LOCATION, asn.getLocation());
+                    writeAV(writer, SCXMLConstants.ATTR_SRC, asn.getSrc());
+                    writeAV(writer, SCXMLConstants.ATTR_EXPR, escapeXML(asn.getExpr()));
                     writer.writeEndElement();
                 }
             } else if (a instanceof Send) {
                 writeSend(writer, (Send) a);
             } else if (a instanceof Cancel) {
                 Cancel c = (Cancel) a;
-                writer.writeStartElement(XMLNS_SCXML, ELEM_CANCEL);
-                writeAV(writer, ATTR_SENDID, c.getSendid());
+                writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_CANCEL);
+                writeAV(writer, SCXMLConstants.ATTR_SENDID, c.getSendid());
                 writer.writeEndElement();
             } else if (a instanceof Foreach) {
                 writeForeach(writer, (Foreach) a);
             } else if (a instanceof Log) {
                 Log lg = (Log) a;
-                writer.writeStartElement(XMLNS_SCXML, ELEM_LOG);
-                writeAV(writer, ATTR_LABEL, lg.getLabel());
-                writeAV(writer, ATTR_EXPR, escapeXML(lg.getExpr()));
+                writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_LOG);
+                writeAV(writer, SCXMLConstants.ATTR_LABEL, lg.getLabel());
+                writeAV(writer, SCXMLConstants.ATTR_EXPR, escapeXML(lg.getExpr()));
                 writer.writeEndElement();
             } else if (a instanceof Raise) {
                 Raise e = (Raise) a;
-                writer.writeStartElement(XMLNS_SCXML, ELEM_RAISE);
-                writeAV(writer, ATTR_EVENT, e.getEvent());
+                writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_RAISE);
+                writeAV(writer, SCXMLConstants.ATTR_EVENT, e.getEvent());
                 writer.writeEndElement();
             } else if (a instanceof Script) {
                 Script s = (Script) a;
-                writer.writeStartElement(XMLNS_SCXML, ELEM_SCRIPT);
+                writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_SCRIPT);
                 if (s.getSrc() != null) {
-                    writeAV(writer, ATTR_SRC, s.getSrc());
+                    writeAV(writer, SCXMLConstants.ATTR_SRC, s.getSrc());
                 } else {
                     writer.writeCData(s.getScript());
                 }
@@ -926,20 +858,36 @@ public class SCXMLWriter {
             } else if (a instanceof If) {
                 writeIf(writer, (If) a);
             } else if (a instanceof Else) {
-                writer.writeEmptyElement(ELEM_ELSE);
+                writer.writeEmptyElement(SCXMLConstants.ELEM_ELSE);
             } else if (a instanceof ElseIf) {
                 ElseIf eif = (ElseIf) a;
-                writer.writeStartElement(XMLNS_SCXML, ELEM_ELSEIF);
-                writeAV(writer, ATTR_COND, escapeXML(eif.getCond()));
+                writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_ELSEIF);
+                writeAV(writer, SCXMLConstants.ATTR_COND, escapeXML(eif.getCond()));
                 writer.writeEndElement();
             } else if (a instanceof Var) {
+                // 'naked' Var custom action, not wrapped in a CustomActionWrapper
                 Var v = (Var) a;
-                writer.writeStartElement(XMLNS_COMMONS_SCXML, ELEM_VAR);
-                writeAV(writer, ATTR_NAME, v.getName());
-                writeAV(writer, ATTR_EXPR, escapeXML(v.getExpr()));
+                writer.writeStartElement(SCXMLConstants.XMLNS_COMMONS_SCXML, SCXMLConstants.ELEM_VAR);
+                writeAV(writer, SCXMLConstants.ATTR_NAME, v.getName());
+                writeAV(writer, SCXMLConstants.ATTR_EXPR, escapeXML(v.getExpr()));
+                writer.writeEndElement();
+            } else if (a instanceof CustomActionWrapper) {
+                CustomActionWrapper actionWrapper = (CustomActionWrapper)a;
+                writer.writeStartElement(createQualifiedName(actionWrapper.getPrefix(), actionWrapper.getLocalName()));
+                if (actionWrapper.getAttributes() != null) {
+                    for (final String attr : actionWrapper.getAttributes().keySet()) {
+                        writer.writeAttribute(attr, escapeXML(actionWrapper.getAttributes().get(attr)));
+                    }
+                }
+                for (final String prefix : actionWrapper.getNamespaces().keySet()) {
+                    writer.writeNamespace(prefix, actionWrapper.getNamespaces().get(prefix));
+                }
+                if (actionWrapper.getAction() instanceof ExternalContent) {
+                    writeExternalContent(writer, (ExternalContent) actionWrapper.getAction());
+                }
                 writer.writeEndElement();
             } else {
-                writer.writeComment("Custom action with class name '" + a.getClass().getName() + "' not serialized");
+                writer.writeComment("Unknown action with class name '" + a.getClass().getName() + "' not serialized");
             }
         }
     }
@@ -955,25 +903,25 @@ public class SCXMLWriter {
     private static void writeSend(final XMLStreamWriter writer, final Send send)
             throws XMLStreamException {
 
-        writer.writeStartElement(XMLNS_SCXML, ELEM_SEND);
-        writeAV(writer, ATTR_ID, send.getId());
-        writeAV(writer, ATTR_IDLOCATION, send.getIdlocation());
-        writeAV(writer, ATTR_EVENT, send.getEvent());
-        writeAV(writer, ATTR_EVENTEXPR, send.getEventexpr());
-        writeAV(writer, ATTR_TARGET, send.getTarget());
-        writeAV(writer, ATTR_TARGETEXPR, send.getTargetexpr());
-        writeAV(writer, ATTR_TYPE, send.getType());
-        writeAV(writer, ATTR_TYPEEXPR, send.getTypeexpr());
-        writeAV(writer, ATTR_DELAY, send.getDelay());
-        writeAV(writer, ATTR_DELAYEXPR, send.getDelayexpr());
-        writeAV(writer, ATTR_NAMELIST, send.getNamelist());
-        writeAV(writer, ATTR_HINTS, send.getHints());
+        writer.writeStartElement(SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_SEND);
+        writeAV(writer, SCXMLConstants.ATTR_ID, send.getId());
+        writeAV(writer, SCXMLConstants.ATTR_IDLOCATION, send.getIdlocation());
+        writeAV(writer, SCXMLConstants.ATTR_EVENT, send.getEvent());
+        writeAV(writer, SCXMLConstants.ATTR_EVENTEXPR, send.getEventexpr());
+        writeAV(writer, SCXMLConstants.ATTR_TARGET, send.getTarget());
+        writeAV(writer, SCXMLConstants.ATTR_TARGETEXPR, send.getTargetexpr());
+        writeAV(writer, SCXMLConstants.ATTR_TYPE, send.getType());
+        writeAV(writer, SCXMLConstants.ATTR_TYPEEXPR, send.getTypeexpr());
+        writeAV(writer, SCXMLConstants.ATTR_DELAY, send.getDelay());
+        writeAV(writer, SCXMLConstants.ATTR_DELAYEXPR, send.getDelayexpr());
+        writeAV(writer, SCXMLConstants.ATTR_NAMELIST, send.getNamelist());
+        writeAV(writer, SCXMLConstants.ATTR_HINTS, send.getHints());
 
         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.writeStartElement(SCXMLConstants.ELEM_PARAM);
+            writeAV(writer, SCXMLConstants.ATTR_NAME, p.getName());
+            writeAV(writer, SCXMLConstants.ATTR_LOCATION, p.getLocation());
+            writeAV(writer, SCXMLConstants.ATTR_EXPR, escapeXML(p.getExpr()));
             writer.writeEndElement();
         }
         writeContent(writer, send.getContent());
@@ -992,8 +940,8 @@ public class SCXMLWriter {
     private static void writeIf(final XMLStreamWriter writer, final If iff)
             throws XMLStreamException {
 
-        writer.writeStartElement(ELEM_IF);
-        writeAV(writer, ATTR_COND, escapeXML(iff.getCond()));
+        writer.writeStartElement(SCXMLConstants.ELEM_IF);
+        writeAV(writer, SCXMLConstants.ATTR_COND, escapeXML(iff.getCond()));
         writeExecutableContent(writer, iff.getActions());
         writer.writeEndElement();
     }
@@ -1009,10 +957,10 @@ public class SCXMLWriter {
     private static void writeForeach(final XMLStreamWriter writer, final Foreach foreach)
             throws XMLStreamException {
 
-        writer.writeStartElement(ELEM_FOREACH);
-        writeAV(writer, ATTR_ITEM, foreach.getItem());
-        writeAV(writer, ATTR_INDEX, foreach.getIndex());
-        writeAV(writer, ATTR_ARRAY, escapeXML(foreach.getArray()));
+        writer.writeStartElement(SCXMLConstants.ELEM_FOREACH);
+        writeAV(writer, SCXMLConstants.ATTR_ITEM, foreach.getItem());
+        writeAV(writer, SCXMLConstants.ATTR_INDEX, foreach.getIndex());
+        writeAV(writer, SCXMLConstants.ATTR_ARRAY, escapeXML(foreach.getArray()));
         writeExecutableContent(writer, foreach.getActions());
         writer.writeEndElement();
     }
@@ -1029,8 +977,8 @@ public class SCXMLWriter {
             throws XMLStreamException {
 
         if (content != null) {
-            writer.writeStartElement(ELEM_CONTENT);
-            writeAV(writer, ATTR_EXPR, content.getExpr());
+            writer.writeStartElement(SCXMLConstants.ELEM_CONTENT);
+            writeAV(writer, SCXMLConstants.ATTR_EXPR, content.getExpr());
             if (content.getBody() != null) {
                 if (content.getBody() instanceof Node) {
                     NodeList nodeList = ((Node)content.getBody()).getChildNodes();
@@ -1131,6 +1079,15 @@ public class SCXMLWriter {
     }
 
     /**
+     * @param prefix prefix
+     * @param localName localName
+     * @return a qualified name from a prefix and localName
+     */
+    private static String createQualifiedName(final String prefix, final String localName) {
+        return (prefix != null && prefix.length() > 0 ? prefix + ":" : "") +localName;
+    }
+
+    /**
      * Write the serialized SCXML document while making attempts to make the serialization human readable. This
      * includes using new-lines and indentation as appropriate, where possible. Exactly one of the stream, writer
      * or result parameters must be provided.
index 0ad998b..ee2f401 100644 (file)
@@ -20,7 +20,6 @@ import java.io.Serializable;
 import java.util.Map;
 
 import org.apache.commons.scxml2.ActionExecutionContext;
-import org.apache.commons.scxml2.Context;
 import org.apache.commons.scxml2.SCXMLExpressionException;
 
 /**
@@ -28,8 +27,7 @@ import org.apache.commons.scxml2.SCXMLExpressionException;
  * such as &lt;assign&gt;, &lt;log&gt; etc.
  *
  */
-public abstract class Action implements NamespacePrefixesHolder,
-        Serializable {
+public abstract class Action implements Serializable {
 
     /**
      * Link to its parent or container.
@@ -37,18 +35,11 @@ public abstract class Action implements NamespacePrefixesHolder,
     private Executable parent;
 
     /**
-     * The current XML namespaces in the SCXML document for this action node,
-     * preserved for deferred XPath evaluation.
-     */
-    private Map<String, String> namespaces;
-
-    /**
      * Constructor.
      */
     public Action() {
         super();
         this.parent = null;
-        this.namespaces = null;
     }
 
     /**
@@ -56,7 +47,7 @@ public abstract class Action implements NamespacePrefixesHolder,
      *
      * @return Returns the parent.
      */
-    public final Executable getParent() {
+    public Executable getParent() {
         return parent;
     }
 
@@ -65,29 +56,11 @@ public abstract class Action implements NamespacePrefixesHolder,
      *
      * @param parent The parent to set.
      */
-    public final void setParent(final Executable parent) {
+    public void setParent(final Executable parent) {
         this.parent = parent;
     }
 
     /**
-     * Get the XML namespaces at this action node in the SCXML document.
-     *
-     * @return Returns the map of namespaces.
-     */
-    public final Map<String, String> getNamespaces() {
-        return namespaces;
-    }
-
-    /**
-     * Set the XML namespaces at this action node in the SCXML document.
-     *
-     * @param namespaces The document namespaces.
-     */
-    public final void setNamespaces(final Map<String, String> namespaces) {
-        this.namespaces = namespaces;
-    }
-
-    /**
      * Return the {@link EnterableState} whose {@link org.apache.commons.scxml2.Context} this action
      * executes in.
      *
@@ -130,15 +103,5 @@ public abstract class Action implements NamespacePrefixesHolder,
      * which then shall stop execution of (possible) following actions within the same executable content block
      */
     public abstract void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException, ActionExecutionError;
-
-    /**
-     * Return the key under which the current document namespaces are saved
-     * in the parent state's context.
-     *
-     * @return The namespaces key
-     */
-    protected static String getNamespacesKey() {
-        return Context.NAMESPACES_KEY;
-    }
 }
 
index 85f3480..824150f 100644 (file)
@@ -23,19 +23,6 @@ import java.util.List;
  */
 public interface ActionsContainer {
 
-    /** The &lt;if&gt; ActionsContainer element name */
-    String ELEM_IF = "if";
-
-    /** The &lt;foreach&gt; ActionsContainer element name */
-    String ELEM_FOREACH = "foreach";
-
-    /**
-     * Get the Document element type for this &lt;container&gt;.
-     *
-     * @return Returns the element type
-     */
-    String getContainerElementName();
-
     /**
      * Get the executable actions contained in this &lt;container&gt;.
      *
index 4658f95..fa7e0dc 100644 (file)
@@ -169,7 +169,6 @@ public class Assign extends Action {
         EnterableState parentState = getParentEnterableState();
         Context ctx = exctx.getContext(parentState);
         Evaluator evaluator = exctx.getEvaluator();
-        ctx.setLocal(getNamespacesKey(), getNamespaces());
         Object data;
         if (expr != null) {
             data = evaluator.eval(ctx, expr);
@@ -188,6 +187,5 @@ public class Assign extends Action {
             TriggerEvent ev = new TriggerEvent(location + ".change", TriggerEvent.CHANGE_EVENT);
             exctx.getInternalIOProcessor().addEvent(ev);
         */
-        ctx.setLocal(getNamespacesKey(), null);
     }
 }
index 16d9900..444a48b 100644 (file)
@@ -93,7 +93,6 @@ public class Cancel extends Action {
     public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
         EnterableState parentState = getParentEnterableState();
         Context ctx = exctx.getContext(parentState);
-        ctx.setLocal(getNamespacesKey(), getNamespaces());
         Evaluator eval = exctx.getEvaluator();
 
         String sendidValue = sendid;
  */
 package org.apache.commons.scxml2.model;
 
-import java.util.Map;
+import java.util.HashMap;
+
+import org.apache.commons.scxml2.SCXMLConstants;
+import org.apache.commons.scxml2.io.SCXMLWriter;
 
 /**
- * A <code>NamespacePrefixesHolder</code> is an entity that retains
- * namespace prefix information from the document for deferred XPath
- * evaluation.
- *
+ * A convenient SCXML instance with the {@link org.apache.commons.scxml2.SCXMLConstants#XMLNS_COMMONS_SCXML} namespace
+ * pre-configured.
+ * <p>
+ * This custom SCXML instance can be used when constructing SCXML instances through Java which uses Commons SCXML
+ * custom actions, like {@link Var}, which then can be written with {@link SCXMLWriter}
+ * without needing to wrap them in a {@link CustomActionWrapper},</p>
  */
-public interface NamespacePrefixesHolder {
-
-    /**
-     * Get the map of namespaces, with keys as prefixes and values as URIs.
-     *
-     * @param namespaces The namespaces prefix map.
-     */
-    void setNamespaces(Map<String, String> namespaces);
-
-    /**
-     * Get the map of namespaces, with keys as prefixes and values as URIs.
-     *
-     * @return The namespaces prefix map.
-     */
-    Map<String, String> getNamespaces();
+public class CommonsSCXML extends SCXML {
 
+    public CommonsSCXML() {
+        setNamespaces(new HashMap<>());
+        getNamespaces().put(SCXMLConstants.XMLNS_COMMONS_SCXML_PREFIX, SCXMLConstants.XMLNS_COMMONS_SCXML);
+    }
 }
-
diff --git a/src/main/java/org/apache/commons/scxml2/model/CustomActionWrapper.java b/src/main/java/org/apache/commons/scxml2/model/CustomActionWrapper.java
new file mode 100644 (file)
index 0000000..d552d44
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+import org.apache.commons.scxml2.ActionExecutionContext;
+import org.apache.commons.scxml2.SCXMLExpressionException;
+
+/**
+ * CustomActionWrapper wraps a custom action and keeps track of its XML element name, and attributes and namespaces
+ * for writing the custom action (back) to XML
+ */
+public class CustomActionWrapper extends Action {
+
+    /**
+     * The custom Action instance
+     */
+    private Action action;
+
+    /**
+     * The custom action XML element prefix;
+     */
+    private String prefix;
+
+    /**
+     * The custom action XML element local name;
+     */
+    private String localName;
+
+    /**
+     * The custom XML namespaces in effect for the custom action element
+     */
+    private final Map<String, String> namespaces = new HashMap<>();
+
+    /**
+     * The attributes defined on the custom action element
+     */
+    private Map<String, String> attributes;
+
+    public Action getAction() {
+        return action;
+    }
+
+    public void setAction(final Action action) {
+        this.action = action;
+    }
+
+    /**
+     * @return the custom action element prefix (might be null)
+     */
+    public String getPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Set the custom action XML element prefix
+     * @param prefix custom action XML element prefix
+     */
+    public void setPrefix(final String prefix) {
+        this.prefix = prefix;
+    }
+
+    /**
+     * @return the custom action XML element local name
+     */
+    public String getLocalName() {
+        return localName;
+    }
+
+    /**
+     * Set the custom action XML element local name
+     * @param localName custom action XML element local name
+     */
+    public void setLocalName(final String localName) {
+        this.localName = localName;
+    }
+
+    /**
+     * Get the custom XML namespaces in effect for this custom action
+     *
+     * @return Returns the map of namespaces.
+     */
+    public final Map<String, String> getNamespaces() {
+        return namespaces;
+    }
+
+    /**
+     * Get the attributes defined on the custom action element
+     *
+     * @return Returns the map of attributes.
+     */
+    public final Map<String, String> getAttributes() {
+        return attributes;
+    }
+
+    /**
+     * Set the attributes defined on the custom action element
+     * @param attributes the attributes to set
+     */
+    public void setAttributes(final Map<String, String> attributes) {
+        this.attributes = attributes;
+    }
+
+    @Override
+    public Executable getParent() {
+        return action.getParent();
+    }
+
+    @Override
+    public void setParent(final Executable parent) {
+        action.setParent(parent);
+    }
+
+    @Override
+    public EnterableState getParentEnterableState() throws ModelException {
+        return action.getParentEnterableState();
+    }
+
+    @Override
+    public void execute(final ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException, ActionExecutionError {
+        action.execute(exctx);
+    }
+}
index 0771bf5..94cebf1 100644 (file)
@@ -26,7 +26,7 @@ import org.w3c.dom.Node;
  * &lt;data&gt; child element of the &lt;datamodel&gt; element.
  *
  */
-public class Data implements NamespacePrefixesHolder, Serializable {
+public class Data implements Serializable {
 
     /**
      * Serial version UID.
@@ -61,13 +61,6 @@ public class Data implements NamespacePrefixesHolder, Serializable {
     private Object value;
 
     /**
-     * The current XML namespaces in the SCXML document for this action node,
-     * preserved for deferred XPath evaluation. Easier than to scrape node
-     * above, given the Builtin API.
-     */
-    private Map<String, String> namespaces;
-
-    /**
      * Get the id.
      *
      * @return String An identifier.
@@ -160,24 +153,5 @@ public class Data implements NamespacePrefixesHolder, Serializable {
     public final void setValue(final Object value) {
         this.value = value;
     }
-
-    /**
-     * Get the XML namespaces at this action node in the SCXML document.
-     *
-     * @return Returns the map of namespaces.
-     */
-    public final Map<String, String> getNamespaces() {
-        return namespaces;
-    }
-
-    /**
-     * Set the XML namespaces at this action node in the SCXML document.
-     *
-     * @param namespaces The document namespaces.
-     */
-    public final void setNamespaces(final Map<String, String> namespaces) {
-        this.namespaces = namespaces;
-    }
-
 }
 
index bdc43f8..9f15beb 100644 (file)
@@ -54,11 +54,6 @@ public class Foreach extends Action implements ActionsContainer {
     }
 
     @Override
-    public final String getContainerElementName() {
-        return ELEM_FOREACH;
-    }
-
-    @Override
     public final List<Action> getActions() {
         return actions;
     }
@@ -101,55 +96,49 @@ public class Foreach extends Action implements ActionsContainer {
     public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
         Context ctx = exctx.getContext(getParentEnterableState());
         Evaluator eval = exctx.getEvaluator();
-        ctx.setLocal(getNamespacesKey(), getNamespaces());
-        try {
-            Object arrayObject = eval.eval(ctx,array);
-            if (arrayObject != null && (arrayObject.getClass().isArray() || arrayObject instanceof Iterable || arrayObject instanceof Map)) {
-                if (arrayObject.getClass().isArray()) {
-                    for (int currentIndex = 0, size = Array.getLength(arrayObject); currentIndex < size; currentIndex++) {
-                        eval.evalAssign(ctx, item, Array.get(arrayObject, currentIndex));
-                        if (index != null) {
-                            ctx.setLocal(index, currentIndex);
-                        }
-                        // The "foreach" statement is a "container"
-                        for (Action aa : actions) {
-                            aa.execute(exctx);
-                        }
-                    }
-                }
-                else {
-                    // In case of Javascript based arrays, the (Nashorn) engine returns a ScriptObjectMirror
-                    // which (also) implements Map<String, Object), so then we can/must use the map values as Iterable
-                    Iterable iterable = arrayObject instanceof Iterable ? (Iterable)arrayObject : ((Map)arrayObject).values();
-
-                    // Spec requires to iterate over a shallow copy of underlying array in a way that modifications to
-                    // the collection during the execution of <foreach> must not affect the iteration behavior.
-                    // For array objects (see above) this isn't needed, but for Iterables we don't have that guarantee
-                    // so we make a copy first
-                    ArrayList<Object> arrayList = new ArrayList<>();
-                    for (Object value: iterable) {
-                        arrayList.add(value);
+        Object arrayObject = eval.eval(ctx,array);
+        if (arrayObject != null && (arrayObject.getClass().isArray() || arrayObject instanceof Iterable || arrayObject instanceof Map)) {
+            if (arrayObject.getClass().isArray()) {
+                for (int currentIndex = 0, size = Array.getLength(arrayObject); currentIndex < size; currentIndex++) {
+                    eval.evalAssign(ctx, item, Array.get(arrayObject, currentIndex));
+                    if (index != null) {
+                        ctx.setLocal(index, currentIndex);
                     }
-                    int currentIndex = 0;
-                    for (Object value : arrayList) {
-                        eval.evalAssign(ctx, item, value);
-                        if (index != null) {
-                            ctx.setLocal(index, currentIndex);
-                        }
-                        // The "foreach" statement is a "container"
-                        for (Action aa : actions) {
-                            aa.execute(exctx);
-                        }
-                        currentIndex++;
+                    // The "foreach" statement is a "container"
+                    for (Action aa : actions) {
+                        aa.execute(exctx);
                     }
                 }
             }
             else {
-                throw new ActionExecutionError("<foreach> in state " + getParentEnterableState().getId()+": invalid array value '"+array+"'");
+                // In case of Javascript based arrays, the (Nashorn) engine returns a ScriptObjectMirror
+                // which (also) implements Map<String, Object), so then we can/must use the map values as Iterable
+                Iterable iterable = arrayObject instanceof Iterable ? (Iterable)arrayObject : ((Map)arrayObject).values();
+
+                // Spec requires to iterate over a shallow copy of underlying array in a way that modifications to
+                // the collection during the execution of <foreach> must not affect the iteration behavior.
+                // For array objects (see above) this isn't needed, but for Iterables we don't have that guarantee
+                // so we make a copy first
+                ArrayList<Object> arrayList = new ArrayList<>();
+                for (Object value: iterable) {
+                    arrayList.add(value);
+                }
+                int currentIndex = 0;
+                for (Object value : arrayList) {
+                    eval.evalAssign(ctx, item, value);
+                    if (index != null) {
+                        ctx.setLocal(index, currentIndex);
+                    }
+                    // The "foreach" statement is a "container"
+                    for (Action aa : actions) {
+                        aa.execute(exctx);
+                    }
+                    currentIndex++;
+                }
             }
         }
-        finally {
-            ctx.setLocal(getNamespacesKey(), null);
+        else {
+            throw new ActionExecutionError("<foreach> in state " + getParentEnterableState().getId()+": invalid array value '"+array+"'");
         }
     }
 }
index 826afab..7f90ade 100644 (file)
@@ -68,11 +68,6 @@ public class If extends Action implements ActionsContainer {
         this.execute = false;
     }
 
-    @Override
-    public final String getContainerElementName() {
-        return ELEM_IF;
-    }
-
     /**
      * Get the executable actions contained in this &lt;if&gt;.
      *
@@ -120,7 +115,6 @@ public class If extends Action implements ActionsContainer {
         EnterableState parentState = getParentEnterableState();
         Context ctx = exctx.getContext(parentState);
         Evaluator eval = exctx.getEvaluator();
-        ctx.setLocal(getNamespacesKey(), getNamespaces());
         Boolean rslt;
         try {
             rslt = eval.evalCond(ctx, cond);
@@ -138,7 +132,6 @@ public class If extends Action implements ActionsContainer {
                     + e.getMessage(), this);
         }
         execute = rslt;
-        ctx.setLocal(getNamespacesKey(), null);
         // The "if" statement is a "container"
         for (Action aa : actions) {
             if (execute && !(aa instanceof ElseIf)) {
@@ -148,9 +141,7 @@ public class If extends Action implements ActionsContainer {
             } else if (aa instanceof Else) {
                 execute = true;
             } else if (aa instanceof ElseIf) {
-                ctx.setLocal(getNamespacesKey(), getNamespaces());
                 execute = eval.evalCond(ctx, ((ElseIf) aa).getCond());
-                ctx.setLocal(getNamespacesKey(), null);
             }
         }
     }
index ef60123..b7ccf37 100644 (file)
@@ -364,7 +364,6 @@ public class Invoke extends Action implements ContentContainer, ParamsContainer
             throw new ModelException("Missing current SCXMLExecutionContext instance in context under key: "+ getCurrentSCXMLExecutionContextKey());
         }
         try {
-            ctx.setLocal(getNamespacesKey(), getNamespaces());
             Evaluator eval = axctx.getEvaluator();
 
             String typeValue = type;
@@ -452,8 +451,5 @@ public class Invoke extends Action implements ContentContainer, ParamsContainer
                         ? ErrorConstants.EXPRESSION_ERROR : ErrorConstants.EXECUTION_ERROR, e.getMessage(), this);
             }
         }
-        finally {
-            ctx.setLocal(getNamespacesKey(), null);
-        }
     }
 }
\ No newline at end of file
index e27e039..e259943 100644 (file)
@@ -94,9 +94,7 @@ public class Log extends Action {
     public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
         Context ctx = exctx.getContext(getParentEnterableState());
         Evaluator eval = exctx.getEvaluator();
-        ctx.setLocal(getNamespacesKey(), getNamespaces());
         exctx.getAppLog().info(label + ": " + String.valueOf(eval.eval(ctx, expr)));
-        ctx.setLocal(getNamespacesKey(), null);
     }
 }
 
index 1eebe43..23eaab2 100644 (file)
@@ -24,7 +24,7 @@ import java.util.Map;
  * &lt;param&gt; SCXML element.
  *
  */
-public class Param implements NamespacePrefixesHolder, Serializable {
+public class Param implements Serializable {
 
     /**
      * Serial version UID.
@@ -48,12 +48,6 @@ public class Param implements NamespacePrefixesHolder, Serializable {
     private String expr;
 
     /**
-     * The current XML namespaces in the SCXML document for this action node,
-     * preserved for deferred XPath evaluation.
-     */
-    private Map<String, String> namespaces;
-
-    /**
      * Default no-args constructor
      */
     public Param() {
@@ -113,24 +107,5 @@ public class Param implements NamespacePrefixesHolder, Serializable {
     public final void setExpr(final String expr) {
         this.expr = expr;
     }
-
-    /**
-     * Get the XML namespaces at this action node in the SCXML document.
-     *
-     * @return Returns the map of namespaces.
-     */
-    public final Map<String, String> getNamespaces() {
-        return namespaces;
-    }
-
-    /**
-     * Set the XML namespaces at this action node in the SCXML document.
-     *
-     * @param namespaces The document namespaces.
-     */
-    public final void setNamespaces(final Map<String, String> namespaces) {
-        this.namespaces = namespaces;
-    }
-
 }
 
index fd62f82..062289c 100644 (file)
@@ -30,7 +30,7 @@ import org.apache.commons.scxml2.PathResolver;
  * root&quot;.
  *
  */
-public class SCXML implements Serializable, Observable, NamespacePrefixesHolder {
+public class SCXML implements Serializable, Observable {
 
     /**
      * Serial version UID.
@@ -38,12 +38,6 @@ public class SCXML implements Serializable, Observable, NamespacePrefixesHolder
     private static final long serialVersionUID = 2L;
 
     /**
-     * The SCXML XMLNS.
-     */
-    @SuppressWarnings("unused")
-    public static final String XMLNS = "http://www.w3.org/2005/07/scxml";
-
-    /**
      * Reserved prefix for auto generated TransitionTarget id values
      */
     public static final String GENERATED_TT_ID_PREFIX = "_generated_tt_id_";
index 7fb2327..22f30c7 100644 (file)
@@ -80,11 +80,8 @@ public class Script extends Action {
     @Override
     public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
         Context ctx = isGlobalScript() ? exctx.getGlobalContext() : exctx.getContext(getParentEnterableState());
-        ctx.setLocal(getNamespacesKey(), getNamespaces());
         Evaluator eval = exctx.getEvaluator();
         eval.evalScript(ctx, getScript());
-        ctx.setLocal(getNamespacesKey(), null);
     }
-
 }
 
index 3eb0cf4..1ff8a7c 100644 (file)
@@ -385,94 +385,88 @@ public class Send extends Action implements ContentContainer, ParamsContainer {
         // Send attributes evaluation
         EnterableState parentState = getParentEnterableState();
         Context ctx = exctx.getContext(parentState);
-        try {
-            ctx.setLocal(getNamespacesKey(), getNamespaces());
-            Evaluator eval = exctx.getEvaluator();
-            // Most attributes of <send> are expressions so need to be
-            // evaluated before the EventDispatcher callback
-            Object hintsValue = null;
-            if (hints != null) {
-                hintsValue = eval.eval(ctx, hints);
-            }
-            if (idlocation != null) {
-                if (id == null) {
-                    id = ctx.getSystemContext().generateSessionId();
-                }
-                eval.evalAssign(ctx, idlocation, id);
-            }
-            String targetValue = target;
-            if (targetValue == null && targetexpr != null) {
-                targetValue = (String)eval.eval(ctx, targetexpr);
-                if ((targetValue == null || targetValue.trim().length() == 0)
-                        && exctx.getAppLog().isWarnEnabled()) {
-                    exctx.getAppLog().warn("<send>: target expression \"" + targetexpr
-                            + "\" evaluated to null or empty String");
-                }
-            }
-            String typeValue = type;
-            if (typeValue == null && typeexpr != null) {
-                typeValue = (String)eval.eval(ctx, typeexpr);
-                if ((typeValue == null || typeValue.trim().length() == 0)
-                        && exctx.getAppLog().isWarnEnabled()) {
-                    exctx.getAppLog().warn("<send>: type expression \"" + typeexpr
-                            + "\" evaluated to null or empty String");
-                }
-            }
-            if (typeValue == null) {
-                // must default to 'scxml' when unspecified
-                typeValue = SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR;
-            } else if (!SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR.equals(typeValue) && typeValue.trim().equalsIgnoreCase(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR)) {
-                typeValue = SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR;
+        Evaluator eval = exctx.getEvaluator();
+        // Most attributes of <send> are expressions so need to be
+        // evaluated before the EventDispatcher callback
+        Object hintsValue = null;
+        if (hints != null) {
+            hintsValue = eval.eval(ctx, hints);
+        }
+        if (idlocation != null) {
+            if (id == null) {
+                id = ctx.getSystemContext().generateSessionId();
             }
-            Object payload = null;
-            Map<String, Object> payloadDataMap = new LinkedHashMap<>();
-            PayloadBuilder.addNamelistDataToPayload(parentState, ctx, eval, exctx.getErrorReporter(), namelist, payloadDataMap);
-            PayloadBuilder.addParamsToPayload(ctx, eval, paramsList, payloadDataMap);
-            if (!payloadDataMap.isEmpty()) {
-                payload = payloadDataMap;
+            eval.evalAssign(ctx, idlocation, id);
+        }
+        String targetValue = target;
+        if (targetValue == null && targetexpr != null) {
+            targetValue = (String)eval.eval(ctx, targetexpr);
+            if ((targetValue == null || targetValue.trim().length() == 0)
+                    && exctx.getAppLog().isWarnEnabled()) {
+                exctx.getAppLog().warn("<send>: target expression \"" + targetexpr
+                        + "\" evaluated to null or empty String");
             }
-            else if (content != null) {
-                if (content.getExpr() != null) {
-                    payload = eval.cloneData(eval.eval(ctx, content.getExpr()));
-                } else if (content.getValue() != null) {
-                    payload = content.getValue();
-                }
-                else if (content.getBody() != null){
-                    payload = eval.cloneData(content.getBody());
-                }
+        }
+        String typeValue = type;
+        if (typeValue == null && typeexpr != null) {
+            typeValue = (String)eval.eval(ctx, typeexpr);
+            if ((typeValue == null || typeValue.trim().length() == 0)
+                    && exctx.getAppLog().isWarnEnabled()) {
+                exctx.getAppLog().warn("<send>: type expression \"" + typeexpr
+                        + "\" evaluated to null or empty String");
             }
-            long wait = 0L;
-            String delayString = delay;
-            if (delayString == null && delayexpr != null) {
-                Object delayValue = eval.eval(ctx, delayexpr);
-                if (delayValue != null) {
-                    delayString = delayValue.toString();
-                }
+        }
+        if (typeValue == null) {
+            // must default to 'scxml' when unspecified
+            typeValue = SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR;
+        } else if (!SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR.equals(typeValue) && typeValue.trim().equalsIgnoreCase(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR)) {
+            typeValue = SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR;
+        }
+        Object payload = null;
+        Map<String, Object> payloadDataMap = new LinkedHashMap<>();
+        PayloadBuilder.addNamelistDataToPayload(parentState, ctx, eval, exctx.getErrorReporter(), namelist, payloadDataMap);
+        PayloadBuilder.addParamsToPayload(ctx, eval, paramsList, payloadDataMap);
+        if (!payloadDataMap.isEmpty()) {
+            payload = payloadDataMap;
+        }
+        else if (content != null) {
+            if (content.getExpr() != null) {
+                payload = eval.cloneData(eval.eval(ctx, content.getExpr()));
+            } else if (content.getValue() != null) {
+                payload = content.getValue();
             }
-            if (delayString != null) {
-                wait = parseDelay(delayString, delayexpr != null, delayexpr != null ? delayexpr : delay);
+            else if (content.getBody() != null){
+                payload = eval.cloneData(content.getBody());
             }
-            String eventValue = event;
-            if (eventValue == null && eventexpr != null) {
-                eventValue = (String)eval.eval(ctx, eventexpr);
-                if ((eventValue == null)) {
-                    throw new SCXMLExpressionException("<send>: event expression \"" + eventexpr
-                            + "\" evaluated to null");
-                }
+        }
+        long wait = 0L;
+        String delayString = delay;
+        if (delayString == null && delayexpr != null) {
+            Object delayValue = eval.eval(ctx, delayexpr);
+            if (delayValue != null) {
+                delayString = delayValue.toString();
             }
-            Map<String, SCXMLIOProcessor> ioProcessors = (Map<String, SCXMLIOProcessor>) ctx.get(SCXMLSystemContext.IOPROCESSORS_KEY);
-            if (exctx.getAppLog().isDebugEnabled()) {
-                exctx.getAppLog().debug("<send>: Dispatching event '" + eventValue
-                        + "' to target '" + targetValue + "' of target type '"
-                        + typeValue + "' with suggested delay of " + wait
-                        + "ms");
+        }
+        if (delayString != null) {
+            wait = parseDelay(delayString, delayexpr != null, delayexpr != null ? delayexpr : delay);
+        }
+        String eventValue = event;
+        if (eventValue == null && eventexpr != null) {
+            eventValue = (String)eval.eval(ctx, eventexpr);
+            if ((eventValue == null)) {
+                throw new SCXMLExpressionException("<send>: event expression \"" + eventexpr
+                        + "\" evaluated to null");
             }
-            exctx.getEventDispatcher().send(ioProcessors, id, targetValue, typeValue, eventValue,
-                    payload, hintsValue, wait);
         }
-        finally {
-            ctx.setLocal(getNamespacesKey(), null);
+        Map<String, SCXMLIOProcessor> ioProcessors = (Map<String, SCXMLIOProcessor>) ctx.get(SCXMLSystemContext.IOPROCESSORS_KEY);
+        if (exctx.getAppLog().isDebugEnabled()) {
+            exctx.getAppLog().debug("<send>: Dispatching event '" + eventValue
+                    + "' to target '" + targetValue + "' of target type '"
+                    + typeValue + "' with suggested delay of " + wait
+                    + "ms");
         }
+        exctx.getEventDispatcher().send(ioProcessors, id, targetValue, typeValue, eventValue,
+                payload, hintsValue, wait);
     }
 
     /**
index 331861d..4334b7f 100644 (file)
@@ -26,8 +26,7 @@ import java.util.Set;
  * &quot;guard-conditions&quot;. Used for &lt;history&gt; or &lt;history&gt; elements.
  *
  */
-public class SimpleTransition extends Executable
-        implements NamespacePrefixesHolder, Observable {
+public class SimpleTransition extends Executable implements Observable {
 
     /**
      * Serial version UID.
@@ -76,12 +75,6 @@ public class SimpleTransition extends Executable
     private String next;
 
     /**
-     * The current XML namespaces in the SCXML document for this action node,
-     * preserved for deferred XPath evaluation.
-     */
-    private Map<String, String> namespaces;
-
-    /**
      * Constructor.
      */
     public SimpleTransition() {
@@ -243,24 +236,6 @@ public class SimpleTransition extends Executable
     }
 
     /**
-     * Get the XML namespaces at this action node in the SCXML document.
-     *
-     * @return Returns the map of namespaces.
-     */
-    public final Map<String, String> getNamespaces() {
-        return namespaces;
-    }
-
-    /**
-     * Set the XML namespaces at this action node in the SCXML document.
-     *
-     * @param namespaces The document namespaces.
-     */
-    public final void setNamespaces(final Map<String, String> namespaces) {
-        this.namespaces = namespaces;
-    }
-
-    /**
      * Get the set of transition targets (may be an empty list).
      *
      * @return Returns the target(s) as specified in SCXML markup.
index 71191d5..205076a 100644 (file)
@@ -19,14 +19,25 @@ package org.apache.commons.scxml2.model;
 import org.apache.commons.scxml2.ActionExecutionContext;
 import org.apache.commons.scxml2.Context;
 import org.apache.commons.scxml2.Evaluator;
+import org.apache.commons.scxml2.SCXMLConstants;
 import org.apache.commons.scxml2.SCXMLExpressionException;
 import org.apache.commons.scxml2.TriggerEvent;
 import org.apache.commons.scxml2.EventBuilder;
+import org.apache.commons.scxml2.io.SCXMLWriter;
 
 /**
- * The class in this SCXML object model that corresponds to the
- * &lt;var&gt; SCXML element.
- *
+ * The class in this SCXML object model that corresponds to the {@link CustomAction} &lt;var&gt; SCXML element.
+ * <p>
+ * When manually constructing or modifying a SCXML model using this custom action, either:
+ * <ul>
+ *     <li>derive from {@link CommonsSCXML}, or</li>
+ *     <li>make sure to add the {@link SCXMLConstants#XMLNS_COMMONS_SCXML} namespace with
+ *     the {@link SCXMLConstants#XMLNS_COMMONS_SCXML_PREFIX} prefix to the SCXML object, or</li>
+ *     <li>wrap the {@link Var} instance in a {@link CustomActionWrapper} (for which the {@link #CUSTOM_ACTION}
+ *     can be useful) before adding it to the object model</li>
+ * </ul>
+ * before write the SCXML model with {@link SCXMLWriter}. The writing will fail otherwise!
+ * </p>
  */
 public class Var extends Action {
 
@@ -35,6 +46,9 @@ public class Var extends Action {
      */
     private static final long serialVersionUID = 1L;
 
+    public static final CustomAction CUSTOM_ACTION =
+            new CustomAction(SCXMLConstants.XMLNS_COMMONS_SCXML, SCXMLConstants.ELEM_VAR, Var.class);
+
     /**
      * The name of the variable to be created.
      */
@@ -97,9 +111,7 @@ public class Var extends Action {
     public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
         Context ctx = exctx.getContext(getParentEnterableState());
         Evaluator eval = exctx.getEvaluator();
-        ctx.setLocal(getNamespacesKey(), getNamespaces());
         Object varObj = eval.eval(ctx, expr);
-        ctx.setLocal(getNamespacesKey(), null);
         ctx.setLocal(name, varObj);
         if (exctx.getAppLog().isDebugEnabled()) {
             exctx.getAppLog().debug("<var>: Defined variable '" + name
index 8fcb938..abc1a22 100644 (file)
@@ -708,7 +708,6 @@ public class SCXMLSemanticsImpl implements SCXMLSemantics {
         if (transition.getCond() != null) {
             Boolean result = Boolean.FALSE;
             Context context = exctx.getScInstance().getContext(transition.getParent());
-            context.setLocal(Context.NAMESPACES_KEY, transition.getNamespaces());
             try {
                 if ((result = exctx.getEvaluator().evalCond(context, transition.getCond())) == null) {
                     result = Boolean.FALSE;
@@ -723,9 +722,6 @@ public class SCXMLSemanticsImpl implements SCXMLSemantics {
                 exctx.getErrorReporter().onError(ErrorConstants.EXPRESSION_ERROR, "Treating as false due to error: "
                         + e.getMessage(), transition);
             }
-            finally {
-                context.setLocal(Context.NAMESPACES_KEY, null);
-            }
             return result;
         }
         return true;
index 9ef6b14..73b27ce 100644 (file)
@@ -111,7 +111,6 @@ public class JSEvaluatorTest {
             fsm.go();
             evaluator = fsm.getEvaluator();
             context = fsm.getGlobalContext();
-            context.set(Context.NAMESPACES_KEY,null);
     }
 
     // CLASS METHODS
index feb7da3..ac9be57 100644 (file)
@@ -35,6 +35,7 @@ import org.apache.commons.scxml2.SCXMLTestHelper;
 import org.apache.commons.scxml2.io.SCXMLReader.Configuration;
 import org.apache.commons.scxml2.model.Action;
 import org.apache.commons.scxml2.model.CustomAction;
+import org.apache.commons.scxml2.model.CustomActionWrapper;
 import org.apache.commons.scxml2.model.Data;
 import org.apache.commons.scxml2.model.Datamodel;
 import org.apache.commons.scxml2.model.EnterableState;
@@ -178,7 +179,7 @@ public class SCXMLReaderTest {
         Assert.assertEquals("actions", state.getId());
         List<Action> actions = state.getOnEntries().get(0).getActions();
         Assert.assertEquals(1, actions.size());
-        MyAction my = (MyAction) actions.get(0);
+        MyAction my = (MyAction)((CustomActionWrapper)actions.get(0)).getAction();
         Assert.assertNotNull(my);
         Assert.assertTrue(my.getExternalNodes().size() > 0);
     }
index 6b18abd..6ce85da 100644 (file)
@@ -18,13 +18,15 @@ package org.apache.commons.scxml2.io;
 
 import java.io.IOException;
 import java.util.LinkedHashMap;
-import java.util.Map;
 
+import org.apache.commons.scxml2.model.CommonsSCXML;
 import org.apache.commons.scxml2.model.ModelException;
+import org.apache.commons.scxml2.model.OnEntry;
 import org.apache.commons.scxml2.model.Parallel;
 import org.apache.commons.scxml2.model.SCXML;
 import org.apache.commons.scxml2.model.Script;
 import org.apache.commons.scxml2.model.State;
+import org.apache.commons.scxml2.model.Var;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -34,10 +36,9 @@ public class SCXMLWriterTest {
 
     @Test
     public void testSerializeSCXMLNoStates() throws IOException, XMLStreamException {
-        SCXML scxml = new SCXML();
-        Map<String, String> namespaces = new LinkedHashMap<String, String>();
-        namespaces.put("", "http://www.w3.org/2005/07/scxml");
-        namespaces.put("cs", "http://commons.apache.org/scxml");
+        SCXML scxml = new CommonsSCXML();
+        // ensure namespaces are stored in insertion order for write->read comparision below
+        LinkedHashMap namespaces = new LinkedHashMap(scxml.getNamespaces());
         namespaces.put("foo", "http://f.o.o");
         namespaces.put("bar", "http://b.a.r");
         scxml.setNamespaces(namespaces);
@@ -58,9 +59,7 @@ public class SCXMLWriterTest {
     
     @Test
     public void testSerializeSCXMLState() throws IOException, XMLStreamException {
-        SCXML scxml = new SCXML();
-        Map<String, String> namespaces = new LinkedHashMap<String, String>();
-        scxml.setNamespaces(namespaces);
+        SCXML scxml = new CommonsSCXML();
         scxml.setVersion("1.0");
         scxml.setInitial("S1");
 
@@ -80,9 +79,7 @@ public class SCXMLWriterTest {
     @Test
     public void testSerializeParallel() throws IOException, XMLStreamException {
 
-        SCXML scxml = new SCXML();
-        Map<String, String> namespaces = new LinkedHashMap<String, String>();
-        scxml.setNamespaces(namespaces);
+        SCXML scxml = new CommonsSCXML();
         scxml.setVersion("1.0");
         scxml.setInitial("par");
 
@@ -129,9 +126,7 @@ public class SCXMLWriterTest {
 
     @Test
     public void testSerializeGlobalScript() throws IOException, ModelException, XMLStreamException {
-        SCXML scxml = new SCXML();
-        Map<String, String> namespaces = new LinkedHashMap<String, String>();
-        scxml.setNamespaces(namespaces);
+        SCXML scxml = new CommonsSCXML();
         scxml.setVersion("1.0");
         scxml.setInitial("S1");