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