View Javadoc

    1   /*
    2    *
    3    *   Copyright 2005-2006 The Apache Software Foundation.
    4    *
    5    *  Licensed under the Apache License, Version 2.0 (the "License");
    6    *  you may not use this file except in compliance with the License.
    7    *  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   */
    18  package org.apache.commons.scxml.io;
    19
    20  import java.io.IOException;
    21  import java.net.URL;
    22  import java.text.MessageFormat;
    23  import java.util.List;
    24  import java.util.Map;
    25
    26  import javax.xml.parsers.DocumentBuilder;
    27  import javax.xml.parsers.DocumentBuilderFactory;
    28  import javax.xml.parsers.ParserConfigurationException;
    29
    30  import org.apache.commons.digester.Digester;
    31  import org.apache.commons.digester.ExtendedBaseRules;
    32  import org.apache.commons.digester.NodeCreateRule;
    33  import org.apache.commons.digester.ObjectCreateRule;
    34  import org.apache.commons.digester.Rule;
    35  import org.apache.commons.digester.SetNextRule;
    36  import org.apache.commons.digester.SetPropertiesRule;
    37  import org.apache.commons.logging.LogFactory;
    38
    39  import org.apache.commons.scxml.PathResolver;
    40  import org.apache.commons.scxml.SCXMLHelper;
    41  import org.apache.commons.scxml.env.URLResolver;
    42  import org.apache.commons.scxml.model.Action;
    43  import org.apache.commons.scxml.model.Assign;
    44  import org.apache.commons.scxml.model.Cancel;
    45  import org.apache.commons.scxml.model.CustomAction;
    46  import org.apache.commons.scxml.model.Data;
    47  import org.apache.commons.scxml.model.Datamodel;
    48  import org.apache.commons.scxml.model.Else;
    49  import org.apache.commons.scxml.model.ElseIf;
    50  import org.apache.commons.scxml.model.Executable;
    51  import org.apache.commons.scxml.model.Exit;
    52  import org.apache.commons.scxml.model.ExternalContent;
    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.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     /*** Prefix for universal digester patterns. */
    648     private static final String STR_UNIVERSAL = "!*";
    649
    650     //---------------------- PRIVATE UTILITY METHODS ----------------------//
    651     /*
    652      * Private utility functions for configuring digester rule base for SCXML.
    653      */
    654     /***
    655      * Initialize the Digester rules for the current document.
    656      *
    657      * @param scxml The parent SCXML document (or null)
    658      * @param pr The PathResolver
    659      * @param customActions The list of custom actions this digester needs
    660      *                      to be able to process
    661      *
    662      * @return scxmlRules The rule set to be used for digestion
    663      */
    664     private static ExtendedBaseRules initRules(final SCXML scxml,
    665             final PathResolver pr, final List customActions) {
    666
    667         ExtendedBaseRules scxmlRules = new ExtendedBaseRules();
    668
    669         //// SCXML
    670         scxmlRules.add(XP_SM, new ObjectCreateRule(SCXML.class));
    671         scxmlRules.add(XP_SM, new SetPropertiesRule());
    672
    673         //// Datamodel at document root i.e. <scxml> datamodel
    674         addDatamodelRules(XP_SM + XPF_DM, scxmlRules, scxml, pr);
    675
    676         //// States
    677         // Level one states
    678         addStateRules(XP_SM_ST, scxmlRules, customActions, scxml, pr, 0);
    679         scxmlRules.add(XP_SM_ST, new SetNextRule("addState"));
    680         // Nested states
    681         addStateRules(XPU_ST_ST, scxmlRules, customActions, scxml, pr, 1);
    682         scxmlRules.add(XPU_ST_ST, new SetNextRule("addChild"));
    683
    684         // Parallel states
    685         addStateRules(XPU_PAR_ST, scxmlRules, customActions, scxml, pr, 1);
    686         scxmlRules.add(XPU_PAR_ST, new SetNextRule("addState"));
    687         // Target states
    688         addStateRules(XPU_TR_TAR_ST, scxmlRules, customActions, scxml, pr, 2);
    689         scxmlRules.add(XPU_TR_TAR_ST, new SetNextRule("setTarget"));
    690
    691         //// Parallels
    692         addParallelRules(XPU_ST_PAR, scxmlRules, pr, customActions, scxml);
    693
    694         //// Ifs
    695         addIfRules(XPU_IF, scxmlRules, pr, customActions);
    696
    697         //// Custom actions
    698         addCustomActionRules(XPU_ONEN, scxmlRules, customActions);
    699         addCustomActionRules(XPU_ONEX, scxmlRules, customActions);
    700         addCustomActionRules(XPU_TR, scxmlRules, customActions);
    701         addCustomActionRules(XPU_IF, scxmlRules, customActions);
    702         addCustomActionRules(XPU_FIN, scxmlRules, customActions);
    703
    704         return scxmlRules;
    705
    706     }
    707
    708     /***
    709      * Add Digester rules for all &lt;state&gt; elements.
    710      *
    711      * @param xp The Digester style XPath expression of the parent
    712      *           XML element
    713      * @param scxmlRules The rule set to be used for digestion
    714      * @param customActions The list of custom actions this digester needs
    715      *                      to be able to process
    716      * @param scxml The parent SCXML document (or null)
    717      * @param pr The PathResolver
    718      * @param parent The distance between this state and its parent
    719      *               state on the Digester stack
    720      */
    721     private static void addStateRules(final String xp,
    722             final ExtendedBaseRules scxmlRules, final List customActions,
    723             final SCXML scxml, final PathResolver pr, final int parent) {
    724         scxmlRules.add(xp, new ObjectCreateRule(State.class));
    725         addStatePropertiesRules(xp, scxmlRules, customActions, pr);
    726         addDatamodelRules(xp + XPF_DM, scxmlRules, scxml, pr);
    727         addInvokeRules(xp + XPF_INV, scxmlRules, customActions, pr, scxml);
    728         addInitialRules(xp + XPF_INI, scxmlRules, customActions, pr, scxml);
    729         addHistoryRules(xp + XPF_HIST, scxmlRules, customActions, pr, scxml);
    730         addParentRule(xp, scxmlRules, parent);
    731         addTransitionRules(xp + XPF_TR, scxmlRules, "addTransition",
    732             pr, customActions);
    733         addHandlerRules(xp, scxmlRules, pr, customActions);
    734         scxmlRules.add(xp, new UpdateModelRule(scxml));
    735     }
    736
    737     /***
    738      * Add Digester rules for all &lt;parallel&gt; elements.
    739      *
    740      * @param xp The Digester style XPath expression of the parent
    741      *           XML element
    742      * @param scxmlRules The rule set to be used for digestion
    743      * @param customActions The list of custom actions this digester needs
    744      *                      to be able to process
    745      * @param pr The {@link PathResolver} for this document
    746      * @param scxml The parent SCXML document (or null)
    747      */
    748     private static void addParallelRules(final String xp,
    749             final ExtendedBaseRules scxmlRules, final PathResolver pr,
    750             final List customActions, final SCXML scxml) {
    751         addSimpleRulesTuple(xp, scxmlRules, Parallel.class, null, null,
    752                 "setParallel");
    753         addHandlerRules(xp, scxmlRules, pr, customActions);
    754         addParentRule(xp, scxmlRules, 1);
    755         scxmlRules.add(xp, new UpdateModelRule(scxml));
    756     }
    757
    758     /***
    759      * Add Digester rules for all &lt;state&gt; element attributes.
    760      *
    761      * @param xp The Digester style XPath expression of the parent
    762      *           XML element
    763      * @param scxmlRules The rule set to be used for digestion
    764      * @param customActions The list of custom actions this digester needs
    765      *                      to be able to process
    766      * @param pr The PathResolver
    767      */
    768     private static void addStatePropertiesRules(final String xp,
    769             final ExtendedBaseRules scxmlRules, final List customActions,
    770             final PathResolver pr) {
    771         scxmlRules.add(xp, new SetPropertiesRule(new String[] {"id", "final"},
    772             new String[] {"id", "isFinal"}));
    773         scxmlRules.add(xp, new DigestSrcAttributeRule(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 SetNextRule("addData"));
    792         try {
    793             scxmlRules.add(xp + XPF_DATA, new ParseDataRule(pr));
    794         } catch (ParserConfigurationException pce) {
    795             org.apache.commons.logging.Log log = LogFactory.
    796                 getLog(SCXMLDigester.class);
    797             log.error(ERR_PARSER_CFG_DATA, pce);
    798         }
    799         scxmlRules.add(xp, new SetNextRule("setDatamodel"));
    800     }
    801
    802     /***
    803      * Add Digester rules for all &lt;invoke&gt; elements.
    804      *
    805      * @param xp The Digester style XPath expression of the parent
    806      *           XML element
    807      * @param scxmlRules The rule set to be used for digestion
    808      * @param customActions The list of {@link CustomAction}s this digester
    809      *              instance will process, can be null or empty
    810      * @param pr The PathResolver
    811      * @param scxml The parent SCXML document (or null)
    812      */
    813     private static void addInvokeRules(final String xp,
    814             final ExtendedBaseRules scxmlRules, final List customActions,
    815             final PathResolver pr, final SCXML scxml) {
    816         scxmlRules.add(xp, new ObjectCreateRule(Invoke.class));
    817         scxmlRules.add(xp, new SetPropertiesRule());
    818         scxmlRules.add(xp, new SetPathResolverRule(pr));
    819         scxmlRules.add(xp + XPF_PRM, new ObjectCreateRule(Param.class));
    820         scxmlRules.add(xp + XPF_PRM, new SetPropertiesRule());
    821         scxmlRules.add(xp + XPF_PRM, new SetNextRule("addParam"));
    822         scxmlRules.add(xp + XPF_FIN, new ObjectCreateRule(Finalize.class));
    823         scxmlRules.add(xp + XPF_FIN, new UpdateFinalizeRule());
    824         addActionRules(xp + XPF_FIN, scxmlRules, pr, customActions);
    825         scxmlRules.add(xp + XPF_FIN, new SetNextRule("setFinalize"));
    826         scxmlRules.add(xp, new SetNextRule("setInvoke"));
    827     }
    828
    829     /***
    830      * Add Digester rules for all &lt;initial&gt; elements.
    831      *
    832      * @param xp The Digester style XPath expression of the parent
    833      *           XML element
    834      * @param scxmlRules The rule set to be used for digestion
    835      * @param customActions The list of custom actions this digester needs
    836      *                      to be able to process
    837      * @param pr The PathResolver
    838      * @param scxml The parent SCXML document (or null)
    839      */
    840     private static void addInitialRules(final String xp,
    841             final ExtendedBaseRules scxmlRules, final List customActions,
    842             final PathResolver pr, final SCXML scxml) {
    843         scxmlRules.add(xp, new ObjectCreateRule(Initial.class));
    844         addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr);
    845         scxmlRules.add(xp, new UpdateModelRule(scxml));
    846         addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition",
    847             pr, customActions);
    848         scxmlRules.add(xp, new SetNextRule("setInitial"));
    849     }
    850
    851     /***
    852      * Add Digester rules for all &lt;history&gt; elements.
    853      *
    854      * @param xp The Digester style XPath expression of the parent
    855      *           XML element
    856      * @param scxmlRules The rule set to be used for digestion
    857      * @param customActions The list of custom actions this digester needs
    858      *                      to be able to process
    859      * @param pr The PathResolver
    860      * @param scxml The parent SCXML document (or null)
    861      */
    862     private static void addHistoryRules(final String xp,
    863             final ExtendedBaseRules scxmlRules, final List customActions,
    864             final PathResolver pr, final SCXML scxml) {
    865         scxmlRules.add(xp, new ObjectCreateRule(History.class));
    866         addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr);
    867         scxmlRules.add(xp, new UpdateModelRule(scxml));
    868         scxmlRules.add(xp, new SetPropertiesRule(new String[] {"type"},
    869             new String[] {"type"}));
    870         addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition",
    871             pr, customActions);
    872         scxmlRules.add(xp, new SetNextRule("addHistory"));
    873     }
    874
    875     /***
    876      * Add Digester rules for all pseudo state (initial, history) element
    877      * attributes.
    878      *
    879      * @param xp The Digester style XPath expression of the parent
    880      *           XML element
    881      * @param scxmlRules The rule set to be used for digestion
    882      * @param customActions The list of custom actions this digester needs
    883      *                      to be able to process
    884      * @param pr The PathResolver
    885      */
    886     private static void addPseudoStatePropertiesRules(final String xp,
    887             final ExtendedBaseRules scxmlRules, final List customActions,
    888             final PathResolver pr) {
    889         scxmlRules.add(xp, new SetPropertiesRule(new String[] {"id"},
    890             new String[] {"id"}));
    891         scxmlRules.add(xp, new DigestSrcAttributeRule(customActions, pr));
    892         addParentRule(xp, scxmlRules, 1);
    893     }
    894
    895     /***
    896      * Add Digester rule for all setting parent state.
    897      *
    898      * @param xp The Digester style XPath expression of the parent
    899      *           XML element
    900      * @param scxmlRules The rule set to be used for digestion
    901      * @param parent The distance between this state and its parent
    902      *               state on the Digester stack
    903      */
    904     private static void addParentRule(final String xp,
    905             final ExtendedBaseRules scxmlRules, final int parent) {
    906         if (parent < 1) {
    907             return;
    908         }
    909         scxmlRules.add(xp, new Rule() {
    910             // A generic version of setTopRule
    911             public void body(final String namespace, final String name,
    912                     final String text) throws Exception {
    913                 TransitionTarget t = (TransitionTarget) getDigester().peek();
    914                 TransitionTarget p = (TransitionTarget) getDigester().peek(
    915                         parent);
    916                 // CHANGE - Moved parent property to TransitionTarget
    917                 t.setParent(p);
    918             }
    919         });
    920     }
    921
    922     /***
    923      * Add Digester rules for all &lt;transition&gt; elements.
    924      *
    925      * @param xp The Digester style XPath expression of the parent
    926      *           XML element
    927      * @param scxmlRules The rule set to be used for digestion
    928      * @param setNextMethod The method name for adding this transition
    929      *             to its parent (defined by the SCXML Java object model).
    930      * @param pr The {@link PathResolver} for this document
    931      * @param customActions The list of custom actions this digester needs
    932      *                      to be able to process
    933      */
    934     private static void addTransitionRules(final String xp,
    935             final ExtendedBaseRules scxmlRules, final String setNextMethod,
    936             final PathResolver pr, final List customActions) {
    937         scxmlRules.add(xp, new ObjectCreateRule(Transition.class));
    938         scxmlRules.add(xp, new SetPropertiesRule(
    939             new String[] {"event", "cond", "target"},
    940             new String[] {"event", "cond", "next"}));
    941         scxmlRules.add(xp + XPF_TAR, new SetPropertiesRule());
    942         addActionRules(xp, scxmlRules, pr, customActions);
    943         scxmlRules.add(xp + XPF_EXT, new Rule() {
    944             public void end(final String namespace, final String name) {
    945                 Transition t = (Transition) getDigester().peek(1);
    946                 State exitState = new State();
    947                 exitState.setIsFinal(true);
    948                 t.setTarget(exitState);
    949             }
    950         });
    951         scxmlRules.add(xp, new SetNextRule(setNextMethod));
    952     }
    953
    954     /***
    955      * Add Digester rules for all &lt;onentry&gt; and &lt;onexit&gt;
    956      * elements.
    957      *
    958      * @param xp The Digester style XPath expression of the parent
    959      *           XML element
    960      * @param scxmlRules The rule set to be used for digestion
    961      * @param pr The {@link PathResolver} for this document
    962      * @param customActions The list of custom actions this digester needs
    963      *                      to be able to process
    964      */
    965     private static void addHandlerRules(final String xp,
    966             final ExtendedBaseRules scxmlRules, final PathResolver pr,
    967             final List customActions) {
    968         scxmlRules.add(xp + XPF_ONEN, new ObjectCreateRule(OnEntry.class));
    969         addActionRules(xp + XPF_ONEN, scxmlRules, pr, customActions);
    970         scxmlRules.add(xp + XPF_ONEN, new SetNextRule("setOnEntry"));
    971         scxmlRules.add(xp + XPF_ONEX, new ObjectCreateRule(OnExit.class));
    972         addActionRules(xp + XPF_ONEX, scxmlRules, pr, customActions);
    973         scxmlRules.add(xp + XPF_ONEX, new SetNextRule("setOnExit"));
    974     }
    975
    976     /***
    977      * Add Digester rules for all actions (&quot;executable&quot; elements).
    978      *
    979      * @param xp The Digester style XPath expression of the parent
    980      *           XML element
    981      * @param scxmlRules The rule set to be used for digestion
    982      * @param pr The {@link PathResolver} for this document
    983      * @param customActions The list of custom actions this digester needs
    984      *                      to be able to process
    985      */
    986     private static void addActionRules(final String xp,
    987             final ExtendedBaseRules scxmlRules, final PathResolver pr,
    988             final List customActions) {
    989         addActionRulesTuple(xp + XPF_ASN, scxmlRules, Assign.class);
    990         scxmlRules.add(xp + XPF_ASN, new SetPathResolverRule(pr));
    991         addActionRulesTuple(xp + XPF_VAR, scxmlRules, Var.class);
    992         addActionRulesTuple(xp + XPF_LOG, scxmlRules, Log.class);
    993         addSendRulesTuple(xp + XPF_SND, scxmlRules);
    994         addActionRulesTuple(xp + XPF_CAN, scxmlRules, Cancel.class);
    995         addActionRulesTuple(xp + XPF_EXT, scxmlRules, Exit.class);
    996         //addCustomActionRules(xp, scxmlRules, customActions);
    997     }
    998
    999     /***
    1000      * Add custom action rules, if any custom actions are provided.
    1001      *
    1002      * @param xp The Digester style XPath expression of the parent
    1003      *           XML element
    1004      * @param scxmlRules The rule set to be used for digestion
    1005      * @param customActions The list of custom actions this digester needs
    1006      *                      to be able to process
    1007      */
    1008     private static void addCustomActionRules(final String xp,
    1009             final ExtendedBaseRules scxmlRules, final List customActions) {
    1010         if (customActions == null || customActions.size() == 0) {
    1011             return;
    1012         }
    1013         for (int i = 0; i < customActions.size(); i++) {
    1014             Object item = customActions.get(i);
    1015             if (item == null || !(item instanceof CustomAction)) {
    1016                 org.apache.commons.logging.Log log = LogFactory.
    1017                     getLog(SCXMLDigester.class);
    1018                 log.warn(ERR_CUSTOM_ACTION_TYPE);
    1019             } else {
    1020                 CustomAction ca = (CustomAction) item;
    1021                 scxmlRules.setNamespaceURI(ca.getNamespaceURI());
    1022                 String xpfLocalName = STR_SLASH + ca.getLocalName();
    1023                 Class klass = ca.getActionClass();
    1024                 if (SCXMLHelper.implementationOf(klass,
    1025                         ExternalContent.class)) {
    1026                     addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules,
    1027                         klass, true);
    1028                 } else {
    1029                     addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules,
    1030                         klass, false);
    1031                 }
    1032             }
    1033         }
    1034         scxmlRules.setNamespaceURI(NAMESPACE_SCXML);
    1035     }
    1036
    1037     /***
    1038      * Add Digester rules that are specific to the &lt;send&gt; action
    1039      * element.
    1040      *
    1041      * @param xp The Digester style XPath expression of &lt;send&gt; element
    1042      * @param scxmlRules The rule set to be used for digestion
    1043      */
    1044     private static void addSendRulesTuple(final String xp,
    1045             final ExtendedBaseRules scxmlRules) {
    1046         addActionRulesTuple(xp, scxmlRules, Send.class);
    1047         try {
    1048             scxmlRules.add(xp, new ParseExternalContentRule());
    1049         } catch (ParserConfigurationException pce) {
    1050             org.apache.commons.logging.Log log = LogFactory.
    1051                 getLog(SCXMLDigester.class);
    1052             log.error(ERR_PARSER_CFG_SEND, pce);
    1053         }
    1054     }
    1055
    1056     /***
    1057      * Add Digester rules for a simple custom action (no body content).
    1058      *
    1059      * @param xp The path to the custom action element
    1060      * @param scxmlRules The rule set to be used for digestion
    1061      * @param klass The <code>Action</code> class implementing the custom
    1062      *              action.
    1063      * @param bodyContent Whether the custom rule has body content
    1064      *              that should be parsed using
    1065      *              <code>NodeCreateRule</code>
    1066      */
    1067     private static void addCustomActionRulesTuple(final String xp,
    1068             final ExtendedBaseRules scxmlRules, final Class klass,
    1069             final boolean bodyContent) {
    1070         addActionRulesTuple(xp, scxmlRules, klass);
    1071         if (bodyContent) {
    1072             try {
    1073                 scxmlRules.add(xp, new ParseExternalContentRule());
    1074             } catch (ParserConfigurationException pce) {
    1075                 org.apache.commons.logging.Log log = LogFactory.
    1076                     getLog(SCXMLDigester.class);
    1077                 log.error(ERR_PARSER_CFG_CUSTOM, pce);
    1078             }
    1079         }
    1080     }
    1081
    1082     /***
    1083      * Add Digester rules for all &lt;if&gt; elements.
    1084      *
    1085      * @param xp The Digester style XPath expression of the parent
    1086      *           XML element
    1087      * @param scxmlRules The rule set to be used for digestion
    1088      * @param pr The {@link PathResolver} for this document
    1089      * @param customActions The list of custom actions this digester needs
    1090      *                      to be able to process
    1091      */
    1092     private static void addIfRules(final String xp,
    1093             final ExtendedBaseRules scxmlRules, final PathResolver pr,
    1094             final List customActions) {
    1095         addActionRulesTuple(xp, scxmlRules, If.class);
    1096         addActionRules(xp, scxmlRules, pr, customActions);
    1097         addActionRulesTuple(xp + XPF_EIF, scxmlRules, ElseIf.class);
    1098         addActionRulesTuple(xp + XPF_ELS, scxmlRules, Else.class);
    1099     }
    1100
    1101     /***
    1102      * Add Digester rules that are common across all actions elements.
    1103      *
    1104      * @param xp The Digester style XPath expression of the parent
    1105      *           XML element
    1106      * @param scxmlRules The rule set to be used for digestion
    1107      * @param klass The class in the Java object model to be instantiated
    1108      *              in the ObjectCreateRule for this action
    1109      */
    1110     private static void addActionRulesTuple(final String xp,
    1111             final ExtendedBaseRules scxmlRules, final Class klass) {
    1112         addSimpleRulesTuple(xp, scxmlRules, klass, null, null, "addAction");
    1113         scxmlRules.add(xp, new SetExecutableParentRule());
    1114     }
    1115
    1116     /***
    1117      * Add the run of the mill Digester rules for any element.
    1118      *
    1119      * @param xp The Digester style XPath expression of the parent
    1120      *           XML element
    1121      * @param scxmlRules The rule set to be used for digestion
    1122      * @param klass The class in the Java object model to be instantiated
    1123      *              in the ObjectCreateRule for this action
    1124      * @param args The attributes to be mapped into the object model
    1125      * @param props The properties that args get mapped to
    1126      * @param addMethod The method that the SetNextRule should call
    1127      */
    1128     private static void addSimpleRulesTuple(final String xp,
    1129             final ExtendedBaseRules scxmlRules, final Class klass,
    1130             final String[] args, final String[] props,
    1131             final String addMethod) {
    1132         scxmlRules.add(xp, new ObjectCreateRule(klass));
    1133         if (args == null) {
    1134             scxmlRules.add(xp, new SetPropertiesRule());
    1135         } else {
    1136             scxmlRules.add(xp, new SetPropertiesRule(args, props));
    1137         }
    1138         scxmlRules.add(xp, new SetNextRule(addMethod));
    1139     }
    1140
    1141     /***
    1142      * Discourage instantiation since this is a utility class.
    1143      */
    1144     private SCXMLDigester() {
    1145         super();
    1146     }
    1147
    1148     /***
    1149      * Custom digestion rule for establishing necessary associations of this
    1150      * TransitionTarget with the root SCXML object.
    1151      * These include: <br>
    1152      * 1) Updation of the SCXML object's global targets Map <br>
    1153      * 2) Obtaining a handle to the SCXML object's NotificationRegistry <br>
    1154      *
    1155      */
    1156     public static class UpdateModelRule extends Rule {
    1157
    1158         /***
    1159          * The root SCXML object.
    1160          */
    1161         private SCXML scxml;
    1162
    1163         /***
    1164          * Constructor.
    1165          * @param scxml The root SCXML object
    1166          */
    1167         public UpdateModelRule(final SCXML scxml) {
    1168             super();
    1169             this.scxml = scxml;
    1170         }
    1171
    1172         /***
    1173          * @see Rule#end(String, String)
    1174          */
    1175         public final void end(final String namespace, final String name) {
    1176             if (scxml == null) {
    1177                 scxml = (SCXML) getDigester()
    1178                         .peek(getDigester().getCount() - 1);
    1179             }
    1180             TransitionTarget tt = (TransitionTarget) getDigester().peek();
    1181             scxml.addTarget(tt);
    1182         }
    1183     }
    1184
    1185     /***
    1186      * Custom digestion rule for setting Executable parent of Action elements.
    1187      *
    1188      */
    1189     public static class SetExecutableParentRule extends Rule {
    1190
    1191         /***
    1192          * Constructor.
    1193          */
    1194         public SetExecutableParentRule() {
    1195             super();
    1196         }
    1197
    1198         /***
    1199          * @see Rule#end(String, String)
    1200          */
    1201         public final void end(final String namespace, final String name) {
    1202             Action child = (Action) getDigester().peek();
    1203             for (int i = 1; i < getDigester().getCount() - 1; i++) {
    1204                 Object ancestor = getDigester().peek(i);
    1205                 if (ancestor instanceof Executable) {
    1206                     child.setParent((Executable) ancestor);
    1207                     return;
    1208                 }
    1209             }
    1210         }
    1211     }
    1212
    1213     /***
    1214      * Custom digestion rule for parsing bodies of
    1215      * <code>ExternalContent</code> elements.
    1216      *
    1217      * @see ExternalContent
    1218      */
    1219     public static class ParseExternalContentRule extends NodeCreateRule {
    1220         /***
    1221          * Constructor.
    1222          * @throws ParserConfigurationException A JAXP configuration error
    1223          */
    1224         public ParseExternalContentRule()
    1225         throws ParserConfigurationException {
    1226             super();
    1227         }
    1228         /***
    1229          * @see Rule#end(String, String)
    1230          */
    1231         public final void end(final String namespace, final String name) {
    1232             Element bodyElement = (Element) getDigester().pop();
    1233             NodeList childNodes = bodyElement.getChildNodes();
    1234             List externalNodes = ((ExternalContent) getDigester().
    1235                 peek()).getExternalNodes();
    1236             for (int i = 0; i < childNodes.getLength(); i++) {
    1237                 externalNodes.add(childNodes.item(i));
    1238             }
    1239         }
    1240     }
    1241
    1242     /***
    1243      * Custom digestion rule for parsing bodies of &lt;data&gt; elements.
    1244      *
    1245      */
    1246     public static class ParseDataRule extends NodeCreateRule {
    1247
    1248         /***
    1249          * The PathResolver used to resolve the src attribute to the
    1250          * SCXML document it points to.
    1251          * @see PathResolver
    1252          */
    1253         private PathResolver pr;
    1254
    1255         /***
    1256          * The "src" attribute, retained to check if body content is legal.
    1257          */
    1258         private String src;
    1259
    1260         /***
    1261          * The "expr" attribute, retained to check if body content is legal.
    1262          */
    1263         private String expr;
    1264
    1265         /***
    1266          * The XML tree for this data, parse as a Node, obtained from
    1267          * either the "src" or the "expr" attributes.
    1268          */
    1269         private Node attrNode;
    1270
    1271         /***
    1272          * Constructor.
    1273          *
    1274          * @param pr The <code>PathResolver</code>
    1275          * @throws ParserConfigurationException A JAXP configuration error
    1276          */
    1277         public ParseDataRule(final PathResolver pr)
    1278         throws ParserConfigurationException {
    1279             super();
    1280             this.pr = pr;
    1281         }
    1282
    1283         /***
    1284          * @see Rule#begin(String, String, Attributes)
    1285          */
    1286         public final void begin(final String namespace, final String name,
    1287                 final Attributes attributes) throws Exception {
    1288             super.begin(namespace, name, attributes);
    1289             src = attributes.getValue("src");
    1290             expr = attributes.getValue("expr");
    1291             if (!SCXMLHelper.isStringEmpty(src)) {
    1292                 String path = null;
    1293                 if (pr == null) {
    1294                     path = src;
    1295                 } else {
    1296                     path = pr.resolvePath(src);
    1297                 }
    1298                 try {
    1299                     DocumentBuilderFactory dbFactory = DocumentBuilderFactory.
    1300                         newInstance();
    1301                     DocumentBuilder db = dbFactory.newDocumentBuilder();
    1302                     attrNode = db.parse(path);
    1303                 } catch (Throwable t) { // you read that correctly
    1304                     org.apache.commons.logging.Log log = LogFactory.
    1305                         getLog(SCXMLDigester.class);
    1306                     log.error(t.getMessage(), t);
    1307                 }
    1308                 return;
    1309             }
    1310         }
    1311
    1312         /***
    1313          * @see Rule#end(String, String)
    1314          */
    1315         public final void end(final String namespace, final String name) {
    1316             Node bodyNode = (Node) getDigester().pop();
    1317             Data data = ((Data) getDigester().peek());
    1318             // Prefer "src" over "expr", "expr" over child nodes
    1319             // "expr" can only be evaluated at execution time
    1320             if (!SCXMLHelper.isStringEmpty(src)) {
    1321                 data.setNode(attrNode);
    1322             } else  if (SCXMLHelper.isStringEmpty(expr)) {
    1323                 // both "src" and "expr" are empty
    1324                 data.setNode(bodyNode);
    1325             }
    1326         }
    1327     }
    1328
    1329     /***
    1330      * Custom digestion rule for external sources, that is, the src attribute of
    1331      * the &lt;state&gt; element.
    1332      *
    1333      */
    1334     public static class DigestSrcAttributeRule extends Rule {
    1335
    1336         /***
    1337          * The PathResolver used to resolve the src attribute to the
    1338          * SCXML document it points to.
    1339          * @see PathResolver
    1340          */
    1341         private PathResolver pr;
    1342
    1343         /***
    1344          * The list of custom actions the parent document is capable of
    1345          * processing (and hence, the child should be, by transitivity).
    1346          * @see CustomAction
    1347          */
    1348         private List customActions;
    1349
    1350         /***
    1351          * Constructor.
    1352          * @param pr The PathResolver
    1353          * @param customActions The list of custom actions this digester needs
    1354          *                      to be able to process
    1355          *
    1356          * @see PathResolver
    1357          * @see CustomAction
    1358          */
    1359         public DigestSrcAttributeRule(final List customActions,
    1360                 final PathResolver pr) {
    1361             super();
    1362             this.customActions = customActions;
    1363             this.pr = pr;
    1364         }
    1365
    1366         /***
    1367          * @see Rule#begin(String, String, Attributes)
    1368          */
    1369         public final void begin(final String namespace, final String name,
    1370                 final Attributes attributes) {
    1371             String src = attributes.getValue("src");
    1372             if (SCXMLHelper.isStringEmpty(src)) {
    1373                 return;
    1374             }
    1375             Digester digester = getDigester();
    1376             SCXML scxml = (SCXML) digester.peek(digester.getCount() - 1);
    1377             // 1) Digest the external SCXML file
    1378             SCXML externalSCXML = null;
    1379             String path;
    1380             Digester externalSrcDigester;
    1381             if (pr == null) {
    1382                 path = src;
    1383                 externalSrcDigester = newInstance(scxml, null, customActions);
    1384             } else {
    1385                 path = pr.resolvePath(src);
    1386                 externalSrcDigester = newInstance(scxml, pr.getResolver(src),
    1387                     customActions);
    1388             }
    1389
    1390             try {
    1391                 externalSCXML = (SCXML) externalSrcDigester.parse(path);
    1392             } catch (Exception e) {
    1393                 org.apache.commons.logging.Log log = LogFactory.
    1394                     getLog(SCXMLDigester.class);
    1395                 log.error(e.getMessage(), e);
    1396             }
    1397             // 2) Adopt the children and datamodel
    1398             if (externalSCXML == null) {
    1399                 return;
    1400             }
    1401             State s = (State) digester.peek();
    1402             Transition t = new Transition();
    1403             t.setNext(externalSCXML.getInitialstate());
    1404             Initial ini = new Initial();
    1405             ini.setTransition(t);
    1406             s.setInitial(ini);
    1407             Map children = externalSCXML.getStates();
    1408             Object[] ids = children.keySet().toArray();
    1409             for (int i = 0; i < ids.length; i++) {
    1410                 s.addChild((State) children.get(ids[i]));
    1411             }
    1412             s.setDatamodel(externalSCXML.getDatamodel());
    1413         }
    1414     }
    1415
    1416     /***
    1417      * Custom digestion rule for setting PathResolver for runtime retrieval.
    1418      *
    1419      */
    1420     public static class SetPathResolverRule extends Rule {
    1421
    1422         /***
    1423          * The PathResolver to set.
    1424          * @see PathResolver
    1425          */
    1426         private PathResolver pr;
    1427
    1428         /***
    1429          * Constructor.
    1430          * @param pr The PathResolver
    1431          *
    1432          * @see PathResolver
    1433          */
    1434         public SetPathResolverRule(final PathResolver pr) {
    1435             super();
    1436             this.pr = pr;
    1437         }
    1438
    1439         /***
    1440          * @see Rule#begin(String, String, Attributes)
    1441          */
    1442         public final void begin(final String namespace, final String name,
    1443                 final Attributes attributes) {
    1444             PathResolverHolder prHolder = (PathResolverHolder) getDigester().
    1445                 peek();
    1446             prHolder.setPathResolver(pr);
    1447         }
    1448     }
    1449
    1450     /***
    1451      * Custom digestion rule for setting state parent of finalize.
    1452      *
    1453      */
    1454     public static class UpdateFinalizeRule extends Rule {
    1455
    1456         /***
    1457          * @see Rule#begin(String, String, Attributes)
    1458          */
    1459         public final void begin(final String namespace, final String name,
    1460                 final Attributes attributes) {
    1461             Finalize finalize = (Finalize) getDigester().peek();
    1462             // state/invoke/finalize --> peek(2)
    1463             TransitionTarget tt = (TransitionTarget) getDigester().peek(2);
    1464             finalize.setParent(tt);
    1465         }
    1466     }
    1467
    1468 }
    1469