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