| /* |
| * 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.io; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| 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.List; |
| import java.util.Map; |
| import java.util.Stack; |
| |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.stream.XMLInputFactory; |
| import javax.xml.stream.XMLReporter; |
| import javax.xml.stream.XMLResolver; |
| import javax.xml.stream.XMLStreamConstants; |
| 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.stream.StreamSource; |
| import javax.xml.validation.Schema; |
| import javax.xml.validation.SchemaFactory; |
| import javax.xml.validation.Validator; |
| |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.commons.scxml2.Evaluator; |
| import org.apache.commons.scxml2.PathResolver; |
| import org.apache.commons.scxml2.env.SimpleErrorHandler; |
| import org.apache.commons.scxml2.env.URLResolver; |
| import org.apache.commons.scxml2.model.Action; |
| import org.apache.commons.scxml2.model.ActionsContainer; |
| import org.apache.commons.scxml2.model.Assign; |
| import org.apache.commons.scxml2.model.Cancel; |
| import org.apache.commons.scxml2.model.Content; |
| import org.apache.commons.scxml2.model.ContentContainer; |
| import org.apache.commons.scxml2.model.CustomAction; |
| import org.apache.commons.scxml2.model.Data; |
| import org.apache.commons.scxml2.model.Datamodel; |
| import org.apache.commons.scxml2.model.Else; |
| import org.apache.commons.scxml2.model.ElseIf; |
| import org.apache.commons.scxml2.model.EnterableState; |
| import org.apache.commons.scxml2.model.Executable; |
| import org.apache.commons.scxml2.model.ExternalContent; |
| import org.apache.commons.scxml2.model.Final; |
| import org.apache.commons.scxml2.model.Finalize; |
| import org.apache.commons.scxml2.model.Foreach; |
| import org.apache.commons.scxml2.model.History; |
| import org.apache.commons.scxml2.model.If; |
| 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; |
| import org.apache.commons.scxml2.model.Param; |
| import org.apache.commons.scxml2.model.ParamsContainer; |
| import org.apache.commons.scxml2.model.Raise; |
| import org.apache.commons.scxml2.model.SCXML; |
| 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.Transition; |
| import org.apache.commons.scxml2.model.TransitionType; |
| import org.apache.commons.scxml2.model.TransitionalState; |
| import org.apache.commons.scxml2.model.Var; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * <p>The SCXMLReader provides the ability to read a SCXML document into |
| * the Java object model provided in the model package.</p> |
| * |
| * <p>See latest version of the SCXML Working Draft for more details.</p> |
| * |
| * <p><b>NOTE:</b> The SCXMLReader assumes that the SCXML document to be |
| * parsed is well-formed and correct. If that assumption does not hold, |
| * any subsequent behavior is undefined.</p> |
| * |
| * @since 1.0 |
| */ |
| 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"; |
| |
| /** |
| * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1 |
| */ |
| private static final String SCXML_REQUIRED_VERSION = "1.0"; |
| /** |
| * The default namespace for attributes. |
| */ |
| private static final String XMLNS_DEFAULT = null; |
| |
| //---- ERROR MESSAGES ----// |
| /** |
| * Null URL passed as argument. |
| */ |
| private static final String ERR_NULL_URL = "Cannot parse null URL"; |
| |
| /** |
| * Null path passed as argument. |
| */ |
| private static final String ERR_NULL_PATH = "Cannot parse null path"; |
| |
| /** |
| * Null InputStream passed as argument. |
| */ |
| private static final String ERR_NULL_ISTR = "Cannot parse null InputStream"; |
| |
| /** |
| * Null Reader passed as argument. |
| */ |
| private static final String ERR_NULL_READ = "Cannot parse null Reader"; |
| |
| /** |
| * Null Source passed as argument. |
| */ |
| private static final String ERR_NULL_SRC = "Cannot parse null Source"; |
| |
| /** |
| * Error message while attempting to define a custom action which does |
| * not extend the Commons SCXML Action base class. |
| */ |
| private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list" |
| + " contained unknown object, class not a Commons SCXML Action class subtype: "; |
| |
| /** |
| * Parser configuration error while trying to parse stream to DOM node(s). |
| */ |
| private static final String ERR_PARSER_CFG = "ParserConfigurationException while trying" |
| + " to parse stream into DOM node(s)."; |
| |
| /** |
| * Error message when the URI in a <state>'s "src" |
| * attribute does not point to a valid SCXML document, and thus cannot be |
| * parsed. |
| */ |
| private static final String ERR_STATE_SRC = |
| "Source attribute in <state src=\"{0}\"> cannot be parsed"; |
| |
| /** |
| * Error message when the target of the URI fragment in a <state>'s |
| * "src" attribute is not defined in the referenced document. |
| */ |
| private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in " |
| + "<state src=\"{0}\"> is an unknown state in referenced document"; |
| |
| /** |
| * Error message when the target of the URI fragment in a <state>'s |
| * "src" attribute is not a <state> or <final> in |
| * the referenced document. |
| */ |
| private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment" |
| + " in <state src=\"{0}\"> does not point to a <state> or <final>"; |
| |
| /** |
| * Error message when the target of the URI fragment in a <state>'s |
| * "src" attribute is not a <state> or <final> in |
| * the referenced document. |
| */ |
| private static final String ERR_REQUIRED_ATTRIBUTE_MISSING = "<{0}> is missing" |
| +" required attribute \"{1}\" value at {2}"; |
| |
| /** |
| * Error message when the target of the URI fragment in a <state>'s |
| * "src" attribute is not a <state> or <final> in |
| * the referenced document. |
| */ |
| private static final String ERR_ATTRIBUTE_NOT_BOOLEAN = "Illegal value \"{0}\"" |
| + "for attribute \"{1}\" in element <{2}> at {3}." |
| +" Only the value \"true\" or \"false\" is allowed."; |
| |
| /** |
| * Error message when the element (state|parallel|final|history) uses an id value |
| * with the reserved prefix {@link SCXML#GENERATED_TT_ID_PREFIX}. |
| */ |
| private static final String ERR_RESERVED_ID_PREFIX = "Reserved id prefix \"" |
| +SCXML.GENERATED_TT_ID_PREFIX+"\" used for <{0} id=\"{1}\"> at {2}"; |
| |
| /** |
| * Error message when the target of the URI fragment in a <state>'s |
| * "src" attribute is not defined in the referenced document. |
| */ |
| private static final String ERR_UNSUPPORTED_TRANSITION_TYPE = "Unsupported transition type " |
| + "for <transition type=\"{0}\"> at {1}."; |
| |
| /** |
| * Error message when the target of the URI fragment in a <state>'s |
| * "src" attribute is not a <state> or <final> in |
| * the referenced document. |
| */ |
| 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"; |
| |
| //---- ATTRIBUTE NAMES ----// |
| private static final String ATTR_ARRAY = "array"; |
| private static final String ATTR_ATTR = "attr"; |
| private static final String ATTR_AUTOFORWARD = "autoforward"; |
| private static final String ATTR_COND = "cond"; |
| private static final String ATTR_DATAMODEL = "datamodel"; |
| 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"; |
| |
| //------------------------- PUBLIC API METHODS -------------------------// |
| /* |
| * Public methods |
| */ |
| /** |
| * Parse the SCXML document at the supplied path. |
| * |
| * @param scxmlPath The real path to the SCXML document. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final String scxmlPath) |
| throws IOException, ModelException, XMLStreamException { |
| |
| return read(scxmlPath, new Configuration()); |
| } |
| |
| /** |
| * Parse the SCXML document at the supplied path with the given {@link Configuration}. |
| * |
| * @param scxmlPath The real path to the SCXML document. |
| * @param configuration The {@link Configuration} to use when parsing the SCXML document. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final String scxmlPath, final Configuration configuration) |
| throws IOException, ModelException, XMLStreamException { |
| |
| if (scxmlPath == null) { |
| throw new IllegalArgumentException(ERR_NULL_PATH); |
| } |
| SCXML scxml = readInternal(configuration, null, scxmlPath, null, null, null); |
| if (scxml != null) { |
| ModelUpdater.updateSCXML(scxml); |
| } |
| return scxml; |
| } |
| |
| /** |
| * Parse the SCXML document at the supplied {@link URL}. |
| * |
| * @param scxmlURL The SCXML document {@link URL} to parse. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final URL scxmlURL) |
| throws IOException, ModelException, XMLStreamException { |
| |
| return read(scxmlURL, new Configuration()); |
| } |
| |
| /** |
| * Parse the SCXML document at the supplied {@link URL} with the given {@link Configuration}. |
| * |
| * @param scxmlURL The SCXML document {@link URL} to parse. |
| * @param configuration The {@link Configuration} to use when parsing the SCXML document. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final URL scxmlURL, final Configuration configuration) |
| throws IOException, ModelException, XMLStreamException { |
| |
| if (scxmlURL == null) { |
| throw new IllegalArgumentException(ERR_NULL_URL); |
| } |
| SCXML scxml = readInternal(configuration, scxmlURL, null, null, null, null); |
| if (scxml != null) { |
| ModelUpdater.updateSCXML(scxml); |
| } |
| return scxml; |
| } |
| |
| /** |
| * Parse the SCXML document supplied by the given {@link InputStream}. |
| * |
| * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final InputStream scxmlStream) |
| throws IOException, ModelException, XMLStreamException { |
| |
| return read(scxmlStream, new Configuration()); |
| } |
| |
| /** |
| * Parse the SCXML document supplied by the given {@link InputStream} with the given {@link Configuration}. |
| * |
| * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use when parsing the SCXML document. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final InputStream scxmlStream, final Configuration configuration) |
| throws IOException, ModelException, XMLStreamException { |
| |
| if (scxmlStream == null) { |
| throw new IllegalArgumentException(ERR_NULL_ISTR); |
| } |
| SCXML scxml = readInternal(configuration, null, null, scxmlStream, null, null); |
| if (scxml != null) { |
| ModelUpdater.updateSCXML(scxml); |
| } |
| return scxml; |
| } |
| |
| /** |
| * Parse the SCXML document supplied by the given {@link Reader}. |
| * |
| * @param scxmlReader The {@link Reader} supplying the SCXML document to parse. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final Reader scxmlReader) |
| throws IOException, ModelException, XMLStreamException { |
| |
| return read(scxmlReader, new Configuration()); |
| } |
| |
| /** |
| * Parse the SCXML document supplied by the given {@link Reader} with the given {@link Configuration}. |
| * |
| * @param scxmlReader The {@link Reader} supplying the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use when parsing the SCXML document. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final Reader scxmlReader, final Configuration configuration) |
| throws IOException, ModelException, XMLStreamException { |
| |
| if (scxmlReader == null) { |
| throw new IllegalArgumentException(ERR_NULL_READ); |
| } |
| SCXML scxml = readInternal(configuration, null, null, null, scxmlReader, null); |
| if (scxml != null) { |
| ModelUpdater.updateSCXML(scxml); |
| } |
| return scxml; |
| } |
| |
| /** |
| * Parse the SCXML document supplied by the given {@link Source}. |
| * |
| * @param scxmlSource The {@link Source} supplying the SCXML document to parse. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final Source scxmlSource) |
| throws IOException, ModelException, XMLStreamException { |
| |
| return read(scxmlSource, new Configuration()); |
| } |
| |
| /** |
| * Parse the SCXML document supplied by the given {@link Source} with the given {@link Configuration}. |
| * |
| * @param scxmlSource The {@link Source} supplying the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use when parsing the SCXML document. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| public static SCXML read(final Source scxmlSource, final Configuration configuration) |
| throws IOException, ModelException, XMLStreamException { |
| |
| if (scxmlSource == null) { |
| throw new IllegalArgumentException(ERR_NULL_SRC); |
| } |
| SCXML scxml = readInternal(configuration, null, null, null, null, scxmlSource); |
| if (scxml != null) { |
| ModelUpdater.updateSCXML(scxml); |
| } |
| return scxml; |
| } |
| |
| //---------------------- PRIVATE UTILITY METHODS ----------------------// |
| /** |
| * Parse the SCXML document at the supplied {@link URL} using the supplied {@link Configuration}, but do not |
| * wire up the object model to be usable just yet. Exactly one of the url, path, stream, reader or source |
| * parameters must be provided. |
| * |
| * @param configuration The {@link Configuration} to use when parsing the SCXML document. |
| * @param scxmlURL The optional SCXML document {@link URL} to parse. |
| * @param scxmlPath The optional real path to the SCXML document as a string. |
| * @param scxmlStream The optional {@link InputStream} providing the SCXML document. |
| * @param scxmlReader The optional {@link Reader} providing the SCXML document. |
| * @param scxmlSource The optional {@link Source} providing the SCXML document. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document |
| * (not wired up to be immediately usable). |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static SCXML readInternal(final Configuration configuration, final URL scxmlURL, final String scxmlPath, |
| final InputStream scxmlStream, final Reader scxmlReader, final Source scxmlSource) |
| throws IOException, ModelException, XMLStreamException { |
| |
| if (configuration.pathResolver == null) { |
| if (scxmlURL != null) { |
| configuration.pathResolver = new URLResolver(scxmlURL); |
| } else if (scxmlPath != null) { |
| configuration.pathResolver = new URLResolver(new URL(scxmlPath)); |
| } |
| } |
| |
| XMLStreamReader reader = getReader(configuration, scxmlURL, scxmlPath, scxmlStream, scxmlReader, scxmlSource); |
| |
| return readDocument(reader, configuration); |
| } |
| |
| /* |
| * Private utility functions for reading the SCXML document. |
| */ |
| /** |
| * Read the SCXML document through the {@link XMLStreamReader}. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * |
| * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document |
| * (not wired up to be immediately usable). |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static SCXML readDocument(final XMLStreamReader reader, final Configuration configuration) |
| throws IOException, ModelException, XMLStreamException { |
| |
| SCXML scxml = new SCXML(); |
| 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_SCXML.equals(name)) { |
| readSCXML(reader, configuration, scxml); |
| } else { |
| reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.NAMESPACE: |
| System.err.println(reader.getNamespaceCount()); |
| break; |
| default: |
| } |
| } |
| return scxml; |
| } |
| |
| /** |
| * Read the contents of this <scxml> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param scxml The root of the object model being parsed. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| 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)); |
| if (!SCXML_REQUIRED_VERSION.equals(scxml.getVersion())) { |
| throw new ModelException(new MessageFormat(ERR_INVALID_VERSION).format(new Object[] {scxml.getVersion()})); |
| } |
| readNamespaces(configuration, scxml); |
| |
| boolean hasGlobalScript = false; |
| |
| 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_STATE.equals(name)) { |
| readState(reader, configuration, scxml, null); |
| } else if (ELEM_PARALLEL.equals(name)) { |
| readParallel(reader, configuration, scxml, null); |
| } else if (ELEM_FINAL.equals(name)) { |
| readFinal(reader, configuration, scxml, null); |
| } else if (ELEM_DATAMODEL.equals(name)) { |
| readDatamodel(reader, configuration, scxml, null); |
| } else if (ELEM_SCRIPT.equals(name) && !hasGlobalScript) { |
| readGlobalScript(reader, configuration, scxml); |
| hasGlobalScript = true; |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| } |
| |
| /** |
| * Read the contents of this <state> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param scxml The root of the object model being parsed. |
| * @param parent The parent {@link TransitionalState} for this state (null for top level state). |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static void readState(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml, |
| final TransitionalState parent) |
| throws IOException, ModelException, XMLStreamException { |
| |
| State state = new State(); |
| state.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_STATE)); |
| String initial = readAV(reader, ATTR_INITIAL); |
| if (initial != null) { |
| state.setFirst(initial); |
| } |
| String src = readAV(reader, ATTR_SRC); |
| if (src != null) { |
| String source = src; |
| Configuration copy = new Configuration(configuration); |
| if (copy.parent == null) { |
| copy.parent = scxml; |
| } |
| if (configuration.pathResolver != null) { |
| source = configuration.pathResolver.resolvePath(src); |
| copy.pathResolver = configuration.pathResolver.getResolver(src); |
| } |
| readTransitionalStateSrc(copy, source, state); |
| } |
| |
| if (parent == null) { |
| scxml.addChild(state); |
| } else if (parent instanceof State) { |
| ((State)parent).addChild(state); |
| } |
| else { |
| ((Parallel)parent).addChild(state); |
| } |
| scxml.addTarget(state); |
| if (configuration.parent != null) { |
| configuration.parent.addTarget(state); |
| } |
| |
| 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_TRANSITION.equals(name)) { |
| state.addTransition(readTransition(reader, configuration)); |
| } else if (ELEM_STATE.equals(name)) { |
| readState(reader, configuration, scxml, state); |
| } else if (ELEM_INITIAL.equals(name)) { |
| readInitial(reader, configuration, state); |
| } else if (ELEM_FINAL.equals(name)) { |
| readFinal(reader, configuration, scxml, state); |
| } else if (ELEM_ONENTRY.equals(name)) { |
| readOnEntry(reader, configuration, state); |
| } else if (ELEM_ONEXIT.equals(name)) { |
| readOnExit(reader, configuration, state); |
| } else if (ELEM_PARALLEL.equals(name)) { |
| readParallel(reader, configuration, scxml, state); |
| } else if (ELEM_DATAMODEL.equals(name)) { |
| readDatamodel(reader, configuration, null, state); |
| } else if (ELEM_INVOKE.equals(name)) { |
| readInvoke(reader, configuration, state); |
| } else if (ELEM_HISTORY.equals(name)) { |
| readHistory(reader, configuration, scxml, state); |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| } |
| |
| /** |
| * Read the contents of this <parallel> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param scxml The root of the object model being parsed. |
| * @param parent The parent {@link TransitionalState} for this parallel (null for top level state). |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static void readParallel(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml, |
| final TransitionalState parent) |
| throws IOException, ModelException, XMLStreamException { |
| |
| Parallel parallel = new Parallel(); |
| parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_PARALLEL)); |
| String src = readAV(reader, ATTR_SRC); |
| if (src != null) { |
| String source = src; |
| Configuration copy = new Configuration(configuration); |
| if (copy.parent == null) { |
| copy.parent = scxml; |
| } |
| if (configuration.pathResolver != null) { |
| source = configuration.pathResolver.resolvePath(src); |
| copy.pathResolver = configuration.pathResolver.getResolver(src); |
| } |
| readTransitionalStateSrc(copy, source, parallel); |
| } |
| |
| if (parent == null) { |
| scxml.addChild(parallel); |
| } else if (parent instanceof State) { |
| ((State)parent).addChild(parallel); |
| } |
| else { |
| ((Parallel)parent).addChild(parallel); |
| } |
| scxml.addTarget(parallel); |
| if (configuration.parent != null) { |
| configuration.parent.addTarget(parallel); |
| } |
| |
| 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_TRANSITION.equals(name)) { |
| parallel.addTransition(readTransition(reader, configuration)); |
| } else if (ELEM_STATE.equals(name)) { |
| readState(reader, configuration, scxml, parallel); |
| } else if (ELEM_PARALLEL.equals(name)) { |
| readParallel(reader, configuration, scxml, parallel); |
| } else if (ELEM_ONENTRY.equals(name)) { |
| readOnEntry(reader, configuration, parallel); |
| } else if (ELEM_ONEXIT.equals(name)) { |
| readOnExit(reader, configuration, parallel); |
| } else if (ELEM_DATAMODEL.equals(name)) { |
| readDatamodel(reader, configuration, null, parallel); |
| } else if (ELEM_INVOKE.equals(name)) { |
| readInvoke(reader, configuration, parallel); |
| } else if (ELEM_HISTORY.equals(name)) { |
| readHistory(reader, configuration, scxml, parallel); |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| } |
| |
| /** |
| * Read the contents of this <final> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param scxml The root of the object model being parsed. |
| * @param parent The parent {@link State} for this final (null for top level state). |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readFinal(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml, |
| final State parent) |
| throws XMLStreamException, ModelException, IOException { |
| |
| Final end = new Final(); |
| end.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_FINAL)); |
| |
| if (parent == null) { |
| scxml.addChild(end); |
| } else { |
| parent.addChild(end); |
| } |
| |
| scxml.addTarget(end); |
| if (configuration.parent != null) { |
| configuration.parent.addTarget(end); |
| } |
| |
| 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_ONENTRY.equals(name)) { |
| readOnEntry(reader, configuration, end); |
| } else if (ELEM_ONEXIT.equals(name)) { |
| readOnExit(reader, configuration, end); |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| } |
| |
| /** |
| * Parse the contents of the SCXML document that this "src" attribute value of a <state> or <parallel> |
| * element points to. Without a URL fragment, the entire state machine is imported as contents of the |
| * <state> or <parallel>. If a URL fragment is present, the fragment must specify the id of the |
| * corresponding <state> or <parallel> to import. |
| * |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param src The "src" attribute value. |
| * @param ts The parent {@link TransitionalState} that specifies this "src" attribute. |
| * |
| * @throws IOException An IO error during parsing. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static void readTransitionalStateSrc(final Configuration configuration, final String src, |
| final TransitionalState ts) |
| throws IOException, ModelException, XMLStreamException { |
| |
| // Check for URI fragment |
| String[] fragments = src.split("#", 2); |
| String location = fragments[0]; |
| String fragment = null; |
| if (fragments.length > 1) { |
| fragment = fragments[1]; |
| } |
| |
| // Parse external document |
| SCXML externalSCXML; |
| try { |
| externalSCXML = SCXMLReader.readInternal(configuration, new URL(location), null, null, null, null); |
| } catch (Exception e) { |
| MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC); |
| String errMsg = msgFormat.format(new Object[] {src}); |
| throw new ModelException(errMsg + " : " + e.getMessage(), e); |
| } |
| |
| // Pull in the parts of the external document as needed |
| if (fragment == null) { |
| // All targets pulled in since its not a src fragment |
| if (ts instanceof State) { |
| State s = (State) ts; |
| Initial ini = new Initial(); |
| SimpleTransition t = new SimpleTransition(); |
| t.setNext(externalSCXML.getInitial()); |
| ini.setTransition(t); |
| s.setInitial(ini); |
| for (EnterableState child : externalSCXML.getChildren()) { |
| s.addChild(child); |
| } |
| s.setDatamodel(externalSCXML.getDatamodel()); |
| } else if (ts instanceof Parallel) { |
| // TODO src attribute for <parallel> |
| } |
| } else { |
| // Need to pull in only descendent targets |
| Object source = externalSCXML.getTargets().get(fragment); |
| if (source == null) { |
| MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT); |
| String errMsg = msgFormat.format(new Object[] {src}); |
| throw new ModelException(errMsg); |
| } |
| if (source instanceof State && ts instanceof State) { |
| State s = (State) ts; |
| State include = (State) source; |
| for (OnEntry onentry : include.getOnEntries()) { |
| s.addOnEntry(onentry); |
| } |
| for (OnExit onexit : include.getOnExits()) { |
| s.addOnExit(onexit); |
| } |
| s.setDatamodel(include.getDatamodel()); |
| List<History> histories = include.getHistory(); |
| for (History h : histories) { |
| s.addHistory(h); |
| configuration.parent.addTarget(h); |
| } |
| for (EnterableState child : include.getChildren()) { |
| s.addChild(child); |
| configuration.parent.addTarget(child); |
| readInExternalTargets(configuration.parent, child); |
| } |
| for (Invoke invoke : include.getInvokes()) { |
| s.addInvoke(invoke); |
| } |
| if (include.getInitial() != null) { |
| s.setInitial(include.getInitial()); |
| } |
| List<Transition> transitions = include.getTransitionsList(); |
| for (Transition t : transitions) { |
| s.addTransition(t); |
| } |
| } else if (ts instanceof Parallel && source instanceof Parallel) { |
| // TODO src attribute for <parallel> |
| } else { |
| MessageFormat msgFormat = |
| new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET); |
| String errMsg = msgFormat.format(new Object[] {src}); |
| throw new ModelException(errMsg); |
| } |
| } |
| } |
| |
| /** |
| * Add all the nested targets from given target to given parent state machine. |
| * |
| * @param parent The state machine |
| * @param es The target to import |
| */ |
| private static void readInExternalTargets(final SCXML parent, final EnterableState es) { |
| if (es instanceof TransitionalState) { |
| for (History h : ((TransitionalState)es).getHistory()) { |
| parent.addTarget(h); |
| } |
| for (EnterableState child : ((TransitionalState) es).getChildren()) { |
| parent.addTarget(child); |
| readInExternalTargets(parent, child); |
| } |
| } |
| } |
| |
| /** |
| * Read the contents of this <datamodel> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param scxml The root of the object model being parsed. |
| * @param parent The parent {@link TransitionalState} for this datamodel (null for top level). |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readDatamodel(final XMLStreamReader reader, final Configuration configuration, |
| final SCXML scxml, final TransitionalState parent) |
| throws XMLStreamException, ModelException { |
| |
| Datamodel dm = new Datamodel(); |
| |
| 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_DATA.equals(name)) { |
| readData(reader, configuration, dm); |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| |
| if (parent == null) { |
| scxml.setDatamodel(dm); |
| } else { |
| parent.setDatamodel(dm); |
| } |
| } |
| |
| /** |
| * Read the contents of this <data> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param dm The parent {@link Datamodel} for this data. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static void readData(final XMLStreamReader reader, final Configuration configuration, final Datamodel dm) |
| throws XMLStreamException, ModelException { |
| |
| Data datum = new Data(); |
| datum.setId(readRequiredAV(reader, ELEM_DATA, ATTR_ID)); |
| datum.setExpr(readAV(reader, ATTR_EXPR)); |
| readNamespaces(configuration, datum); |
| Node node = readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[]{"id"}); |
| 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); |
| } |
| } |
| dm.addData(datum); |
| } |
| |
| /** |
| * Read the contents of this <invoke> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param parent The parent {@link TransitionalState} for this invoke. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readInvoke(final XMLStreamReader reader, final Configuration configuration, |
| final TransitionalState parent) |
| throws XMLStreamException, ModelException { |
| |
| Invoke invoke = new Invoke(); |
| invoke.setId(readAV(reader, ATTR_ID)); |
| 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.setPathResolver(configuration.pathResolver); |
| readNamespaces(configuration, invoke); |
| |
| 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)) { |
| readParam(reader, configuration, invoke); |
| } else if (ELEM_FINALIZE.equals(name)) { |
| readFinalize(reader, configuration, parent, invoke); |
| } else if (ELEM_CONTENT.equals(name)) { |
| readContent(reader, configuration, invoke); |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| |
| parent.addInvoke(invoke); |
| } |
| |
| /** |
| * Read the contents of this <param> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param parent The parent {@link org.apache.commons.scxml2.model.ParamsContainer} for this param. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static void readParam(final XMLStreamReader reader, final Configuration configuration, |
| final ParamsContainer parent) |
| 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); |
| if (expr != null) { |
| if (location != null) { |
| reportConflictingAttribute(reader, configuration, ELEM_PARAM, ATTR_LOCATION, ATTR_EXPR); |
| } |
| else { |
| param.setExpr(expr); |
| } |
| } |
| else if (location == null) { |
| // force error missing required location or expr: use location attr for this |
| param.setLocation(readRequiredAV(reader, ELEM_PARAM, ATTR_LOCATION)); |
| } |
| else { |
| param.setLocation(location); |
| } |
| readNamespaces(configuration, param); |
| parent.getParams().add(param); |
| skipToEndElement(reader); |
| } |
| |
| /** |
| * Read the contents of this <finalize> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param state The {@link TransitionalState} which contains the parent {@link Invoke}. |
| * @param invoke The parent {@link Invoke} for this finalize. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readFinalize(final XMLStreamReader reader, final Configuration configuration, |
| final TransitionalState state, final Invoke invoke) |
| throws XMLStreamException, ModelException { |
| |
| Finalize finalize = new Finalize(); |
| readExecutableContext(reader, configuration, finalize, null); |
| invoke.setFinalize(finalize); |
| finalize.setParent(state); |
| } |
| |
| /** |
| * Read the contents of this <content> 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) |
| throws XMLStreamException { |
| |
| Content content = new Content(); |
| content.setExpr(readAV(reader, ATTR_EXPR)); |
| if (content.getExpr() != null) { |
| skipToEndElement(reader); |
| } |
| else { |
| Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_CONTENT, new String[]{}); |
| if (body.hasChildNodes()) { |
| NodeList children = body.getChildNodes(); |
| if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) { |
| content.setBody(children.item(0).getNodeValue()); |
| } |
| else { |
| content.setBody(body); |
| } |
| } |
| } |
| contentContainer.setContent(content); |
| } |
| |
| /** |
| * Read the contents of this <initial> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param state The parent composite {@link State} for this initial. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readInitial(final XMLStreamReader reader, final Configuration configuration, |
| final State state) |
| throws XMLStreamException, ModelException { |
| |
| Initial initial = new Initial(); |
| |
| 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_TRANSITION.equals(name)) { |
| initial.setTransition(readSimpleTransition(reader, configuration)); |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| |
| state.setInitial(initial); |
| } |
| |
| /** |
| * Read the contents of this <history> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param scxml The root of the object model being parsed. |
| * @param ts The parent {@link org.apache.commons.scxml2.model.TransitionalState} for this history. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readHistory(final XMLStreamReader reader, final Configuration configuration, |
| final SCXML scxml, final TransitionalState ts) |
| throws XMLStreamException, ModelException { |
| |
| History history = new History(); |
| history.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_HISTORY)); |
| history.setType(readAV(reader, ATTR_TYPE)); |
| |
| ts.addHistory(history); |
| scxml.addTarget(history); |
| |
| 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_TRANSITION.equals(name)) { |
| history.setTransition(readTransition(reader, configuration)); |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| } |
| |
| /** |
| * Read the contents of this <onentry> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param es The parent {@link EnterableState} for this onentry. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readOnEntry(final XMLStreamReader reader, final Configuration configuration, |
| final EnterableState es) |
| throws XMLStreamException, ModelException { |
| |
| OnEntry onentry = new OnEntry(); |
| onentry.setRaiseEvent(readBooleanAV(reader, ELEM_ONENTRY, ATTR_EVENT)); |
| readExecutableContext(reader, configuration, onentry, null); |
| es.addOnEntry(onentry); |
| } |
| |
| /** |
| * Read the contents of this <onexit> element. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param es The parent {@link EnterableState} for this onexit. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readOnExit(final XMLStreamReader reader, final Configuration configuration, |
| final EnterableState es) |
| throws XMLStreamException, ModelException { |
| |
| OnExit onexit = new OnExit(); |
| onexit.setRaiseEvent(readBooleanAV(reader, ELEM_ONEXIT, ATTR_EVENT)); |
| readExecutableContext(reader, configuration, onexit, null); |
| es.addOnExit(onexit); |
| } |
| |
| /** |
| * Read the contents of this simple <transition> element. |
| * |
| * @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}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static SimpleTransition readSimpleTransition(final XMLStreamReader reader, final Configuration configuration) |
| throws XMLStreamException, ModelException { |
| |
| SimpleTransition transition = new SimpleTransition(); |
| transition.setNext(readAV(reader, ATTR_TARGET)); |
| String type = readAV(reader, ATTR_TYPE); |
| if (type != null) { |
| try { |
| transition.setType(TransitionType.valueOf(type)); |
| } |
| catch (IllegalArgumentException e) { |
| MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE); |
| String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()}); |
| throw new ModelException(errMsg); |
| } |
| } |
| |
| readNamespaces(configuration, transition); |
| readExecutableContext(reader, configuration, transition, null); |
| |
| return transition; |
| } |
| |
| /** |
| * Read the contents of this <transition> element. |
| * |
| * @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}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static Transition readTransition(final XMLStreamReader reader, final Configuration configuration) |
| 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); |
| if (type != null) { |
| try { |
| transition.setType(TransitionType.valueOf(type)); |
| } |
| catch (IllegalArgumentException e) { |
| MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE); |
| String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()}); |
| throw new ModelException(errMsg); |
| } |
| } |
| |
| readNamespaces(configuration, transition); |
| readExecutableContext(reader, configuration, transition, null); |
| |
| return transition; |
| } |
| |
| /** |
| * Read this set of executable content elements. |
| * |
| * @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} to which this content belongs. |
| * @param parent The optional parent {@link ActionsContainer} if this is child content of an ActionsContainer action. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readExecutableContext(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| throws XMLStreamException, ModelException { |
| |
| String end = ""; |
| if (parent != null) { |
| end = parent.getContainerElementName(); |
| } else if (executable instanceof SimpleTransition) { |
| end = ELEM_TRANSITION; |
| } else if (executable instanceof OnEntry) { |
| end = ELEM_ONENTRY; |
| } else if (executable instanceof OnExit) { |
| end = ELEM_ONEXIT; |
| } else if (executable instanceof Finalize) { |
| end = 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)) { |
| readRaise(reader, configuration, executable, parent); |
| } else if (ELEM_FOREACH.equals(name)) { |
| readForeach(reader, configuration, executable, parent); |
| } else if (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)) { |
| readAssign(reader, configuration, executable, parent); |
| } else if (ELEM_SEND.equals(name)) { |
| readSend(reader, configuration, executable, parent); |
| } else if (ELEM_CANCEL.equals(name)) { |
| readCancel(reader, configuration, executable, parent); |
| } else if (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 { |
| reportIgnoredElement(reader, configuration, end, nsURI, name); |
| } |
| } else if (XMLNS_COMMONS_SCXML.equals(nsURI)) { |
| if (ELEM_VAR.equals(name)) { |
| readVar(reader, configuration, executable, parent); |
| } else { |
| reportIgnoredElement(reader, configuration, end, nsURI, name); |
| } |
| } else { // custom action |
| CustomAction customAction = null; |
| if (!configuration.customActions.isEmpty()) { |
| for (CustomAction ca : configuration.customActions) { |
| if (ca.getNamespaceURI().equals(nsURI) && ca.getLocalName().equals(name)) { |
| customAction = ca; |
| } |
| } |
| } |
| if (customAction != null) { |
| readCustomAction(reader, configuration, customAction, executable, parent); |
| } else { |
| reportIgnoredElement(reader, configuration, end, nsURI, name); |
| } |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| } |
| |
| /** |
| * Read the contents of this <raise> 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}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readRaise(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| throws XMLStreamException, ModelException { |
| |
| if (executable instanceof Finalize) { |
| // 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); |
| } |
| else { |
| Raise raise = new Raise(); |
| raise.setEvent(readAV(reader, ATTR_EVENT)); |
| readNamespaces(configuration, raise); |
| raise.setParent(executable); |
| if (parent != null) { |
| parent.addAction(raise); |
| } else { |
| executable.addAction(raise); |
| } |
| skipToEndElement(reader); |
| } |
| } |
| |
| /** |
| * Read the contents of this <if> 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 <if> is a child of one. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readIf(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| throws XMLStreamException, ModelException { |
| |
| If iff = new If(); |
| iff.setCond(readRequiredAV(reader, ELEM_IF, ATTR_COND)); |
| readNamespaces(configuration, iff); |
| iff.setParent(executable); |
| if (parent != null) { |
| parent.addAction(iff); |
| } else { |
| executable.addAction(iff); |
| } |
| readExecutableContext(reader, configuration, executable, iff); |
| } |
| |
| /** |
| * Read the contents of this <elseif> 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 <elseif>. |
| * |
| * @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) |
| throws XMLStreamException, ModelException { |
| |
| ElseIf elseif = new ElseIf(); |
| elseif.setCond(readRequiredAV(reader, ELEM_ELSEIF, ATTR_COND)); |
| readNamespaces(configuration, elseif); |
| elseif.setParent(executable); |
| iff.addAction(elseif); |
| skipToEndElement(reader); |
| } |
| |
| /** |
| * Read the contents of this <else> 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 <else>. |
| * |
| * @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) |
| throws XMLStreamException { |
| |
| Else els = new Else(); |
| readNamespaces(configuration, els); |
| els.setParent(executable); |
| iff.addAction(els); |
| skipToEndElement(reader); |
| } |
| |
| /** |
| * Read the contents of this <foreach> 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 <foreach> is a child of one. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readForeach(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| 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.setParent(executable); |
| if (parent != null) { |
| parent.addAction(fe); |
| } else { |
| executable.addAction(fe); |
| } |
| readExecutableContext(reader, configuration, executable, fe); |
| } |
| |
| /** |
| * Read the contents of this <log> 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) |
| throws XMLStreamException { |
| |
| Log log = new Log(); |
| log.setExpr(readAV(reader, ATTR_EXPR)); |
| log.setLabel(readAV(reader, ATTR_LABEL)); |
| readNamespaces(configuration, log); |
| log.setParent(executable); |
| if (parent != null) { |
| parent.addAction(log); |
| } else { |
| executable.addAction(log); |
| } |
| skipToEndElement(reader); |
| } |
| |
| /** |
| * Read the contents of this <assign> 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 readAssign(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| throws XMLStreamException, ModelException { |
| |
| Assign assign = new Assign(); |
| assign.setExpr(readAV(reader, ATTR_EXPR)); |
| assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION)); |
| String attrValue = readAV(reader, ATTR_TYPE); |
| if (attrValue != null) { |
| assign.setType(Evaluator.AssignType.fromValue(attrValue)); |
| if (assign.getType() == null) { |
| reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_TYPE, attrValue); |
| } |
| } |
| attrValue = readAV(reader, ATTR_ATTR); |
| if (attrValue != null) { |
| if (Evaluator.AssignType.ADD_ATTRIBUTE.equals(assign.getType())) { |
| assign.setAttr(attrValue); |
| } |
| else { |
| reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_ATTR, attrValue); |
| } |
| } |
| assign.setSrc(readAV(reader, ATTR_SRC)); |
| assign.setPathResolver(configuration.pathResolver); |
| readNamespaces(configuration, assign); |
| assign.setParent(executable); |
| if (parent != null) { |
| parent.addAction(assign); |
| } else { |
| executable.addAction(assign); |
| } |
| skipToEndElement(reader); |
| } |
| |
| /** |
| * Read the contents of this <send> 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}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void readSend(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| throws XMLStreamException, ModelException { |
| |
| if (executable instanceof Finalize) { |
| // 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); |
| return; |
| } |
| |
| Send send = new Send(); |
| send.setId(readAV(reader, ATTR_ID)); |
| String attrValue = readAV(reader, ATTR_IDLOCATION); |
| if (attrValue != null) { |
| if (send.getId() != null) { |
| reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_ID, ATTR_IDLOCATION); |
| } |
| else { |
| send.setIdlocation(attrValue); |
| } |
| } |
| send.setDelay(readAV(reader, ATTR_DELAY)); |
| attrValue = readAV(reader, ATTR_DELAYEXPR); |
| if (attrValue != null) { |
| if (send.getDelay() != null) { |
| reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_DELAY, ATTR_DELAYEXPR); |
| } |
| else { |
| send.setDelayexpr(attrValue); |
| } |
| } |
| send.setEvent(readAV(reader, ATTR_EVENT)); |
| attrValue = readAV(reader, ATTR_EVENTEXPR); |
| if (attrValue != null) { |
| if (send.getEvent() != null) { |
| reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_EVENT, 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); |
| if (attrValue != null) { |
| if (send.getTarget() != null) { |
| reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TARGET, ATTR_TARGETEXPR); |
| } |
| else { |
| send.setTargetexpr(attrValue); |
| } |
| } |
| send.setType(readAV(reader, ATTR_TYPE)); |
| attrValue = readAV(reader, ATTR_TYPEEXPR); |
| if (attrValue != null) { |
| if (send.getType() != null) { |
| reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TYPE, 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 (send.getContent() == null) { |
| readParam(reader, configuration, send); |
| } |
| else { |
| reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); |
| } |
| } else if (ELEM_CONTENT.equals(name)) { |
| if (send.getNamelist() == null && send.getParams().isEmpty()) { |
| readContent(reader, configuration, send); |
| } |
| else { |
| reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); |
| } |
| } else { |
| reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| break loop; |
| default: |
| } |
| } |
| |
| send.setParent(executable); |
| if (parent != null) { |
| parent.addAction(send); |
| } else { |
| executable.addAction(send); |
| } |
| } |
| |
| /** |
| * Read the contents of this <cancel> 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 readCancel(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| throws XMLStreamException, ModelException { |
| |
| Cancel cancel = new Cancel(); |
| cancel.setSendid(readAV(reader, ATTR_SENDID)); |
| String attrValue = readAV(reader, ATTR_SENDIDEXPR); |
| if (attrValue != null) { |
| if (cancel.getSendid() != null) { |
| reportConflictingAttribute(reader, configuration, ELEM_CANCEL, ATTR_SENDID, ATTR_SENDIDEXPR); |
| } |
| else { |
| cancel.setSendidexpr(attrValue); |
| } |
| } |
| readNamespaces(configuration, cancel); |
| cancel.setParent(executable); |
| if (parent != null) { |
| parent.addAction(cancel); |
| } else { |
| executable.addAction(cancel); |
| } |
| skipToEndElement(reader); |
| } |
| |
| /** |
| * Read the contents of this <script> 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 readScript(final XMLStreamReader reader, final Configuration configuration, |
| final Executable executable, final ActionsContainer parent) |
| throws XMLStreamException { |
| |
| Script script = new Script(); |
| readNamespaces(configuration, script); |
| script.setBody(readBody(reader)); |
| script.setParent(executable); |
| if (parent != null) { |
| parent.addAction(script); |
| } else { |
| executable.addAction(script); |
| } |
| } |
| |
| /** |
| * Read the contents of the initial <script> element. |
| * @see <a href="http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml"> |
| * http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml<a> section 3.2.2 |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param scxml The root of the object model being parsed. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static void readGlobalScript(final XMLStreamReader reader, final Configuration configuration, |
| final SCXML scxml) |
| throws XMLStreamException { |
| |
| Script globalScript = new Script(); |
| globalScript.setGlobalScript(true); |
| readNamespaces(configuration, globalScript); |
| globalScript.setBody(readBody(reader)); |
| scxml.setGlobalScript(globalScript); |
| } |
| |
| /** |
| * Read the contents of this <var> 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. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param customAction The {@link CustomAction} to read. |
| * @param executable The parent {@link Executable} for this custom action. |
| * @param parent The optional parent {@link ActionsContainer} if this custom action is a child of one. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration, |
| final CustomAction customAction, final Executable executable, |
| final ActionsContainer parent) |
| throws XMLStreamException { |
| |
| // Instantiate custom action |
| Object actionObject; |
| String className = customAction.getActionClass().getName(); |
| ClassLoader cl = configuration.customActionClassLoader; |
| if (configuration.useContextClassLoaderForCustomActions) { |
| cl = Thread.currentThread().getContextClassLoader(); |
| } |
| if (cl == null) { |
| cl = SCXMLReader.class.getClassLoader(); |
| } |
| Class<?> clazz; |
| try { |
| clazz = cl.loadClass(className); |
| actionObject = clazz.newInstance(); |
| } catch (ClassNotFoundException cnfe) { |
| throw new XMLStreamException("Cannot find custom action class:" + className, cnfe); |
| } catch (IllegalAccessException iae) { |
| throw new XMLStreamException("Cannot access custom action class:" + className, iae); |
| } catch (InstantiationException ie) { |
| throw new XMLStreamException("Cannot instantiate custom action class:" + className, ie); |
| } |
| if (!(actionObject instanceof Action)) { |
| throw new IllegalArgumentException(ERR_CUSTOM_ACTION_TYPE + className); |
| } |
| |
| // Set the attribute values as properties |
| Action action = (Action) actionObject; |
| for (int i = 0; i < reader.getAttributeCount(); i++) { |
| String name = reader.getAttributeLocalName(i); |
| String value = reader.getAttributeValue(i); |
| 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); |
| } catch (InvocationTargetException ite) { |
| throw new XMLStreamException("Exception calling setter for string property:" + name + " in class:" |
| + className, ite); |
| } catch (IllegalAccessException iae) { |
| throw new XMLStreamException("Cannot access setter for string property:" + name + " in class:" |
| + className, iae); |
| } |
| } |
| |
| // Add any body content if necessary |
| if (action instanceof ExternalContent) { |
| Node body = readNode(reader, configuration, customAction.getNamespaceURI(), |
| customAction.getLocalName(), new String [] {}); |
| NodeList childNodes = body.getChildNodes(); |
| List<Node> externalNodes = ((ExternalContent) action).getExternalNodes(); |
| for (int i = 0; i < childNodes.getLength(); i++) { |
| externalNodes.add(childNodes.item(i)); |
| } |
| } |
| else { |
| skipToEndElement(reader); |
| } |
| |
| // Wire in the action and add to parent |
| readNamespaces(configuration, action); |
| action.setParent(executable); |
| if (parent != null) { |
| parent.addAction(action); |
| } else { |
| executable.addAction(action); |
| } |
| } |
| |
| /** |
| * Read the following contents into a DOM {@link Node}. |
| * |
| * @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}. |
| * |
| * @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) |
| throws XMLStreamException { |
| |
| // Create a document in which to build the DOM node |
| Document document; |
| try { |
| document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); |
| } catch (ParserConfigurationException pce) { |
| throw new XMLStreamException(ERR_PARSER_CFG); |
| } |
| |
| // 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); |
| } |
| document.appendChild(root); |
| |
| boolean children = false; |
| Node parent = root; |
| |
| // 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: |
| if (!children && root.hasChildNodes()) { |
| // remove any children |
| root.setTextContent(null); |
| } |
| children = true; |
| pushNamespaces(reader, configuration); |
| nsURI = reader.getNamespaceURI(); |
| name = reader.getLocalName(); |
| Element elem = document.createElementNS(nsURI, name); |
| 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.setValue(reader.getAttributeValue(i)); |
| elem.setAttributeNodeNS(attr); |
| } |
| parent.appendChild(elem); |
| parent = elem; |
| break; |
| case XMLStreamConstants.SPACE: |
| case XMLStreamConstants.CHARACTERS: |
| case XMLStreamConstants.ENTITY_REFERENCE: |
| if (!children || parent != root) { |
| child = document.createTextNode(reader.getText()); |
| } |
| break; |
| case XMLStreamConstants.CDATA: |
| children = true; |
| child = document.createCDATASection(reader.getText()); |
| break; |
| case XMLStreamConstants.COMMENT: |
| children = true; |
| child = document.createComment(reader.getText()); |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| popNamespaces(reader, configuration); |
| parent = parent.getParentNode(); |
| if (parent == document) { |
| break loop; |
| } |
| break; |
| default: // rest is ignored |
| } |
| if (child != null) { |
| parent.appendChild(child); |
| } |
| } |
| if (!children && root.hasChildNodes()) { |
| root.setTextContent(root.getTextContent().trim()); |
| } |
| return root; |
| } |
| |
| /** |
| * Read the following body contents into a String. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * |
| * @return The body content read into a String. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| */ |
| private static String readBody(final XMLStreamReader reader) |
| 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()); |
| skipToEndElement(reader); |
| break; |
| case XMLStreamConstants.SPACE: |
| case XMLStreamConstants.CHARACTERS: |
| case XMLStreamConstants.ENTITY_REFERENCE: |
| case XMLStreamConstants.CDATA: |
| case XMLStreamConstants.COMMENT: |
| body.append(reader.getText()); |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| break loop; |
| default: // rest is ignored |
| } |
| } |
| return body.toString(); |
| } |
| |
| /** |
| * @param input input string to check if null or empty after trim |
| * @return null if input is null or empty after trim() |
| */ |
| private static String nullIfEmpty(String input) { |
| return input == null || input.trim().length()==0 ? null : input.trim(); |
| } |
| |
| /** |
| * Get the attribute value at the current reader location. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param attrLocalName The attribute name whose value is needed. |
| * |
| * @return The value of the attribute. |
| */ |
| private static String readAV(final XMLStreamReader reader, final String attrLocalName) { |
| return nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName)); |
| } |
| |
| /** |
| * Get the Boolean attribute value at the current reader location. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param elementName The name of the element for which the attribute value is needed. |
| * @param attrLocalName The attribute name whose value is needed. |
| * |
| * @return The Boolean value of the attribute. |
| * @throws ModelException When the attribute value is not empty but neither "true" or "false". |
| */ |
| private static Boolean readBooleanAV(final XMLStreamReader reader, final String elementName, |
| final String attrLocalName) |
| throws ModelException { |
| String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName)); |
| Boolean result = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null; |
| if (result == null && value != null) { |
| MessageFormat msgFormat = new MessageFormat(ERR_ATTRIBUTE_NOT_BOOLEAN); |
| String errMsg = msgFormat.format(new Object[] {value, attrLocalName, elementName, reader.getLocation()}); |
| throw new ModelException(errMsg); |
| } |
| return result; |
| } |
| |
| /** |
| * Get a required attribute value at the current reader location, |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param elementName The name of the element for which the attribute value is needed. |
| * @param attrLocalName The attribute name whose value is needed. |
| * |
| * @return The value of the attribute. |
| * @throws ModelException When the required attribute is missing or empty. |
| */ |
| private static String readRequiredAV(final XMLStreamReader reader, final String elementName, final String attrLocalName) |
| throws ModelException { |
| String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName)); |
| if (value == null) { |
| MessageFormat msgFormat = new MessageFormat(ERR_REQUIRED_ATTRIBUTE_MISSING); |
| String errMsg = msgFormat.format(new Object[] {elementName, attrLocalName, reader.getLocation()}); |
| throw new ModelException(errMsg); |
| } |
| return value; |
| } |
| |
| private static String readOrGeneratedTransitionTargetId(final XMLStreamReader reader, final SCXML scxml, |
| final String elementName) |
| throws ModelException { |
| String id = readAV(reader, ATTR_ID); |
| if (id == null) { |
| id = scxml.generateTransitionTargetId(); |
| } |
| else if (id.startsWith(SCXML.GENERATED_TT_ID_PREFIX)) { |
| MessageFormat msgFormat = new MessageFormat(ERR_RESERVED_ID_PREFIX); |
| String errMsg = msgFormat.format(new Object[] {elementName, id, reader.getLocation()}); |
| throw new ModelException(errMsg); |
| } |
| return id; |
| } |
| |
| /** |
| * Read the current active namespace declarations into the namespace prefixes holder. |
| * |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param holder The {@link NamespacePrefixesHolder} to populate. |
| */ |
| private static void readNamespaces(final Configuration configuration, final NamespacePrefixesHolder holder) { |
| |
| holder.setNamespaces(configuration.getCurrentNamespaces()); |
| } |
| |
| /** |
| * Report an ignored element via the {@link XMLReporter} if available and the class |
| * {@link org.apache.commons.logging.Log}. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param parent The parent element local name in the SCXML namespace. |
| * @param nsURI The namespace URI of the ignored element. |
| * @param name The local name of the ignored element. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void reportIgnoredElement(final XMLStreamReader reader, final Configuration configuration, |
| 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.isStrict()) { |
| throw new ModelException(sb.toString()); |
| } |
| XMLReporter reporter = configuration.reporter; |
| if (reporter != null) { |
| reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); |
| } |
| skipToEndElement(reader); |
| } |
| |
| /** |
| * Advances the XMLStreamReader until after the end of the current element: all children will be skipped as well |
| * @param reader the reader |
| * @throws XMLStreamException |
| */ |
| private static void skipToEndElement(final XMLStreamReader reader) throws XMLStreamException { |
| int elementsToSkip = 1; |
| while (elementsToSkip > 0 && reader.hasNext()) { |
| int next = reader.next(); |
| if (next == XMLStreamConstants.START_ELEMENT) { |
| elementsToSkip++; |
| } |
| else if (next == XMLStreamConstants.END_ELEMENT) { |
| elementsToSkip--; |
| } |
| } |
| } |
| |
| /** |
| * Report an ignored attribute via the {@link XMLReporter} if available and the class |
| * {@link org.apache.commons.logging.Log}. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param element The element name. |
| * @param attr The attribute which is ignored. |
| * @param value The value of the attribute which is ignored. |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void reportIgnoredAttribute(final XMLStreamReader reader, final Configuration configuration, |
| final String element, final String attr, final String value) |
| throws XMLStreamException, ModelException { |
| |
| org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class); |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Ignoring unknown or invalid <").append(element).append("> attribute ").append(attr) |
| .append("=\"").append(value).append("\" at ").append(reader.getLocation()); |
| if (!configuration.isSilent() && log.isWarnEnabled()) { |
| log.warn(sb.toString()); |
| } |
| if (configuration.isStrict()) { |
| throw new ModelException(sb.toString()); |
| } |
| XMLReporter reporter = configuration.reporter; |
| if (reporter != null) { |
| reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); |
| } |
| } |
| |
| /** |
| * Report a conflicting attribute via the {@link XMLReporter} if available and the class |
| * {@link org.apache.commons.logging.Log}. |
| * |
| * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. |
| * @param configuration The {@link Configuration} to use while parsing. |
| * @param element The element name. |
| * @param attr The attribute with which a conflict is detected. |
| * @param conflictingAttr The conflicting attribute |
| * |
| * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. |
| * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes |
| * errors in the SCXML document that may not be identified by the schema). |
| */ |
| private static void reportConflictingAttribute(final XMLStreamReader reader, final Configuration configuration, |
| 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.isStrict()) { |
| throw new ModelException(sb.toString()); |
| } |
| XMLReporter reporter = configuration.reporter; |
| if (reporter != null) { |
| reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); |
| } |
| } |
| |
| /** |
| * 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. |
| * |
| * @param configuration The {@link Configuration} to be used. |
| * @param url The {@link URL} to the SCXML document to read. |
| * @param path The optional real path to the SCXML document as a string. |
| * @param stream The optional {@link InputStream} providing the SCXML document. |
| * @param reader The optional {@link Reader} providing the SCXML document. |
| * @param source The optional {@link Source} providing the SCXML document. |
| * |
| * @return The appropriately configured {@link XMLStreamReader}. |
| * |
| * @throws IOException Exception with the URL IO. |
| * @throws XMLStreamException A problem with the XML stream creation or an wrapped {@link SAXException} |
| * thrown in trying to validate the document against the XML Schema for SCXML. |
| */ |
| private static XMLStreamReader getReader(final Configuration configuration, final URL url, final String path, |
| final InputStream stream, final Reader reader, final Source source) |
| throws IOException, XMLStreamException { |
| |
| // Instantiate the XMLInputFactory |
| XMLInputFactory factory = XMLInputFactory.newInstance(); |
| if (configuration.factoryId != null && configuration.factoryClassLoader != null) { |
| factory = XMLInputFactory.newFactory(configuration.factoryId, configuration.factoryClassLoader); |
| } |
| factory.setEventAllocator(configuration.allocator); |
| for (Map.Entry<String, Object> property : configuration.properties.entrySet()) { |
| factory.setProperty(property.getKey(), property.getValue()); |
| } |
| factory.setXMLReporter(configuration.reporter); |
| factory.setXMLResolver(configuration.resolver); |
| |
| // Consolidate InputStream options |
| InputStream urlStream = null; |
| if (url != null || path != null) { |
| URL scxml = (url != null ? url : new URL(path)); |
| URLConnection conn = scxml.openConnection(); |
| conn.setUseCaches(false); |
| urlStream = conn.getInputStream(); |
| } else if (stream != null) { |
| urlStream = stream; |
| } |
| |
| // Create the XMLStreamReader |
| XMLStreamReader xsr = null; |
| |
| if (configuration.validate) { |
| // Validation requires us to use a Source |
| |
| URL scxmlSchema = new URL("TODO"); // TODO, point to appropriate location |
| SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); |
| Schema schema; |
| try { |
| schema = schemaFactory.newSchema(scxmlSchema); |
| } catch (SAXException se) { |
| throw new XMLStreamException("Failed to create SCXML Schema for validation", se); |
| } |
| |
| Validator validator = schema.newValidator(); |
| validator.setErrorHandler(new SimpleErrorHandler()); |
| |
| Source src = null; |
| if (urlStream != null) { |
| // configuration.encoding is ignored |
| if (configuration.systemId != null) { |
| src = new StreamSource(urlStream, configuration.systemId); |
| } else { |
| src = new StreamSource(urlStream); |
| } |
| } else if (reader != null) { |
| if (configuration.systemId != null) { |
| src = new StreamSource(reader, configuration.systemId); |
| } else { |
| src = new StreamSource(reader); |
| } |
| } else if (source != null) { |
| src = source; |
| } |
| xsr = factory.createXMLStreamReader(src); |
| try { |
| validator.validate(src); |
| } catch (SAXException se) { |
| throw new XMLStreamException("Failed to create apply SCXML Validator", se); |
| } |
| |
| } else { |
| // We can use the more direct XMLInputFactory API if validation isn't needed |
| |
| if (urlStream != null) { |
| // systemId gets preference, then encoding if either are present |
| if (configuration.systemId != null) { |
| xsr = factory.createXMLStreamReader(configuration.systemId, urlStream); |
| } else if (configuration.encoding != null) { |
| xsr = factory.createXMLStreamReader(urlStream, configuration.encoding); |
| } else { |
| xsr = factory.createXMLStreamReader(urlStream); |
| } |
| } else if (reader != null) { |
| if (configuration.systemId != null) { |
| xsr = factory.createXMLStreamReader(configuration.systemId, reader); |
| } else { |
| xsr = factory.createXMLStreamReader(reader); |
| } |
| } else if (source != null) { |
| xsr = factory.createXMLStreamReader(source); |
| } |
| |
| } |
| |
| return xsr; |
| } |
| |
| /** |
| * Discourage instantiation since this is a utility class. |
| */ |
| private SCXMLReader() { |
| super(); |
| } |
| |
| //------------------------- CONFIGURATION CLASS -------------------------// |
| /** |
| * <p> |
| * Configuration for the {@link SCXMLReader}. The configuration properties necessary for the following are |
| * covered: |
| * </p> |
| * |
| * <ul> |
| * <li>{@link XMLInputFactory} configuration properties such as {@link XMLReporter}, {@link XMLResolver} and |
| * {@link XMLEventAllocator}</li> |
| * <li>{@link XMLStreamReader} configuration properties such as <code>systemId</code> and <code>encoding</code> |
| * </li> |
| * <li>Commons SCXML object model configuration properties such as the list of custom actions and the |
| * {@link PathResolver} to use.</li> |
| * </ul> |
| */ |
| public static class Configuration { |
| |
| /* |
| * Configuration properties for this {@link SCXMLReader}. |
| */ |
| // XMLInputFactory configuration properties. |
| /** |
| * The <code>factoryId</code> to use for the {@link XMLInputFactory}. |
| */ |
| final String factoryId; |
| |
| /** |
| * The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create. |
| */ |
| final ClassLoader factoryClassLoader; |
| |
| /** |
| * The {@link XMLEventAllocator} for the {@link XMLInputFactory}. |
| */ |
| final XMLEventAllocator allocator; |
| |
| /** |
| * The map of properties (keys are property name strings, values are object property values) for the |
| * {@link XMLInputFactory}. |
| */ |
| final Map<String, Object> properties; |
| |
| /** |
| * The {@link XMLResolver} for the {@link XMLInputFactory}. |
| */ |
| final XMLResolver resolver; |
| |
| /** |
| * The {@link XMLReporter} for the {@link XMLInputFactory}. |
| */ |
| final XMLReporter reporter; |
| |
| // XMLStreamReader configuration properties. |
| /** |
| * The <code>encoding</code> to use for the {@link XMLStreamReader}. |
| */ |
| final String encoding; |
| |
| /** |
| * The <code>systemId</code> to use for the {@link XMLStreamReader}. |
| */ |
| final String systemId; |
| |
| /** |
| * Whether to validate the input with the XML Schema for SCXML. |
| */ |
| final boolean validate; |
| |
| // Commons SCXML object model configuration properties. |
| /** |
| * The list of Commons SCXML custom actions that will be available for this document. |
| */ |
| final List<CustomAction> customActions; |
| |
| /** |
| * The {@link ClassLoader} to use for loading the {@link CustomAction} instances to create. |
| */ |
| final ClassLoader customActionClassLoader; |
| |
| /** |
| * Whether to use the thread context {@link ClassLoader} for loading any {@link CustomAction} classes. |
| */ |
| 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 <state> or <parallel> element's |
| * "src" attribute. |
| */ |
| SCXML parent; |
| |
| /** |
| * The Commons SCXML {@link PathResolver} to use for this document. |
| */ |
| PathResolver pathResolver; |
| |
| /** |
| * Whether to silently ignore any unknown or invalid elements |
| * or to leave warning logs for those. |
| */ |
| boolean silent; |
| |
| /** |
| * Whether to strictly throw a model exception when there are any unknown or invalid elements |
| * or to leniently allow to read the model even with those. |
| */ |
| boolean strict; |
| |
| ContentParser contentParser; |
| |
| /* |
| * Public constructors |
| */ |
| /** |
| * Default constructor. |
| */ |
| public Configuration() { |
| this(null, null); |
| } |
| |
| /** |
| * Minimal convenience constructor. |
| * |
| * @param reporter The {@link XMLReporter} to use for this reading. |
| * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading. |
| */ |
| public Configuration(final XMLReporter reporter, final PathResolver pathResolver) { |
| this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, null, null, false); |
| } |
| |
| /** |
| * Convenience constructor. |
| * |
| * @param reporter The {@link XMLReporter} to use for this reading. |
| * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading. |
| * @param customActions The list of Commons SCXML custom actions that will be available for this document. |
| */ |
| public Configuration(final XMLReporter reporter, final PathResolver pathResolver, |
| final List<CustomAction> customActions) { |
| this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, customActions, null, |
| false); |
| } |
| |
| /** |
| * All purpose constructor. Any of the parameters passed in can be <code>null</code> (booleans should default |
| * to <code>false</code>). |
| * |
| * @param factoryId The <code>factoryId</code> to use. |
| * @param classLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create. |
| * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}. |
| * @param properties The map of properties (keys are property name strings, values are object property values) |
| * for the {@link XMLInputFactory}. |
| * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}. |
| * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. |
| * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader} |
| * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader} |
| * @param validate Whether to validate the input with the XML Schema for SCXML. |
| * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. |
| * @param customActions The list of Commons SCXML custom actions that will be available for this document. |
| * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to |
| * create. |
| * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the |
| * {@link CustomAction} instances to create. |
| */ |
| public Configuration(final String factoryId, final ClassLoader classLoader, final XMLEventAllocator allocator, |
| final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter, |
| final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver, |
| final List<CustomAction> customActions, final ClassLoader customActionClassLoader, |
| final boolean useContextClassLoaderForCustomActions) { |
| this(factoryId, classLoader, allocator, properties, resolver, reporter, encoding, systemId, validate, |
| pathResolver, null, customActions, customActionClassLoader, |
| useContextClassLoaderForCustomActions); |
| } |
| |
| /* |
| * Package access constructors |
| */ |
| /** |
| * Convenience package access constructor. |
| * |
| * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. |
| * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. |
| * @param parent The parent SCXML document if this document is src'ed in via the <state> or |
| * <parallel> element's "src" attribute. |
| */ |
| Configuration(final XMLReporter reporter, final PathResolver pathResolver, final SCXML parent) { |
| this(null, null, null, null, null, reporter, null, null, false, pathResolver, parent, null, null, false); |
| } |
| |
| /** |
| * Package access copy constructor. |
| * |
| * @param source The source {@link Configuration} to replicate. |
| */ |
| Configuration(final Configuration source) { |
| this(source.factoryId, source.factoryClassLoader, source.allocator, source.properties, source.resolver, |
| source.reporter, source.encoding, source.systemId, source.validate, source.pathResolver, |
| source.parent, source.customActions, source.customActionClassLoader, |
| source.useContextClassLoaderForCustomActions, source.silent, source.strict); |
| } |
| |
| /** |
| * All-purpose package access constructor. |
| * |
| * @param factoryId The <code>factoryId</code> to use. |
| * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to |
| * create. |
| * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}. |
| * @param properties The map of properties (keys are property name strings, values are object property values) |
| * for the {@link XMLInputFactory}. |
| * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}. |
| * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. |
| * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader} |
| * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader} |
| * @param validate Whether to validate the input with the XML Schema for SCXML. |
| * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. |
| * @param parent The parent SCXML document if this document is src'ed in via the <state> or |
| * <parallel> element's "src" attribute. |
| * @param customActions The list of Commons SCXML custom actions that will be available for this document. |
| * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to |
| * create. |
| * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the |
| * {@link CustomAction} instances to create. |
| */ |
| Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator, |
| final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter, |
| final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver, |
| final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader, |
| final boolean useContextClassLoaderForCustomActions) { |
| this(factoryId, factoryClassLoader, allocator, properties, resolver, reporter, encoding, systemId, |
| validate, pathResolver, parent, customActions, customActionClassLoader, |
| useContextClassLoaderForCustomActions, false, false); |
| } |
| |
| /** |
| * All-purpose package access constructor. |
| * |
| * @param factoryId The <code>factoryId</code> to use. |
| * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to |
| * create. |
| * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}. |
| * @param properties The map of properties (keys are property name strings, values are object property values) |
| * for the {@link XMLInputFactory}. |
| * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}. |
| * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. |
| * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader} |
| * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader} |
| * @param validate Whether to validate the input with the XML Schema for SCXML. |
| * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. |
| * @param parent The parent SCXML document if this document is src'ed in via the <state> or |
| * <parallel> element's "src" attribute. |
| * @param customActions The list of Commons SCXML custom actions that will be available for this document. |
| * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to |
| * create. |
| * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the |
| * {@link CustomAction} instances to create. |
| * @param silent Whether to silently ignore any unknown or invalid elements or to leave warning logs for those. |
| * @param strict Whether to strictly throw a model exception when there are any unknown or invalid elements |
| * or to leniently allow to read the model even with those. |
| */ |
| Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator, |
| final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter, |
| final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver, |
| final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader, |
| final boolean useContextClassLoaderForCustomActions, final boolean silent, final boolean strict) { |
| this.factoryId = factoryId; |
| this.factoryClassLoader = factoryClassLoader; |
| this.allocator = allocator; |
| this.properties = (properties == null ? new HashMap<String, Object>() : properties); |
| this.resolver = resolver; |
| this.reporter = reporter; |
| this.encoding = encoding; |
| this.systemId = systemId; |
| this.validate = validate; |
| this.pathResolver = pathResolver; |
| this.parent = parent; |
| 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(); |
| } |
| |
| /* |
| * 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. |
| * @return true if it is set to read models silently without any model error warning logs |
| * @see #silent |
| */ |
| public boolean isSilent() { |
| return silent; |
| } |
| |
| /** |
| * Turn on/off silent mode (whether to read models silently without any model error warning logs) |
| * @param silent silent mode (whether to read models silently without any model error warning logs) |
| * @see #silent |
| */ |
| public void setSilent(boolean silent) { |
| this.silent = silent; |
| } |
| |
| /** |
| * Returns true if it is set to check model strictly with throwing exceptions on any model error. |
| * @return true if it is set to check model strictly with throwing exceptions on any model error |
| * @see #strict |
| */ |
| public boolean isStrict() { |
| return strict; |
| } |
| |
| /** |
| * Turn on/off strict model (whether to check model strictly with throwing exception on any model error) |
| * @param strict strict model (whether to check model strictly with throwing exception on any model error) |
| * @see #strict |
| */ |
| public void setStrict(boolean strict) { |
| this.strict = strict; |
| } |
| } |
| } |