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