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