View Javadoc

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