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