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