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