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