001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.io;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.Reader;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.net.URL;
025import java.net.URLConnection;
026import java.text.MessageFormat;
027import java.util.ArrayList;
028import java.util.EmptyStackException;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.Stack;
033
034import javax.xml.parsers.DocumentBuilderFactory;
035import javax.xml.parsers.ParserConfigurationException;
036import javax.xml.stream.XMLInputFactory;
037import javax.xml.stream.XMLReporter;
038import javax.xml.stream.XMLResolver;
039import javax.xml.stream.XMLStreamConstants;
040import javax.xml.stream.XMLStreamException;
041import javax.xml.stream.XMLStreamReader;
042import javax.xml.stream.util.XMLEventAllocator;
043import javax.xml.transform.Source;
044import javax.xml.transform.stream.StreamSource;
045import javax.xml.validation.Schema;
046import javax.xml.validation.SchemaFactory;
047import javax.xml.validation.Validator;
048
049import org.apache.commons.logging.LogFactory;
050import org.apache.commons.scxml2.PathResolver;
051import org.apache.commons.scxml2.env.SimpleErrorHandler;
052import org.apache.commons.scxml2.env.URLResolver;
053import org.apache.commons.scxml2.model.Action;
054import org.apache.commons.scxml2.model.ActionsContainer;
055import org.apache.commons.scxml2.model.Assign;
056import org.apache.commons.scxml2.model.Cancel;
057import org.apache.commons.scxml2.model.CustomAction;
058import org.apache.commons.scxml2.model.Data;
059import org.apache.commons.scxml2.model.Datamodel;
060import org.apache.commons.scxml2.model.Else;
061import org.apache.commons.scxml2.model.ElseIf;
062import org.apache.commons.scxml2.model.EnterableState;
063import org.apache.commons.scxml2.model.TransitionalState;
064import org.apache.commons.scxml2.model.Raise;
065import org.apache.commons.scxml2.model.Executable;
066import org.apache.commons.scxml2.model.ExternalContent;
067import org.apache.commons.scxml2.model.Final;
068import org.apache.commons.scxml2.model.Finalize;
069import org.apache.commons.scxml2.model.Foreach;
070import org.apache.commons.scxml2.model.History;
071import org.apache.commons.scxml2.model.If;
072import org.apache.commons.scxml2.model.Initial;
073import org.apache.commons.scxml2.model.Invoke;
074import org.apache.commons.scxml2.model.Log;
075import org.apache.commons.scxml2.model.ModelException;
076import org.apache.commons.scxml2.model.NamespacePrefixesHolder;
077import org.apache.commons.scxml2.model.OnEntry;
078import org.apache.commons.scxml2.model.OnExit;
079import org.apache.commons.scxml2.model.Parallel;
080import org.apache.commons.scxml2.model.Param;
081import org.apache.commons.scxml2.model.SCXML;
082import org.apache.commons.scxml2.model.Script;
083import org.apache.commons.scxml2.model.Send;
084import org.apache.commons.scxml2.model.SimpleTransition;
085import org.apache.commons.scxml2.model.State;
086import org.apache.commons.scxml2.model.Transition;
087import org.apache.commons.scxml2.model.TransitionType;
088import org.apache.commons.scxml2.model.Var;
089import org.w3c.dom.Attr;
090import org.w3c.dom.Document;
091import org.w3c.dom.Element;
092import org.w3c.dom.Node;
093import org.w3c.dom.NodeList;
094import org.xml.sax.SAXException;
095
096/**
097 * <p>The SCXMLReader provides the ability to read a SCXML document into
098 * the Java object model provided in the model package.</p>
099 *
100 * <p>See latest version of the SCXML Working Draft for more details.</p>
101 *
102 * <p><b>NOTE:</b> The SCXMLReader assumes that the SCXML document to be
103 * parsed is well-formed and correct. If that assumption does not hold,
104 * any subsequent behavior is undefined.</p>
105 *
106 * @since 1.0
107 */
108public final class SCXMLReader {
109
110    //---------------------- PRIVATE CONSTANTS ----------------------//
111    //---- NAMESPACES ----//
112    /**
113     * The SCXML namespace that this Reader is built for. Any document
114     * that is intended to be parsed by this reader <b>must</b>
115     * bind the SCXML elements to this namespace.
116     */
117    private static final String XMLNS_SCXML =
118            "http://www.w3.org/2005/07/scxml";
119
120    /**
121     * The namespace that defines any custom actions defined by the Commons
122     * SCXML implementation. Any document that intends to use these custom
123     * actions needs to ensure that they are in the correct namespace. Use
124     * of actions in this namespace makes the document non-portable across
125     * implementations.
126     */
127    private static final String XMLNS_COMMONS_SCXML =
128            "http://commons.apache.org/scxml";
129
130    /**
131     * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1
132     */
133    private static final String SCXML_REQUIRED_VERSION = "1.0";
134    /**
135     * The default namespace for attributes.
136     */
137    private static final String XMLNS_DEFAULT = null;
138
139    //---- ERROR MESSAGES ----//
140    /**
141     * Null URL passed as argument.
142     */
143    private static final String ERR_NULL_URL = "Cannot parse null URL";
144
145    /**
146     * Null path passed as argument.
147     */
148    private static final String ERR_NULL_PATH = "Cannot parse null path";
149
150    /**
151     * Null InputStream passed as argument.
152     */
153    private static final String ERR_NULL_ISTR = "Cannot parse null InputStream";
154
155    /**
156     * Null Reader passed as argument.
157     */
158    private static final String ERR_NULL_READ = "Cannot parse null Reader";
159
160    /**
161     * Null Source passed as argument.
162     */
163    private static final String ERR_NULL_SRC = "Cannot parse null Source";
164
165    /**
166     * Error message while attempting to define a custom action which does
167     * not extend the Commons SCXML Action base class.
168     */
169    private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list"
170            + " contained unknown object, class not a Commons SCXML Action class subtype: ";
171
172    /**
173     * Parser configuration error while trying to parse stream to DOM node(s).
174     */
175    private static final String ERR_PARSER_CFG = "ParserConfigurationException while trying"
176            + " to parse stream into DOM node(s).";
177
178    /**
179     * Error message when the URI in a &lt;state&gt;'s &quot;src&quot;
180     * attribute does not point to a valid SCXML document, and thus cannot be
181     * parsed.
182     */
183    private static final String ERR_STATE_SRC =
184            "Source attribute in <state src=\"{0}\"> cannot be parsed";
185
186    /**
187     * Error message when the target of the URI fragment in a &lt;state&gt;'s
188     * &quot;src&quot; attribute is not defined in the referenced document.
189     */
190    private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in "
191            + "<state src=\"{0}\"> is an unknown state in referenced document";
192
193    /**
194     * Error message when the target of the URI fragment in a &lt;state&gt;'s
195     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
196     * the referenced document.
197     */
198    private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment"
199            + " in <state src=\"{0}\"> does not point to a <state> or <final>";
200
201    /**
202     * Error message when the target of the URI fragment in a &lt;state&gt;'s
203     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
204     * the referenced document.
205     */
206    private static final String ERR_REQUIRED_ATTRIBUTE_MISSING = "<{0}> is missing"
207            +" required attribute \"{1}\" value at {2}";
208
209    /**
210     * Error message when the target of the URI fragment in a &lt;state&gt;'s
211     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
212     * the referenced document.
213     */
214    private static final String ERR_ATTRIBUTE_NOT_BOOLEAN = "Illegal value \"{0}\""
215            + "for attribute \"{1}\" in element <{2}> at {3}."
216            +" Only the value \"true\" or \"false\" is allowed.";
217
218    /**
219     * Error message when the element (state|parallel|final|history) uses an id value
220     * with the reserved prefix {@link SCXML#GENERATED_TT_ID_PREFIX}.
221     */
222    private static final String ERR_RESERVED_ID_PREFIX = "Reserved id prefix \""
223            +SCXML.GENERATED_TT_ID_PREFIX+"\" used for <{0} id=\"{1}\"> at {2}";
224
225    /**
226     * Error message when the target of the URI fragment in a &lt;state&gt;'s
227     * &quot;src&quot; attribute is not defined in the referenced document.
228     */
229    private static final String ERR_UNSUPPORTED_TRANSITION_TYPE = "Unsupported transition type "
230            + "for <transition type=\"{0}\"> at {1}.";
231
232    /**
233     * Error message when the target of the URI fragment in a &lt;state&gt;'s
234     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
235     * the referenced document.
236     */
237    private static final String ERR_INVALID_VERSION = "The <scxml> element defines"
238            +" an unsupported version \"{0}\", only version \"1.0\" is supported.";
239
240    //--------------------------- XML VOCABULARY ---------------------------//
241    //---- ELEMENT NAMES ----//
242    private static final String ELEM_ASSIGN = "assign";
243    private static final String ELEM_CANCEL = "cancel";
244    private static final String ELEM_CONTENT = "content";
245    private static final String ELEM_DATA = "data";
246    private static final String ELEM_DATAMODEL = "datamodel";
247    private static final String ELEM_ELSE = "else";
248    private static final String ELEM_ELSEIF = "elseif";
249    private static final String ELEM_RAISE = "raise";
250    private static final String ELEM_FINAL = "final";
251    private static final String ELEM_FINALIZE = "finalize";
252    private static final String ELEM_HISTORY = "history";
253    private static final String ELEM_IF = "if";
254    private static final String ELEM_INITIAL = "initial";
255    private static final String ELEM_INVOKE = "invoke";
256    private static final String ELEM_FOREACH = "foreach";
257    private static final String ELEM_LOG = "log";
258    private static final String ELEM_ONENTRY = "onentry";
259    private static final String ELEM_ONEXIT = "onexit";
260    private static final String ELEM_PARALLEL = "parallel";
261    private static final String ELEM_PARAM = "param";
262    private static final String ELEM_SCRIPT = "script";
263    private static final String ELEM_SCXML = "scxml";
264    private static final String ELEM_SEND = "send";
265    private static final String ELEM_STATE = "state";
266    private static final String ELEM_TRANSITION = "transition";
267    private static final String ELEM_VAR = "var";
268
269    //---- ATTRIBUTE NAMES ----//
270    private static final String ATTR_ARRAY = "array";
271    private static final String ATTR_AUTOFORWARD = "autoforward";
272    private static final String ATTR_COND = "cond";
273    private static final String ATTR_DELAY = "delay";
274    private static final String ATTR_EVENT = "event";
275    private static final String ATTR_EXMODE = "exmode";
276    private static final String ATTR_EXPR = "expr";
277    private static final String ATTR_HINTS = "hints";
278    private static final String ATTR_ID = "id";
279    private static final String ATTR_INDEX = "index";
280    private static final String ATTR_INITIAL = "initial";
281    private static final String ATTR_ITEM = "item";
282    private static final String ATTR_LABEL = "label";
283    private static final String ATTR_LOCATION = "location";
284    private static final String ATTR_NAME = "name";
285    private static final String ATTR_NAMELIST = "namelist";
286    private static final String ATTR_PROFILE = "profile";
287    private static final String ATTR_SENDID = "sendid";
288    private static final String ATTR_SRC = "src";
289    private static final String ATTR_SRCEXPR = "srcexpr";
290    private static final String ATTR_TARGET = "target";
291    private static final String ATTR_TYPE = "type";
292    private static final String ATTR_VERSION = "version";
293
294    //------------------------- PUBLIC API METHODS -------------------------//
295    /*
296     * Public methods
297     */
298    /**
299     * Parse the SCXML document at the supplied path.
300     *
301     * @param scxmlPath The real path to the SCXML document.
302     *
303     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
304     *
305     * @throws IOException An IO error during parsing.
306     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
307     *                        errors in the SCXML document that may not be identified by the schema).
308     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
309     */
310    public static SCXML read(final String scxmlPath)
311            throws IOException, ModelException, XMLStreamException {
312
313        return read(scxmlPath, new Configuration());
314    }
315
316    /**
317     * Parse the SCXML document at the supplied path with the given {@link Configuration}.
318     *
319     * @param scxmlPath The real path to the SCXML document.
320     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
321     *
322     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
323     *
324     * @throws IOException An IO error during parsing.
325     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
326     *                        errors in the SCXML document that may not be identified by the schema).
327     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
328     */
329    public static SCXML read(final String scxmlPath, final Configuration configuration)
330            throws IOException, ModelException, XMLStreamException {
331
332        if (scxmlPath == null) {
333            throw new IllegalArgumentException(ERR_NULL_PATH);
334        }
335        SCXML scxml = readInternal(configuration, null, scxmlPath, null, null, null);
336        if (scxml != null) {
337            ModelUpdater.updateSCXML(scxml);
338        }
339        return scxml;
340    }
341
342    /**
343     * Parse the SCXML document at the supplied {@link URL}.
344     *
345     * @param scxmlURL The SCXML document {@link URL} to parse.
346     *
347     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
348     *
349     * @throws IOException An IO error during parsing.
350     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
351     *                        errors in the SCXML document that may not be identified by the schema).
352     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
353     */
354    public static SCXML read(final URL scxmlURL)
355            throws IOException, ModelException, XMLStreamException {
356
357        return read(scxmlURL, new Configuration());
358    }
359
360    /**
361     * Parse the SCXML document at the supplied {@link URL} with the given {@link Configuration}.
362     *
363     * @param scxmlURL The SCXML document {@link URL} to parse.
364     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
365     *
366     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
367     *
368     * @throws IOException An IO error during parsing.
369     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
370     *                        errors in the SCXML document that may not be identified by the schema).
371     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
372     */
373    public static SCXML read(final URL scxmlURL, final Configuration configuration)
374            throws IOException, ModelException, XMLStreamException {
375
376        if (scxmlURL == null) {
377            throw new IllegalArgumentException(ERR_NULL_URL);
378        }
379        SCXML scxml = readInternal(configuration, scxmlURL, null, null, null, null);
380        if (scxml != null) {
381            ModelUpdater.updateSCXML(scxml);
382        }
383        return scxml;
384    }
385
386    /**
387     * Parse the SCXML document supplied by the given {@link InputStream}.
388     *
389     * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
390     *
391     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
392     *
393     * @throws IOException An IO error during parsing.
394     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
395     *                        errors in the SCXML document that may not be identified by the schema).
396     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
397     */
398    public static SCXML read(final InputStream scxmlStream)
399            throws IOException, ModelException, XMLStreamException {
400
401        return read(scxmlStream, new Configuration());
402    }
403
404    /**
405     * Parse the SCXML document supplied by the given {@link InputStream} with the given {@link Configuration}.
406     *
407     * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
408     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
409     *
410     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
411     *
412     * @throws IOException An IO error during parsing.
413     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
414     *                        errors in the SCXML document that may not be identified by the schema).
415     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
416     */
417    public static SCXML read(final InputStream scxmlStream, final Configuration configuration)
418            throws IOException, ModelException, XMLStreamException {
419
420        if (scxmlStream == null) {
421            throw new IllegalArgumentException(ERR_NULL_ISTR);
422        }
423        SCXML scxml = readInternal(configuration, null, null, scxmlStream, null, null);
424        if (scxml != null) {
425            ModelUpdater.updateSCXML(scxml);
426        }
427        return scxml;
428    }
429
430    /**
431     * Parse the SCXML document supplied by the given {@link Reader}.
432     *
433     * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
434     *
435     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
436     *
437     * @throws IOException An IO error during parsing.
438     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
439     *                        errors in the SCXML document that may not be identified by the schema).
440     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
441     */
442    public static SCXML read(final Reader scxmlReader)
443            throws IOException, ModelException, XMLStreamException {
444
445        return read(scxmlReader, new Configuration());
446    }
447
448    /**
449     * Parse the SCXML document supplied by the given {@link Reader} with the given {@link Configuration}.
450     *
451     * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
452     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
453     *
454     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
455     *
456     * @throws IOException An IO error during parsing.
457     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
458     *                        errors in the SCXML document that may not be identified by the schema).
459     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
460     */
461    public static SCXML read(final Reader scxmlReader, final Configuration configuration)
462            throws IOException, ModelException, XMLStreamException {
463
464        if (scxmlReader == null) {
465            throw new IllegalArgumentException(ERR_NULL_READ);
466        }
467        SCXML scxml = readInternal(configuration, null, null, null, scxmlReader, null);
468        if (scxml != null) {
469            ModelUpdater.updateSCXML(scxml);
470        }
471        return scxml;
472    }
473
474    /**
475     * Parse the SCXML document supplied by the given {@link Source}.
476     *
477     * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
478     *
479     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
480     *
481     * @throws IOException An IO error during parsing.
482     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
483     *                        errors in the SCXML document that may not be identified by the schema).
484     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
485     */
486    public static SCXML read(final Source scxmlSource)
487            throws IOException, ModelException, XMLStreamException {
488
489        return read(scxmlSource, new Configuration());
490    }
491
492    /**
493     * Parse the SCXML document supplied by the given {@link Source} with the given {@link Configuration}.
494     *
495     * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
496     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
497     *
498     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
499     *
500     * @throws IOException An IO error during parsing.
501     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
502     *                        errors in the SCXML document that may not be identified by the schema).
503     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
504     */
505    public static SCXML read(final Source scxmlSource, final Configuration configuration)
506            throws IOException, ModelException, XMLStreamException {
507
508        if (scxmlSource == null) {
509            throw new IllegalArgumentException(ERR_NULL_SRC);
510        }
511        SCXML scxml = readInternal(configuration, null, null, null, null, scxmlSource);
512        if (scxml != null) {
513            ModelUpdater.updateSCXML(scxml);
514        }
515        return scxml;
516    }
517
518    //---------------------- PRIVATE UTILITY METHODS ----------------------//
519    /**
520     * Parse the SCXML document at the supplied {@link URL} using the supplied {@link Configuration}, but do not
521     * wire up the object model to be usable just yet. Exactly one of the url, path, stream, reader or source
522     * parameters must be provided.
523     *
524     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
525     * @param scxmlURL The optional SCXML document {@link URL} to parse.
526     * @param scxmlPath The optional real path to the SCXML document as a string.
527     * @param scxmlStream The optional {@link InputStream} providing the SCXML document.
528     * @param scxmlReader The optional {@link Reader} providing the SCXML document.
529     * @param scxmlSource The optional {@link Source} providing the SCXML document.
530     *
531     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
532     *         (not wired up to be immediately usable).
533     *
534     * @throws IOException An IO error during parsing.
535     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
536     *                        errors in the SCXML document that may not be identified by the schema).
537     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
538     */
539    private static SCXML readInternal(final Configuration configuration, final URL scxmlURL, final String scxmlPath,
540                                      final InputStream scxmlStream, final Reader scxmlReader, final Source scxmlSource)
541            throws IOException, ModelException, XMLStreamException {
542
543        if (configuration.pathResolver == null) {
544            if (scxmlURL != null) {
545                configuration.pathResolver = new URLResolver(scxmlURL);
546            } else if (scxmlPath != null) {
547                configuration.pathResolver = new URLResolver(new URL(scxmlPath));
548            }
549        }
550
551        XMLStreamReader reader = getReader(configuration, scxmlURL, scxmlPath, scxmlStream, scxmlReader, scxmlSource);
552
553        return readDocument(reader, configuration);
554    }
555
556    /*
557     * Private utility functions for reading the SCXML document.
558     */
559    /**
560     * Read the SCXML document through the {@link XMLStreamReader}.
561     *
562     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
563     * @param configuration The {@link Configuration} to use while parsing.
564     *
565     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
566     *         (not wired up to be immediately usable).
567     *
568     * @throws IOException An IO error during parsing.
569     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
570     *                        errors in the SCXML document that may not be identified by the schema).
571     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
572     */
573    private static SCXML readDocument(final XMLStreamReader reader, final Configuration configuration)
574            throws IOException, ModelException, XMLStreamException {
575
576        SCXML scxml = new SCXML();
577        while (reader.hasNext()) {
578            String name, nsURI;
579            switch (reader.next()) {
580                case XMLStreamConstants.START_ELEMENT:
581                    pushNamespaces(reader, configuration);
582                    nsURI = reader.getNamespaceURI();
583                    name = reader.getLocalName();
584                    if (XMLNS_SCXML.equals(nsURI)) {
585                        if (ELEM_SCXML.equals(name)) {
586                            readSCXML(reader, configuration, scxml);
587                        } else {
588                            reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
589                        }
590                    } else {
591                        reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
592                    }
593                    break;
594                case XMLStreamConstants.NAMESPACE:
595                    System.err.println(reader.getNamespaceCount());
596                    break;
597                default:
598            }
599        }
600        return scxml;
601    }
602
603    /**
604     * Read the contents of this &lt;scxml&gt; element.
605     *
606     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
607     * @param configuration The {@link Configuration} to use while parsing.
608     * @param scxml The root of the object model being parsed.
609     *
610     * @throws IOException An IO error during parsing.
611     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
612     *                        errors in the SCXML document that may not be identified by the schema).
613     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
614     */
615    private static void readSCXML(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml)
616            throws IOException, ModelException, XMLStreamException {
617
618        scxml.setExmode(readAV(reader, ATTR_EXMODE));
619        scxml.setInitial(readAV(reader, ATTR_INITIAL));
620        scxml.setName(readAV(reader, ATTR_NAME));
621        scxml.setProfile(readAV(reader, ATTR_PROFILE));
622        scxml.setVersion(readRequiredAV(reader, ELEM_SCXML, ATTR_VERSION));
623        if (!SCXML_REQUIRED_VERSION.equals(scxml.getVersion())) {
624            throw new ModelException(new MessageFormat(ERR_INVALID_VERSION).format(new Object[] {scxml.getVersion()}));
625        }
626        readNamespaces(configuration, scxml);
627
628        boolean hasGlobalScript = false;
629
630        loop : while (reader.hasNext()) {
631            String name, nsURI;
632            switch (reader.next()) {
633                case XMLStreamConstants.START_ELEMENT:
634                    pushNamespaces(reader, configuration);
635                    nsURI = reader.getNamespaceURI();
636                    name = reader.getLocalName();
637                    if (XMLNS_SCXML.equals(nsURI)) {
638                        if (ELEM_STATE.equals(name)) {
639                            readState(reader, configuration, scxml, null);
640                        } else if (ELEM_PARALLEL.equals(name)) {
641                            readParallel(reader, configuration, scxml, null);
642                        } else if (ELEM_FINAL.equals(name)) {
643                            readFinal(reader, configuration, scxml, null);
644                        } else if (ELEM_DATAMODEL.equals(name)) {
645                            readDatamodel(reader, configuration, scxml, null);
646                        } else if (ELEM_SCRIPT.equals(name) && !hasGlobalScript) {
647                            readGlobalScript(reader, configuration, scxml);
648                            hasGlobalScript = true;
649                        } else {
650                            reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
651                        }
652                    } else {
653                        reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
654                    }
655                    break;
656                case XMLStreamConstants.END_ELEMENT:
657                    popNamespaces(reader, configuration);
658                    nsURI = reader.getNamespaceURI();
659                    name = reader.getLocalName();
660                    if (XMLNS_SCXML.equals(nsURI) && ELEM_SCXML.equals(name)) {
661                        break loop;
662                    }
663                    break;
664                default:
665            }
666        }
667    }
668
669    /**
670     * Read the contents of this &lt;state&gt; element.
671     *
672     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
673     * @param configuration The {@link Configuration} to use while parsing.
674     * @param scxml The root of the object model being parsed.
675     * @param parent The parent {@link TransitionalState} for this state (null for top level state).
676     *
677     * @throws IOException An IO error during parsing.
678     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
679     *                        errors in the SCXML document that may not be identified by the schema).
680     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
681     */
682    private static void readState(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
683                                  final TransitionalState parent)
684            throws IOException, ModelException, XMLStreamException {
685
686        State state = new State();
687        state.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_STATE));
688        String initial = readAV(reader, ATTR_INITIAL);
689        if (initial != null) {
690            state.setFirst(initial);
691        }
692        String src = readAV(reader, ATTR_SRC);
693        if (src != null) {
694            String source = src;
695            Configuration copy = new Configuration(configuration);
696            if (copy.parent == null) {
697                copy.parent = scxml;
698            }
699            if (configuration.pathResolver != null) {
700                source = configuration.pathResolver.resolvePath(src);
701                copy.pathResolver = configuration.pathResolver.getResolver(src);
702            }
703            readTransitionalStateSrc(copy, source, state);
704        }
705
706        if (parent == null) {
707            scxml.addChild(state);
708        } else if (parent instanceof State) {
709            ((State)parent).addChild(state);
710        }
711        else {
712            ((Parallel)parent).addChild(state);
713        }
714        scxml.addTarget(state);
715        if (configuration.parent != null) {
716            configuration.parent.addTarget(state);
717        }
718
719        loop : while (reader.hasNext()) {
720            String name, nsURI;
721            switch (reader.next()) {
722                case XMLStreamConstants.START_ELEMENT:
723                    pushNamespaces(reader, configuration);
724                    nsURI = reader.getNamespaceURI();
725                    name = reader.getLocalName();
726                    if (XMLNS_SCXML.equals(nsURI)) {
727                        if (ELEM_TRANSITION.equals(name)) {
728                            state.addTransition(readTransition(reader, configuration));
729                        } else if (ELEM_STATE.equals(name)) {
730                            readState(reader, configuration, scxml, state);
731                        } else if (ELEM_INITIAL.equals(name)) {
732                            readInitial(reader, configuration, state);
733                        } else if (ELEM_FINAL.equals(name)) {
734                            readFinal(reader, configuration, scxml, state);
735                        } else if (ELEM_ONENTRY.equals(name)) {
736                            readOnEntry(reader, configuration, state);
737                        } else if (ELEM_ONEXIT.equals(name)) {
738                            readOnExit(reader, configuration, state);
739                        } else if (ELEM_PARALLEL.equals(name)) {
740                            readParallel(reader, configuration, scxml, state);
741                        } else if (ELEM_DATAMODEL.equals(name)) {
742                            readDatamodel(reader, configuration, null, state);
743                        } else if (ELEM_INVOKE.equals(name)) {
744                            readInvoke(reader, configuration, state);
745                        } else if (ELEM_HISTORY.equals(name)) {
746                            readHistory(reader, configuration, scxml, state);
747                        } else {
748                            reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
749                        }
750                    } else {
751                        reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
752                    }
753                    break;
754                case XMLStreamConstants.END_ELEMENT:
755                    popNamespaces(reader, configuration);
756                    nsURI = reader.getNamespaceURI();
757                    name = reader.getLocalName();
758                    if (XMLNS_SCXML.equals(nsURI) && ELEM_STATE.equals(name)) {
759                        break loop;
760                    }
761                    break;
762                default:
763            }
764        }
765    }
766
767    /**
768     * Read the contents of this &lt;parallel&gt; element.
769     *
770     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
771     * @param configuration The {@link Configuration} to use while parsing.
772     * @param scxml The root of the object model being parsed.
773     * @param parent The parent {@link TransitionalState} for this parallel (null for top level state).
774     *
775     * @throws IOException An IO error during parsing.
776     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
777     *                        errors in the SCXML document that may not be identified by the schema).
778     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
779     */
780    private static void readParallel(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
781                                     final TransitionalState parent)
782            throws IOException, ModelException, XMLStreamException {
783
784        Parallel parallel = new Parallel();
785        parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_PARALLEL));
786        String src = readAV(reader, ATTR_SRC);
787        if (src != null) {
788            String source = src;
789            Configuration copy = new Configuration(configuration);
790            if (copy.parent == null) {
791                copy.parent = scxml;
792            }
793            if (configuration.pathResolver != null) {
794                source = configuration.pathResolver.resolvePath(src);
795                copy.pathResolver = configuration.pathResolver.getResolver(src);
796            }
797            readTransitionalStateSrc(copy, source, parallel);
798        }
799
800        if (parent == null) {
801            scxml.addChild(parallel);
802        } else if (parent instanceof State) {
803            ((State)parent).addChild(parallel);
804        }
805        else {
806            ((Parallel)parent).addChild(parallel);
807        }
808        scxml.addTarget(parallel);
809        if (configuration.parent != null) {
810            configuration.parent.addTarget(parallel);
811        }
812
813        loop : while (reader.hasNext()) {
814            String name, nsURI;
815            switch (reader.next()) {
816                case XMLStreamConstants.START_ELEMENT:
817                    pushNamespaces(reader, configuration);
818                    nsURI = reader.getNamespaceURI();
819                    name = reader.getLocalName();
820                    if (XMLNS_SCXML.equals(nsURI)) {
821                        if (ELEM_TRANSITION.equals(name)) {
822                            parallel.addTransition(readTransition(reader, configuration));
823                        } else if (ELEM_STATE.equals(name)) {
824                            readState(reader, configuration, scxml, parallel);
825                        } else if (ELEM_PARALLEL.equals(name)) {
826                            readParallel(reader, configuration, scxml, parallel);
827                        } else if (ELEM_ONENTRY.equals(name)) {
828                            readOnEntry(reader, configuration, parallel);
829                        } else if (ELEM_ONEXIT.equals(name)) {
830                            readOnExit(reader, configuration, parallel);
831                        } else if (ELEM_DATAMODEL.equals(name)) {
832                            readDatamodel(reader, configuration, null, parallel);
833                        } else if (ELEM_INVOKE.equals(name)) {
834                            readInvoke(reader, configuration, parallel);
835                        } else if (ELEM_HISTORY.equals(name)) {
836                            readHistory(reader, configuration, scxml, parallel);
837                        } else {
838                            reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
839                        }
840                    } else {
841                        reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
842                    }
843                    break;
844                case XMLStreamConstants.END_ELEMENT:
845                    popNamespaces(reader, configuration);
846                    nsURI = reader.getNamespaceURI();
847                    name = reader.getLocalName();
848                    if (XMLNS_SCXML.equals(nsURI) && ELEM_PARALLEL.equals(name)) {
849                        break loop;
850                    }
851                    break;
852                default:
853            }
854        }
855    }
856
857    /**
858     * Read the contents of this &lt;final&gt; element.
859     *
860     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
861     * @param configuration The {@link Configuration} to use while parsing.
862     * @param scxml The root of the object model being parsed.
863     * @param parent The parent {@link State} for this final (null for top level state).
864     *
865     * @throws IOException An IO error during parsing.
866     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
867     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
868     *                        errors in the SCXML document that may not be identified by the schema).
869     */
870    private static void readFinal(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
871                                  final State parent)
872            throws XMLStreamException, ModelException, IOException {
873
874        Final end = new Final();
875        end.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_FINAL));
876
877        if (parent == null) {
878            scxml.addChild(end);
879        } else {
880            parent.addChild(end);
881        }
882
883        scxml.addTarget(end);
884        if (configuration.parent != null) {
885            configuration.parent.addTarget(end);
886        }
887
888        loop : while (reader.hasNext()) {
889            String name, nsURI;
890            switch (reader.next()) {
891                case XMLStreamConstants.START_ELEMENT:
892                    pushNamespaces(reader, configuration);
893                    nsURI = reader.getNamespaceURI();
894                    name = reader.getLocalName();
895                    if (XMLNS_SCXML.equals(nsURI)) {
896                        if (ELEM_ONENTRY.equals(name)) {
897                            readOnEntry(reader, configuration, end);
898                        } else if (ELEM_ONEXIT.equals(name)) {
899                            readOnExit(reader, configuration, end);
900                        } else {
901                            reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
902                        }
903                    } else {
904                        reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
905                    }
906                    break;
907                case XMLStreamConstants.END_ELEMENT:
908                    popNamespaces(reader, configuration);
909                    nsURI = reader.getNamespaceURI();
910                    name = reader.getLocalName();
911                    if (XMLNS_SCXML.equals(nsURI) && ELEM_FINAL.equals(name)) {
912                        break loop;
913                    }
914                    break;
915                default:
916            }
917        }
918    }
919
920    /**
921     * Parse the contents of the SCXML document that this "src" attribute value of a &lt;state&gt; or &lt;parallel&gt;
922     * element points to. Without a URL fragment, the entire state machine is imported as contents of the
923     * &lt;state&gt; or &lt;parallel&gt;. If a URL fragment is present, the fragment must specify the id of the
924     * corresponding &lt;state&gt; or &lt;parallel&gt; to import.
925     *
926     * @param configuration The {@link Configuration} to use while parsing.
927     * @param src The "src" attribute value.
928     * @param ts The parent {@link TransitionalState} that specifies this "src" attribute.
929     *
930     * @throws IOException An IO error during parsing.
931     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
932     *                        errors in the SCXML document that may not be identified by the schema).
933     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
934     */
935    private static void readTransitionalStateSrc(final Configuration configuration, final String src,
936                                                 final TransitionalState ts)
937            throws IOException, ModelException, XMLStreamException {
938
939        // Check for URI fragment
940        String[] fragments = src.split("#", 2);
941        String location = fragments[0];
942        String fragment = null;
943        if (fragments.length > 1) {
944            fragment = fragments[1];
945        }
946
947        // Parse external document
948        SCXML externalSCXML = null;
949        try {
950            externalSCXML = SCXMLReader.readInternal(configuration, new URL(location), null, null, null, null);
951        } catch (Exception e) {
952            MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC);
953            String errMsg = msgFormat.format(new Object[] {src});
954            throw new ModelException(errMsg + " : " + e.getMessage(), e);
955        }
956
957        // Pull in the parts of the external document as needed
958        if (fragment == null) {
959            // All targets pulled in since its not a src fragment
960            if (ts instanceof State) {
961                State s = (State) ts;
962                Initial ini = new Initial();
963                SimpleTransition t = new SimpleTransition();
964                t.setNext(externalSCXML.getInitial());
965                ini.setTransition(t);
966                s.setInitial(ini);
967                for (EnterableState child : externalSCXML.getChildren()) {
968                    s.addChild(child);
969                }
970                s.setDatamodel(externalSCXML.getDatamodel());
971            } else if (ts instanceof Parallel) {
972                // TODO src attribute for <parallel>
973            }
974        } else {
975            // Need to pull in only descendent targets
976            Object source = externalSCXML.getTargets().get(fragment);
977            if (source == null) {
978                MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT);
979                String errMsg = msgFormat.format(new Object[] {src});
980                throw new ModelException(errMsg);
981            }
982            if (source instanceof State && ts instanceof State) {
983                State s = (State) ts;
984                State include = (State) source;
985                for (OnEntry onentry : include.getOnEntries()) {
986                    s.addOnEntry(onentry);
987                }
988                for (OnExit onexit : include.getOnExits()) {
989                    s.addOnExit(onexit);
990                }
991                s.setDatamodel(include.getDatamodel());
992                List<History> histories = include.getHistory();
993                for (History h : histories) {
994                    s.addHistory(h);
995                    configuration.parent.addTarget(h);
996                }
997                for (EnterableState child : include.getChildren()) {
998                    s.addChild(child);
999                    configuration.parent.addTarget(child);
1000                    readInExternalTargets(configuration.parent, child);
1001                }
1002                for (Invoke invoke : include.getInvokes()) {
1003                    s.addInvoke(invoke);
1004                }
1005                if (include.getInitial() != null) {
1006                    s.setInitial(include.getInitial());
1007                }
1008                List<Transition> transitions = include.getTransitionsList();
1009                for (Transition t : transitions) {
1010                    s.addTransition(t);
1011                }
1012            } else if (ts instanceof Parallel && source instanceof Parallel) {
1013                // TODO src attribute for <parallel>
1014            } else {
1015                MessageFormat msgFormat =
1016                        new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET);
1017                String errMsg = msgFormat.format(new Object[] {src});
1018                throw new ModelException(errMsg);
1019            }
1020        }
1021    }
1022
1023    /**
1024     * Add all the nested targets from given target to given parent state machine.
1025     *
1026     * @param parent The state machine
1027     * @param es The target to import
1028     */
1029    private static void readInExternalTargets(final SCXML parent, final EnterableState es) {
1030        if (es instanceof TransitionalState) {
1031            for (History h : ((TransitionalState)es).getHistory()) {
1032                parent.addTarget(h);
1033            }
1034            for (EnterableState child : ((TransitionalState) es).getChildren()) {
1035                parent.addTarget(child);
1036                readInExternalTargets(parent, child);
1037            }
1038        }
1039    }
1040
1041    /**
1042     * Read the contents of this &lt;datamodel&gt; element.
1043     *
1044     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1045     * @param configuration The {@link Configuration} to use while parsing.
1046     * @param scxml The root of the object model being parsed.
1047     * @param parent The parent {@link TransitionalState} for this datamodel (null for top level).
1048     *
1049     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1050     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1051     *                        errors in the SCXML document that may not be identified by the schema).
1052     */
1053    private static void readDatamodel(final XMLStreamReader reader, final Configuration configuration,
1054                                      final SCXML scxml, final TransitionalState parent)
1055            throws XMLStreamException, ModelException {
1056
1057        Datamodel dm = new Datamodel();
1058
1059        loop : while (reader.hasNext()) {
1060            String name, nsURI;
1061            switch (reader.next()) {
1062                case XMLStreamConstants.START_ELEMENT:
1063                    pushNamespaces(reader, configuration);
1064                    nsURI = reader.getNamespaceURI();
1065                    name = reader.getLocalName();
1066                    if (XMLNS_SCXML.equals(nsURI)) {
1067                        if (ELEM_DATA.equals(name)) {
1068                            readData(reader, configuration, dm);
1069                        } else {
1070                            reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
1071                        }
1072                    } else {
1073                        reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
1074                    }
1075                    break;
1076                case XMLStreamConstants.END_ELEMENT:
1077                    popNamespaces(reader, configuration);
1078                    nsURI = reader.getNamespaceURI();
1079                    name = reader.getLocalName();
1080                    if (XMLNS_SCXML.equals(nsURI) && ELEM_DATAMODEL.equals(name)) {
1081                        break loop;
1082                    }
1083                    break;
1084                default:
1085            }
1086        }
1087
1088        if (parent == null) {
1089            scxml.setDatamodel(dm);
1090        } else {
1091            parent.setDatamodel(dm);
1092        }
1093    }
1094
1095    /**
1096     * Read the contents of this &lt;data&gt; element.
1097     *
1098     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1099     * @param configuration The {@link Configuration} to use while parsing.
1100     * @param dm The parent {@link Datamodel} for this data.
1101     *
1102     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1103     */
1104    private static void readData(final XMLStreamReader reader, final Configuration configuration, final Datamodel dm)
1105            throws XMLStreamException, ModelException {
1106
1107        Data datum = new Data();
1108        datum.setId(readRequiredAV(reader, ELEM_DATA, ATTR_ID));
1109        datum.setExpr(readAV(reader, ATTR_EXPR));
1110        readNamespaces(configuration, datum);
1111        datum.setNode(readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[] {"id"}));
1112        dm.addData(datum);
1113    }
1114
1115    /**
1116     * Read the contents of this &lt;invoke&gt; element.
1117     *
1118     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1119     * @param configuration The {@link Configuration} to use while parsing.
1120     * @param parent The parent {@link TransitionalState} for this invoke.
1121     *
1122     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1123     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1124     *                        errors in the SCXML document that may not be identified by the schema).
1125     */
1126    private static void readInvoke(final XMLStreamReader reader, final Configuration configuration,
1127                                   final TransitionalState parent)
1128            throws XMLStreamException, ModelException {
1129
1130        Invoke invoke = new Invoke();
1131        invoke.setId(readAV(reader, ATTR_ID));
1132        invoke.setSrc(readAV(reader, ATTR_SRC));
1133        invoke.setSrcexpr(readAV(reader, ATTR_SRCEXPR));
1134        invoke.setType(readAV(reader, ATTR_TYPE));
1135        invoke.setAutoForward(readBooleanAV(reader, ELEM_INVOKE, ATTR_AUTOFORWARD));
1136        invoke.setPathResolver(configuration.pathResolver);
1137        readNamespaces(configuration, invoke);
1138
1139        loop : while (reader.hasNext()) {
1140            String name, nsURI;
1141            switch (reader.next()) {
1142                case XMLStreamConstants.START_ELEMENT:
1143                    pushNamespaces(reader, configuration);
1144                    nsURI = reader.getNamespaceURI();
1145                    name = reader.getLocalName();
1146                    if (XMLNS_SCXML.equals(nsURI)) {
1147                        if (ELEM_PARAM.equals(name)) {
1148                            readParam(reader, configuration, invoke);
1149                        } else if (ELEM_FINALIZE.equals(name)) {
1150                            readFinalize(reader, configuration, parent, invoke);
1151                        } else if (ELEM_CONTENT.equals(name)) {
1152                            readContent(reader, configuration, parent, invoke);
1153                        } else {
1154                            reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
1155                        }
1156                    } else {
1157                        reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
1158                    }
1159                    break;
1160                case XMLStreamConstants.END_ELEMENT:
1161                    popNamespaces(reader, configuration);
1162                    nsURI = reader.getNamespaceURI();
1163                    name = reader.getLocalName();
1164                    if (XMLNS_SCXML.equals(nsURI) && ELEM_INVOKE.equals(name)) {
1165                        break loop;
1166                    }
1167                    break;
1168                default:
1169            }
1170        }
1171
1172        parent.addInvoke(invoke);
1173    }
1174
1175    /**
1176     * Read the contents of this &lt;param&gt; element.
1177     *
1178     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1179     * @param configuration The {@link Configuration} to use while parsing.
1180     * @param parent The parent {@link Invoke} for this param.
1181     *
1182     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1183     */
1184    private static void readParam(final XMLStreamReader reader, final Configuration configuration,
1185                                  final Invoke parent)
1186            throws XMLStreamException, ModelException {
1187
1188        Param param = new Param();
1189        param.setName(readRequiredAV(reader, ELEM_PARAM, ATTR_NAME));
1190        param.setExpr(readAV(reader, ATTR_EXPR));
1191        readNamespaces(configuration, param);
1192        parent.addParam(param);
1193    }
1194
1195    /**
1196     * Read the contents of this &lt;finalize&gt; element.
1197     *
1198     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1199     * @param configuration The {@link Configuration} to use while parsing.
1200     * @param state The {@link TransitionalState} which contains the parent {@link Invoke}.
1201     * @param invoke The parent {@link Invoke} for this finalize.
1202     *
1203     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1204     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1205     *                        errors in the SCXML document that may not be identified by the schema).
1206     */
1207    private static void readFinalize(final XMLStreamReader reader, final Configuration configuration,
1208                                     final TransitionalState state, final Invoke invoke)
1209            throws XMLStreamException, ModelException {
1210
1211        Finalize finalize = new Finalize();
1212        readExecutableContext(reader, configuration, finalize, null);
1213        invoke.setFinalize(finalize);
1214        finalize.setParent(state);
1215    }
1216
1217    /**
1218     * Read the contents of this &lt;content&gt; element.
1219     *
1220     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1221     * @param configuration The {@link Configuration} to use while parsing.
1222     * @param state The {@link TransitionalState} which contains the parent {@link Invoke}.
1223     * @param invoke The parent {@link Invoke} for this content.
1224     *
1225     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1226     */
1227    private static void readContent(final XMLStreamReader reader, final Configuration configuration,
1228                                    final TransitionalState state, final Invoke invoke)
1229            throws XMLStreamException {
1230
1231        // TODO content support
1232    }
1233
1234    /**
1235     * Read the contents of this &lt;initial&gt; element.
1236     *
1237     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1238     * @param configuration The {@link Configuration} to use while parsing.
1239     * @param state The parent composite {@link State} for this initial.
1240     *
1241     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1242     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1243     *                        errors in the SCXML document that may not be identified by the schema).
1244     */
1245    private static void readInitial(final XMLStreamReader reader, final Configuration configuration,
1246                                    final State state)
1247            throws XMLStreamException, ModelException {
1248
1249        Initial initial = new Initial();
1250
1251        loop : while (reader.hasNext()) {
1252            String name, nsURI;
1253            switch (reader.next()) {
1254                case XMLStreamConstants.START_ELEMENT:
1255                    pushNamespaces(reader, configuration);
1256                    nsURI = reader.getNamespaceURI();
1257                    name = reader.getLocalName();
1258                    if (XMLNS_SCXML.equals(nsURI)) {
1259                        if (ELEM_TRANSITION.equals(name)) {
1260                            initial.setTransition(readSimpleTransition(reader, configuration));
1261                        } else {
1262                            reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
1263                        }
1264                    } else {
1265                        reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
1266                    }
1267                    break;
1268                case XMLStreamConstants.END_ELEMENT:
1269                    popNamespaces(reader, configuration);
1270                    nsURI = reader.getNamespaceURI();
1271                    name = reader.getLocalName();
1272                    if (XMLNS_SCXML.equals(nsURI) && ELEM_INITIAL.equals(name)) {
1273                        break loop;
1274                    }
1275                    break;
1276                default:
1277            }
1278        }
1279
1280        state.setInitial(initial);
1281    }
1282
1283    /**
1284     * Read the contents of this &lt;history&gt; element.
1285     *
1286     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1287     * @param configuration The {@link Configuration} to use while parsing.
1288     * @param scxml The root of the object model being parsed.
1289     * @param ts The parent {@link org.apache.commons.scxml2.model.TransitionalState} for this history.
1290     *
1291     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1292     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1293     *                        errors in the SCXML document that may not be identified by the schema).
1294     */
1295    private static void readHistory(final XMLStreamReader reader, final Configuration configuration,
1296                                    final SCXML scxml, final TransitionalState ts)
1297            throws XMLStreamException, ModelException {
1298
1299        History history = new History();
1300        history.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_HISTORY));
1301        history.setType(readAV(reader, ATTR_TYPE));
1302
1303        ts.addHistory(history);
1304        scxml.addTarget(history);
1305
1306        loop : while (reader.hasNext()) {
1307            String name, nsURI;
1308            switch (reader.next()) {
1309                case XMLStreamConstants.START_ELEMENT:
1310                    pushNamespaces(reader, configuration);
1311                    nsURI = reader.getNamespaceURI();
1312                    name = reader.getLocalName();
1313                    if (XMLNS_SCXML.equals(nsURI)) {
1314                        if (ELEM_TRANSITION.equals(name)) {
1315                            history.setTransition(readTransition(reader, configuration));
1316                        } else {
1317                            reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
1318                        }
1319                    } else {
1320                        reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
1321                    }
1322                    break;
1323                case XMLStreamConstants.END_ELEMENT:
1324                    popNamespaces(reader, configuration);
1325                    nsURI = reader.getNamespaceURI();
1326                    name = reader.getLocalName();
1327                    if (XMLNS_SCXML.equals(nsURI) && ELEM_HISTORY.equals(name)) {
1328                        break loop;
1329                    }
1330                    break;
1331                default:
1332            }
1333        }
1334    }
1335
1336    /**
1337     * Read the contents of this &lt;onentry&gt; element.
1338     *
1339     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1340     * @param configuration The {@link Configuration} to use while parsing.
1341     * @param es The parent {@link EnterableState} for this onentry.
1342     *
1343     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1344     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1345     *                        errors in the SCXML document that may not be identified by the schema).
1346     */
1347    private static void readOnEntry(final XMLStreamReader reader, final Configuration configuration,
1348                                    final EnterableState es)
1349            throws XMLStreamException, ModelException {
1350
1351        OnEntry onentry = new OnEntry();
1352        onentry.setRaiseEvent(readBooleanAV(reader, ELEM_ONENTRY, ATTR_EVENT));
1353        readExecutableContext(reader, configuration, onentry, null);
1354        es.addOnEntry(onentry);
1355    }
1356
1357    /**
1358     * Read the contents of this &lt;onexit&gt; element.
1359     *
1360     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1361     * @param configuration The {@link Configuration} to use while parsing.
1362     * @param es The parent {@link EnterableState} for this onexit.
1363     *
1364     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1365     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1366     *                        errors in the SCXML document that may not be identified by the schema).
1367     */
1368    private static void readOnExit(final XMLStreamReader reader, final Configuration configuration,
1369                                   final EnterableState es)
1370            throws XMLStreamException, ModelException {
1371
1372        OnExit onexit = new OnExit();
1373        onexit.setRaiseEvent(readBooleanAV(reader, ELEM_ONEXIT, ATTR_EVENT));
1374        readExecutableContext(reader, configuration, onexit, null);
1375        es.addOnExit(onexit);
1376    }
1377
1378    /**
1379     * Read the contents of this simple &lt;transition&gt; element.
1380     *
1381     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1382     * @param configuration The {@link Configuration} to use while parsing.
1383     *
1384     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1385     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1386     *                        errors in the SCXML document that may not be identified by the schema).
1387     */
1388    private static SimpleTransition readSimpleTransition(final XMLStreamReader reader, final Configuration configuration)
1389            throws XMLStreamException, ModelException {
1390
1391        SimpleTransition transition = new SimpleTransition();
1392        transition.setNext(readAV(reader, ATTR_TARGET));
1393        String type = readAV(reader, ATTR_TYPE);
1394        if (type != null) {
1395            try {
1396                transition.setType(TransitionType.valueOf(type));
1397            }
1398            catch (IllegalArgumentException e) {
1399                MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
1400                String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()});
1401                throw new ModelException(errMsg);
1402            }
1403        }
1404
1405        readNamespaces(configuration, transition);
1406        readExecutableContext(reader, configuration, transition, null);
1407
1408        return transition;
1409    }
1410
1411    /**
1412     * Read the contents of this &lt;transition&gt; element.
1413     *
1414     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1415     * @param configuration The {@link Configuration} to use while parsing.
1416     *
1417     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1418     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1419     *                        errors in the SCXML document that may not be identified by the schema).
1420     */
1421    private static Transition readTransition(final XMLStreamReader reader, final Configuration configuration)
1422            throws XMLStreamException, ModelException {
1423
1424        Transition transition = new Transition();
1425        transition.setCond(readAV(reader, ATTR_COND));
1426        transition.setEvent(readAV(reader, ATTR_EVENT));
1427        transition.setNext(readAV(reader, ATTR_TARGET));
1428        String type = readAV(reader, ATTR_TYPE);
1429        if (type != null) {
1430            try {
1431                transition.setType(TransitionType.valueOf(type));
1432            }
1433            catch (IllegalArgumentException e) {
1434                MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
1435                String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()});
1436                throw new ModelException(errMsg);
1437            }
1438        }
1439
1440        readNamespaces(configuration, transition);
1441        readExecutableContext(reader, configuration, transition, null);
1442
1443        return transition;
1444    }
1445
1446    /**
1447     * Read this set of executable content elements.
1448     *
1449     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1450     * @param configuration The {@link Configuration} to use while parsing.
1451     * @param executable The parent {@link Executable} to which this content belongs.
1452     * @param parent The optional parent {@link ActionsContainer} if this is child content of an ActionsContainer action.
1453     *
1454     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1455     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1456     *                        errors in the SCXML document that may not be identified by the schema).
1457     */
1458    private static void readExecutableContext(final XMLStreamReader reader, final Configuration configuration,
1459                                              final Executable executable, final ActionsContainer parent)
1460            throws XMLStreamException, ModelException {
1461
1462        String end = "";
1463        if (parent != null) {
1464            end = parent.getContainerElementName();
1465        } else if (executable instanceof SimpleTransition) {
1466            end = ELEM_TRANSITION;
1467        } else if (executable instanceof OnEntry) {
1468            end = ELEM_ONENTRY;
1469        } else if (executable instanceof OnExit) {
1470            end = ELEM_ONEXIT;
1471        } else if (executable instanceof Finalize) {
1472            end = ELEM_FINALIZE;
1473        }
1474
1475        loop : while (reader.hasNext()) {
1476            String name, nsURI;
1477            switch (reader.next()) {
1478                case XMLStreamConstants.START_ELEMENT:
1479                    pushNamespaces(reader, configuration);
1480                    nsURI = reader.getNamespaceURI();
1481                    name = reader.getLocalName();
1482                    if (XMLNS_SCXML.equals(nsURI)) {
1483                        if (ELEM_RAISE.equals(name)) {
1484                            readRaise(reader, configuration, executable, parent);
1485                        } else if (ELEM_FOREACH.equals(name)) {
1486                            readForeach(reader, configuration, executable, parent);
1487                        } else if (ELEM_IF.equals(name)) {
1488                            readIf(reader, configuration, executable, parent);
1489                        } else if (ELEM_LOG.equals(name)) {
1490                            readLog(reader, configuration, executable, parent);
1491                        } else if (ELEM_ASSIGN.equals(name)) {
1492                            readAssign(reader, configuration, executable, parent);
1493                        } else if (ELEM_SEND.equals(name)) {
1494                            readSend(reader, configuration, executable, parent);
1495                        } else if (ELEM_CANCEL.equals(name)) {
1496                            readCancel(reader, configuration, executable, parent);
1497                        } else if (ELEM_SCRIPT.equals(name)) {
1498                            readScript(reader, configuration, executable, parent);
1499                        } else if (ELEM_IF.equals(end) && ELEM_ELSEIF.equals(name)) {
1500                            readElseIf(reader, configuration, executable, (If) parent);
1501                        } else if (ELEM_IF.equals(end) && ELEM_ELSE.equals(name)) {
1502                            readElse(reader, configuration, executable, (If)parent);
1503                        } else {
1504                            reportIgnoredElement(reader, configuration, end, nsURI, name);
1505                        }
1506                    } else if (XMLNS_COMMONS_SCXML.equals(nsURI)) {
1507                        if (ELEM_VAR.equals(name)) {
1508                            readVar(reader, configuration, executable, parent);
1509                        } else {
1510                            reportIgnoredElement(reader, configuration, end, nsURI, name);
1511                        }
1512                    } else { // custom action
1513                        CustomAction customAction = null;
1514                        if (!configuration.customActions.isEmpty()) {
1515                            for (CustomAction ca : configuration.customActions) {
1516                                if (ca.getNamespaceURI().equals(nsURI) && ca.getLocalName().equals(name)) {
1517                                    customAction = ca;
1518                                }
1519                            }
1520                        }
1521                        if (customAction != null) {
1522                            readCustomAction(reader, configuration, customAction, executable, parent);
1523                        } else {
1524                            reportIgnoredElement(reader, configuration, end, nsURI, name);
1525                        }
1526                    }
1527                    break;
1528                case XMLStreamConstants.END_ELEMENT:
1529                    popNamespaces(reader, configuration);
1530                    nsURI = reader.getNamespaceURI();
1531                    name = reader.getLocalName();
1532                    if (XMLNS_SCXML.equals(nsURI) && end.equals(name)) {
1533                        break loop;
1534                    }
1535                    break;
1536                default:
1537            }
1538        }
1539    }
1540
1541    /**
1542     * Read the contents of this &lt;raise&gt; element.
1543     *
1544     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1545     * @param configuration The {@link Configuration} to use while parsing.
1546     * @param executable The parent {@link Executable} for this action.
1547     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1548     *
1549     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1550     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1551     *                        errors in the SCXML document that may not be identified by the schema).
1552     */
1553    private static void readRaise(final XMLStreamReader reader, final Configuration configuration,
1554                                  final Executable executable, final ActionsContainer parent)
1555            throws XMLStreamException, ModelException {
1556
1557        if (executable instanceof Finalize) {
1558            // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize
1559            // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions.
1560            // In particular, the <send> and <raise> elements MUST NOT occur.
1561            reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_RAISE);
1562        }
1563
1564        Raise raise = new Raise();
1565        raise.setEvent(readAV(reader, ATTR_EVENT));
1566        readNamespaces(configuration, raise);
1567        raise.setParent(executable);
1568        if (parent != null) {
1569            parent.addAction(raise);
1570        } else {
1571            executable.addAction(raise);
1572        }
1573    }
1574
1575    /**
1576     * Read the contents of this &lt;if&gt; element.
1577     *
1578     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1579     * @param configuration The {@link Configuration} to use while parsing.
1580     * @param executable The parent {@link Executable} for this action.
1581     * @param parent The optional parent {@link ActionsContainer} if this &lt;if&gt; is a child of one.
1582     *
1583     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1584     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1585     *                        errors in the SCXML document that may not be identified by the schema).
1586     */
1587    private static void readIf(final XMLStreamReader reader, final Configuration configuration,
1588                               final Executable executable, final ActionsContainer parent)
1589            throws XMLStreamException, ModelException {
1590
1591        If iff = new If();
1592        iff.setCond(readRequiredAV(reader, ELEM_IF, ATTR_COND));
1593        readNamespaces(configuration, iff);
1594        iff.setParent(executable);
1595        if (parent != null) {
1596            parent.addAction(iff);
1597        } else {
1598            executable.addAction(iff);
1599        }
1600        readExecutableContext(reader, configuration, executable, iff);
1601    }
1602
1603    /**
1604     * Read the contents of this &lt;elseif&gt; element.
1605     *
1606     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1607     * @param configuration The {@link Configuration} to use while parsing.
1608     * @param executable The parent {@link Executable} for this action.
1609     * @param iff The parent {@link If} for this &lt;elseif&gt;.
1610     *
1611     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1612     */
1613    private static void readElseIf(final XMLStreamReader reader, final Configuration configuration,
1614                                   final Executable executable, final If iff)
1615            throws XMLStreamException, ModelException {
1616
1617        ElseIf elseif = new ElseIf();
1618        elseif.setCond(readRequiredAV(reader, ELEM_ELSEIF, ATTR_COND));
1619        readNamespaces(configuration, elseif);
1620        elseif.setParent(executable);
1621        iff.addAction(elseif);
1622    }
1623
1624    /**
1625     * Read the contents of this &lt;else&gt; element.
1626     *
1627     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1628     * @param configuration The {@link Configuration} to use while parsing.
1629     * @param executable The parent {@link Executable} for this action.
1630     * @param iff The parent {@link If} for this &lt;else&gt;.
1631     *
1632     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1633     */
1634    private static void readElse(final XMLStreamReader reader, final Configuration configuration,
1635                                 final Executable executable, final If iff)
1636            throws XMLStreamException {
1637
1638        Else els = new Else();
1639        readNamespaces(configuration, els);
1640        els.setParent(executable);
1641        iff.addAction(els);
1642    }
1643
1644    /**
1645     * Read the contents of this &lt;foreach&gt; element.
1646     *
1647     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1648     * @param configuration The {@link Configuration} to use while parsing.
1649     * @param executable The parent {@link Executable} for this action.
1650     * @param parent The optional parent {@link ActionsContainer} if this &lt;foreach&gt; is a child of one.
1651     *
1652     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1653     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1654     *                        errors in the SCXML document that may not be identified by the schema).
1655     */
1656    private static void readForeach(final XMLStreamReader reader, final Configuration configuration,
1657                                    final Executable executable, final ActionsContainer parent)
1658            throws XMLStreamException, ModelException {
1659
1660        Foreach fe = new Foreach();
1661        fe.setArray(readRequiredAV(reader, ELEM_FOREACH, ATTR_ARRAY));
1662        fe.setItem(readRequiredAV(reader, ELEM_FOREACH, ATTR_ITEM));
1663        fe.setIndex(readAV(reader, ATTR_INDEX));
1664        readNamespaces(configuration, fe);
1665        fe.setParent(executable);
1666        if (parent != null) {
1667            parent.addAction(fe);
1668        } else {
1669            executable.addAction(fe);
1670        }
1671        readExecutableContext(reader, configuration, executable, fe);
1672    }
1673
1674    /**
1675     * Read the contents of this &lt;log&gt; element.
1676     *
1677     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1678     * @param configuration The {@link Configuration} to use while parsing.
1679     * @param executable The parent {@link Executable} for this action.
1680     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1681     *
1682     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1683     */
1684    private static void readLog(final XMLStreamReader reader, final Configuration configuration,
1685                                final Executable executable, final ActionsContainer parent)
1686            throws XMLStreamException {
1687
1688        Log log = new Log();
1689        log.setExpr(readAV(reader, ATTR_EXPR));
1690        log.setLabel(readAV(reader, ATTR_LABEL));
1691        readNamespaces(configuration, log);
1692        log.setParent(executable);
1693        if (parent != null) {
1694            parent.addAction(log);
1695        } else {
1696            executable.addAction(log);
1697        }
1698    }
1699
1700    /**
1701     * Read the contents of this &lt;assign&gt; element.
1702     *
1703     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1704     * @param configuration The {@link Configuration} to use while parsing.
1705     * @param executable The parent {@link Executable} for this action.
1706     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1707     *
1708     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1709     */
1710    private static void readAssign(final XMLStreamReader reader, final Configuration configuration,
1711                                   final Executable executable, final ActionsContainer parent)
1712            throws XMLStreamException, ModelException {
1713
1714        Assign assign = new Assign();
1715        assign.setExpr(readAV(reader, ATTR_EXPR));
1716        assign.setName(readAV(reader, ATTR_NAME));
1717        if (assign.getName() != null && assign.getName().trim().length() > 0) {
1718            // if 'non-standard' name attribute is defined, don't require location (as per the spec. 20130831)
1719            assign.setLocation(readAV(reader, ATTR_LOCATION));
1720        }
1721        else {
1722            assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION));
1723        }
1724        assign.setSrc(readAV(reader, ATTR_SRC));
1725        assign.setPathResolver(configuration.pathResolver);
1726        readNamespaces(configuration, assign);
1727        assign.setParent(executable);
1728        if (parent != null) {
1729            parent.addAction(assign);
1730        } else {
1731            executable.addAction(assign);
1732        }
1733    }
1734
1735    /**
1736     * Read the contents of this &lt;send&gt; element.
1737     *
1738     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1739     * @param configuration The {@link Configuration} to use while parsing.
1740     * @param executable The parent {@link Executable} for this action.
1741     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1742     *
1743     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1744     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1745     *                        errors in the SCXML document that may not be identified by the schema).
1746     */
1747    private static void readSend(final XMLStreamReader reader, final Configuration configuration,
1748                                 final Executable executable, final ActionsContainer parent)
1749            throws XMLStreamException, ModelException {
1750
1751        if (executable instanceof Finalize) {
1752            // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize
1753            // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions.
1754            // In particular, the <send> and <raise> elements MUST NOT occur.
1755            reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_SEND);
1756        }
1757
1758        Send send = new Send();
1759        send.setDelay(readAV(reader, ATTR_DELAY));
1760        send.setEvent(readAV(reader, ATTR_EVENT));
1761        send.setHints(readAV(reader, ATTR_HINTS));
1762        send.setNamelist(readAV(reader, ATTR_NAMELIST));
1763        send.setSendid(readAV(reader, ATTR_SENDID));
1764        send.setTarget(readAV(reader, ATTR_TARGET));
1765        send.setType(readAV(reader, ATTR_TYPE));
1766        readNamespaces(configuration, send);
1767
1768        Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_SEND, new String [] {});
1769        NodeList childNodes = body.getChildNodes();
1770        List<Node> externalNodes = send.getExternalNodes();
1771        for (int i = 0; i < childNodes.getLength(); i++) {
1772            externalNodes.add(childNodes.item(i));
1773        }
1774
1775        send.setParent(executable);
1776        if (parent != null) {
1777            parent.addAction(send);
1778        } else {
1779            executable.addAction(send);
1780        }
1781    }
1782
1783    /**
1784     * Read the contents of this &lt;cancel&gt; element.
1785     *
1786     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1787     * @param configuration The {@link Configuration} to use while parsing.
1788     * @param executable The parent {@link Executable} for this action.
1789     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1790     *
1791     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1792     */
1793    private static void readCancel(final XMLStreamReader reader, final Configuration configuration,
1794                                   final Executable executable, final ActionsContainer parent)
1795            throws XMLStreamException {
1796
1797        Cancel cancel = new Cancel();
1798        readNamespaces(configuration, cancel);
1799        cancel.setParent(executable);
1800        if (parent != null) {
1801            parent.addAction(cancel);
1802        } else {
1803            executable.addAction(cancel);
1804        }
1805    }
1806
1807    /**
1808     * Read the contents of this &lt;script&gt; element.
1809     *
1810     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1811     * @param configuration The {@link Configuration} to use while parsing.
1812     * @param executable The parent {@link Executable} for this action.
1813     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1814     *
1815     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1816     */
1817    private static void readScript(final XMLStreamReader reader, final Configuration configuration,
1818                                   final Executable executable, final ActionsContainer parent)
1819            throws XMLStreamException {
1820
1821        Script script = new Script();
1822        readNamespaces(configuration, script);
1823        script.setBody(readBody(reader, configuration, XMLNS_SCXML, ELEM_SCRIPT));
1824        script.setParent(executable);
1825        if (parent != null) {
1826            parent.addAction(script);
1827        } else {
1828            executable.addAction(script);
1829        }
1830    }
1831
1832    /**
1833     * Read the contents of the initial &lt;script&gt; element.
1834     * @see <a href="http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml">
1835     *     http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml<a> section 3.2.2
1836     *
1837     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1838     * @param configuration The {@link Configuration} to use while parsing.
1839     * @param scxml The root of the object model being parsed.
1840     *
1841     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1842     */
1843    private static void readGlobalScript(final XMLStreamReader reader, final Configuration configuration,
1844                                         final SCXML scxml)
1845            throws XMLStreamException {
1846
1847        Script globalScript = new Script();
1848        globalScript.setGlobalScript(true);
1849        readNamespaces(configuration, globalScript);
1850        globalScript.setBody(readBody(reader, configuration, XMLNS_SCXML, ELEM_SCRIPT));
1851        scxml.setGlobalScript(globalScript);
1852    }
1853
1854    /**
1855     * Read the contents of this &lt;var&gt; element.
1856     *
1857     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1858     * @param configuration The {@link Configuration} to use while parsing.
1859     * @param executable The parent {@link Executable} for this action.
1860     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1861     *
1862     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1863     */
1864    private static void readVar(final XMLStreamReader reader, final Configuration configuration,
1865                                final Executable executable, final ActionsContainer parent)
1866            throws XMLStreamException {
1867
1868        Var var = new Var();
1869        var.setName(readAV(reader, ATTR_NAME));
1870        var.setExpr(readAV(reader, ATTR_EXPR));
1871        readNamespaces(configuration, var);
1872        var.setParent(executable);
1873        if (parent != null) {
1874            parent.addAction(var);
1875        } else {
1876            executable.addAction(var);
1877        }
1878    }
1879
1880    /**
1881     * Read the contents of this custom action.
1882     *
1883     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1884     * @param configuration The {@link Configuration} to use while parsing.
1885     * @param customAction The {@link CustomAction} to read.
1886     * @param executable The parent {@link Executable} for this custom action.
1887     * @param parent The optional parent {@link ActionsContainer} if this custom action is a child of one.
1888     *
1889     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1890     */
1891    private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration,
1892                                         final CustomAction customAction, final Executable executable,
1893                                         final ActionsContainer parent)
1894            throws XMLStreamException {
1895
1896        // Instantiate custom action
1897        Object actionObject = null;
1898        String className = customAction.getActionClass().getName();
1899        ClassLoader cl = configuration.customActionClassLoader;
1900        if (configuration.useContextClassLoaderForCustomActions) {
1901            cl = Thread.currentThread().getContextClassLoader();
1902        }
1903        if (cl == null) {
1904            cl = SCXMLReader.class.getClassLoader();
1905        }
1906        Class<?> clazz = null;
1907        try {
1908            clazz = cl.loadClass(className);
1909            actionObject = clazz.newInstance();
1910        } catch (ClassNotFoundException cnfe) {
1911            throw new XMLStreamException("Cannot find custom action class:" + className, cnfe);
1912        } catch (IllegalAccessException iae) {
1913            throw new XMLStreamException("Cannot access custom action class:" + className, iae);
1914        } catch (InstantiationException ie) {
1915            throw new XMLStreamException("Cannot instantiate custom action class:" + className, ie);
1916        }
1917        if (!(actionObject instanceof Action)) {
1918            throw new IllegalArgumentException(ERR_CUSTOM_ACTION_TYPE + className);
1919        }
1920
1921        // Set the attribute values as properties
1922        Action action = (Action) actionObject;
1923        for (int i = 0; i < reader.getAttributeCount(); i++) {
1924            String name = reader.getAttributeLocalName(i);
1925            String value = reader.getAttributeValue(i);
1926            String setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
1927            Method method = null;
1928            try {
1929                method = clazz.getMethod(setter, String.class);
1930                method.invoke(action, value);
1931            } catch (NoSuchMethodException nsme) {
1932                throw new XMLStreamException("No setter in class:" + className + ", for string property:" + name,
1933                        nsme);
1934            } catch (InvocationTargetException ite) {
1935                throw new XMLStreamException("Exception calling setter for string property:" + name + " in class:"
1936                        + className, ite);
1937            } catch (IllegalAccessException iae) {
1938                throw new XMLStreamException("Cannot access setter for string property:" + name + " in class:"
1939                        + className, iae);
1940            }
1941        }
1942
1943        // Add any body content if necessary
1944        if (action instanceof ExternalContent) {
1945            Node body = readNode(reader, configuration, customAction.getNamespaceURI(),
1946                    customAction.getLocalName(), new String [] {});
1947            NodeList childNodes = body.getChildNodes();
1948            List<Node> externalNodes = ((ExternalContent) action).getExternalNodes();
1949            for (int i = 0; i < childNodes.getLength(); i++) {
1950                externalNodes.add(childNodes.item(i));
1951            }
1952        }
1953
1954        // Wire in the action and add to parent
1955        readNamespaces(configuration, action);
1956        action.setParent(executable);
1957        if (parent != null) {
1958            parent.addAction(action);
1959        } else {
1960            executable.addAction(action);
1961        }
1962    }
1963
1964    /**
1965     * Read the following contents into a DOM {@link Node}.
1966     *
1967     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1968     * @param configuration The {@link Configuration} to use while parsing.
1969     * @param namespaceURI The namespace URI of the parent element (we will stop parsing into DOM nodes when we reach
1970     *                     the corresponding end tag)
1971     * @param localName The local name of the parent element (we will stop parsing into DOM nodes when we reach the
1972     *                  corresponding end tag)
1973     * @param attrs The attributes that will be read into the root DOM node.
1974     *
1975     * @return The parsed content as a DOM {@link Node}.
1976     *
1977     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1978     */
1979    private static Node readNode(final XMLStreamReader reader, final Configuration configuration,
1980                                 final String namespaceURI, final String localName, final String[] attrs)
1981            throws XMLStreamException {
1982
1983        // Create a document in which to build the DOM node
1984        Document document = null;
1985        try {
1986            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
1987        } catch (ParserConfigurationException pce) {
1988            throw new XMLStreamException(ERR_PARSER_CFG);
1989        }
1990
1991        // This root element will be returned, add any attributes as specified
1992        Element root = document.createElementNS(namespaceURI, localName);
1993        for (int i = 0; i < attrs.length; i++) {
1994            Attr attr = document.createAttributeNS(XMLNS_DEFAULT, attrs[i]);
1995            attr.setValue(readAV(reader, attrs[i]));
1996            root.setAttributeNodeNS(attr);
1997        }
1998        document.appendChild(root);
1999
2000        // <data> can have only one child element, flags to skip any text nodes on either side
2001        boolean one = (XMLNS_SCXML.equals(namespaceURI) && ELEM_DATA.equals(localName));
2002        boolean text = false;
2003        Node parent = root;
2004        String nodeNSURI = null, nodeName = null;
2005
2006        // Convert stream to DOM node(s) while maintaining parent child relationships
2007        loop : while (reader.hasNext()) {
2008            String name, nsURI;
2009            Node child = null;
2010            switch (reader.next()) {
2011                case XMLStreamConstants.START_ELEMENT:
2012                    pushNamespaces(reader, configuration);
2013                    nsURI = reader.getNamespaceURI();
2014                    name = reader.getLocalName();
2015                    if (!text) {
2016                        nodeNSURI = nsURI;
2017                        nodeName = name;
2018                        text = true;
2019                    }
2020                    Element elem = document.createElementNS(nsURI, name);
2021                    for (int i = 0; i < reader.getAttributeCount(); i++) {
2022                        nsURI = reader.getAttributeNamespace(i);
2023                        name = reader.getAttributeLocalName(i);
2024                        String prefix = reader.getAttributePrefix(i);
2025                        if (prefix != null && prefix.length() > 0) {
2026                            name = prefix + ":" + name;
2027                        }
2028                        Attr attr = document.createAttributeNS(nsURI, name);
2029                        attr.setValue(reader.getAttributeValue(i));
2030                        elem.setAttributeNodeNS(attr);
2031                    }
2032                    parent.appendChild(elem);
2033                    parent = elem;
2034                    break;
2035                case XMLStreamConstants.SPACE:
2036                case XMLStreamConstants.CHARACTERS:
2037                case XMLStreamConstants.ENTITY_REFERENCE:
2038                    if (text || !one) {
2039                        child = document.createTextNode(reader.getText());
2040                    }
2041                    break;
2042                case XMLStreamConstants.CDATA:
2043                    child = document.createCDATASection(reader.getText());
2044                    break;
2045                case XMLStreamConstants.COMMENT:
2046                    child = document.createComment(reader.getText());
2047                    break;
2048                case XMLStreamConstants.END_ELEMENT:
2049                    popNamespaces(reader, configuration);
2050                    nsURI = reader.getNamespaceURI();
2051                    name = reader.getLocalName();
2052                    if (namespaceURI.equals(nsURI) && localName.equals(name)) {
2053                        break loop;
2054                    }
2055                    if (((nodeNSURI == null && nsURI == null) || (nodeNSURI != null && nodeNSURI.equals(nsURI)))
2056                            && name.equals(nodeName)) {
2057                        text = false;
2058                    }
2059                    parent = parent.getParentNode();
2060                    if (parent == document) {
2061                        parent = root;
2062                    }
2063                    break;
2064                default: // rest is ignored
2065            }
2066            if (child != null) {
2067                parent.appendChild(child);
2068            }
2069        }
2070        return root;
2071    }
2072
2073    /**
2074     * Read the following body contents into a String.
2075     *
2076     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2077     * @param configuration The {@link Configuration} to use while parsing.
2078     * @param namespaceURI The namespace URI of the parent element (we will stop reading content when we reach
2079     *                     the corresponding end tag)
2080     * @param localName The local name of the parent element (we will stop reading content when we reach the
2081     *                  corresponding end tag)
2082     *
2083     * @return The body content read into a String.
2084     *
2085     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2086     */
2087    private static String readBody(final XMLStreamReader reader, final Configuration configuration,
2088                                   final String namespaceURI, final String localName)
2089            throws XMLStreamException {
2090
2091        StringBuffer body = new StringBuffer();
2092        org.apache.commons.logging.Log log;
2093
2094        // Add all body content to StringBuffer
2095        loop : while (reader.hasNext()) {
2096            String name, nsURI;
2097            switch (reader.next()) {
2098                case XMLStreamConstants.START_ELEMENT:
2099                    log = LogFactory.getLog(SCXMLReader.class);
2100                    log.warn("Ignoring XML content in <script> element, encountered start tag with local name: "
2101                            + reader.getLocalName());
2102                    break;
2103                case XMLStreamConstants.SPACE:
2104                case XMLStreamConstants.CHARACTERS:
2105                case XMLStreamConstants.ENTITY_REFERENCE:
2106                case XMLStreamConstants.CDATA:
2107                case XMLStreamConstants.COMMENT:
2108                    body.append(reader.getText());
2109                    break;
2110                case XMLStreamConstants.END_ELEMENT:
2111                    nsURI = reader.getNamespaceURI();
2112                    name = reader.getLocalName();
2113                    if (namespaceURI.equals(nsURI) && localName.equals(name)) {
2114                        popNamespaces(reader, configuration);
2115                        break loop;
2116                    }
2117                    log = LogFactory.getLog(SCXMLReader.class);
2118                    log.warn("Ignoring XML content in <script> element, encountered end tag with local name: "
2119                            + reader.getLocalName());
2120                    break;
2121                default: // rest is ignored
2122            }
2123        }
2124        return body.toString();
2125    }
2126
2127    /**
2128     * @param input input string to check if null or empty after trim
2129     * @return null if input is null or empty after trim()
2130     */
2131    private static String nullIfEmpty(String input) {
2132        return input == null || input.trim().length()==0 ? null : input.trim();
2133    }
2134
2135    /**
2136     * Get the attribute value at the current reader location.
2137     *
2138     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2139     * @param attrLocalName The attribute name whose value is needed.
2140     *
2141     * @return The value of the attribute.
2142     */
2143    private static String readAV(final XMLStreamReader reader, final String attrLocalName) {
2144        return nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
2145    }
2146
2147    /**
2148     * Get the Boolean attribute value at the current reader location.
2149     *
2150     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2151     * @param elementName The name of the element for which the attribute value is needed.
2152     * @param attrLocalName The attribute name whose value is needed.
2153     *
2154     * @return The Boolean value of the attribute.
2155     * @throws ModelException When the attribute value is not empty but neither "true" or "false".
2156     */
2157    private static Boolean readBooleanAV(final XMLStreamReader reader, final String elementName,
2158                                         final String attrLocalName)
2159            throws ModelException {
2160        String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
2161        Boolean result = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null;
2162        if (result == null && value != null) {
2163            MessageFormat msgFormat = new MessageFormat(ERR_ATTRIBUTE_NOT_BOOLEAN);
2164            String errMsg = msgFormat.format(new Object[] {value, attrLocalName, elementName, reader.getLocation()});
2165            throw new ModelException(errMsg);
2166        }
2167        return result;
2168    }
2169
2170    /**
2171     * Get a required attribute value at the current reader location,
2172     *
2173     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2174     * @param elementName The name of the element for which the attribute value is needed.
2175     * @param attrLocalName The attribute name whose value is needed.
2176     *
2177     * @return The value of the attribute.
2178     * @throws ModelException When the required attribute is missing or empty.
2179     */
2180    private static String readRequiredAV(final XMLStreamReader reader, final String elementName, final String attrLocalName)
2181            throws ModelException {
2182        String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
2183        if (value == null) {
2184            MessageFormat msgFormat = new MessageFormat(ERR_REQUIRED_ATTRIBUTE_MISSING);
2185            String errMsg = msgFormat.format(new Object[] {elementName, attrLocalName, reader.getLocation()});
2186            throw new ModelException(errMsg);
2187        }
2188        return value;
2189    }
2190
2191    private static String readOrGeneratedTransitionTargetId(final XMLStreamReader reader, final SCXML scxml,
2192                                                            final String elementName)
2193            throws ModelException {
2194        String id = readAV(reader, ATTR_ID);
2195        if (id == null) {
2196            id = scxml.generateTransitionTargetId();
2197        }
2198        else if (id.startsWith(SCXML.GENERATED_TT_ID_PREFIX)) {
2199            MessageFormat msgFormat = new MessageFormat(ERR_RESERVED_ID_PREFIX);
2200            String errMsg = msgFormat.format(new Object[] {elementName, id, reader.getLocation()});
2201            throw new ModelException(errMsg);
2202        }
2203        return id;
2204    }
2205
2206    /**
2207     * Read the current active namespace declarations into the namespace prefixes holder.
2208     *
2209     * @param configuration The {@link Configuration} to use while parsing.
2210     * @param holder The {@link NamespacePrefixesHolder} to populate.
2211     */
2212    private static void readNamespaces(final Configuration configuration, final NamespacePrefixesHolder holder) {
2213
2214        holder.setNamespaces(configuration.getCurrentNamespaces());
2215    }
2216
2217    /**
2218     * Report an ignored element via the {@link XMLReporter} if available and the class
2219     * {@link org.apache.commons.logging.Log}.
2220     *
2221     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2222     * @param configuration The {@link Configuration} to use while parsing.
2223     * @param parent The parent element local name in the SCXML namespace.
2224     * @param nsURI The namespace URI of the ignored element.
2225     * @param name The local name of the ignored element.
2226     *
2227     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2228     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
2229     *                        errors in the SCXML document that may not be identified by the schema).
2230     */
2231    private static void reportIgnoredElement(final XMLStreamReader reader, final Configuration configuration,
2232                                             final String parent, final String nsURI, final String name)
2233            throws XMLStreamException, ModelException {
2234
2235        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
2236        StringBuilder sb = new StringBuilder();
2237        sb.append("Ignoring unknown or invalid element <").append(name)
2238                .append("> in namespace \"").append(nsURI)
2239                .append("\" as child of <").append(parent)
2240                .append("> at ").append(reader.getLocation());
2241        if (!configuration.isSilent() && log.isWarnEnabled()) {
2242            log.warn(sb.toString());
2243        }
2244        if (configuration.isStrict()) {
2245            throw new ModelException(sb.toString());
2246        }
2247        XMLReporter reporter = configuration.reporter;
2248        if (reporter != null) {
2249            reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
2250        }
2251    }
2252
2253    /**
2254     * Push any new namespace declarations on the configuration namespaces map.
2255     *
2256     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2257     * @param configuration The {@link Configuration} to use while parsing.
2258     */
2259    private static void pushNamespaces(final XMLStreamReader reader, final Configuration configuration) {
2260
2261        for (int i = 0; i < reader.getNamespaceCount(); i++) {
2262            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
2263            if (stack == null) {
2264                stack = new Stack<String>();
2265                configuration.namespaces.put(reader.getNamespacePrefix(i), stack);
2266            }
2267            stack.push(reader.getNamespaceURI(i));
2268        }
2269    }
2270
2271    /**
2272     * Pop any expiring namespace declarations from the configuration namespaces map.
2273     *
2274     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2275     * @param configuration The {@link Configuration} to use while parsing.
2276     *
2277     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2278     */
2279    private static void popNamespaces(final XMLStreamReader reader, final Configuration configuration)
2280            throws XMLStreamException {
2281
2282        for (int i = 0; i < reader.getNamespaceCount(); i++) {
2283            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
2284            if (stack == null) {
2285                throw new XMLStreamException("Configuration namespaces stack null");
2286            }
2287            try {
2288                stack.pop();
2289                if (stack.empty()) {
2290                    configuration.namespaces.remove(reader.getNamespacePrefix(i));
2291                }
2292            } catch (EmptyStackException e) {
2293                throw new XMLStreamException("Configuration namespaces stack popped too many times");
2294            }
2295        }
2296    }
2297
2298    /**
2299     * Use the supplied {@link Configuration} to create an appropriate {@link XMLStreamReader} for this
2300     * {@link SCXMLReader}. Exactly one of the url, path, stream, reader or source parameters must be provided.
2301     *
2302     * @param configuration The {@link Configuration} to be used.
2303     * @param url The {@link URL} to the SCXML document to read.
2304     * @param path The optional real path to the SCXML document as a string.
2305     * @param stream The optional {@link InputStream} providing the SCXML document.
2306     * @param reader The optional {@link Reader} providing the SCXML document.
2307     * @param source The optional {@link Source} providing the SCXML document.
2308     *
2309     * @return The appropriately configured {@link XMLStreamReader}.
2310     *
2311     * @throws IOException Exception with the URL IO.
2312     * @throws XMLStreamException A problem with the XML stream creation or an wrapped {@link SAXException}
2313     *                            thrown in trying to validate the document against the XML Schema for SCXML.
2314     */
2315    private static XMLStreamReader getReader(final Configuration configuration, final URL url, final String path,
2316                                             final InputStream stream, final Reader reader, final Source source)
2317            throws IOException, XMLStreamException {
2318
2319        // Instantiate the XMLInputFactory
2320        XMLInputFactory factory = XMLInputFactory.newInstance();
2321        if (configuration.factoryId != null && configuration.factoryClassLoader != null) {
2322            factory = XMLInputFactory.newFactory(configuration.factoryId, configuration.factoryClassLoader);
2323        }
2324        factory.setEventAllocator(configuration.allocator);
2325        for (Map.Entry<String, Object> property : configuration.properties.entrySet()) {
2326            factory.setProperty(property.getKey(), property.getValue());
2327        }
2328        factory.setXMLReporter(configuration.reporter);
2329        factory.setXMLResolver(configuration.resolver);
2330
2331        // Consolidate InputStream options
2332        InputStream urlStream = null;
2333        if (url != null || path != null) {
2334            URL scxml = (url != null ? url : new URL(path));
2335            URLConnection conn = scxml.openConnection();
2336            conn.setUseCaches(false);
2337            urlStream = conn.getInputStream();
2338        } else if (stream != null) {
2339            urlStream = stream;
2340        }
2341
2342        // Create the XMLStreamReader
2343        XMLStreamReader xsr = null;
2344
2345        if (configuration.validate) {
2346            // Validation requires us to use a Source
2347
2348            URL scxmlSchema = new URL("TODO"); // TODO, point to appropriate location
2349            SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
2350            Schema schema = null;
2351            try {
2352                schema = schemaFactory.newSchema(scxmlSchema);
2353            } catch (SAXException se) {
2354                throw new XMLStreamException("Failed to create SCXML Schema for validation", se);
2355            }
2356
2357            Validator validator = schema.newValidator();
2358            validator.setErrorHandler(new SimpleErrorHandler());
2359
2360            Source src = null;
2361            if (urlStream != null) {
2362                // configuration.encoding is ignored
2363                if (configuration.systemId != null) {
2364                    src = new StreamSource(urlStream, configuration.systemId);
2365                } else {
2366                    src = new StreamSource(urlStream);
2367                }
2368            } else if (reader != null) {
2369                if (configuration.systemId != null) {
2370                    src = new StreamSource(reader, configuration.systemId);
2371                } else {
2372                    src = new StreamSource(reader);
2373                }
2374            } else if (source != null) {
2375                src = source;
2376            }
2377            xsr = factory.createXMLStreamReader(src);
2378            try {
2379                validator.validate(src);
2380            } catch (SAXException se) {
2381                throw new XMLStreamException("Failed to create apply SCXML Validator", se);
2382            }
2383
2384        } else {
2385            // We can use the more direct XMLInputFactory API if validation isn't needed
2386
2387            if (urlStream != null) {
2388                // systemId gets preference, then encoding if either are present
2389                if (configuration.systemId != null) {
2390                    xsr = factory.createXMLStreamReader(configuration.systemId, urlStream);
2391                } else if (configuration.encoding != null) {
2392                    xsr = factory.createXMLStreamReader(urlStream, configuration.encoding);
2393                } else {
2394                    xsr = factory.createXMLStreamReader(urlStream);
2395                }
2396            } else if (reader != null) {
2397                if (configuration.systemId != null) {
2398                    xsr = factory.createXMLStreamReader(configuration.systemId, reader);
2399                } else {
2400                    xsr = factory.createXMLStreamReader(reader);
2401                }
2402            } else if (source != null) {
2403                xsr = factory.createXMLStreamReader(source);
2404            }
2405
2406        }
2407
2408        return xsr;
2409    }
2410
2411    /**
2412     * Discourage instantiation since this is a utility class.
2413     */
2414    private SCXMLReader() {
2415        super();
2416    }
2417
2418    //------------------------- CONFIGURATION CLASS -------------------------//
2419    /**
2420     * <p>
2421     * Configuration for the {@link SCXMLReader}. The configuration properties necessary for the following are
2422     * covered:
2423     * </p>
2424     *
2425     * <ul>
2426     *   <li>{@link XMLInputFactory} configuration properties such as {@link XMLReporter}, {@link XMLResolver} and
2427     *   {@link XMLEventAllocator}</li>
2428     *   <li>{@link XMLStreamReader} configuration properties such as <code>systemId</code> and <code>encoding</code>
2429     *   </li>
2430     *   <li>Commons SCXML object model configuration properties such as the list of custom actions and the
2431     *   {@link PathResolver} to use.</li>
2432     * </ul>
2433     */
2434    public static class Configuration {
2435
2436        /*
2437         * Configuration properties for this {@link SCXMLReader}.
2438         */
2439        // XMLInputFactory configuration properties.
2440        /**
2441         * The <code>factoryId</code> to use for the {@link XMLInputFactory}.
2442         */
2443        final String factoryId;
2444
2445        /**
2446         * The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create.
2447         */
2448        final ClassLoader factoryClassLoader;
2449
2450        /**
2451         * The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2452         */
2453        final XMLEventAllocator allocator;
2454
2455        /**
2456         * The map of properties (keys are property name strings, values are object property values) for the
2457         * {@link XMLInputFactory}.
2458         */
2459        final Map<String, Object> properties;
2460
2461        /**
2462         * The {@link XMLResolver} for the {@link XMLInputFactory}.
2463         */
2464        final XMLResolver resolver;
2465
2466        /**
2467         * The {@link XMLReporter} for the {@link XMLInputFactory}.
2468         */
2469        final XMLReporter reporter;
2470
2471        // XMLStreamReader configuration properties.
2472        /**
2473         * The <code>encoding</code> to use for the {@link XMLStreamReader}.
2474         */
2475        final String encoding;
2476
2477        /**
2478         * The <code>systemId</code> to use for the {@link XMLStreamReader}.
2479         */
2480        final String systemId;
2481
2482        /**
2483         * Whether to validate the input with the XML Schema for SCXML.
2484         */
2485        final boolean validate;
2486
2487        // Commons SCXML object model configuration properties.
2488        /**
2489         * The list of Commons SCXML custom actions that will be available for this document.
2490         */
2491        final List<CustomAction> customActions;
2492
2493        /**
2494         * The {@link ClassLoader} to use for loading the {@link CustomAction} instances to create.
2495         */
2496        final ClassLoader customActionClassLoader;
2497
2498        /**
2499         * Whether to use the thread context {@link ClassLoader} for loading any {@link CustomAction} classes.
2500         */
2501        final boolean useContextClassLoaderForCustomActions;
2502
2503        /**
2504         * The map for bookkeeping the current active namespace declarations. The keys are prefixes and the values are
2505         * {@link Stack}s containing the corresponding namespaceURIs, with the active one on top.
2506         */
2507        final Map<String, Stack<String>> namespaces;
2508
2509        // Mutable Commons SCXML object model configuration properties.
2510        /**
2511         * The parent SCXML document if this document is src'ed in via the &lt;state&gt; or &lt;parallel&gt; element's
2512         * "src" attribute.
2513         */
2514        SCXML parent;
2515
2516        /**
2517         * The Commons SCXML {@link PathResolver} to use for this document.
2518         */
2519        PathResolver pathResolver;
2520
2521        /**
2522         * Whether to silently ignore any unknown or invalid elements
2523         * or to leave warning logs for those.
2524         */
2525        boolean silent;
2526
2527        /**
2528         * Whether to strictly throw a model exception when there are any unknown or invalid elements
2529         * or to leniently allow to read the model even with those.
2530         */
2531        boolean strict;
2532
2533        /*
2534         * Public constructors
2535         */
2536        /**
2537         * Default constructor.
2538         */
2539        public Configuration() {
2540            this(null, null);
2541        }
2542
2543        /**
2544         * Minimal convenience constructor.
2545         *
2546         * @param reporter The {@link XMLReporter} to use for this reading.
2547         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading.
2548         */
2549        public Configuration(final XMLReporter reporter, final PathResolver pathResolver) {
2550            this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, null, null, false);
2551        }
2552
2553        /**
2554         * Convenience constructor.
2555         *
2556         * @param reporter The {@link XMLReporter} to use for this reading.
2557         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading.
2558         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2559         */
2560        public Configuration(final XMLReporter reporter, final PathResolver pathResolver,
2561                             final List<CustomAction> customActions) {
2562            this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, customActions, null,
2563                    false);
2564        }
2565
2566        /**
2567         * All purpose constructor. Any of the parameters passed in can be <code>null</code> (booleans should default
2568         * to <code>false</code>).
2569         *
2570         * @param factoryId The <code>factoryId</code> to use.
2571         * @param classLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create.
2572         * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2573         * @param properties The map of properties (keys are property name strings, values are object property values)
2574         *                   for the {@link XMLInputFactory}.
2575         * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
2576         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2577         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
2578         * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
2579         * @param validate Whether to validate the input with the XML Schema for SCXML.
2580         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2581         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2582         * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
2583         *                                create.
2584         * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
2585         *                                             {@link CustomAction} instances to create.
2586         */
2587        public Configuration(final String factoryId, final ClassLoader classLoader, final XMLEventAllocator allocator,
2588                             final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
2589                             final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
2590                             final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
2591                             final boolean useContextClassLoaderForCustomActions) {
2592            this(factoryId, classLoader, allocator, properties, resolver, reporter, encoding, systemId, validate,
2593                    pathResolver, null, customActions, customActionClassLoader,
2594                    useContextClassLoaderForCustomActions);
2595        }
2596
2597        /*
2598         * Package access constructors
2599         */
2600        /**
2601         * Convenience package access constructor.
2602         *
2603         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2604         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2605         * @param parent The parent SCXML document if this document is src'ed in via the &lt;state&gt; or
2606         *               &lt;parallel&gt; element's "src" attribute.
2607         */
2608        Configuration(final XMLReporter reporter, final PathResolver pathResolver, final SCXML parent) {
2609            this(null, null, null, null, null, reporter, null, null, false, pathResolver, parent, null, null, false);
2610        }
2611
2612        /**
2613         * Package access copy constructor.
2614         *
2615         * @param source The source {@link Configuration} to replicate.
2616         */
2617        Configuration(final Configuration source) {
2618            this(source.factoryId, source.factoryClassLoader, source.allocator, source.properties, source.resolver,
2619                    source.reporter, source.encoding, source.systemId, source.validate, source.pathResolver,
2620                    source.parent, source.customActions, source.customActionClassLoader,
2621                    source.useContextClassLoaderForCustomActions, source.silent, source.strict);
2622        }
2623
2624        /**
2625         * All-purpose package access constructor.
2626         *
2627         * @param factoryId The <code>factoryId</code> to use.
2628         * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to
2629         *                           create.
2630         * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2631         * @param properties The map of properties (keys are property name strings, values are object property values)
2632         *                   for the {@link XMLInputFactory}.
2633         * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
2634         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2635         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
2636         * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
2637         * @param validate Whether to validate the input with the XML Schema for SCXML.
2638         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2639         * @param parent The parent SCXML document if this document is src'ed in via the &lt;state&gt; or
2640         *               &lt;parallel&gt; element's "src" attribute.
2641         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2642         * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
2643         *                                create.
2644         * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
2645         *                                             {@link CustomAction} instances to create.
2646         */
2647        Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator,
2648                      final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
2649                      final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
2650                      final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
2651                      final boolean useContextClassLoaderForCustomActions) {
2652            this(factoryId, factoryClassLoader, allocator, properties, resolver, reporter, encoding, systemId,
2653                    validate, pathResolver, parent, customActions, customActionClassLoader,
2654                    useContextClassLoaderForCustomActions, false, false);
2655        }
2656
2657        /**
2658         * All-purpose package access constructor.
2659         *
2660         * @param factoryId The <code>factoryId</code> to use.
2661         * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to
2662         *                           create.
2663         * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2664         * @param properties The map of properties (keys are property name strings, values are object property values)
2665         *                   for the {@link XMLInputFactory}.
2666         * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
2667         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2668         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
2669         * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
2670         * @param validate Whether to validate the input with the XML Schema for SCXML.
2671         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2672         * @param parent The parent SCXML document if this document is src'ed in via the &lt;state&gt; or
2673         *               &lt;parallel&gt; element's "src" attribute.
2674         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2675         * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
2676         *                                create.
2677         * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
2678         *                                             {@link CustomAction} instances to create.
2679         * @param silent Whether to silently ignore any unknown or invalid elements or to leave warning logs for those.
2680         * @param strict Whether to strictly throw a model exception when there are any unknown or invalid elements
2681         *               or to leniently allow to read the model even with those.
2682         */
2683        Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator,
2684                      final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
2685                      final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
2686                      final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
2687                      final boolean useContextClassLoaderForCustomActions, final boolean silent, final boolean strict) {
2688            this.factoryId = factoryId;
2689            this.factoryClassLoader = factoryClassLoader;
2690            this.allocator = allocator;
2691            this.properties = (properties == null ? new HashMap<String, Object>() : properties);
2692            this.resolver = resolver;
2693            this.reporter = reporter;
2694            this.encoding = encoding;
2695            this.systemId = systemId;
2696            this.validate = validate;
2697            this.pathResolver = pathResolver;
2698            this.parent = parent;
2699            this.customActions = (customActions == null ? new ArrayList<CustomAction>() : customActions);
2700            this.customActionClassLoader = customActionClassLoader;
2701            this.useContextClassLoaderForCustomActions = useContextClassLoaderForCustomActions;
2702            this.namespaces = new HashMap<String, Stack<String>>();
2703            this.silent = silent;
2704            this.strict = strict;
2705        }
2706
2707        /*
2708         * Package access convenience methods
2709         */
2710        /**
2711         * Get the current namespaces at this point in the StAX reading.
2712         *
2713         * @return Map<String,String> The namespace map (keys are prefixes and values are the corresponding current
2714         *                            namespace URIs).
2715         */
2716        Map<String, String> getCurrentNamespaces() {
2717            Map<String, String> currentNamespaces = new HashMap<String, String>();
2718            for (Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet()) {
2719                currentNamespaces.put(nsEntry.getKey(), nsEntry.getValue().peek());
2720            }
2721            return currentNamespaces;
2722        }
2723
2724        /**
2725         * Returns true if it is set to read models silently without any model error warning logs.
2726         * @return
2727         * @see {@link #silent}
2728         */
2729        public boolean isSilent() {
2730            return silent;
2731        }
2732
2733        /**
2734         * Turn on/off silent mode (whether to read models silently without any model error warning logs)
2735         * @param silent
2736         * @see {@link #silent}
2737         */
2738        public void setSilent(boolean silent) {
2739            this.silent = silent;
2740        }
2741
2742        /**
2743         * Returns true if it is set to check model strictly with throwing exceptions on any model error.
2744         * @return
2745         * @see {@link #strict}
2746         */
2747        public boolean isStrict() {
2748            return strict;
2749        }
2750
2751        /**
2752         * Turn on/off strict model (whether to check model strictly with throwing exception on any model error)
2753         * @param strict
2754         * @see {@link #strict}
2755         */
2756        public void setStrict(boolean strict) {
2757            this.strict = strict;
2758        }
2759    }
2760}