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