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