View Javadoc

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