    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     */
    017    package org.apache.commons.scxml.io;
    018
    019    import java.io.IOException;
    020    import java.net.URL;
    021    import java.text.MessageFormat;
    022    import java.util.Iterator;
    023    import java.util.List;
    024    import java.util.Map;
    025
    026    import javax.xml.parsers.DocumentBuilder;
    027    import javax.xml.parsers.DocumentBuilderFactory;
    028    import javax.xml.parsers.ParserConfigurationException;
    029
    030    import org.apache.commons.digester.Digester;
    031    import org.apache.commons.digester.ExtendedBaseRules;
    032    import org.apache.commons.digester.NodeCreateRule;
    033    import org.apache.commons.digester.ObjectCreateRule;
    034    import org.apache.commons.digester.Rule;
    035    import org.apache.commons.digester.SetNextRule;
    036    import org.apache.commons.digester.SetPropertiesRule;
    037    import org.apache.commons.digester.WithDefaultsRulesWrapper;
    038    import org.apache.commons.logging.LogFactory;
    039    import org.apache.commons.scxml.PathResolver;
    040    import org.apache.commons.scxml.SCXMLHelper;
    041    import org.apache.commons.scxml.env.URLResolver;
    042    import org.apache.commons.scxml.model.Action;
    043    import org.apache.commons.scxml.model.Assign;
    044    import org.apache.commons.scxml.model.Cancel;
    045    import org.apache.commons.scxml.model.CustomAction;
    046    import org.apache.commons.scxml.model.Data;
    047    import org.apache.commons.scxml.model.Datamodel;
    048    import org.apache.commons.scxml.model.Else;
    049    import org.apache.commons.scxml.model.ElseIf;
    050    import org.apache.commons.scxml.model.Event;
    051    import org.apache.commons.scxml.model.Executable;
    052    import org.apache.commons.scxml.model.Exit;
    053    import org.apache.commons.scxml.model.ExternalContent;
    054    import org.apache.commons.scxml.model.Final;
    055    import org.apache.commons.scxml.model.Finalize;
    056    import org.apache.commons.scxml.model.History;
    057    import org.apache.commons.scxml.model.If;
    058    import org.apache.commons.scxml.model.Initial;
    059    import org.apache.commons.scxml.model.Invoke;
    060    import org.apache.commons.scxml.model.Log;
    061    import org.apache.commons.scxml.model.ModelException;
    062    import org.apache.commons.scxml.model.NamespacePrefixesHolder;
    063    import org.apache.commons.scxml.model.OnEntry;
    064    import org.apache.commons.scxml.model.OnExit;
    065    import org.apache.commons.scxml.model.Parallel;
    066    import org.apache.commons.scxml.model.Param;
    067    import org.apache.commons.scxml.model.PathResolverHolder;
    068    import org.apache.commons.scxml.model.SCXML;
    069    import org.apache.commons.scxml.model.Send;
    070    import org.apache.commons.scxml.model.State;
    071    import org.apache.commons.scxml.model.Transition;
    072    import org.apache.commons.scxml.model.TransitionTarget;
    073    import org.apache.commons.scxml.model.Var;
    074    import org.w3c.dom.Element;
    075    import org.w3c.dom.Node;
    076    import org.w3c.dom.NodeList;
    077    import org.xml.sax.Attributes;
    078    import org.xml.sax.ErrorHandler;
    079    import org.xml.sax.InputSource;
    080    import org.xml.sax.Locator;
    081    import org.xml.sax.SAXException;
    082
    083    /**
    084     * <p>The SCXMLParser provides the ability to parse a SCXML document into
    085     * the Java object model provided in the model package.</p>
    086     * <p>The SCXMLParser can be used for:</p>
    087     * <ol>
    088     *  <li>Parse a SCXML file into the Commons SCXML Java object model.</li>
    089     *  <li>Obtain a SCXML Parser for further customization of the default
    090     *      ruleset.</li>
    091     * </ol>
    092     *
    093     * <p>If switching from {@link SCXMLDigester}, changes need to be made to
    094     * the SCXML documents, such as:</p>
    095     * <ul>
    096     *  <li>A <parallel> should not be wrapped in a <state> element
    097     *      unless otherwise necessary</li>
    098     *  <li><var> and <exit> elements continue to be supported by
    099     *      Commons SCXML, but in the Commons SCXML namespace:
    100     *      <code>http://commons.apache.org/scxml</code></li>
    101     *  <li><event> is now supported</li>
    102     * </ul>
    103     * <p>See latest version of the SCXML Working Draft for more details.</p>
    104     *
    105     * <p><b>NOTE:</b> The SCXMLParser assumes that the SCXML document to be
    106     * parsed is well-formed and correct. If that assumption does not hold,
    107     * any subsequent behavior is undefined.</p>
    108     *
    109     * @since 0.7
    110     */
    111    public final class SCXMLParser {
    112
    113        /**
    114         * The SCXML namespace that this Digester is built for. Any document
    115         * that is intended to be parsed by this digester <b>must</b>
    116         * bind the SCXML elements to this namespace.
    117         */
    118        private static final String NAMESPACE_SCXML =
    119            "http://www.w3.org/2005/07/scxml";
    120
    121        /**
    122         * The namespace that defines any custom actions defined by the Commons
    123         * SCXML implementation. Any document that intends to use these custom
    124         * actions needs to ensure that they are in the correct namespace. Use
    125         * of actions in this namespace makes the document non-portable across
    126         * implementations.
    127         */
    128        private static final String NAMESPACE_COMMONS_SCXML =
    129            "http://commons.apache.org/scxml";
    130
    131        //---------------------- PUBLIC METHODS ----------------------//
    132        /**
    133         * <p>API for standalone usage where the SCXML document is a URL.</p>
    134         *
    135         * @param scxmlURL
    136         *            a canonical absolute URL to parse (relative URLs within the
    137         *            top level document are to be resovled against this URL).
    138         * @param errHandler
    139         *            The SAX ErrorHandler
    140         *
    141         * @return SCXML The SCXML object corresponding to the file argument
    142         *
    143         * @throws IOException Underlying Digester parsing threw an IOException
    144         * @throws SAXException Underlying Digester parsing threw a SAXException
    145         * @throws ModelException If the resulting document model has flaws
    146         *
    147         * @see ErrorHandler
    148         * @see PathResolver
    149         */
    150        public static SCXML parse(final URL scxmlURL,
    151                final ErrorHandler errHandler)
    152        throws IOException, SAXException, ModelException {
    153
    154            if (scxmlURL == null) {
    155                throw new IllegalArgumentException(ERR_NULL_URL);
    156            }
    157
    158            return parse(scxmlURL, errHandler, null);
    159
    160        }
    161
    162        /**
    163         * <p>API for standalone usage where the SCXML document is a URI.
    164         * A PathResolver must be provided.</p>
    165         *
    166         * @param pathResolver
    167         *            The PathResolver for this context
    168         * @param documentRealPath
    169         *            The String pointing to the absolute (real) path of the
    170         *            SCXML document
    171         * @param errHandler
    172         *            The SAX ErrorHandler
    173         *
    174         * @return SCXML The SCXML object corresponding to the file argument
    175         *
    176         * @throws IOException Underlying Digester parsing threw an IOException
    177         * @throws SAXException Underlying Digester parsing threw a SAXException
    178         * @throws ModelException If the resulting document model has flaws
    179         *
    180         * @see ErrorHandler
    181         * @see PathResolver
    182         */
    183        public static SCXML parse(final String documentRealPath,
    184                final ErrorHandler errHandler, final PathResolver pathResolver)
    185        throws IOException, SAXException, ModelException {
    186
    187            return parse(documentRealPath, errHandler, pathResolver, null);
    188
    189        }
    190
    191        /**
    192         * <p>API for standalone usage where the SCXML document is an
    193         * InputSource. This method may be used when the SCXML document is
    194         * packaged in a Java archive, or part of a compound document
    195         * where the SCXML root is available as a
    196         * <code>org.w3c.dom.Element</code> or via a <code>java.io.Reader</code>.
    197         * </p>
    198         *
    199         * <p><em>Note:</em> Since there is no path resolution, the SCXML document
    200         * must not have external state sources.</p>
    201         *
    202         * @param documentInputSource
    203         *            The InputSource for the SCXML document
    204         * @param errHandler
    205         *            The SAX ErrorHandler
    206         *
    207         * @return SCXML The SCXML object corresponding to the file argument
    208         *
    209         * @throws IOException Underlying Digester parsing threw an IOException
    210         * @throws SAXException Underlying Digester parsing threw a SAXException
    211         * @throws ModelException If the resulting document model has flaws
    212         *
    213         * @see ErrorHandler
    214         */
    215        public static SCXML parse(final InputSource documentInputSource,
    216                final ErrorHandler errHandler)
    217        throws IOException, SAXException, ModelException {
    218
    219            if (documentInputSource == null) {
    220                throw new IllegalArgumentException(ERR_NULL_ISRC);
    221            }
    222
    223            return parse(documentInputSource, errHandler, null);
    224
    225        }
    226
    227        /**
    228         * <p>API for standalone usage where the SCXML document is a URL, and
    229         * the document uses custom actions.</p>
    230         *
    231         * @param scxmlURL
    232         *            a canonical absolute URL to parse (relative URLs within the
    233         *            top level document are to be resovled against this URL).
    234         * @param errHandler
    235         *            The SAX ErrorHandler
    236         * @param customActions
    237         *            The list of {@link CustomAction}s this digester
    238         *            instance will process, can be null or empty
    239         *
    240         * @return SCXML The SCXML object corresponding to the file argument
    241         *
    242         * @throws IOException Underlying Digester parsing threw an IOException
    243         * @throws SAXException Underlying Digester parsing threw a SAXException
    244         * @throws ModelException If the resulting document model has flaws
    245         *
    246         * @see ErrorHandler
    247         * @see PathResolver
    248         */
    249        public static SCXML parse(final URL scxmlURL,
    250                final ErrorHandler errHandler, final List customActions)
    251        throws IOException, SAXException, ModelException {
    252
    253            SCXML scxml = null;
    254            Digester scxmlParser = SCXMLParser
    255                    .newInstance(null, new URLResolver(scxmlURL), customActions);
    256            scxmlParser.setErrorHandler(errHandler);
    257
    258            try {
    259                scxml = (SCXML) scxmlParser.parse(scxmlURL.toString());
    260            } catch (RuntimeException rte) {
    261                // Intercept runtime exceptions, only to log them with a
    262                // sensible error message about failure in document parsing
    263                MessageFormat msgFormat = new MessageFormat(ERR_DOC_PARSE_FAIL);
    264                String errMsg = msgFormat.format(new Object[] {
    265                    String.valueOf(scxmlURL), rte.getMessage()
    266                });
    267                org.apache.commons.logging.Log log = LogFactory.
    268                    getLog(SCXMLParser.class);
    269                log.error(errMsg, rte);
    270                throw rte;
    271            }
    272
    273            if (scxml != null) {
    274                ModelUpdater.updateSCXML(scxml);
    275            }
    276
    277            return scxml;
    278
    279        }
    280
    281        /**
    282         * <p>API for standalone usage where the SCXML document is a URI.
    283         * A PathResolver must be provided.</p>
    284         *
    285         * @param pathResolver
    286         *            The PathResolver for this context
    287         * @param documentRealPath
    288         *            The String pointing to the absolute (real) path of the
    289         *            SCXML document
    290         * @param errHandler
    291         *            The SAX ErrorHandler
    292         * @param customActions
    293         *            The list of {@link CustomAction}s this digester
    294         *            instance will process, can be null or empty
    295         *
    296         * @return SCXML The SCXML object corresponding to the file argument
    297         *
    298         * @throws IOException Underlying Digester parsing threw an IOException
    299         * @throws SAXException Underlying Digester parsing threw a SAXException
    300         * @throws ModelException If the resulting document model has flaws
    301         *
    302         * @see ErrorHandler
    303         * @see PathResolver
    304         */
    305        public static SCXML parse(final String documentRealPath,
    306                final ErrorHandler errHandler, final PathResolver pathResolver,
    307                final List customActions)
    308        throws IOException, SAXException, ModelException {
    309
    310            if (documentRealPath == null) {
    311                throw new IllegalArgumentException(ERR_NULL_PATH);
    312            }
    313
    314            SCXML scxml = null;
    315            Digester scxmlParser = SCXMLParser.newInstance(null, pathResolver,
    316                customActions);
    317            scxmlParser.setErrorHandler(errHandler);
    318
    319            try {
    320                scxml = (SCXML) scxmlParser.parse(documentRealPath);
    321            } catch (RuntimeException rte) {
    322                // Intercept runtime exceptions, only to log them with a
    323                // sensible error message about failure in document parsing
    324                MessageFormat msgFormat = new MessageFormat(ERR_DOC_PARSE_FAIL);
    325                String errMsg = msgFormat.format(new Object[] {
    326                    documentRealPath, rte.getMessage()
    327                });
    328                org.apache.commons.logging.Log log = LogFactory.
    329                    getLog(SCXMLParser.class);
    330                log.error(errMsg, rte);
    331                throw rte;
    332            }
    333
    334            if (scxml != null) {
    335                ModelUpdater.updateSCXML(scxml);
    336            }
    337
    338            return scxml;
    339
    340        }
    341
    342        /**
    343         * <p>API for standalone usage where the SCXML document is an
    344         * InputSource. This method may be used when the SCXML document is
    345         * packaged in a Java archive, or part of a compound document
    346         * where the SCXML root is available as a
    347         * <code>org.w3c.dom.Element</code> or via a <code>java.io.Reader</code>.
    348         * </p>
    349         *
    350         * <p><em>Note:</em> Since there is no path resolution, the SCXML document
    351         * must not have external state sources.</p>
    352         *
    353         * @param documentInputSource
    354         *            The InputSource for the SCXML document
    355         * @param errHandler
    356         *            The SAX ErrorHandler
    357         * @param customActions
    358         *            The list of {@link CustomAction}s this digester
    359         *            instance will process, can be null or empty
    360         *
    361         * @return SCXML The SCXML object corresponding to the file argument
    362         *
    363         * @throws IOException Underlying Digester parsing threw an IOException
    364         * @throws SAXException Underlying Digester parsing threw a SAXException
    365         * @throws ModelException If the resulting document model has flaws
    366         *
    367         * @see ErrorHandler
    368         */
    369        public static SCXML parse(final InputSource documentInputSource,
    370                final ErrorHandler errHandler, final List customActions)
    371        throws IOException, SAXException, ModelException {
    372
    373            Digester scxmlParser = SCXMLParser.newInstance(null, null,
    374                customActions);
    375            scxmlParser.setErrorHandler(errHandler);
    376
    377            SCXML scxml = null;
    378            try {
    379                scxml = (SCXML) scxmlParser.parse(documentInputSource);
    380            }  catch (RuntimeException rte) {
    381                // Intercept runtime exceptions, only to log them with a
    382                // sensible error message about failure in document parsing
    383                org.apache.commons.logging.Log log = LogFactory.
    384                    getLog(SCXMLParser.class);
    385                log.error(ERR_ISRC_PARSE_FAIL, rte);
    386                throw rte;
    387            }
    388
    389            if (scxml != null) {
    390                ModelUpdater.updateSCXML(scxml);
    391            }
    392
    393            return scxml;
    394
    395        }
    396
    397        /**
    398         * <p>Obtain a SCXML digester instance for further customization.</p>
    399         * <b>API Notes:</b>
    400         * <ul>
    401         *   <li>Use the digest() convenience methods if you do not
    402         *       need a custom digester.</li>
    403         *   <li>After the SCXML document is parsed by the customized digester,
    404         *       the object model <b>must</b> be made executor-ready by calling
    405         *       <code>updateSCXML(SCXML)</code> method in this class.</li>
    406         * </ul>
    407         *
    408         * @return Digester A newly configured SCXML digester instance
    409         *
    410         * @see SCXMLParser#updateSCXML(SCXML)
    411         */
    412        public static Digester newInstance() {
    413
    414            return newInstance(null, null, null);
    415
    416        }
    417
    418        /**
    419         * <p>Obtain a SCXML digester instance for further customization.</p>
    420         * <b>API Notes:</b>
    421         * <ul>
    422         *   <li>Use the digest() convenience methods if you do not
    423         *       need a custom digester.</li>
    424         *   <li>After the SCXML document is parsed by the customized digester,
    425         *       the object model <b>must</b> be made executor-ready by calling
    426         *       <code>updateSCXML(SCXML)</code> method in this class.</li>
    427         * </ul>
    428         *
    429         * @param pr The PathResolver, may be null for standalone documents
    430         * @return Digester A newly configured SCXML digester instance
    431         *
    432         * @see SCXMLParser#updateSCXML(SCXML)
    433         */
    434        public static Digester newInstance(final PathResolver pr) {
    435
    436            return newInstance(null, pr, null);
    437
    438        }
    439
    440        /**
    441         * <p>Obtain a SCXML digester instance for further customization.</p>
    442         * <b>API Notes:</b>
    443         * <ul>
    444         *   <li>Use the digest() convenience methods if you do not
    445         *       need a custom digester.</li>
    446         *   <li>After the SCXML document is parsed by the customized digester,
    447         *       the object model <b>must</b> be made executor-ready by calling
    448         *       <code>updateSCXML(SCXML)</code> method in this class.</li>
    449         * </ul>
    450         *
    451         * @param scxml The parent SCXML document if there is one (in case of
    452         *              state templates for example), null otherwise
    453         * @param pr The PathResolver, may be null for standalone documents
    454         * @return Digester A newly configured SCXML digester instance
    455         *
    456         * @see SCXMLParser#updateSCXML(SCXML)
    457         */
    458        public static Digester newInstance(final SCXML scxml,
    459                final PathResolver pr) {
    460
    461            return newInstance(scxml, pr, null);
    462
    463        }
    464
    465        /**
    466         * <p>Obtain a SCXML digester instance for further customization.</p>
    467         * <b>API Notes:</b>
    468         * <ul>
    469         *   <li>Use the digest() convenience methods if you do not
    470         *       need a custom digester.</li>
    471         *   <li>After the SCXML document is parsed by the customized digester,
    472         *       the object model <b>must</b> be made executor-ready by calling
    473         *       <code>updateSCXML(SCXML)</code> method in this class.</li>
    474         * </ul>
    475         *
    476         * @param scxml The parent SCXML document if there is one (in case of
    477         *              state templates for example), null otherwise
    478         * @param pr The PathResolver, may be null for standalone documents
    479         * @param customActions The list of {@link CustomAction}s this digester
    480         *              instance will process, can be null or empty
    481         * @return Digester A newly configured SCXML digester instance
    482         *
    483         * @see SCXMLParser#updateSCXML(SCXML)
    484         */
    485        public static Digester newInstance(final SCXML scxml,
    486                final PathResolver pr, final List customActions) {
    487
    488            Digester digester = new Digester();
    489            digester.setNamespaceAware(true);
    490            //Uncomment next line after SCXML DTD is available
    491            //digester.setValidating(true);
    492            WithDefaultsRulesWrapper rules =
    493                new WithDefaultsRulesWrapper(initRules(scxml, pr, customActions));
    494            rules.addDefault(new IgnoredElementRule());
    495            digester.setRules(rules);
    496            return digester;
    497        }
    498
    499        /**
    500         * <p>Update the SCXML object model and make it SCXMLExecutor ready.
    501         * This is part of post-digester processing, and sets up the necessary
    502         * object references throughtout the SCXML object model for the parsed
    503         * document. Should be used only if a customized digester obtained
    504         * using the <code>newInstance()</code> methods is needed.</p>
    505         *
    506         * @param scxml The SCXML object (output from Digester)
    507         * @throws ModelException If the document model has flaws
    508         */
    509       public static void updateSCXML(final SCXML scxml)
    510       throws ModelException {
    511           ModelUpdater.updateSCXML(scxml);
    512       }
    513
    514        //---------------------- PRIVATE CONSTANTS ----------------------//
    515        //// Patterns to get the digestion going, prefixed by XP_
    516        /** Root <scxml> element. */
    517        private static final String XP_SM = "scxml";
    518
    519        /** <state> children of root <scxml> element. */
    520        private static final String XP_SM_ST = "scxml/state";
    521
    522        /** <state> children of root <scxml> element. */
    523        private static final String XP_SM_PAR = "scxml/parallel";
    524
    525        /** <final> children of root <scxml> element. */
    526        private static final String XP_SM_FIN = "scxml/final";
    527
    528        //// Universal matches, prefixed by XPU_
    529        // State
    530        /** <state> children of <state> elements. */
    531        private static final String XPU_ST_ST = "!*/state/state";
    532
    533        /** <final> children of <state> elements. */
    534        private static final String XPU_ST_FIN = "!*/state/final";
    535
    536        /** <state> children of <parallel> elements. */
    537        private static final String XPU_PAR_ST = "!*/parallel/state";
    538
    539        // Parallel
    540        /** <parallel> child of <state> elements. */
    541        private static final String XPU_ST_PAR = "!*/state/parallel";
    542
    543        // If
    544        /** <if> element. */
    545        private static final String XPU_IF = "!*/if";
    546
    547        // Executables, next three patterns useful when adding custom actions
    548        /** <onentry> element. */
    549        private static final String XPU_ONEN = "!*/onentry";
    550
    551        /** <onexit> element. */
    552        private static final String XPU_ONEX = "!*/onexit";
    553
    554        /** <transition> element. */
    555        private static final String XPU_TR = "!*/transition";
    556
    557        /** <finalize> element. */
    558        private static final String XPU_FIN = "!*/finalize";
    559
    560        //// Path Fragments, constants prefixed by XPF_
    561        // Onentries and Onexits
    562        /** <onentry> child element. */
    563        private static final String XPF_ONEN = "/onentry";
    564
    565        /** <onexit> child element. */
    566        private static final String XPF_ONEX = "/onexit";
    567
    568        // Datamodel section
    569        /** <datamodel> child element. */
    570        private static final String XPF_DM = "/datamodel";
    571
    572        /** Individual <data> elements. */
    573        private static final String XPF_DATA = "/data";
    574
    575        // Initial
    576        /** <initial> child element. */
    577        private static final String XPF_INI = "/initial";
    578
    579        // Invoke, param and finalize
    580        /** <invoke> child element of <state>. */
    581        private static final String XPF_INV = "/invoke";
    582
    583        /** <param> child element of <invoke>. */
    584        private static final String XPF_PRM = "/param";
    585
    586        /** <finalize> child element of <invoke>. */
    587        private static final String XPF_FIN = "/finalize";
    588
    589        // History
    590        /** <history> child element. */
    591        private static final String XPF_HIST = "/history";
    592
    593        // Transition, target and exit
    594        /** <transition> child element. */
    595        private static final String XPF_TR = "/transition";
    596
    597        /** <exit> child element, a Commons SCXML custom action. */
    598        private static final String XPF_EXT = "/exit";
    599
    600        // Actions
    601        /** <assign> child element. */
    602        private static final String XPF_ASN = "/assign";
    603
    604        /** <event> child element. */
    605        private static final String XPF_EVT = "/event";
    606
    607        /** <send> child element. */
    608        private static final String XPF_SND = "/send";
    609
    610        /** <cancel> child element. */
    611        private static final String XPF_CAN = "/cancel";
    612
    613        /** <elseif> child element. */
    614        private static final String XPF_EIF = "/elseif";
    615
    616        /** <else> child element. */
    617        private static final String XPF_ELS = "/else";
    618
    619        // Custom Commons SCXML actions
    620        /** <var> child element. */
    621        private static final String XPF_VAR = "/var";
    622
    623        /** <log> child element. */
    624        private static final String XPF_LOG = "/log";
    625
    626        //// Other constants
    627        // Error messages
    628        /**
    629         * Null URL passed as argument.
    630         */
    631        private static final String ERR_NULL_URL = "Cannot parse null URL";
    632
    633        /**
    634         * Null path passed as argument.
    635         */
    636        private static final String ERR_NULL_PATH = "Cannot parse null URL";
    637
    638        /**
    639         * Null InputSource passed as argument.
    640         */
    641        private static final String ERR_NULL_ISRC = "Cannot parse null URL";
    642
    643        /**
    644         * Parsing SCXML document has failed.
    645         */
    646        private static final String ERR_DOC_PARSE_FAIL = "Error parsing "
    647            + "SCXML document: \"{0}\", with message: \"{1}\"\n";
    648
    649        /**
    650         * Parsing SCXML document InputSource has failed.
    651         */
    652        private static final String ERR_ISRC_PARSE_FAIL =
    653            "Could not parse SCXML InputSource";
    654
    655        /**
    656         * Parser configuration error while registering data rule.
    657         */
    658        private static final String ERR_PARSER_CFG_DATA = "XML Parser "
    659            + "misconfiguration, error registering <data> element rule";
    660
    661        /**
    662         * Parser configuration error while registering send rule.
    663         */
    664        private static final String ERR_PARSER_CFG_SEND = "XML Parser "
    665            + "misconfiguration, error registering <send> element rule";
    666
    667        /**
    668         * Parser configuration error while registering body content rule for
    669         * custom action.
    670         */
    671        private static final String ERR_PARSER_CFG_CUSTOM = "XML Parser "
    672            + "misconfiguration, error registering custom action rules";
    673
    674        /**
    675         * Error message while attempting to define a custom action which does
    676         * not extend the Commons SCXML Action base class.
    677         */
    678        private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list"
    679            + " contained unknown object (not a Commons SCXML Action subtype)";
    680
    681        /**
    682         * Error message when the URI in a <state>'s "src"
    683         * attribute does not point to a valid SCXML document, and thus cannot be
    684         * parsed.
    685         */
    686        private static final String ERR_STATE_SRC =
    687            "Source attribute in <state src=\"{0}\"> cannot be parsed";
    688
    689        /**
    690         * Error message when the target of the URI fragment in a <state>'s
    691         * "src" attribute is not defined in the referenced document.
    692         */
    693        private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in "
    694            + "<state src=\"{0}\"> is an unknown state in referenced document";
    695
    696        /**
    697         * Error message when the target of the URI fragment in a <state>'s
    698         * "src" attribute is not a <state> or <final> in
    699         * the referenced document.
    700         */
    701        private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment"
    702            + " in <state src=\"{0}\"> does not point to a <state> or <final>";
    703
    704        // String constants
    705        /** Slash. */
    706        private static final String STR_SLASH = "/";
    707
    708        //---------------------- PRIVATE UTILITY METHODS ----------------------//
    709        /*
    710         * Private utility functions for configuring digester rule base for SCXML.
    711         */
    712        /**
    713         * Initialize the Digester rules for the current document.
    714         *
    715         * @param scxml The parent SCXML document (or null)
    716         * @param pr The PathResolver
    717         * @param customActions The list of custom actions this digester needs
    718         *                      to be able to process
    719         *
    720         * @return scxmlRules The rule set to be used for digestion
    721         */
    722        private static ExtendedBaseRules initRules(final SCXML scxml,
    723                final PathResolver pr, final List customActions) {
    724
    725            ExtendedBaseRules scxmlRules = new ExtendedBaseRules();
    726            scxmlRules.setNamespaceURI(NAMESPACE_SCXML);
    727
    728            //// SCXML
    729            scxmlRules.add(XP_SM, new ObjectCreateRule(SCXML.class));
    730            scxmlRules.add(XP_SM, new SetPropertiesRule());
    731            scxmlRules.add(XP_SM, new SetCurrentNamespacesRule());
    732
    733            //// Datamodel at document root i.e. <scxml> datamodel
    734            addDatamodelRules(XP_SM + XPF_DM, scxmlRules, scxml, pr);
    735
    736            //// States
    737            // Level one states
    738            addStateRules(XP_SM_ST, scxmlRules, customActions, scxml, pr);
    739
    740            // Nested states
    741            addStateRules(XPU_ST_ST, scxmlRules, customActions, scxml, pr);
    742
    743            // Orthogonal states
    744            addStateRules(XPU_PAR_ST, scxmlRules, customActions, scxml, pr);
    745
    746            //// Parallels
    747            // Level one parallels
    748            addParallelRules(XP_SM_PAR, scxmlRules, customActions, scxml, pr);
    749
    750            // Parallel children of composite states
    751            addParallelRules(XPU_ST_PAR, scxmlRules, customActions, scxml, pr);
    752
    753            //// Finals
    754            // Level one finals
    755            addFinalRules(XP_SM_FIN, scxmlRules, customActions, scxml, pr);
    756
    757            // Final children of composite states
    758            addFinalRules(XPU_ST_FIN, scxmlRules, customActions, scxml, pr);
    759
    760            //// Ifs
    761            addIfRules(XPU_IF, scxmlRules, pr, customActions);
    762
    763            //// Custom actions
    764            addCustomActionRules(XPU_ONEN, scxmlRules, customActions);
    765            addCustomActionRules(XPU_ONEX, scxmlRules, customActions);
    766            addCustomActionRules(XPU_TR, scxmlRules, customActions);
    767            addCustomActionRules(XPU_IF, scxmlRules, customActions);
    768            addCustomActionRules(XPU_FIN, scxmlRules, customActions);
    769
    770            return scxmlRules;
    771
    772        }
    773
    774        /**
    775         * Add Digester rules for all <state> elements.
    776         *
    777         * @param xp The Digester style XPath expression of the parent
    778         *           XML element
    779         * @param scxmlRules The rule set to be used for digestion
    780         * @param customActions The list of custom actions this digester needs
    781         *                      to be able to process
    782         * @param scxml The parent SCXML document (or null)
    783         * @param pr The PathResolver
    784         */
    785        private static void addStateRules(final String xp,
    786                final ExtendedBaseRules scxmlRules, final List customActions,
    787                final SCXML scxml, final PathResolver pr) {
    788            scxmlRules.add(xp, new ObjectCreateRule(State.class));
    789            addStatePropertiesRules(xp, scxmlRules, customActions, pr, scxml);
    790            addDatamodelRules(xp + XPF_DM, scxmlRules, scxml, pr);
    791            addInvokeRules(xp + XPF_INV, scxmlRules, customActions, pr, scxml);
    792            addInitialRules(xp + XPF_INI, scxmlRules, customActions, pr, scxml);
    793            addHistoryRules(xp + XPF_HIST, scxmlRules, customActions, pr, scxml);
    794            addTransitionRules(xp + XPF_TR, scxmlRules, "addTransition",
    795                pr, customActions);
    796            addHandlerRules(xp, scxmlRules, pr, customActions);
    797            scxmlRules.add(xp, new UpdateModelRule(scxml));
    798            scxmlRules.add(xp, new SetNextRule("addChild"));
    799        }
    800
    801        /**
    802         * Add Digester rules for all <parallel> elements.
    803         *
    804         * @param xp The Digester style XPath expression of the parent
    805         *           XML element
    806         * @param scxmlRules The rule set to be used for digestion
    807         * @param customActions The list of custom actions this digester needs
    808         *                      to be able to process
    809         * @param pr The {@link PathResolver} for this document
    810         * @param scxml The parent SCXML document (or null)
    811         */
    812        private static void addParallelRules(final String xp,
    813                final ExtendedBaseRules scxmlRules, final List customActions,
    814                final SCXML scxml, final PathResolver pr) {
    815            addSimpleRulesTuple(xp, scxmlRules, Parallel.class, null, null,
    816                    "addChild");
    817            addDatamodelRules(xp + XPF_DM, scxmlRules, scxml, pr);
    818            addTransitionRules(xp + XPF_TR, scxmlRules, "addTransition",
    819                pr, customActions);
    820            addHandlerRules(xp, scxmlRules, pr, customActions);
    821            scxmlRules.add(xp, new UpdateModelRule(scxml));
    822        }
    823
    824        /**
    825         * Add Digester rules for all <final> elements.
    826         *
    827         * @param xp The Digester style XPath expression of the parent
    828         *           XML element
    829         * @param scxmlRules The rule set to be used for digestion
    830         * @param customActions The list of custom actions this digester needs
    831         *                      to be able to process
    832         * @param scxml The parent SCXML document (or null)
    833         * @param pr The {@link PathResolver} for this document
    834         */
    835        private static void addFinalRules(final String xp,
    836                final ExtendedBaseRules scxmlRules, final List customActions,
    837                final SCXML scxml, final PathResolver pr) {
    838            addSimpleRulesTuple(xp, scxmlRules, Final.class, null, null,
    839                "addChild");
    840            addHandlerRules(xp, scxmlRules, pr, customActions);
    841            scxmlRules.add(xp, new UpdateModelRule(scxml));
    842        }
    843
    844        /**
    845         * Add Digester rules for all <state> element attributes.
    846         *
    847         * @param xp The Digester style XPath expression of the parent
    848         *           XML element
    849         * @param scxmlRules The rule set to be used for digestion
    850         * @param customActions The list of custom actions this digester needs
    851         *                      to be able to process
    852         * @param pr The PathResolver
    853         * @param scxml The root document, if this one is src'ed in
    854         */
    855        private static void addStatePropertiesRules(final String xp,
    856                final ExtendedBaseRules scxmlRules, final List customActions,
    857                final PathResolver pr, final SCXML scxml) {
    858            scxmlRules.add(xp, new SetPropertiesRule(
    859                    new String[] {"id", "final", "initial"},
    860                    new String[] {"id", "final", "first"}));
    861            scxmlRules.add(xp, new DigestSrcAttributeRule(scxml,
    862                customActions, pr));
    863        }
    864
    865        /**
    866         * Add Digester rules for all <datamodel> elements.
    867         *
    868         * @param xp The Digester style XPath expression of the parent
    869         *           XML element
    870         * @param scxmlRules The rule set to be used for digestion
    871         * @param pr The PathResolver
    872         * @param scxml The parent SCXML document (or null)
    873         */
    874        private static void addDatamodelRules(final String xp,
    875                final ExtendedBaseRules scxmlRules, final SCXML scxml,
    876                final PathResolver pr) {
    877            scxmlRules.add(xp, new ObjectCreateRule(Datamodel.class));
    878            scxmlRules.add(xp + XPF_DATA, new ObjectCreateRule(Data.class));
    879            scxmlRules.add(xp + XPF_DATA, new SetPropertiesRule());
    880            scxmlRules.add(xp + XPF_DATA, new SetCurrentNamespacesRule());
    881            scxmlRules.add(xp + XPF_DATA, new SetNextRule("addData"));
    882            try {
    883                scxmlRules.add(xp + XPF_DATA, new ParseDataRule(pr));
    884            } catch (ParserConfigurationException pce) {
    885                org.apache.commons.logging.Log log = LogFactory.
    886                    getLog(SCXMLParser.class);
    887                log.error(ERR_PARSER_CFG_DATA, pce);
    888            }
    889            scxmlRules.add(xp, new SetNextRule("setDatamodel"));
    890        }
    891
    892        /**
    893         * Add Digester rules for all <invoke> elements.
    894         *
    895         * @param xp The Digester style XPath expression of the parent
    896         *           XML element
    897         * @param scxmlRules The rule set to be used for digestion
    898         * @param customActions The list of {@link CustomAction}s this digester
    899         *              instance will process, can be null or empty
    900         * @param pr The PathResolver
    901         * @param scxml The parent SCXML document (or null)
    902         */
    903        private static void addInvokeRules(final String xp,
    904                final ExtendedBaseRules scxmlRules, final List customActions,
    905                final PathResolver pr, final SCXML scxml) {
    906            scxmlRules.add(xp, new ObjectCreateRule(Invoke.class));
    907            scxmlRules.add(xp, new SetPropertiesRule());
    908            scxmlRules.add(xp, new SetCurrentNamespacesRule());
    909            scxmlRules.add(xp, new SetPathResolverRule(pr));
    910            scxmlRules.add(xp + XPF_PRM, new ObjectCreateRule(Param.class));
    911            scxmlRules.add(xp + XPF_PRM, new SetPropertiesRule());
    912            scxmlRules.add(xp + XPF_PRM, new SetCurrentNamespacesRule());
    913            scxmlRules.add(xp + XPF_PRM, new SetNextRule("addParam"));
    914            scxmlRules.add(xp + XPF_FIN, new ObjectCreateRule(Finalize.class));
    915            scxmlRules.add(xp + XPF_FIN, new UpdateFinalizeRule());
    916            addActionRules(xp + XPF_FIN, scxmlRules, pr, customActions);
    917            scxmlRules.add(xp + XPF_FIN, new SetNextRule("setFinalize"));
    918            scxmlRules.add(xp, new SetNextRule("setInvoke"));
    919        }
    920
    921        /**
    922         * Add Digester rules for all <initial> elements.
    923         *
    924         * @param xp The Digester style XPath expression of the parent
    925         *           XML element
    926         * @param scxmlRules The rule set to be used for digestion
    927         * @param customActions The list of custom actions this digester needs
    928         *                      to be able to process
    929         * @param pr The PathResolver
    930         * @param scxml The parent SCXML document (or null)
    931         */
    932        private static void addInitialRules(final String xp,
    933                final ExtendedBaseRules scxmlRules, final List customActions,
    934                final PathResolver pr, final SCXML scxml) {
    935            scxmlRules.add(xp, new ObjectCreateRule(Initial.class));
    936            addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr,
    937                scxml);
    938            scxmlRules.add(xp, new UpdateModelRule(scxml));
    939            addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition",
    940                pr, customActions);
    941            scxmlRules.add(xp, new SetNextRule("setInitial"));
    942        }
    943
    944        /**
    945         * Add Digester rules for all <history> elements.
    946         *
    947         * @param xp The Digester style XPath expression of the parent
    948         *           XML element
    949         * @param scxmlRules The rule set to be used for digestion
    950         * @param customActions The list of custom actions this digester needs
    951         *                      to be able to process
    952         * @param pr The PathResolver
    953         * @param scxml The parent SCXML document (or null)
    954         */
    955        private static void addHistoryRules(final String xp,
    956                final ExtendedBaseRules scxmlRules, final List customActions,
    957                final PathResolver pr, final SCXML scxml) {
    958            scxmlRules.add(xp, new ObjectCreateRule(History.class));
    959            addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr,
    960                scxml);
    961            scxmlRules.add(xp, new UpdateModelRule(scxml));
    962            scxmlRules.add(xp, new SetPropertiesRule(new String[] {"type"},
    963                new String[] {"type"}));
    964            addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition",
    965                pr, customActions);
    966            scxmlRules.add(xp, new SetNextRule("addHistory"));
    967        }
    968
    969        /**
    970         * Add Digester rules for all pseudo state (initial, history) element
    971         * attributes.
    972         *
    973         * @param xp The Digester style XPath expression of the parent
    974         *           XML element
    975         * @param scxmlRules The rule set to be used for digestion
    976         * @param customActions The list of custom actions this digester needs
    977         *                      to be able to process
    978         * @param pr The PathResolver
    979         * @param scxml The root document, if this one is src'ed in
    980         */
    981        private static void addPseudoStatePropertiesRules(final String xp,
    982                final ExtendedBaseRules scxmlRules, final List customActions,
    983                final PathResolver pr, final SCXML scxml) {
    984            scxmlRules.add(xp, new SetPropertiesRule(new String[] {"id"},
    985                new String[] {"id"}));
    986            scxmlRules.add(xp, new DigestSrcAttributeRule(scxml, customActions,
    987                pr));
    988        }
    989
    990        /**
    991         * Add Digester rules for all <transition> elements.
    992         *
    993         * @param xp The Digester style XPath expression of the parent
    994         *           XML element
    995         * @param scxmlRules The rule set to be used for digestion
    996         * @param setNextMethod The method name for adding this transition
    997         *             to its parent (defined by the SCXML Java object model).
    998         * @param pr The {@link PathResolver} for this document
    999         * @param customActions The list of custom actions this digester needs
    1000         *                      to be able to process
    1001         */
    1002        private static void addTransitionRules(final String xp,
    1003                final ExtendedBaseRules scxmlRules, final String setNextMethod,
    1004                final PathResolver pr, final List customActions) {
    1005            scxmlRules.add(xp, new ObjectCreateRule(Transition.class));
    1006            scxmlRules.add(xp, new SetPropertiesRule(
    1007                new String[] {"event", "cond", "target"},
    1008                new String[] {"event", "cond", "next"}));
    1009            scxmlRules.add(xp, new SetCurrentNamespacesRule());
    1010            addActionRules(xp, scxmlRules, pr, customActions);
    1011
    1012            // Add <exit> custom action rule in Commons SCXML namespace
    1013            scxmlRules.setNamespaceURI(NAMESPACE_COMMONS_SCXML);
    1014            scxmlRules.add(xp + XPF_EXT, new Rule() {
    1015                public void end(final String namespace, final String name) {
    1016                    Transition t = (Transition) getDigester().peek(1);
    1017                    TransitionTarget tt = (TransitionTarget) getDigester().
    1018                        peek(2);
    1019                    if (tt instanceof Initial) {
    1020                        org.apache.commons.logging.Log log = LogFactory.
    1021                            getLog(SCXMLParser.class);
    1022                        log.warn("Ignored <exit> action in <initial>");
    1023                    } else {
    1024                        State exitState = new State();
    1025                        exitState.setFinal(true);
    1026                        t.getTargets().add(exitState);
    1027                    }
    1028                }
    1029            });
    1030            scxmlRules.setNamespaceURI(NAMESPACE_SCXML);
    1031
    1032            scxmlRules.add(xp, new SetNextRule(setNextMethod));
    1033        }
    1034
    1035        /**
    1036         * Add Digester rules for all <onentry> and <onexit>
    1037         * elements.
    1038         *
    1039         * @param xp The Digester style XPath expression of the parent
    1040         *           XML element
    1041         * @param scxmlRules The rule set to be used for digestion
    1042         * @param pr The {@link PathResolver} for this document
    1043         * @param customActions The list of custom actions this digester needs
    1044         *                      to be able to process
    1045         */
    1046        private static void addHandlerRules(final String xp,
    1047                final ExtendedBaseRules scxmlRules, final PathResolver pr,
    1048                final List customActions) {
    1049            scxmlRules.add(xp + XPF_ONEN, new ObjectCreateRule(OnEntry.class));
    1050            addActionRules(xp + XPF_ONEN, scxmlRules, pr, customActions);
    1051            scxmlRules.add(xp + XPF_ONEN, new SetNextRule("setOnEntry"));
    1052            scxmlRules.add(xp + XPF_ONEX, new ObjectCreateRule(OnExit.class));
    1053            addActionRules(xp + XPF_ONEX, scxmlRules, pr, customActions);
    1054            scxmlRules.add(xp + XPF_ONEX, new SetNextRule("setOnExit"));
    1055        }
    1056
    1057        /**
    1058         * Add Digester rules for all actions ("executable" elements).
    1059         *
    1060         * @param xp The Digester style XPath expression of the parent
    1061         *           XML element
    1062         * @param scxmlRules The rule set to be used for digestion
    1063         * @param pr The {@link PathResolver} for this document
    1064         * @param customActions The list of custom actions this digester needs
    1065         *                      to be able to process
    1066         */
    1067        private static void addActionRules(final String xp,
    1068                final ExtendedBaseRules scxmlRules, final PathResolver pr,
    1069                final List customActions) {
    1070            // Actions in SCXML namespace
    1071            addActionRulesTuple(xp + XPF_ASN, scxmlRules, Assign.class);
    1072            scxmlRules.add(xp + XPF_ASN, new SetPathResolverRule(pr));
    1073            addActionRulesTuple(xp + XPF_EVT, scxmlRules, Event.class);
    1074            addSendRulesTuple(xp + XPF_SND, scxmlRules);
    1075            addActionRulesTuple(xp + XPF_CAN, scxmlRules, Cancel.class);
    1076            addActionRulesTuple(xp + XPF_LOG, scxmlRules, Log.class);
    1077
    1078            // Actions in Commons SCXML namespace
    1079            scxmlRules.setNamespaceURI(NAMESPACE_COMMONS_SCXML);
    1080
    1081            addActionRulesTuple(xp + XPF_VAR, scxmlRules, Var.class);
    1082            addActionRulesTuple(xp + XPF_EXT, scxmlRules, Exit.class);
    1083
    1084            // Reset namespace
    1085            scxmlRules.setNamespaceURI(NAMESPACE_SCXML);
    1086        }
    1087
    1088        /**
    1089         * Add custom action rules, if any custom actions are provided.
    1090         *
    1091         * @param xp The Digester style XPath expression of the parent
    1092         *           XML element
    1093         * @param scxmlRules The rule set to be used for digestion
    1094         * @param customActions The list of custom actions this digester needs
    1095         *                      to be able to process
    1096         */
    1097        private static void addCustomActionRules(final String xp,
    1098                final ExtendedBaseRules scxmlRules, final List customActions) {
    1099            if (customActions == null || customActions.size() == 0) {
    1100                return;
    1101            }
    1102            for (int i = 0; i < customActions.size(); i++) {
    1103                Object item = customActions.get(i);
    1104                if (item == null || !(item instanceof CustomAction)) {
    1105                    org.apache.commons.logging.Log log = LogFactory.
    1106                        getLog(SCXMLParser.class);
    1107                    log.warn(ERR_CUSTOM_ACTION_TYPE);
    1108                } else {
    1109                    CustomAction ca = (CustomAction) item;
    1110                    scxmlRules.setNamespaceURI(ca.getNamespaceURI());
    1111                    String xpfLocalName = STR_SLASH + ca.getLocalName();
    1112                    Class klass = ca.getActionClass();
    1113                    if (SCXMLHelper.implementationOf(klass,
    1114                            ExternalContent.class)) {
    1115                        addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules,
    1116                            klass, true);
    1117                    } else {
    1118                        addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules,
    1119                            klass, false);
    1120                    }
    1121                }
    1122            }
    1123            scxmlRules.setNamespaceURI(NAMESPACE_SCXML);
    1124        }
    1125
    1126        /**
    1127         * Add Digester rules that are specific to the <send> action
    1128         * element.
    1129         *
    1130         * @param xp The Digester style XPath expression of <send> element
    1131         * @param scxmlRules The rule set to be used for digestion
    1132         */
    1133        private static void addSendRulesTuple(final String xp,
    1134                final ExtendedBaseRules scxmlRules) {
    1135            addActionRulesTuple(xp, scxmlRules, Send.class);
    1136            try {
    1137                scxmlRules.add(xp, new ParseExternalContentRule());
    1138            } catch (ParserConfigurationException pce) {
    1139                org.apache.commons.logging.Log log = LogFactory.
    1140                    getLog(SCXMLParser.class);
    1141                log.error(ERR_PARSER_CFG_SEND, pce);
    1142            }
    1143        }
    1144
    1145        /**
    1146         * Add Digester rules for a simple custom action (no body content).
    1147         *
    1148         * @param xp The path to the custom action element
    1149         * @param scxmlRules The rule set to be used for digestion
    1150         * @param klass The <code>Action</code> class implementing the custom
    1151         *              action.
    1152         * @param bodyContent Whether the custom rule has body content
    1153         *              that should be parsed using
    1154         *              <code>NodeCreateRule</code>
    1155         */
    1156        private static void addCustomActionRulesTuple(final String xp,
    1157                final ExtendedBaseRules scxmlRules, final Class klass,
    1158                final boolean bodyContent) {
    1159            addActionRulesTuple(xp, scxmlRules, klass);
    1160            if (bodyContent) {
    1161                try {
    1162                    scxmlRules.add(xp, new ParseExternalContentRule());
    1163                } catch (ParserConfigurationException pce) {
    1164                    org.apache.commons.logging.Log log = LogFactory.
    1165                        getLog(SCXMLParser.class);
    1166                    log.error(ERR_PARSER_CFG_CUSTOM, pce);
    1167                }
    1168            }
    1169        }
    1170
    1171        /**
    1172         * Add Digester rules for all <if> elements.
    1173         *
    1174         * @param xp The Digester style XPath expression of the parent
    1175         *           XML element
    1176         * @param scxmlRules The rule set to be used for digestion
    1177         * @param pr The {@link PathResolver} for this document
    1178         * @param customActions The list of custom actions this digester needs
    1179         *                      to be able to process
    1180         */
    1181        private static void addIfRules(final String xp,
    1182                final ExtendedBaseRules scxmlRules, final PathResolver pr,
    1183                final List customActions) {
    1184            addActionRulesTuple(xp, scxmlRules, If.class);
    1185            addActionRules(xp, scxmlRules, pr, customActions);
    1186            addActionRulesTuple(xp + XPF_EIF, scxmlRules, ElseIf.class);
    1187            addActionRulesTuple(xp + XPF_ELS, scxmlRules, Else.class);
    1188        }
    1189
    1190        /**
    1191         * Add Digester rules that are common across all actions elements.
    1192         *
    1193         * @param xp The Digester style XPath expression of the parent
    1194         *           XML element
    1195         * @param scxmlRules The rule set to be used for digestion
    1196         * @param klass The class in the Java object model to be instantiated
    1197         *              in the ObjectCreateRule for this action
    1198         */
    1199        private static void addActionRulesTuple(final String xp,
    1200                final ExtendedBaseRules scxmlRules, final Class klass) {
    1201            addSimpleRulesTuple(xp, scxmlRules, klass, null, null, "addAction");
    1202            scxmlRules.add(xp, new SetExecutableParentRule());
    1203            scxmlRules.add(xp, new SetCurrentNamespacesRule());
    1204        }
    1205
    1206        /**
    1207         * Add the run of the mill Digester rules for any element.
    1208         *
    1209         * @param xp The Digester style XPath expression of the parent
    1210         *           XML element
    1211         * @param scxmlRules The rule set to be used for digestion
    1212         * @param klass The class in the Java object model to be instantiated
    1213         *              in the ObjectCreateRule for this action
    1214         * @param args The attributes to be mapped into the object model
    1215         * @param props The properties that args get mapped to
    1216         * @param addMethod The method that the SetNextRule should call
    1217         */
    1218        private static void addSimpleRulesTuple(final String xp,
    1219                final ExtendedBaseRules scxmlRules, final Class klass,
    1220                final String[] args, final String[] props,
    1221                final String addMethod) {
    1222            scxmlRules.add(xp, new ObjectCreateRule(klass));
    1223            if (args == null) {
    1224                scxmlRules.add(xp, new SetPropertiesRule());
    1225            } else {
    1226                scxmlRules.add(xp, new SetPropertiesRule(args, props));
    1227            }
    1228            scxmlRules.add(xp, new SetNextRule(addMethod));
    1229        }
    1230
    1231        /**
    1232         * Discourage instantiation since this is a utility class.
    1233         */
    1234        private SCXMLParser() {
    1235            super();
    1236        }
    1237
    1238        /**
    1239         * Custom digestion rule for establishing necessary associations of this
    1240         * TransitionTarget with the root SCXML object.
    1241         * These include: <br>
    1242         * 1) Updation of the SCXML object's global targets Map <br>
    1243         * 2) Obtaining a handle to the SCXML object's NotificationRegistry <br>
    1244         */
    1245        private static class UpdateModelRule extends Rule {
    1246
    1247            /**
    1248             * The root SCXML object.
    1249             */
    1250            private SCXML scxml;
    1251
    1252            /**
    1253             * Constructor.
    1254             * @param scxml The root SCXML object
    1255             */
    1256            UpdateModelRule(final SCXML scxml) {
    1257                super();
    1258                this.scxml = scxml;
    1259            }
    1260
    1261            /**
    1262             * @see Rule#end(String, String)
    1263             */
    1264            public final void end(final String namespace, final String name) {
    1265                if (scxml == null) {
    1266                    scxml = (SCXML) getDigester()
    1267                            .peek(getDigester().getCount() - 1);
    1268                }
    1269                TransitionTarget tt = (TransitionTarget) getDigester().peek();
    1270                scxml.addTarget(tt);
    1271            }
    1272        }
    1273
    1274        /**
    1275         * Custom digestion rule for setting Executable parent of Action elements.
    1276         */
    1277        private static class SetExecutableParentRule extends Rule {
    1278
    1279            /**
    1280             * Constructor.
    1281             */
    1282            SetExecutableParentRule() {
    1283                super();
    1284            }
    1285
    1286            /**
    1287             * @see Rule#end(String, String)
    1288             */
    1289            public final void end(final String namespace, final String name) {
    1290                Action child = (Action) getDigester().peek();
    1291                for (int i = 1; i < getDigester().getCount() - 1; i++) {
    1292                    Object ancestor = getDigester().peek(i);
    1293                    if (ancestor instanceof Executable) {
    1294                        child.setParent((Executable) ancestor);
    1295                        return;
    1296                    }
    1297                }
    1298            }
    1299        }
    1300
    1301        /**
    1302         * Custom digestion rule for parsing bodies of
    1303         * <code>ExternalContent</code> elements.
    1304         *
    1305         * @see ExternalContent
    1306         */
    1307        private static class ParseExternalContentRule extends NodeCreateRule {
    1308            /**
    1309             * Constructor.
    1310             * @throws ParserConfigurationException A JAXP configuration error
    1311             */
    1312            ParseExternalContentRule()
    1313            throws ParserConfigurationException {
    1314                super();
    1315            }
    1316            /**
    1317             * @see Rule#end(String, String)
    1318             */
    1319            public final void end(final String namespace, final String name) {
    1320                Element bodyElement = (Element) getDigester().pop();
    1321                NodeList childNodes = bodyElement.getChildNodes();
    1322                List externalNodes = ((ExternalContent) getDigester().
    1323                    peek()).getExternalNodes();
    1324                for (int i = 0; i < childNodes.getLength(); i++) {
    1325                    externalNodes.add(childNodes.item(i));
    1326                }
    1327            }
    1328        }
    1329
    1330        /**
    1331         * Custom digestion rule for parsing bodies of <data> elements.
    1332         */
    1333        private static class ParseDataRule extends NodeCreateRule {
    1334
    1335            /**
    1336             * The PathResolver used to resolve the src attribute to the
    1337             * SCXML document it points to.
    1338             * @see PathResolver
    1339             */
    1340            private PathResolver pr;
    1341
    1342            /**
    1343             * The "src" attribute, retained to check if body content is legal.
    1344             */
    1345            private String src;
    1346
    1347            /**
    1348             * The "expr" attribute, retained to check if body content is legal.
    1349             */
    1350            private String expr;
    1351
    1352            /**
    1353             * The XML tree for this data, parse as a Node, obtained from
    1354             * either the "src" or the "expr" attributes.
    1355             */
    1356            private Node attrNode;
    1357
    1358            /**
    1359             * Constructor.
    1360             *
    1361             * @param pr The <code>PathResolver</code>
    1362             * @throws ParserConfigurationException A JAXP configuration error
    1363             */
    1364            ParseDataRule(final PathResolver pr)
    1365            throws ParserConfigurationException {
    1366                super();
    1367                this.pr = pr;
    1368            }
    1369
    1370            /**
    1371             * @see Rule#begin(String, String, Attributes)
    1372             */
    1373            public final void begin(final String namespace, final String name,
    1374                    final Attributes attributes) throws Exception {
    1375                super.begin(namespace, name, attributes);
    1376                src = attributes.getValue("src");
    1377                expr = attributes.getValue("expr");
    1378                if (!SCXMLHelper.isStringEmpty(src)) {
    1379                    String path = null;
    1380                    if (pr == null) {
    1381                        path = src;
    1382                    } else {
    1383                        path = pr.resolvePath(src);
    1384                    }
    1385                    try {
    1386                        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.
    1387                            newInstance();
    1388                        DocumentBuilder db = dbFactory.newDocumentBuilder();
    1389                        attrNode = db.parse(path);
    1390                    } catch (Throwable t) { // you read that correctly
    1391                        org.apache.commons.logging.Log log = LogFactory.
    1392                            getLog(SCXMLParser.class);
    1393                        log.error(t.getMessage(), t);
    1394                    }
    1395                    return;
    1396                }
    1397            }
    1398
    1399            /**
    1400             * @see Rule#end(String, String)
    1401             */
    1402            public final void end(final String namespace, final String name) {
    1403                Node bodyNode = (Node) getDigester().pop();
    1404                Data data = ((Data) getDigester().peek());
    1405                // Prefer "src" over "expr", "expr" over child nodes
    1406                // "expr" can only be evaluated at execution time
    1407                if (!SCXMLHelper.isStringEmpty(src)) {
    1408                    data.setNode(attrNode);
    1409                } else  if (SCXMLHelper.isStringEmpty(expr)) {
    1410                    // both "src" and "expr" are empty
    1411                    data.setNode(bodyNode);
    1412                }
    1413            }
    1414        }
    1415
    1416        /**
    1417         * Custom digestion rule for external sources, that is, the src attribute of
    1418         * the <state> element.
    1419         */
    1420        private static class DigestSrcAttributeRule extends Rule {
    1421
    1422            /**
    1423             * The PathResolver used to resolve the src attribute to the
    1424             * SCXML document it points to.
    1425             * @see PathResolver
    1426             */
    1427            private PathResolver pr;
    1428
    1429            /**
    1430             * The root document.
    1431             */
    1432            private SCXML root;
    1433
    1434            /**
    1435             * The list of custom actions the parent document is capable of
    1436             * processing (and hence, the child should be, by transitivity).
    1437             * @see CustomAction
    1438             */
    1439            private List customActions;
    1440
    1441            /**
    1442             * Constructor.
    1443             * @param pr The PathResolver
    1444             * @param customActions The list of custom actions this digester needs
    1445             *                      to be able to process
    1446             *
    1447             * @see PathResolver
    1448             * @see CustomAction
    1449             */
    1450            DigestSrcAttributeRule(final List customActions,
    1451                    final PathResolver pr) {
    1452                super();
    1453                this.customActions = customActions;
    1454                this.pr = pr;
    1455            }
    1456
    1457            /**
    1458             * Constructor.
    1459             * @param root The root document, if this one is src'ed in
    1460             * @param pr The PathResolver
    1461             * @param customActions The list of custom actions this digester needs
    1462             *                      to be able to process
    1463             *
    1464             * @see PathResolver
    1465             * @see CustomAction
    1466             */
    1467            DigestSrcAttributeRule(final SCXML root,
    1468                    final List customActions, final PathResolver pr) {
    1469                super();
    1470                this.root = root;
    1471                this.customActions = customActions;
    1472                this.pr = pr;
    1473            }
    1474
    1475            /**
    1476             * @see Rule#begin(String, String, Attributes)
    1477             */
    1478            public final void begin(final String namespace, final String name,
    1479                    final Attributes attributes) throws ModelException {
    1480                String src = attributes.getValue("src");
    1481                if (SCXMLHelper.isStringEmpty(src)) {
    1482                    return;
    1483                }
    1484
    1485                // 1) Digest the external SCXML file
    1486                Digester digester = getDigester();
    1487                SCXML scxml = (SCXML) digester.peek(digester.getCount() - 1);
    1488                SCXML parent = root;
    1489                if (parent == null) {
    1490                    parent = scxml;
    1491                }
    1492                String path;
    1493                PathResolver nextpr = null;
    1494                if (pr == null) {
    1495                    path = src;
    1496                } else {
    1497                    path = pr.resolvePath(src);
    1498                    nextpr = pr.getResolver(src);
    1499                }
    1500                String[] fragments = path.split("#", 2);
    1501                String location = fragments[0];
    1502                String fragment = null;
    1503                if (fragments.length > 1) {
    1504                    fragment = fragments[1];
    1505                }
    1506                Digester externalSrcDigester;
    1507                if (fragment != null) {
    1508                    // Cannot pull in all targets just yet, i.e. null parent
    1509                    externalSrcDigester = newInstance(null, nextpr,
    1510                        customActions);
    1511                } else {
    1512                    externalSrcDigester = newInstance(parent, nextpr,
    1513                        customActions);
    1514                }
    1515                SCXML externalSCXML = null;
    1516                try {
    1517                    externalSCXML = (SCXML) externalSrcDigester.parse(location);
    1518                } catch (Exception e) {
    1519                    MessageFormat msgFormat =
    1520                        new MessageFormat(ERR_STATE_SRC);
    1521                    String errMsg = msgFormat.format(new Object[] {
    1522                        path
    1523                    });
    1524                    throw new ModelException(errMsg + " : " + e.getMessage(), e);
    1525                }
    1526
    1527                // 2) Adopt the children and datamodel
    1528                if (externalSCXML == null) {
    1529                    return;
    1530                }
    1531                State s = (State) digester.peek();
    1532                if (fragment == null) {
    1533                    // All targets pulled in since its not a src fragment
    1534                    Initial ini = new Initial();
    1535                    Transition t = new Transition();
    1536                    t.setNext(externalSCXML.getInitial());
    1537                    ini.setTransition(t);
    1538                    s.setInitial(ini);
    1539                    Map children = externalSCXML.getChildren();
    1540                    Iterator childIter = children.values().iterator();
    1541                    while (childIter.hasNext()) {
    1542                        s.addChild((TransitionTarget) childIter.next());
    1543                    }
    1544                    s.setDatamodel(externalSCXML.getDatamodel());
    1545                } else {
    1546                    // Need to pull in descendent targets
    1547                    Object source = externalSCXML.getTargets().get(fragment);
    1548                    if (source == null) {
    1549                        MessageFormat msgFormat =
    1550                            new MessageFormat(ERR_STATE_SRC_FRAGMENT);
    1551                        String errMsg = msgFormat.format(new Object[] {
    1552                            path
    1553                        });
    1554                        throw new ModelException(errMsg);
    1555                    }
    1556                    if (source instanceof State) {
    1557                        State include = (State) source;
    1558                        s.setOnEntry(include.getOnEntry());
    1559                        s.setOnExit(include.getOnExit());
    1560                        s.setDatamodel(include.getDatamodel());
    1561                        List histories = include.getHistory();
    1562                        for (int i = 0; i < histories.size(); i++) {
    1563                            History h = (History) histories.get(i);
    1564                            s.addHistory(h);
    1565                            parent.addTarget(h);
    1566                        }
    1567                        Iterator childIter = include.getChildren().values().iterator();
    1568                        while (childIter.hasNext()) {
    1569                            TransitionTarget tt = (TransitionTarget) childIter.next();
    1570                            s.addChild(tt);
    1571                            parent.addTarget(tt);
    1572                            addTargets(parent, tt);
    1573                        }
    1574                        s.setInvoke(include.getInvoke());
    1575                        s.setFinal(include.isFinal());
    1576                        if (include.getInitial() != null) {
    1577                            s.setInitial(include.getInitial());
    1578                        }
    1579                        Iterator transIter = include.getTransitionsList().iterator();
    1580                        while (transIter.hasNext()) {
    1581                            s.addTransition((Transition) transIter.next());
    1582                        }
    1583                    } else {
    1584                        MessageFormat msgFormat =
    1585                            new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET);
    1586                        String errMsg = msgFormat.format(new Object[] {
    1587                            path
    1588                        });
    1589                        throw new ModelException(errMsg);
    1590                    }
    1591                }
    1592            }
    1593
    1594            /**
    1595             * Add all the nested targets from given target to given parent state machine.
    1596             *
    1597             * @param parent The state machine
    1598             * @param tt The transition target to import
    1599             */
    1600            private static void addTargets(final SCXML parent, final TransitionTarget tt) {
    1601                Iterator histIter = tt.getHistory().iterator();
    1602                while (histIter.hasNext()) {
    1603                    History h = (History) histIter.next();
    1604                    parent.addTarget(h);
    1605                }
    1606                if (tt instanceof State) {
    1607                    Iterator childIter = ((State) tt).getChildren().values().iterator();
    1608                    while (childIter.hasNext()) {
    1609                        TransitionTarget child = (TransitionTarget) childIter.next();
    1610                        parent.addTarget(child);
    1611                        addTargets(parent, child);
    1612                    }
    1613                } else if (tt instanceof Parallel) {
    1614                    Iterator childIter = ((Parallel) tt).getChildren().iterator();
    1615                    while (childIter.hasNext()) {
    1616                        TransitionTarget child = (TransitionTarget) childIter.next();
    1617                        parent.addTarget(child);
    1618                        addTargets(parent, child);
    1619                    }
    1620                }
    1621            }
    1622        }
    1623
    1624        /**
    1625         * Custom digestion rule for setting PathResolver for runtime retrieval.
    1626         */
    1627        private static class SetPathResolverRule extends Rule {
    1628
    1629            /**
    1630             * The PathResolver to set.
    1631             * @see PathResolver
    1632             */
    1633            private PathResolver pr;
    1634
    1635            /**
    1636             * Constructor.
    1637             * @param pr The PathResolver
    1638             *
    1639             * @see PathResolver
    1640             */
    1641            SetPathResolverRule(final PathResolver pr) {
    1642                super();
    1643                this.pr = pr;
    1644            }
    1645
    1646            /**
    1647             * @see Rule#begin(String, String, Attributes)
    1648             */
    1649            public final void begin(final String namespace, final String name,
    1650                    final Attributes attributes) {
    1651                PathResolverHolder prHolder = (PathResolverHolder) getDigester().
    1652                    peek();
    1653                prHolder.setPathResolver(pr);
    1654            }
    1655        }
    1656
    1657        /**
    1658         * Custom digestion rule for setting state parent of finalize.
    1659         */
    1660        private static class UpdateFinalizeRule extends Rule {
    1661
    1662            /**
    1663             * @see Rule#begin(String, String, Attributes)
    1664             */
    1665            public final void begin(final String namespace, final String name,
    1666                    final Attributes attributes) {
    1667                Finalize finalize = (Finalize) getDigester().peek();
    1668                // state/invoke/finalize --> peek(2)
    1669                TransitionTarget tt = (TransitionTarget) getDigester().peek(2);
    1670                finalize.setParent(tt);
    1671            }
    1672        }
    1673
    1674
    1675        /**
    1676         * Custom digestion rule for attaching a snapshot of current namespaces
    1677         * to SCXML actions for deferred XPath evaluation.
    1678         */
    1679        private static class SetCurrentNamespacesRule extends Rule {
    1680
    1681            /**
    1682             * @see Rule#begin(String, String, Attributes)
    1683             */
    1684            public final void begin(final String namespace, final String name,
    1685                    final Attributes attributes) {
    1686                NamespacePrefixesHolder nsHolder =
    1687                    (NamespacePrefixesHolder) getDigester().peek();
    1688                nsHolder.setNamespaces(getDigester().getCurrentNamespaces());
    1689            }
    1690        }
    1691
    1692        /**
    1693         * Custom digestion rule logging ignored elements.
    1694         */
    1695        private static class IgnoredElementRule extends Rule {
    1696
    1697            /**
    1698             * @see Rule#begin(String, String, Attributes)
    1699             */
    1700            public final void begin(final String namespace, final String name,
    1701                    final Attributes attributes) {
    1702                org.apache.commons.logging.Log log = LogFactory.
    1703                    getLog(SCXMLParser.class);
    1704                Locator l = digester.getDocumentLocator();
    1705                String identifier = l.getSystemId();
    1706                if (identifier == null) {
    1707                    identifier = l.getPublicId();
    1708                }
    1709                StringBuffer sb = new StringBuffer();
    1710                sb.append("Ignoring element <").append(name).
    1711                    append("> in namespace \"").append(namespace).
    1712                    append("\" at ").append(identifier).append(":").
    1713                    append(l.getLineNumber()).append(":").
    1714                    append(l.getColumnNumber()).append(" and digester match \"").
    1715                    append(digester.getMatch()).append("\"");
    1716                log.warn(sb.toString());
    1717            }
    1718        }
    1719
    1720    }
    1721