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