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