SCXML-276 Simplify and generalize reading and writing SCXML content and data element...
authorAte Douma <ate@apache.org>
Sun, 17 Dec 2017 23:22:05 +0000 (00:22 +0100)
committerAte Douma <ate@apache.org>
Sun, 17 Dec 2017 23:22:45 +0000 (00:22 +0100)
18 files changed:
src/main/java/org/apache/commons/scxml2/SCInstance.java
src/main/java/org/apache/commons/scxml2/io/ContentParser.java
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/Assign.java
src/main/java/org/apache/commons/scxml2/model/Content.java
src/main/java/org/apache/commons/scxml2/model/Data.java
src/main/java/org/apache/commons/scxml2/model/Final.java
src/main/java/org/apache/commons/scxml2/model/Invoke.java
src/main/java/org/apache/commons/scxml2/model/JsonValue.java [new file with mode: 0644]
src/main/java/org/apache/commons/scxml2/model/NodeListValue.java [moved from src/main/java/org/apache/commons/scxml2/model/ExternalContent.java with 67% similarity]
src/main/java/org/apache/commons/scxml2/model/NodeTextValue.java [new file with mode: 0644]
src/main/java/org/apache/commons/scxml2/model/NodeValue.java [new file with mode: 0644]
src/main/java/org/apache/commons/scxml2/model/ParsedValue.java [new file with mode: 0644]
src/main/java/org/apache/commons/scxml2/model/ParsedValueContainer.java [new file with mode: 0644]
src/main/java/org/apache/commons/scxml2/model/Send.java
src/main/java/org/apache/commons/scxml2/model/TextValue.java [new file with mode: 0644]
src/test/java/org/apache/commons/scxml2/io/SCXMLReaderTest.java

