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