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