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