index 51c0cf7..c293afa 100644 (file)
@@ -345,7 +345,8 @@ public class SCInstance implements Serializable {
                     resolvedSrc = pr.resolvePath(resolvedSrc);
                 }
                 try {
-                    value = ContentParser.DEFAULT_PARSER.parseResource(resolvedSrc);
+                    datum.setParsedValue(ContentParser.DEFAULT_PARSER.parseResource(resolvedSrc));
+                    value = evaluator.cloneData(datum.getParsedValue().getValue());
                     setValue = true;
                 } catch (IOException e) {
                     if (internalIOProcessor != null) {
@@ -365,8 +366,12 @@ public class SCInstance implements Serializable {
                     errorReporter.onError(ErrorConstants.EXPRESSION_ERROR, see.getMessage(), datum);
                 }
             }
+            else if (datum.getParsedValue() != null) {
+                value = evaluator.cloneData(datum.getParsedValue().getValue());
+                setValue = true;
+            }
             else {
-                value = evaluator.cloneData(datum.getValue());
+                // initialize data value with null
                 setValue = true;
             }
             if (setValue) {
index 1f4290e..a4b07ad 100644 (file)
@@ -35,6 +35,10 @@ import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.scxml2.model.JsonValue;
+import org.apache.commons.scxml2.model.NodeValue;
+import org.apache.commons.scxml2.model.ParsedValue;
+import org.apache.commons.scxml2.model.TextValue;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
@@ -163,6 +167,16 @@ public class ContentParser {
     }
 
     /**
+     * Transforms a jsonObject to a json String
+     * @param jsonObject object to transform
+     * @return json string
+     * @throws IOException
+     */
+    public String toJson(final Object jsonObject) throws IOException {
+        return jsonObjectMapper.writeValueAsString(jsonObject);
+    }
+
+    /**
      * Parse an XML String and return the document element
      * @param xmlString XML String to parse
      * @return document element
@@ -180,20 +194,30 @@ public class ContentParser {
         return doc != null ? doc.getDocumentElement() : null;
     }
 
-    public String transformXml(final Node node) throws TransformerException {
-        StringWriter writer = new StringWriter();
-        Transformer transformer = TransformerFactory.newInstance().newTransformer();
-        Properties outputProps = new Properties();
-        outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "no");
-        outputProps.put(OutputKeys.STANDALONE, "no");
-        outputProps.put(OutputKeys.INDENT, "yes");
-        transformer.setOutputProperties(outputProps);
-        transformer.transform(new DOMSource(node), new StreamResult(writer));
-        return writer.toString();
+    /**
+     * Transforms a XML Node to XML
+     * @param node node to transform
+     * @return XML string
+     * @throws IOException
+     */
+    public String toXml(final Node node) throws IOException {
+        try {
+            StringWriter writer = new StringWriter();
+            Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            Properties outputProps = new Properties();
+            outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "no");
+            outputProps.put(OutputKeys.STANDALONE, "no");
+            outputProps.put(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperties(outputProps);
+            transformer.transform(new DOMSource(node), new StreamResult(writer));
+            return writer.toString();
+        } catch (TransformerException e) {
+            throw new IOException(e);
+        }
     }
 
     /**
-     * Parse a string into a content object, following the SCXML rules as specified for the ECMAscript (section B.2.1) Data Model
+     * Parse a string into a ParsedValue content object, following the SCXML rules as specified for the ECMAscript (section B.2.1) Data Model
      * <ul>
      *   <li>if the content can be interpreted as JSON, it will be parsed as JSON into an 'raw' object model</li>
      *   <li>if the content can be interpreted as XML, it will be parsed into a XML DOM element</li>
@@ -203,27 +227,27 @@ public class ContentParser {
      * @return the parsed content object
      * @throws IOException In case of parsing exceptions
      */
-    public Object parseContent(final String content) throws IOException {
+    public ParsedValue parseContent(final String content) throws IOException {
         if (content != null) {
             String src = trimContent(content);
             if (hasJsonSignature(src)) {
-                return parseJson(src);
+                return new JsonValue(parseJson(src), false);
             }
             else if (hasXmlSignature(src)) {
-                return parseXml(src);
+                return new NodeValue(parseXml(src));
             }
-            return spaceNormalizeContent(src);
+            return new TextValue(spaceNormalizeContent(src), false);
         }
         return null;
     }
 
     /**
-     * Load a resource (URL) as an UTF-8 encoded content string to be parsed into a content object through {@link #parseContent(String)}
+     * Load a resource (URL) as an UTF-8 encoded content string to be parsed into a ParsedValue content object through {@link #parseContent(String)}
      * @param resourceURL Resource URL to load content from
      * @return the parsed content object
      * @throws IOException In case of loading or parsing exceptions
      */
-    public Object parseResource(final String resourceURL) throws IOException {
+    public ParsedValue parseResource(final String resourceURL) throws IOException {
         try (InputStream in = new URL(resourceURL).openStream()) {
             String content = IOUtils.toString(in, "UTF-8");
             return parseContent(content);
index 235f1b5..eeac7f2 100644 (file)
@@ -41,7 +41,6 @@ import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
 import javax.xml.stream.util.XMLEventAllocator;
 import javax.xml.transform.Source;
-import javax.xml.transform.TransformerException;
 import javax.xml.transform.stream.StreamSource;
 import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
@@ -68,7 +67,10 @@ import org.apache.commons.scxml2.model.Else;
 import org.apache.commons.scxml2.model.ElseIf;
 import org.apache.commons.scxml2.model.EnterableState;
 import org.apache.commons.scxml2.model.Executable;
-import org.apache.commons.scxml2.model.ExternalContent;
+import org.apache.commons.scxml2.model.JsonValue;
+import org.apache.commons.scxml2.model.NodeListValue;
+import org.apache.commons.scxml2.model.NodeValue;
+import org.apache.commons.scxml2.model.ParsedValueContainer;
 import org.apache.commons.scxml2.model.Final;
 import org.apache.commons.scxml2.model.Finalize;
 import org.apache.commons.scxml2.model.Foreach;
@@ -89,10 +91,12 @@ import org.apache.commons.scxml2.model.Script;
 import org.apache.commons.scxml2.model.Send;
 import org.apache.commons.scxml2.model.SimpleTransition;
 import org.apache.commons.scxml2.model.State;
+import org.apache.commons.scxml2.model.TextValue;
 import org.apache.commons.scxml2.model.Transition;
 import org.apache.commons.scxml2.model.TransitionType;
 import org.apache.commons.scxml2.model.TransitionalState;
 import org.apache.commons.scxml2.model.Var;
+import org.apache.commons.scxml2.model.NodeTextValue;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -116,6 +120,12 @@ public final class SCXMLReader {
 
     private static final org.apache.commons.logging.Log logger = LogFactory.getLog(SCXMLReader.class);
 
+    /**
+     * By default Sun/Oracle XMLStreamReader implementation doesn't report CDATA events.
+     * This can be turned on (as needed by SCXMLReader) by setting this property TRUE
+     */
+    public final static String XMLInputFactory_JDK_PROP_REPORT_CDATA = "http://java.sun.com/xml/stream/properties/report-cdata-event";
+
     //---------------------- PRIVATE CONSTANTS ----------------------//
     /**
      * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1
@@ -872,7 +882,7 @@ public final class SCXMLReader {
                             }
                         } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
                             if (doneData.getParams().isEmpty()) {
-                                readContent(reader, doneData);
+                                readContent(reader, configuration, doneData);
                             }
                             else {
                                 reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
@@ -1081,32 +1091,12 @@ public final class SCXMLReader {
                 reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_DATA, SCXMLConstants.ATTR_EXPR, SCXMLConstants.ATTR_SRC);
             }
             datum.setExpr(expr);
+            skipToEndElement(reader);
+        } else if (src != null ) {
+            datum.setSrc(src);
+            skipToEndElement(reader);
         } else {
-            if (src != null) {
-                datum.setSrc(src);
-            }
-        }
-
-        Element node = readElement(reader);
-        datum.setNode(node);
-        if (node.hasChildNodes()) {
-            NodeList children = node.getChildNodes();
-            if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) {
-                String text = configuration.contentParser.trimContent(children.item(0).getNodeValue());
-                if (configuration.contentParser.hasJsonSignature(text)) {
-                    try {
-                        datum.setValue(configuration.contentParser.parseJson(text));
-                    } catch (IOException e) {
-                        throw new ModelException(e);
-                    }
-                }
-                else {
-                    datum.setValue(configuration.contentParser.spaceNormalizeContent(text));
-                }
-            }
-            if (datum.getValue() == null) {
-                datum.setValue(node);
-            }
+            readParsedValue(reader, configuration, datum, false);
         }
         dm.addData(datum);
     }
@@ -1147,7 +1137,7 @@ public final class SCXMLReader {
                         } else if (SCXMLConstants.ELEM_FINALIZE.equals(name)) {
                             readFinalize(reader, configuration, parent, invoke);
                         } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
-                            readContent(reader, invoke);
+                            readContent(reader, configuration, invoke);
                         } else {
                             reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, nsURI, name);
                         }
@@ -1226,12 +1216,14 @@ 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 ContentContainer contentContainer)
-            throws XMLStreamException {
+    private static void readContent(final XMLStreamReader reader, final Configuration configuration,
+                                    final ContentContainer contentContainer)
+            throws XMLStreamException, ModelException {
 
         Content content = new Content();
         content.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
@@ -1239,39 +1231,7 @@ public final class SCXMLReader {
             skipToEndElement(reader);
         }
         else {
-            Element body = readElement(reader);
-            if (body.hasChildNodes()) {
-                content.setBody(body);
-                NodeList children = body.getChildNodes();
-                if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) {
-                    try {
-                        content.setValue(ContentParser.DEFAULT_PARSER.parseContent(children.item(0).getNodeValue()));
-                    }
-                    catch (IOException ioe) {
-                        throw new XMLStreamException(ioe);
-                    }
-                }
-                else {
-                    // find only use first child element
-                    for (int i = 0, size = children.getLength(); i < size; i++) {
-                        Node child = children.item(i);
-                        if (child.getNodeType() == Node.ELEMENT_NODE) {
-                            if (contentContainer instanceof Invoke) {
-                                // transform invoke <content/> to string upfront as we currently only can handle string input
-                                try {
-                                    content.setValue(ContentParser.DEFAULT_PARSER.transformXml(child));
-                                } catch (TransformerException e) {
-                                    throw new XMLStreamException(e);
-                                }
-                            }
-                            else {
-                                content.setValue(child);
-                            }
-                            break;
-                        }
-                    }
-                }
-            }
+            readParsedValue(reader, configuration, content, contentContainer instanceof Invoke);
         }
         contentContainer.setContent(content);
     }
@@ -1751,34 +1711,15 @@ public final class SCXMLReader {
                 resolvedSrc = configuration.pathResolver.resolvePath(resolvedSrc);
             }
             try {
-                assign.setValue(ContentParser.DEFAULT_PARSER.parseResource(resolvedSrc));
+                assign.setParsedValue(ContentParser.DEFAULT_PARSER.parseResource(resolvedSrc));
             } catch (IOException e) {
                 throw new ModelException(e);
             }
         }
         else {
             Location location = reader.getLocation();
-            Element node = readElement(reader);
-            if (node.hasChildNodes()) {
-                assign.setNode(node);
-                NodeList children = node.getChildNodes();
-                if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) {
-                    String text = configuration.contentParser.trimContent(children.item(0).getNodeValue());
-                    if (configuration.contentParser.hasJsonSignature(text)) {
-                        try {
-                            assign.setValue(configuration.contentParser.parseJson(text));
-                        } catch (IOException e) {
-                            throw new ModelException(e);
-                        }
-                    }
-                    else {
-                        assign.setValue(configuration.contentParser.spaceNormalizeContent(text));
-                    }
-                } else {
-                    // can only handle a single node: pick the first child
-                    assign.setValue(children.item(0));
-                }
-            } else {
+            readParsedValue(reader, configuration, assign, false);
+            if (assign.getParsedValue() == null) {
                 // report missing expression (as most common use-case)
                 reportMissingAttribute(location, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_EXPR);
             }
@@ -1886,7 +1827,7 @@ public final class SCXMLReader {
                             }
                         } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
                             if (send.getNamelist() == null && send.getParams().isEmpty()) {
-                                readContent(reader, send);
+                                readContent(reader, configuration, send);
                             }
                             else {
                                 reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
@@ -2035,7 +1976,7 @@ public final class SCXMLReader {
     private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration,
                                          final CustomAction customAction, final Executable executable,
                                          final ActionsContainer parent)
-            throws XMLStreamException {
+            throws XMLStreamException, ModelException {
 
         // Instantiate custom action
         Object actionObject;
@@ -2098,13 +2039,8 @@ public final class SCXMLReader {
         }
 
         // Add any body content if necessary
-        if (action instanceof ExternalContent) {
-            Element body = readElement(reader);
-            NodeList childNodes = body.getChildNodes();
-            List<Node> externalNodes = ((ExternalContent) action).getExternalNodes();
-            for (int i = 0; i < childNodes.getLength(); i++) {
-                externalNodes.add(childNodes.item(i));
-            }
+        if (action instanceof ParsedValueContainer) {
+            readParsedValue(reader, configuration, (ParsedValueContainer)action, false);
         }
         else {
             skipToEndElement(reader);
@@ -2120,6 +2056,76 @@ public final class SCXMLReader {
     }
 
     /**
+     * Read and parse the body of a {@link ParsedValueContainer} element.
+     *
+     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
+     * @param configuration The {@link Configuration} to use while parsing.
+     * @param valueContainer The {@link ParsedValueContainer} element which body to read
+     * @param invokeContent flag indicating if the valueContainer is a <invoke><content> element, which get special
+     *                      treatment
+     *
+     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+     */
+    private static void readParsedValue(final XMLStreamReader reader, final Configuration configuration,
+                                        final ParsedValueContainer valueContainer, final boolean invokeContent)
+            throws XMLStreamException, ModelException {
+        Element element = readElement(reader);
+        if (element.hasChildNodes()) {
+            NodeList children = element.getChildNodes();
+            Node child = children.item(0);
+            boolean cdata = child.getNodeType() == Node.CDATA_SECTION_NODE;
+            if (invokeContent) {
+                // search or and only use first <scxml> element
+                if (child.getNodeType() != Node.ELEMENT_NODE) {
+                    for (int i = 1, size = children.getLength(); i < size; i++) {
+                        child = children.item(i);
+                        if (child.getNodeType() == Node.ELEMENT_NODE) {
+                            break;
+                        }
+                    }
+                }
+                if (child.getNodeType() == Node.ELEMENT_NODE) {
+                    if (SCXMLConstants.ELEM_SCXML.equals(child.getLocalName()) &&
+                            SCXMLConstants.XMLNS_SCXML.equals(child.getNamespaceURI())) {
+                        // transform <invoke><content><scxml> back to text
+                        try {
+                            valueContainer.setParsedValue(new NodeTextValue(ContentParser.DEFAULT_PARSER.toXml(child)));
+                        } catch (IOException e) {
+                            throw new XMLStreamException(e);
+                        }
+                    }
+                }
+                if (valueContainer.getParsedValue() == null) {
+                    reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, SCXMLConstants.XMLNS_SCXML,
+                            SCXMLConstants.ELEM_CONTENT);
+                }
+            }
+            else if (children.getLength() == 1 && (cdata || child.getNodeType() == Node.TEXT_NODE )) {
+                String text = configuration.contentParser.trimContent(child.getNodeValue());
+                if (configuration.contentParser.hasJsonSignature(text)) {
+                    try {
+                        valueContainer.setParsedValue(new JsonValue(configuration.contentParser.parseJson(text), cdata));
+                    } catch (IOException e) {
+                        throw new ModelException(e);
+                    }
+                }
+                else {
+                    valueContainer.setParsedValue(new TextValue(configuration.contentParser.spaceNormalizeContent(text),
+                            cdata));
+                }
+            } else if (children.getLength() == 1) {
+                valueContainer.setParsedValue(new NodeValue(child));
+            } else {
+                ArrayList<Node> nodeList = new ArrayList<>();
+                for (int i = 0, size = children.getLength(); i < size; i++) {
+                    nodeList.add(children.item(i));
+                }
+                valueContainer.setParsedValue(new NodeListValue(nodeList));
+            }
+        }
+    }
+
+    /**
      * Read the current element into a DOM {@link Element}.
      *
      * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
@@ -2143,7 +2149,7 @@ public final class SCXMLReader {
         Element root = document.createElementNS(reader.getNamespaceURI(), createQualifiedName(reader.getPrefix(), reader.getLocalName()));
         document.appendChild(root);
 
-        boolean children = false;
+        boolean children = false, cdata = false;
         Node parent = root;
 
         // Convert stream to DOM node(s) while maintaining parent child relationships
@@ -2175,12 +2181,10 @@ public final class SCXMLReader {
                     }
                     break;
                 case XMLStreamConstants.CDATA:
-                    children = true;
-                    child = document.createCDATASection(reader.getText());
-                    break;
-                case XMLStreamConstants.COMMENT:
-                    children = true;
-                    child = document.createComment(reader.getText());
+                    if (!children || parent != root) {
+                        cdata = true;
+                        child = document.createCDATASection(reader.getText());
+                    }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
                     parent = parent.getParentNode();
@@ -2194,8 +2198,14 @@ public final class SCXMLReader {
                 parent.appendChild(child);
             }
         }
-        if (!children && root.hasChildNodes()) {
-            root.setTextContent(root.getTextContent().trim());
+        if (!children && root.hasChildNodes() && root.getChildNodes().getLength() > 1) {
+            String text = root.getTextContent().trim();
+            if (!cdata) {
+                root.setTextContent(text);
+            } else {
+                root.setTextContent(null);
+                root.appendChild(document.createCDATASection(text));
+            }
         }
         return root;
     }
@@ -2505,8 +2515,13 @@ public final class SCXMLReader {
             factory = XMLInputFactory.newFactory(configuration.factoryId, configuration.factoryClassLoader);
         }
         factory.setEventAllocator(configuration.allocator);
+        if (factory.isPropertySupported(XMLInputFactory_JDK_PROP_REPORT_CDATA)) {
+            factory.setProperty(XMLInputFactory_JDK_PROP_REPORT_CDATA, Boolean.TRUE);
+        }
         for (Map.Entry<String, Object> property : configuration.properties.entrySet()) {
-            factory.setProperty(property.getKey(), property.getValue());
+            if (factory.isPropertySupported(property.getKey())) {
+                factory.setProperty(property.getKey(), property.getValue());
+            }
         }
         factory.setXMLReporter(configuration.reporter);
         factory.setXMLResolver(configuration.resolver);
index 23f3016..d3f7b33 100644 (file)
@@ -53,8 +53,13 @@ import org.apache.commons.scxml2.model.Datamodel;
 import org.apache.commons.scxml2.model.Else;
 import org.apache.commons.scxml2.model.ElseIf;
 import org.apache.commons.scxml2.model.EnterableState;
+import org.apache.commons.scxml2.model.JsonValue;
+import org.apache.commons.scxml2.model.NodeListValue;
+import org.apache.commons.scxml2.model.NodeTextValue;
+import org.apache.commons.scxml2.model.NodeValue;
+import org.apache.commons.scxml2.model.ParsedValue;
 import org.apache.commons.scxml2.model.Raise;
-import org.apache.commons.scxml2.model.ExternalContent;
+import org.apache.commons.scxml2.model.ParsedValueContainer;
 import org.apache.commons.scxml2.model.Final;
 import org.apache.commons.scxml2.model.Finalize;
 import org.apache.commons.scxml2.model.Foreach;
@@ -72,11 +77,11 @@ import org.apache.commons.scxml2.model.Script;
 import org.apache.commons.scxml2.model.Send;
 import org.apache.commons.scxml2.model.SimpleTransition;
 import org.apache.commons.scxml2.model.State;
+import org.apache.commons.scxml2.model.TextValue;
 import org.apache.commons.scxml2.model.Transition;
 import org.apache.commons.scxml2.model.TransitionTarget;
 import org.apache.commons.scxml2.model.Var;
 import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
 
 /**
  * <p>Utility class for serializing the Commons SCXML Java object
@@ -481,21 +486,13 @@ public class SCXMLWriter {
         }
 
         writer.writeStartElement(SCXMLConstants.ELEM_DATAMODEL);
-        if (datamodel.getData().size() > 0 && XFORMER == null) {
-            writer.writeComment("Datamodel was not serialized");
-        } else {
-            for (Data d : datamodel.getData()) {
-                Node n = d.getNode();
-                if (n != null) {
-                    writeNode(writer, n);
-                } else {
-                    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();
-                }
-            }
+        for (Data d : datamodel.getData()) {
+            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()));
+            writeParsedValue(writer, d.getParsedValue());
+            writer.writeEndElement();
         }
         writer.writeEndElement();
     }
@@ -817,15 +814,12 @@ public class SCXMLWriter {
         for (Action a : actions) {
             if (a instanceof Assign) {
                 Assign asn = (Assign) a;
-                if (asn.getNode() != null) {
-                    writeNode(writer, asn.getNode());
-                } else {
-                    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();
-                }
+                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()));
+                writeParsedValue(writer, ((Assign) a).getParsedValue());
+                writer.writeEndElement();
             } else if (a instanceof Send) {
                 writeSend(writer, (Send) a);
             } else if (a instanceof Cancel) {
@@ -882,8 +876,8 @@ public class SCXMLWriter {
                 for (final String prefix : actionWrapper.getNamespaces().keySet()) {
                     writer.writeNamespace(prefix, actionWrapper.getNamespaces().get(prefix));
                 }
-                if (actionWrapper.getAction() instanceof ExternalContent) {
-                    writeExternalContent(writer, (ExternalContent) actionWrapper.getAction());
+                if (actionWrapper.getAction() instanceof ParsedValueContainer) {
+                    writeParsedValue(writer, ((ParsedValueContainer)actionWrapper.getAction()).getParsedValue());
                 }
                 writer.writeEndElement();
             } else {
@@ -979,46 +973,64 @@ public class SCXMLWriter {
         if (content != null) {
             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();
-                    if (nodeList.getLength() > 0 && XFORMER == null) {
-                        writer.writeComment("External content was not serialized");
-                    }
-                    else {
-                        for (int i = 0, size = nodeList.getLength(); i < size; i++) {
-                            writeNode(writer, nodeList.item(i));
-                        }
-                    }
-                }
-                else {
-                    writer.writeCharacters(content.getBody().toString());
-                }
-            }
+            writeParsedValue(writer, content.getParsedValue());
             writer.writeEndElement();
         }
     }
 
     /**
-     * Write the serialized body of this {@link ExternalContent} element.
+     * Write out this {@link ParsedValue} object as body of its containing {@link ParsedValueContainer} element.
      *
      * @param writer The {@link XMLStreamWriter} in use for the serialization.
-     * @param externalContent The model element containing the external body content.
+     * @param parsedValue The {@link ParsedValue} to serialize.
      *
      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
      */
-    private static void writeExternalContent(final XMLStreamWriter writer,
-                                             final ExternalContent externalContent)
+    private static void writeParsedValue(final XMLStreamWriter writer, final ParsedValue parsedValue)
             throws XMLStreamException {
-
-        List<Node> externalNodes = externalContent.getExternalNodes();
-
-        if (externalNodes.size() > 0 && XFORMER == null) {
-            writer.writeComment("External content was not serialized");
-        } else {
-            for (Node n : externalNodes) {
-                writeNode(writer, n);
+        try {
+            if (parsedValue != null) {
+                switch (parsedValue.getType()) {
+                    case TEXT:
+                        final TextValue textValue = (TextValue)parsedValue;
+                        if (textValue.isCDATA()) {
+                            writer.writeCData(textValue.getValue());
+                        } else {
+                            writer.writeCharacters(textValue.getValue());
+                        }
+                        break;
+                    case JSON:
+                        final String value = ContentParser.DEFAULT_PARSER.toJson(parsedValue.getValue());
+                        if (((JsonValue) parsedValue).isCDATA()) {
+                            writer.writeCData(value);
+                        } else {
+                            writer.writeCharacters(value);
+                        }
+                        break;
+                    case NODE:
+                        if (XFORMER == null) {
+                            writer.writeComment("element body was not serialized");
+                        } else {
+                            writeNode(writer, ((NodeValue)parsedValue).getValue());
+                        }
+                        break;
+                    case NODE_LIST:
+                        List<Node> nodeList = ((NodeListValue)parsedValue).getValue();
+                        if (!nodeList.isEmpty() && XFORMER == null) {
+                            writer.writeComment("element body was not serialized");
+                        } else {
+                            for (final Node node : nodeList) {
+                                writeNode(writer, node);
+                            }
+                        }
+                        break;
+                    case NODE_TEXT:
+                        writeNode(writer, ContentParser.DEFAULT_PARSER.parseXml((String)parsedValue.getValue()));
+                        break;
+                }
             }
+        } catch (IOException e) {
+            throw new XMLStreamException(e);
         }
     }
 
index fa7e0dc..21e66d1 100644 (file)
@@ -20,14 +20,13 @@ import org.apache.commons.scxml2.ActionExecutionContext;
 import org.apache.commons.scxml2.Context;
 import org.apache.commons.scxml2.Evaluator;
 import org.apache.commons.scxml2.SCXMLExpressionException;
-import org.w3c.dom.Node;
 
 /**
  * The class in this SCXML object model that corresponds to the
  * &lt;assign&gt; SCXML element.
  *
  */
-public class Assign extends Action {
+public class Assign extends Action implements ParsedValueContainer {
 
     /**
      * Serial version UID.
@@ -51,14 +50,9 @@ public class Assign extends Action {
     private String expr;
 
     /**
-     * The assign definition parsed as a standalone DocumentFragment Node (only used by the SCXMLWriter)
+     * The assign element body value, or the value from external {@link #getSrc()}, may be null.
      */
-    private Node node;
-
-    /**
-     * The parsed value for the child XML data tree or the content of the external src
-     */
-    private Object value;
+    private ParsedValue assignValue;
 
     /**
      * Constructor.
@@ -122,45 +116,22 @@ public class Assign extends Action {
     }
 
     /**
-     * Get the assign definition parsed as standalone DocumentFragment Node.
+     * Get the assign value
      *
-     * @return Node The assign definition parsed as a standalone DocumentFragment <code>Node</code>.
+     * @return The assign value
      */
-    public final Node getNode() {
-        return node;
+    public final ParsedValue getParsedValue() {
+        return assignValue;
     }
 
     /**
-     * Set the assign definition parsed as standalone DocumentFragment Node.
+     * Set the assign value
      *
-     * @param node The child XML data tree, parsed as a standalone DocumentFragment <code>Node</code>.
-     */
-    public final void setNode(final Node node) {
-        this.node = node;
-    }
-
-    /**
-     * Get the parsed value for the child XML data tree or the content of the external src
-     * @see #setValue(Object)
-     * @return The parsed value
-     */
-    public final Object getValue() {
-        return value;
-    }
-
-    /**
-     * Sets the parsed value for the child XML data tree or the content of the external src
-     * @param value a serializable object:
-     * <ul>
-     *   <li>"Raw" JSON mapped object tree (array->ArrayList, object->LinkedHashMap based)</li>
-     *   <li>XML Node (equals {@link #getNode()})</li>
-     *   <li>space-normalized String</li>
-     * </ul>
+     * @param assignValue The assign value
      */
-    public final void setValue(final Object value) {
-        this.value = value;
+    public final void setParsedValue(final ParsedValue assignValue) {
+        this.assignValue = assignValue;
     }
-
     /**
      * {@inheritDoc}
      */
@@ -169,12 +140,12 @@ public class Assign extends Action {
         EnterableState parentState = getParentEnterableState();
         Context ctx = exctx.getContext(parentState);
         Evaluator evaluator = exctx.getEvaluator();
-        Object data;
+        Object data = null;
         if (expr != null) {
             data = evaluator.eval(ctx, expr);
         }
-        else {
-            data = evaluator.cloneData(value);
+        else if (assignValue != null) {
+            data = evaluator.cloneData(assignValue.getValue());
         }
 
         evaluator.evalAssign(ctx, location, data);
index 2df2844..8fa112a 100644 (file)
  */
 package org.apache.commons.scxml2.model;
 
-import java.io.Serializable;
-
-import org.w3c.dom.Node;
-
 /**
  * The class in this SCXML object model that corresponds to the
  * &lt;content&gt; SCXML element.
  *
  */
-public class Content implements Serializable {
+public class Content implements ParsedValueContainer {
 
     /**
      * Serial version UID.
@@ -38,14 +34,9 @@ public class Content implements Serializable {
     private String expr;
 
     /**
-     * The body of this content parsed as Node, may be null.
+     * The content body, may be null.
      */
-    private Node body;
-
-    /**
-     * The parsed content of the body of this content, may be null.
-     */
-    private Object value;
+    private ParsedValue contentBody;
 
     /**
      * Get the expression for this content.
@@ -66,35 +57,20 @@ public class Content implements Serializable {
     }
 
     /**
-     * Returns the content body as Node
+     * Get the content element body
      *
-     * @return the content body as Node
-     */
-    public Node getBody() {
-        return body;
-    }
-
-    /**
-     * Sets the content body as Node
-     * @param body the content body as Node
+     * @return The content element body.
      */
-    public void setBody(final Node body) {
-        this.body = body;
+    public final ParsedValue getParsedValue() {
+        return contentBody;
     }
 
     /**
-     * Returns the parsed content of the body
-     * @return the parsed content of the body
-     */
-    public Object getValue() {
-        return value;
-    }
-
-    /**
-     * Sets the parsed content of the body
-     * @param value the parsed content of the body
+     * Set the content element body
+     *
+     * @param contentBody The content element body
      */
-    public void setValue(final Object value) {
-        this.value = value;
+    public final void setParsedValue(final ParsedValue contentBody) {
+        this.contentBody = contentBody;
     }
 }
index 94cebf1..b668c04 100644 (file)
  */
 package org.apache.commons.scxml2.model;
 
-import java.io.Serializable;
-import java.util.Map;
-
-import org.w3c.dom.Node;
-
 /**
  * The class in this SCXML object model that corresponds to the SCXML
  * &lt;data&gt; child element of the &lt;datamodel&gt; element.
  *
  */
-public class Data implements Serializable {
+public class Data implements ParsedValueContainer {
 
     /**
      * Serial version UID.
@@ -50,15 +45,9 @@ public class Data implements Serializable {
     private String expr;
 
     /**
-     * The child XML data tree, parsed as a Node
-     * instance.
+     * The data element body value, or the value from external {@link #getSrc()}, may be null.
      */
-    private Node node;
-
-    /**
-     * The parsed value for the child XML data tree or the external src (with early-binding), to be cloned before usage
-     */
-    private Object value;
+    private ParsedValue dataValue;
 
     /**
      * Get the id.
@@ -115,43 +104,21 @@ public class Data implements Serializable {
     }
 
     /**
-     * Get the child XML data tree.
+     * Get the data value
      *
-     * @return Node The child XML data tree, parsed as a standalone DocumentFragment <code>Node</code>.
+     * @return The data value
      */
-    public final Node getNode() {
-        return node;
+    public final ParsedValue getParsedValue() {
+        return dataValue;
     }
 
     /**
-     * Set the child XML data tree.
+     * Set the data value
      *
-     * @param node The child XML data tree, parsed as a standalone DocumentFragment <code>Node</code>.
-     */
-    public final void setNode(final Node node) {
-        this.node = node;
-    }
-
-    /**
-     * Get the parsed value for the child XML data tree or the external src (with early-binding), to be cloned before usage.
-     * @see #setValue(Object)
-     * @return The parsed Data value
-     */
-    public final Object getValue() {
-        return value;
-    }
-
-    /**
-     * Sets the parsed value for the child XML data tree or the external src (with early-binding), to be cloned before usage.
-     * @param value a serializable object:
-     * <ul>
-     *   <li>"Raw" JSON mapped object tree (array->ArrayList, object->LinkedHashMap based)</li>
-     *   <li>XML Node (equals {@link #getNode()})</li>
-     *   <li>space-normalized String</li>
-     * </ul>
+     * @param dataValue The data value
      */
-    public final void setValue(final Object value) {
-        this.value = value;
+    public final void setParsedValue(final ParsedValue dataValue) {
+        this.dataValue = dataValue;
     }
 }
 
index 04aaa16..ff8f95d 100644 (file)
@@ -102,11 +102,8 @@ public class Final extends EnterableState {
                             evalResult = "";
                         }
                         result = eval.cloneData(evalResult);
-                    } else if (content.getValue() != null) {
-                        result = content.getValue();
-                    }
-                    else if (content.getBody() != null){
-                        result = eval.cloneData(content.getBody());
+                    } else if (content.getParsedValue() != null) {
+                        result = eval.cloneData(content.getParsedValue().getValue());
                     }
                 } else {
                     Map<String, Object> payloadDataMap = new LinkedHashMap<>();
index a4a0d21..fd8cef2 100644 (file)
  */
 package org.apache.commons.scxml2.model;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import javax.xml.transform.TransformerException;
-
 import org.apache.commons.scxml2.ActionExecutionContext;
 import org.apache.commons.scxml2.Context;
 import org.apache.commons.scxml2.Evaluator;
 import org.apache.commons.scxml2.PathResolver;
+import org.apache.commons.scxml2.SCXMLConstants;
 import org.apache.commons.scxml2.SCXMLExecutionContext;
 import org.apache.commons.scxml2.SCXMLExpressionException;
 import org.apache.commons.scxml2.SCXMLSystemContext;
@@ -411,22 +411,23 @@ public class Invoke extends Action implements ContentContainer, ParamsContainer
                                         + ", Using empty value instead.", getParent());
                         contentValue = "";
                     }
-                } else if (content.getValue() != null) {
-                    contentValue = content.getValue();
+                } else if (content.getParsedValue() != null) {
+                    contentValue = content.getParsedValue().getValue();
                 }
                 if (contentValue instanceof String) {
                     // inline content
                 } else if (contentValue instanceof Element) {
                     // xml based content (must be assigned through data)
                     Element contentElement = (Element)contentValue;
-                    if (contentElement.getLocalName().equals("scxml")) {
+                    if (SCXMLConstants.ELEM_SCXML.equals(contentElement.getLocalName()) &&
+                            SCXMLConstants.XMLNS_SCXML.equals(contentElement.getNamespaceURI())) {
                         // statemachine definition: transform to string as we cannot (yet) pass XML directly to invoker
                         try {
-                            contentValue = ContentParser.DEFAULT_PARSER.transformXml(contentElement);
+                            contentValue = ContentParser.DEFAULT_PARSER.toXml(contentElement);
                         }
-                        catch (TransformerException e) {
+                        catch (IOException e) {
                             throw new ActionExecutionError("<invoke> for state "+parentState.getId() +
-                                    ": invalid <content> definition");
+                                    ": invalid <content><scxml> definition");
                         }
                     } else {
                         throw new ActionExecutionError("<invoke> for state "+parentState.getId() +
diff --git a/src/main/java/org/apache/commons/scxml2/model/JsonValue.java b/src/main/java/org/apache/commons/scxml2/model/JsonValue.java
new file mode 100644 (file)
index 0000000..be7df47
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * Plain Java Object {@link ParsedValue} implementation mapped from a json string
+ * <p>
+ * For serialization back to XML flag {@link #isCDATA()} is recorded to wrap the json string in a CDATA section if needed.
+ * </p>
+ */
+public class JsonValue implements ParsedValue {
+
+    /**
+     * The Java Object mapped from a json string
+     */
+    private final Object jsonObject;
+
+    /**
+     * The flag indicating if the json string requires wrapping in a CDATA section when writing out to XML
+     */
+    private final boolean cdata;
+
+    public JsonValue(final Object jsonObject, final boolean cdata) {
+        this.jsonObject = jsonObject;
+        this.cdata = cdata;
+    }
+
+    @Override
+    public final ValueType getType() {
+        return ValueType.JSON;
+    }
+
+    /**
+     * @return true if the json string requires wrapping in a CDATA section when writing out to XML
+     */
+    public final boolean isCDATA() {
+        return cdata;
+    }
+
+    @Override
+    public final Object getValue() {
+        return jsonObject;
+    }
+}
@@ -21,21 +21,26 @@ import java.util.List;
 import org.w3c.dom.Node;
 
 /**
- * An <code>ExternalContent</code> implementation represents an
- * element in the SCXML document that may contain &quot;body
- * content&quot;, which means an arbitrary number of child nodes
- * belonging to external namespaces.
- *
+ * List of XML DOM Nodes {@link ParsedValue} implementation
  */
-public interface ExternalContent {
+public class NodeListValue implements ParsedValue {
 
     /**
-     * Return the list of external namespaced children as
-     * DOM node instances.
-     *
-     * @return The list of (external namespaced) child nodes.
+     * The list of nodes
      */
-    List<Node> getExternalNodes();
+    private final List<Node> nodeList;
 
-}
+    public NodeListValue(final List<Node> nodeList) {
+        this.nodeList = nodeList;
+    }
 
+    @Override
+    public final ValueType getType() {
+        return ValueType.NODE_LIST;
+    }
+
+    @Override
+    public final List<Node> getValue() {
+        return nodeList;
+    }
+}
diff --git a/src/main/java/org/apache/commons/scxml2/model/NodeTextValue.java b/src/main/java/org/apache/commons/scxml2/model/NodeTextValue.java
new file mode 100644 (file)
index 0000000..af1c9b5
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * XML DOM Node stored as text {@link ParsedValue} implementation.
+ */
+public class NodeTextValue implements ParsedValue {
+
+    /**
+     * the XML node as text
+     */
+    private String nodeText;
+
+    public NodeTextValue(final String nodeText) {
+        this.nodeText = nodeText;
+    }
+
+    @Override
+    public final ValueType getType() {
+        return ValueType.NODE_TEXT;
+    }
+
+    @Override
+    public String getValue() {
+        return nodeText;
+    }
+}
diff --git a/src/main/java/org/apache/commons/scxml2/model/NodeValue.java b/src/main/java/org/apache/commons/scxml2/model/NodeValue.java
new file mode 100644 (file)
index 0000000..2f56626
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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 org.w3c.dom.Node;
+
+/**
+ * Single XML DOM Node {@link ParsedValue} implementation
+ */
+public class NodeValue implements ParsedValue {
+
+    /**
+     * The Node object
+     */
+    private final Node node;
+
+    public NodeValue(final Node node) {
+        this.node = node;
+    }
+
+    @Override
+    public final ValueType getType() {
+        return ValueType.NODE;
+    }
+
+    @Override
+    public final Node getValue() {
+        return node;
+    }
+}
diff --git a/src/main/java/org/apache/commons/scxml2/model/ParsedValue.java b/src/main/java/org/apache/commons/scxml2/model/ParsedValue.java
new file mode 100644 (file)
index 0000000..3db7fc0
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.io.Serializable;
+
+/**
+ * A <code>ParsedValue</code> holds the parsed content of the body of a {@link ParsedValueContainer},
+ * or from an external src for &lt;data&gt; or &lt;assign&gt;.
+ * <p>Supported types are defined by enum {@link ValueType}, each of which have a specific implementation:
+ * <ul>
+ *   <li>{@link TextValue}</li>
+ *   <li>{@link JsonValue}</li>
+ *   <li>{@link NodeValue}</li>
+ *   <li>{@link NodeListValue}</li>
+ *   <li>{@link NodeTextValue}</li>
+ * </ul>
+ * For a &lt;invoke&gt; &lt;content&gt; body the special {@link NodeTextValue} implementation is used,
+ * which stored the (only supported) embedded &lt;scxml&gt; document as plain XML text for the &lt;invoke&gt;
+ * execution to parse (again) at runtime.
+ * </p>
+ */
+public interface ParsedValue extends Serializable {
+
+    /**
+     * The supported body value types
+     */
+    enum ValueType {
+        TEXT,
+        JSON,
+        NODE,
+        NODE_LIST,
+        NODE_TEXT
+    }
+
+    /**
+     * @return the parsed value type
+     */
+    ValueType getType();
+
+    /**
+     * @return the parsed value
+     */
+    Object getValue();
+}
diff --git a/src/main/java/org/apache/commons/scxml2/model/ParsedValueContainer.java b/src/main/java/org/apache/commons/scxml2/model/ParsedValueContainer.java
new file mode 100644 (file)
index 0000000..6f956c2
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.io.Serializable;
+
+/**
+ * A <code>ParsedValueContainer</code> represents an element in the SCXML document that may contain
+ * &quot;body content&quot;, or content from an external source, which will be captured in a {@link ParsedValue} instance.
+ */
+public interface ParsedValueContainer extends Serializable {
+
+    ParsedValue getParsedValue();
+    void setParsedValue(final ParsedValue parsedValue);
+}
+
index f38a0b4..32d9d23 100644 (file)
@@ -446,11 +446,8 @@ public class Send extends Action implements ContentContainer, ParamsContainer {
                     evalResult = "";
                 }
                 payload = eval.cloneData(evalResult);
-            } else if (content.getValue() != null) {
-                payload = content.getValue();
-            }
-            else if (content.getBody() != null){
-                payload = eval.cloneData(content.getBody());
+            } else if (content.getParsedValue() != null) {
+                payload = content.getParsedValue().getValue();
             }
         }
         long wait = 0L;
@@ -531,4 +528,3 @@ public class Send extends Action implements ContentContainer, ParamsContainer {
         return wait;
     }
 }
-
diff --git a/src/main/java/org/apache/commons/scxml2/model/TextValue.java b/src/main/java/org/apache/commons/scxml2/model/TextValue.java
new file mode 100644 (file)
index 0000000..13f3095
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * Plain text {@link ParsedValue} implementation.
+ * <p>
+ * For serialization back to XML flag {@link #isCDATA()} is recorded to wrap the text in a CDATA section if needed.
+ * </p>
+ */
+public class TextValue implements ParsedValue {
+
+    /**
+     * The text value
+     */
+    private final String text;
+
+    /**
+     * The flag indicating if the text requires wrapping in a CDATA section when writing out to XML
+     */
+    private final boolean cdata;
+
+    public TextValue(final String text, final boolean cdata) {
+        this.text = text;
+        this.cdata = cdata;
+    }
+
+    @Override
+    public final ValueType getType() {
+        return ValueType.TEXT;
+    }
+
+    /**
+     * @return true if the text requires wrapping in a CDATA section when writing out to XML
+     */
+    public final boolean isCDATA() {
+        return cdata;
+    }
+
+    @Override
+    public final String getValue() {
+        return text;
+    }
+}
index ac9be57..cb5707b 100644 (file)
@@ -39,20 +39,19 @@ 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;
-import org.apache.commons.scxml2.model.ExternalContent;
+import org.apache.commons.scxml2.model.ParsedValue;
+import org.apache.commons.scxml2.model.ParsedValueContainer;
 import org.apache.commons.scxml2.model.Final;
 import org.apache.commons.scxml2.model.ModelException;
 import org.apache.commons.scxml2.model.SCXML;
 import org.apache.commons.scxml2.model.Send;
 import org.apache.commons.scxml2.model.State;
 import org.apache.commons.scxml2.model.Transition;
-import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.w3c.dom.Node;
 
 /**
  * Unit tests {@link org.apache.commons.scxml2.io.SCXMLReader}.
@@ -181,7 +180,7 @@ public class SCXMLReaderTest {
         Assert.assertEquals(1, actions.size());
         MyAction my = (MyAction)((CustomActionWrapper)actions.get(0)).getAction();
         Assert.assertNotNull(my);
-        Assert.assertTrue(my.getExternalNodes().size() > 0);
+        Assert.assertTrue(my.getParsedValue() != null);
     }
 
     @Test
@@ -352,10 +351,10 @@ public class SCXMLReaderTest {
         }
     }
 
-    public static class MyAction extends Action implements ExternalContent {
+    public static class MyAction extends Action implements ParsedValueContainer {
         private static final long serialVersionUID = 1L;
 
-        private List<Node> nodes = new ArrayList<Node>();
+        private ParsedValue parsedValue;
 
         @Override
         public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
@@ -363,10 +362,14 @@ public class SCXMLReaderTest {
         }
 
         @Override
-        public List<Node> getExternalNodes() {
-            return nodes;
+        public ParsedValue getParsedValue() {
+            return parsedValue;
         }
 
+        @Override
+        public void setParsedValue(final ParsedValue parsedValue) {
+            this.parsedValue = parsedValue;
+        }
     }
 
     /**