View Javadoc

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