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 */ 017package org.apache.commons.scxml2.io; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.Reader; 022import java.lang.reflect.InvocationTargetException; 023import java.lang.reflect.Method; 024import java.net.URL; 025import java.net.URLConnection; 026import java.text.MessageFormat; 027import java.util.ArrayList; 028import java.util.EmptyStackException; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.Stack; 033 034import javax.xml.parsers.DocumentBuilderFactory; 035import javax.xml.parsers.ParserConfigurationException; 036import javax.xml.stream.XMLInputFactory; 037import javax.xml.stream.XMLReporter; 038import javax.xml.stream.XMLResolver; 039import javax.xml.stream.XMLStreamConstants; 040import javax.xml.stream.XMLStreamException; 041import javax.xml.stream.XMLStreamReader; 042import javax.xml.stream.util.XMLEventAllocator; 043import javax.xml.transform.Source; 044import javax.xml.transform.stream.StreamSource; 045import javax.xml.validation.Schema; 046import javax.xml.validation.SchemaFactory; 047import javax.xml.validation.Validator; 048 049import org.apache.commons.logging.LogFactory; 050import org.apache.commons.scxml2.Evaluator; 051import org.apache.commons.scxml2.PathResolver; 052import org.apache.commons.scxml2.env.SimpleErrorHandler; 053import org.apache.commons.scxml2.env.URLResolver; 054import org.apache.commons.scxml2.model.Action; 055import org.apache.commons.scxml2.model.ActionsContainer; 056import org.apache.commons.scxml2.model.Assign; 057import org.apache.commons.scxml2.model.Cancel; 058import org.apache.commons.scxml2.model.Content; 059import org.apache.commons.scxml2.model.ContentContainer; 060import org.apache.commons.scxml2.model.CustomAction; 061import org.apache.commons.scxml2.model.Data; 062import org.apache.commons.scxml2.model.Datamodel; 063import org.apache.commons.scxml2.model.Else; 064import org.apache.commons.scxml2.model.ElseIf; 065import org.apache.commons.scxml2.model.EnterableState; 066import org.apache.commons.scxml2.model.Executable; 067import org.apache.commons.scxml2.model.ExternalContent; 068import org.apache.commons.scxml2.model.Final; 069import org.apache.commons.scxml2.model.Finalize; 070import org.apache.commons.scxml2.model.Foreach; 071import org.apache.commons.scxml2.model.History; 072import org.apache.commons.scxml2.model.If; 073import org.apache.commons.scxml2.model.Initial; 074import org.apache.commons.scxml2.model.Invoke; 075import org.apache.commons.scxml2.model.Log; 076import org.apache.commons.scxml2.model.ModelException; 077import org.apache.commons.scxml2.model.NamespacePrefixesHolder; 078import org.apache.commons.scxml2.model.OnEntry; 079import org.apache.commons.scxml2.model.OnExit; 080import org.apache.commons.scxml2.model.Parallel; 081import org.apache.commons.scxml2.model.Param; 082import org.apache.commons.scxml2.model.ParamsContainer; 083import org.apache.commons.scxml2.model.Raise; 084import org.apache.commons.scxml2.model.SCXML; 085import org.apache.commons.scxml2.model.Script; 086import org.apache.commons.scxml2.model.Send; 087import org.apache.commons.scxml2.model.SimpleTransition; 088import org.apache.commons.scxml2.model.State; 089import org.apache.commons.scxml2.model.Transition; 090import org.apache.commons.scxml2.model.TransitionType; 091import org.apache.commons.scxml2.model.TransitionalState; 092import org.apache.commons.scxml2.model.Var; 093import org.w3c.dom.Attr; 094import org.w3c.dom.Document; 095import org.w3c.dom.Element; 096import org.w3c.dom.Node; 097import org.w3c.dom.NodeList; 098import org.xml.sax.SAXException; 099 100/** 101 * <p>The SCXMLReader provides the ability to read a SCXML document into 102 * the Java object model provided in the model package.</p> 103 * 104 * <p>See latest version of the SCXML Working Draft for more details.</p> 105 * 106 * <p><b>NOTE:</b> The SCXMLReader assumes that the SCXML document to be 107 * parsed is well-formed and correct. If that assumption does not hold, 108 * any subsequent behavior is undefined.</p> 109 * 110 * @since 1.0 111 */ 112public final class SCXMLReader { 113 114 //---------------------- PRIVATE CONSTANTS ----------------------// 115 //---- NAMESPACES ----// 116 /** 117 * The SCXML namespace that this Reader is built for. Any document 118 * that is intended to be parsed by this reader <b>must</b> 119 * bind the SCXML elements to this namespace. 120 */ 121 private static final String XMLNS_SCXML = 122 "http://www.w3.org/2005/07/scxml"; 123 124 /** 125 * The namespace that defines any custom actions defined by the Commons 126 * SCXML implementation. Any document that intends to use these custom 127 * actions needs to ensure that they are in the correct namespace. Use 128 * of actions in this namespace makes the document non-portable across 129 * implementations. 130 */ 131 private static final String XMLNS_COMMONS_SCXML = 132 "http://commons.apache.org/scxml"; 133 134 /** 135 * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1 136 */ 137 private static final String SCXML_REQUIRED_VERSION = "1.0"; 138 /** 139 * The default namespace for attributes. 140 */ 141 private static final String XMLNS_DEFAULT = null; 142 143 //---- ERROR MESSAGES ----// 144 /** 145 * Null URL passed as argument. 146 */ 147 private static final String ERR_NULL_URL = "Cannot parse null URL"; 148 149 /** 150 * Null path passed as argument. 151 */ 152 private static final String ERR_NULL_PATH = "Cannot parse null path"; 153 154 /** 155 * Null InputStream passed as argument. 156 */ 157 private static final String ERR_NULL_ISTR = "Cannot parse null InputStream"; 158 159 /** 160 * Null Reader passed as argument. 161 */ 162 private static final String ERR_NULL_READ = "Cannot parse null Reader"; 163 164 /** 165 * Null Source passed as argument. 166 */ 167 private static final String ERR_NULL_SRC = "Cannot parse null Source"; 168 169 /** 170 * Error message while attempting to define a custom action which does 171 * not extend the Commons SCXML Action base class. 172 */ 173 private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list" 174 + " contained unknown object, class not a Commons SCXML Action class subtype: "; 175 176 /** 177 * Parser configuration error while trying to parse stream to DOM node(s). 178 */ 179 private static final String ERR_PARSER_CFG = "ParserConfigurationException while trying" 180 + " to parse stream into DOM node(s)."; 181 182 /** 183 * Error message when the URI in a <state>'s "src" 184 * attribute does not point to a valid SCXML document, and thus cannot be 185 * parsed. 186 */ 187 private static final String ERR_STATE_SRC = 188 "Source attribute in <state src=\"{0}\"> cannot be parsed"; 189 190 /** 191 * Error message when the target of the URI fragment in a <state>'s 192 * "src" attribute is not defined in the referenced document. 193 */ 194 private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in " 195 + "<state src=\"{0}\"> is an unknown state in referenced document"; 196 197 /** 198 * Error message when the target of the URI fragment in a <state>'s 199 * "src" attribute is not a <state> or <final> in 200 * the referenced document. 201 */ 202 private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment" 203 + " in <state src=\"{0}\"> does not point to a <state> or <final>"; 204 205 /** 206 * Error message when the target of the URI fragment in a <state>'s 207 * "src" attribute is not a <state> or <final> in 208 * the referenced document. 209 */ 210 private static final String ERR_REQUIRED_ATTRIBUTE_MISSING = "<{0}> is missing" 211 +" required attribute \"{1}\" value at {2}"; 212 213 /** 214 * Error message when the target of the URI fragment in a <state>'s 215 * "src" attribute is not a <state> or <final> in 216 * the referenced document. 217 */ 218 private static final String ERR_ATTRIBUTE_NOT_BOOLEAN = "Illegal value \"{0}\"" 219 + "for attribute \"{1}\" in element <{2}> at {3}." 220 +" Only the value \"true\" or \"false\" is allowed."; 221 222 /** 223 * Error message when the element (state|parallel|final|history) uses an id value 224 * with the reserved prefix {@link SCXML#GENERATED_TT_ID_PREFIX}. 225 */ 226 private static final String ERR_RESERVED_ID_PREFIX = "Reserved id prefix \"" 227 +SCXML.GENERATED_TT_ID_PREFIX+"\" used for <{0} id=\"{1}\"> at {2}"; 228 229 /** 230 * Error message when the target of the URI fragment in a <state>'s 231 * "src" attribute is not defined in the referenced document. 232 */ 233 private static final String ERR_UNSUPPORTED_TRANSITION_TYPE = "Unsupported transition type " 234 + "for <transition type=\"{0}\"> at {1}."; 235 236 /** 237 * Error message when the target of the URI fragment in a <state>'s 238 * "src" attribute is not a <state> or <final> in 239 * the referenced document. 240 */ 241 private static final String ERR_INVALID_VERSION = "The <scxml> element defines" 242 +" an unsupported version \"{0}\", only version \"1.0\" is supported."; 243 244 //--------------------------- XML VOCABULARY ---------------------------// 245 //---- ELEMENT NAMES ----// 246 private static final String ELEM_ASSIGN = "assign"; 247 private static final String ELEM_CANCEL = "cancel"; 248 private static final String ELEM_CONTENT = "content"; 249 private static final String ELEM_DATA = "data"; 250 private static final String ELEM_DATAMODEL = "datamodel"; 251 private static final String ELEM_ELSE = "else"; 252 private static final String ELEM_ELSEIF = "elseif"; 253 private static final String ELEM_RAISE = "raise"; 254 private static final String ELEM_FINAL = "final"; 255 private static final String ELEM_FINALIZE = "finalize"; 256 private static final String ELEM_HISTORY = "history"; 257 private static final String ELEM_IF = "if"; 258 private static final String ELEM_INITIAL = "initial"; 259 private static final String ELEM_INVOKE = "invoke"; 260 private static final String ELEM_FOREACH = "foreach"; 261 private static final String ELEM_LOG = "log"; 262 private static final String ELEM_ONENTRY = "onentry"; 263 private static final String ELEM_ONEXIT = "onexit"; 264 private static final String ELEM_PARALLEL = "parallel"; 265 private static final String ELEM_PARAM = "param"; 266 private static final String ELEM_SCRIPT = "script"; 267 private static final String ELEM_SCXML = "scxml"; 268 private static final String ELEM_SEND = "send"; 269 private static final String ELEM_STATE = "state"; 270 private static final String ELEM_TRANSITION = "transition"; 271 private static final String ELEM_VAR = "var"; 272 273 //---- ATTRIBUTE NAMES ----// 274 private static final String ATTR_ARRAY = "array"; 275 private static final String ATTR_ATTR = "attr"; 276 private static final String ATTR_AUTOFORWARD = "autoforward"; 277 private static final String ATTR_COND = "cond"; 278 private static final String ATTR_DATAMODEL = "datamodel"; 279 private static final String ATTR_DELAY = "delay"; 280 private static final String ATTR_DELAYEXPR = "delayexpr"; 281 private static final String ATTR_EVENT = "event"; 282 private static final String ATTR_EVENTEXPR = "eventexpr"; 283 private static final String ATTR_EXMODE = "exmode"; 284 private static final String ATTR_EXPR = "expr"; 285 private static final String ATTR_HINTS = "hints"; 286 private static final String ATTR_ID = "id"; 287 private static final String ATTR_IDLOCATION = "idlocation"; 288 private static final String ATTR_INDEX = "index"; 289 private static final String ATTR_INITIAL = "initial"; 290 private static final String ATTR_ITEM = "item"; 291 private static final String ATTR_LABEL = "label"; 292 private static final String ATTR_LOCATION = "location"; 293 private static final String ATTR_NAME = "name"; 294 private static final String ATTR_NAMELIST = "namelist"; 295 private static final String ATTR_PROFILE = "profile"; 296 private static final String ATTR_SENDID = "sendid"; 297 private static final String ATTR_SENDIDEXPR = "sendidexpr"; 298 private static final String ATTR_SRC = "src"; 299 private static final String ATTR_SRCEXPR = "srcexpr"; 300 private static final String ATTR_TARGET = "target"; 301 private static final String ATTR_TARGETEXPR = "targetexpr"; 302 private static final String ATTR_TYPE = "type"; 303 private static final String ATTR_TYPEEXPR = "typeexpr"; 304 private static final String ATTR_VERSION = "version"; 305 306 //------------------------- PUBLIC API METHODS -------------------------// 307 /* 308 * Public methods 309 */ 310 /** 311 * Parse the SCXML document at the supplied path. 312 * 313 * @param scxmlPath The real path to the SCXML document. 314 * 315 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 316 * 317 * @throws IOException An IO error during parsing. 318 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 319 * errors in the SCXML document that may not be identified by the schema). 320 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 321 */ 322 public static SCXML read(final String scxmlPath) 323 throws IOException, ModelException, XMLStreamException { 324 325 return read(scxmlPath, new Configuration()); 326 } 327 328 /** 329 * Parse the SCXML document at the supplied path with the given {@link Configuration}. 330 * 331 * @param scxmlPath The real path to the SCXML document. 332 * @param configuration The {@link Configuration} to use when parsing the SCXML document. 333 * 334 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 335 * 336 * @throws IOException An IO error during parsing. 337 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 338 * errors in the SCXML document that may not be identified by the schema). 339 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 340 */ 341 public static SCXML read(final String scxmlPath, final Configuration configuration) 342 throws IOException, ModelException, XMLStreamException { 343 344 if (scxmlPath == null) { 345 throw new IllegalArgumentException(ERR_NULL_PATH); 346 } 347 SCXML scxml = readInternal(configuration, null, scxmlPath, null, null, null); 348 if (scxml != null) { 349 ModelUpdater.updateSCXML(scxml); 350 } 351 return scxml; 352 } 353 354 /** 355 * Parse the SCXML document at the supplied {@link URL}. 356 * 357 * @param scxmlURL The SCXML document {@link URL} to parse. 358 * 359 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 360 * 361 * @throws IOException An IO error during parsing. 362 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 363 * errors in the SCXML document that may not be identified by the schema). 364 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 365 */ 366 public static SCXML read(final URL scxmlURL) 367 throws IOException, ModelException, XMLStreamException { 368 369 return read(scxmlURL, new Configuration()); 370 } 371 372 /** 373 * Parse the SCXML document at the supplied {@link URL} with the given {@link Configuration}. 374 * 375 * @param scxmlURL The SCXML document {@link URL} to parse. 376 * @param configuration The {@link Configuration} to use when parsing the SCXML document. 377 * 378 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 379 * 380 * @throws IOException An IO error during parsing. 381 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 382 * errors in the SCXML document that may not be identified by the schema). 383 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 384 */ 385 public static SCXML read(final URL scxmlURL, final Configuration configuration) 386 throws IOException, ModelException, XMLStreamException { 387 388 if (scxmlURL == null) { 389 throw new IllegalArgumentException(ERR_NULL_URL); 390 } 391 SCXML scxml = readInternal(configuration, scxmlURL, null, null, null, null); 392 if (scxml != null) { 393 ModelUpdater.updateSCXML(scxml); 394 } 395 return scxml; 396 } 397 398 /** 399 * Parse the SCXML document supplied by the given {@link InputStream}. 400 * 401 * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse. 402 * 403 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 404 * 405 * @throws IOException An IO error during parsing. 406 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 407 * errors in the SCXML document that may not be identified by the schema). 408 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 409 */ 410 public static SCXML read(final InputStream scxmlStream) 411 throws IOException, ModelException, XMLStreamException { 412 413 return read(scxmlStream, new Configuration()); 414 } 415 416 /** 417 * Parse the SCXML document supplied by the given {@link InputStream} with the given {@link Configuration}. 418 * 419 * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse. 420 * @param configuration The {@link Configuration} to use when parsing the SCXML document. 421 * 422 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 423 * 424 * @throws IOException An IO error during parsing. 425 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 426 * errors in the SCXML document that may not be identified by the schema). 427 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 428 */ 429 public static SCXML read(final InputStream scxmlStream, final Configuration configuration) 430 throws IOException, ModelException, XMLStreamException { 431 432 if (scxmlStream == null) { 433 throw new IllegalArgumentException(ERR_NULL_ISTR); 434 } 435 SCXML scxml = readInternal(configuration, null, null, scxmlStream, null, null); 436 if (scxml != null) { 437 ModelUpdater.updateSCXML(scxml); 438 } 439 return scxml; 440 } 441 442 /** 443 * Parse the SCXML document supplied by the given {@link Reader}. 444 * 445 * @param scxmlReader The {@link Reader} supplying the SCXML document to parse. 446 * 447 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 448 * 449 * @throws IOException An IO error during parsing. 450 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 451 * errors in the SCXML document that may not be identified by the schema). 452 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 453 */ 454 public static SCXML read(final Reader scxmlReader) 455 throws IOException, ModelException, XMLStreamException { 456 457 return read(scxmlReader, new Configuration()); 458 } 459 460 /** 461 * Parse the SCXML document supplied by the given {@link Reader} with the given {@link Configuration}. 462 * 463 * @param scxmlReader The {@link Reader} supplying the SCXML document to parse. 464 * @param configuration The {@link Configuration} to use when parsing the SCXML document. 465 * 466 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 467 * 468 * @throws IOException An IO error during parsing. 469 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 470 * errors in the SCXML document that may not be identified by the schema). 471 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 472 */ 473 public static SCXML read(final Reader scxmlReader, final Configuration configuration) 474 throws IOException, ModelException, XMLStreamException { 475 476 if (scxmlReader == null) { 477 throw new IllegalArgumentException(ERR_NULL_READ); 478 } 479 SCXML scxml = readInternal(configuration, null, null, null, scxmlReader, null); 480 if (scxml != null) { 481 ModelUpdater.updateSCXML(scxml); 482 } 483 return scxml; 484 } 485 486 /** 487 * Parse the SCXML document supplied by the given {@link Source}. 488 * 489 * @param scxmlSource The {@link Source} supplying the SCXML document to parse. 490 * 491 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 492 * 493 * @throws IOException An IO error during parsing. 494 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 495 * errors in the SCXML document that may not be identified by the schema). 496 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 497 */ 498 public static SCXML read(final Source scxmlSource) 499 throws IOException, ModelException, XMLStreamException { 500 501 return read(scxmlSource, new Configuration()); 502 } 503 504 /** 505 * Parse the SCXML document supplied by the given {@link Source} with the given {@link Configuration}. 506 * 507 * @param scxmlSource The {@link Source} supplying the SCXML document to parse. 508 * @param configuration The {@link Configuration} to use when parsing the SCXML document. 509 * 510 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document. 511 * 512 * @throws IOException An IO error during parsing. 513 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 514 * errors in the SCXML document that may not be identified by the schema). 515 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 516 */ 517 public static SCXML read(final Source scxmlSource, final Configuration configuration) 518 throws IOException, ModelException, XMLStreamException { 519 520 if (scxmlSource == null) { 521 throw new IllegalArgumentException(ERR_NULL_SRC); 522 } 523 SCXML scxml = readInternal(configuration, null, null, null, null, scxmlSource); 524 if (scxml != null) { 525 ModelUpdater.updateSCXML(scxml); 526 } 527 return scxml; 528 } 529 530 //---------------------- PRIVATE UTILITY METHODS ----------------------// 531 /** 532 * Parse the SCXML document at the supplied {@link URL} using the supplied {@link Configuration}, but do not 533 * wire up the object model to be usable just yet. Exactly one of the url, path, stream, reader or source 534 * parameters must be provided. 535 * 536 * @param configuration The {@link Configuration} to use when parsing the SCXML document. 537 * @param scxmlURL The optional SCXML document {@link URL} to parse. 538 * @param scxmlPath The optional real path to the SCXML document as a string. 539 * @param scxmlStream The optional {@link InputStream} providing the SCXML document. 540 * @param scxmlReader The optional {@link Reader} providing the SCXML document. 541 * @param scxmlSource The optional {@link Source} providing the SCXML document. 542 * 543 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document 544 * (not wired up to be immediately usable). 545 * 546 * @throws IOException An IO error during parsing. 547 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 548 * errors in the SCXML document that may not be identified by the schema). 549 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 550 */ 551 private static SCXML readInternal(final Configuration configuration, final URL scxmlURL, final String scxmlPath, 552 final InputStream scxmlStream, final Reader scxmlReader, final Source scxmlSource) 553 throws IOException, ModelException, XMLStreamException { 554 555 if (configuration.pathResolver == null) { 556 if (scxmlURL != null) { 557 configuration.pathResolver = new URLResolver(scxmlURL); 558 } else if (scxmlPath != null) { 559 configuration.pathResolver = new URLResolver(new URL(scxmlPath)); 560 } 561 } 562 563 XMLStreamReader reader = getReader(configuration, scxmlURL, scxmlPath, scxmlStream, scxmlReader, scxmlSource); 564 565 return readDocument(reader, configuration); 566 } 567 568 /* 569 * Private utility functions for reading the SCXML document. 570 */ 571 /** 572 * Read the SCXML document through the {@link XMLStreamReader}. 573 * 574 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 575 * @param configuration The {@link Configuration} to use while parsing. 576 * 577 * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document 578 * (not wired up to be immediately usable). 579 * 580 * @throws IOException An IO error during parsing. 581 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 582 * errors in the SCXML document that may not be identified by the schema). 583 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 584 */ 585 private static SCXML readDocument(final XMLStreamReader reader, final Configuration configuration) 586 throws IOException, ModelException, XMLStreamException { 587 588 SCXML scxml = new SCXML(); 589 while (reader.hasNext()) { 590 String name, nsURI; 591 switch (reader.next()) { 592 case XMLStreamConstants.START_ELEMENT: 593 pushNamespaces(reader, configuration); 594 nsURI = reader.getNamespaceURI(); 595 name = reader.getLocalName(); 596 if (XMLNS_SCXML.equals(nsURI)) { 597 if (ELEM_SCXML.equals(name)) { 598 readSCXML(reader, configuration, scxml); 599 } else { 600 reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name); 601 } 602 } else { 603 reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name); 604 } 605 break; 606 case XMLStreamConstants.NAMESPACE: 607 System.err.println(reader.getNamespaceCount()); 608 break; 609 default: 610 } 611 } 612 return scxml; 613 } 614 615 /** 616 * Read the contents of this <scxml> element. 617 * 618 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 619 * @param configuration The {@link Configuration} to use while parsing. 620 * @param scxml The root of the object model being parsed. 621 * 622 * @throws IOException An IO error during parsing. 623 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 624 * errors in the SCXML document that may not be identified by the schema). 625 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 626 */ 627 private static void readSCXML(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml) 628 throws IOException, ModelException, XMLStreamException { 629 630 scxml.setDatamodelName(readAV(reader, ATTR_DATAMODEL)); 631 scxml.setExmode(readAV(reader, ATTR_EXMODE)); 632 scxml.setInitial(readAV(reader, ATTR_INITIAL)); 633 scxml.setName(readAV(reader, ATTR_NAME)); 634 scxml.setProfile(readAV(reader, ATTR_PROFILE)); 635 scxml.setVersion(readRequiredAV(reader, ELEM_SCXML, ATTR_VERSION)); 636 if (!SCXML_REQUIRED_VERSION.equals(scxml.getVersion())) { 637 throw new ModelException(new MessageFormat(ERR_INVALID_VERSION).format(new Object[] {scxml.getVersion()})); 638 } 639 readNamespaces(configuration, scxml); 640 641 boolean hasGlobalScript = false; 642 643 loop : while (reader.hasNext()) { 644 String name, nsURI; 645 switch (reader.next()) { 646 case XMLStreamConstants.START_ELEMENT: 647 pushNamespaces(reader, configuration); 648 nsURI = reader.getNamespaceURI(); 649 name = reader.getLocalName(); 650 if (XMLNS_SCXML.equals(nsURI)) { 651 if (ELEM_STATE.equals(name)) { 652 readState(reader, configuration, scxml, null); 653 } else if (ELEM_PARALLEL.equals(name)) { 654 readParallel(reader, configuration, scxml, null); 655 } else if (ELEM_FINAL.equals(name)) { 656 readFinal(reader, configuration, scxml, null); 657 } else if (ELEM_DATAMODEL.equals(name)) { 658 readDatamodel(reader, configuration, scxml, null); 659 } else if (ELEM_SCRIPT.equals(name) && !hasGlobalScript) { 660 readGlobalScript(reader, configuration, scxml); 661 hasGlobalScript = true; 662 } else { 663 reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name); 664 } 665 } else { 666 reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name); 667 } 668 break; 669 case XMLStreamConstants.END_ELEMENT: 670 popNamespaces(reader, configuration); 671 break loop; 672 default: 673 } 674 } 675 } 676 677 /** 678 * Read the contents of this <state> element. 679 * 680 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 681 * @param configuration The {@link Configuration} to use while parsing. 682 * @param scxml The root of the object model being parsed. 683 * @param parent The parent {@link TransitionalState} for this state (null for top level state). 684 * 685 * @throws IOException An IO error during parsing. 686 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 687 * errors in the SCXML document that may not be identified by the schema). 688 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 689 */ 690 private static void readState(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml, 691 final TransitionalState parent) 692 throws IOException, ModelException, XMLStreamException { 693 694 State state = new State(); 695 state.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_STATE)); 696 String initial = readAV(reader, ATTR_INITIAL); 697 if (initial != null) { 698 state.setFirst(initial); 699 } 700 String src = readAV(reader, ATTR_SRC); 701 if (src != null) { 702 String source = src; 703 Configuration copy = new Configuration(configuration); 704 if (copy.parent == null) { 705 copy.parent = scxml; 706 } 707 if (configuration.pathResolver != null) { 708 source = configuration.pathResolver.resolvePath(src); 709 copy.pathResolver = configuration.pathResolver.getResolver(src); 710 } 711 readTransitionalStateSrc(copy, source, state); 712 } 713 714 if (parent == null) { 715 scxml.addChild(state); 716 } else if (parent instanceof State) { 717 ((State)parent).addChild(state); 718 } 719 else { 720 ((Parallel)parent).addChild(state); 721 } 722 scxml.addTarget(state); 723 if (configuration.parent != null) { 724 configuration.parent.addTarget(state); 725 } 726 727 loop : while (reader.hasNext()) { 728 String name, nsURI; 729 switch (reader.next()) { 730 case XMLStreamConstants.START_ELEMENT: 731 pushNamespaces(reader, configuration); 732 nsURI = reader.getNamespaceURI(); 733 name = reader.getLocalName(); 734 if (XMLNS_SCXML.equals(nsURI)) { 735 if (ELEM_TRANSITION.equals(name)) { 736 state.addTransition(readTransition(reader, configuration)); 737 } else if (ELEM_STATE.equals(name)) { 738 readState(reader, configuration, scxml, state); 739 } else if (ELEM_INITIAL.equals(name)) { 740 readInitial(reader, configuration, state); 741 } else if (ELEM_FINAL.equals(name)) { 742 readFinal(reader, configuration, scxml, state); 743 } else if (ELEM_ONENTRY.equals(name)) { 744 readOnEntry(reader, configuration, state); 745 } else if (ELEM_ONEXIT.equals(name)) { 746 readOnExit(reader, configuration, state); 747 } else if (ELEM_PARALLEL.equals(name)) { 748 readParallel(reader, configuration, scxml, state); 749 } else if (ELEM_DATAMODEL.equals(name)) { 750 readDatamodel(reader, configuration, null, state); 751 } else if (ELEM_INVOKE.equals(name)) { 752 readInvoke(reader, configuration, state); 753 } else if (ELEM_HISTORY.equals(name)) { 754 readHistory(reader, configuration, scxml, state); 755 } else { 756 reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name); 757 } 758 } else { 759 reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name); 760 } 761 break; 762 case XMLStreamConstants.END_ELEMENT: 763 popNamespaces(reader, configuration); 764 break loop; 765 default: 766 } 767 } 768 } 769 770 /** 771 * Read the contents of this <parallel> element. 772 * 773 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 774 * @param configuration The {@link Configuration} to use while parsing. 775 * @param scxml The root of the object model being parsed. 776 * @param parent The parent {@link TransitionalState} for this parallel (null for top level state). 777 * 778 * @throws IOException An IO error during parsing. 779 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 780 * errors in the SCXML document that may not be identified by the schema). 781 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 782 */ 783 private static void readParallel(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml, 784 final TransitionalState parent) 785 throws IOException, ModelException, XMLStreamException { 786 787 Parallel parallel = new Parallel(); 788 parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_PARALLEL)); 789 String src = readAV(reader, ATTR_SRC); 790 if (src != null) { 791 String source = src; 792 Configuration copy = new Configuration(configuration); 793 if (copy.parent == null) { 794 copy.parent = scxml; 795 } 796 if (configuration.pathResolver != null) { 797 source = configuration.pathResolver.resolvePath(src); 798 copy.pathResolver = configuration.pathResolver.getResolver(src); 799 } 800 readTransitionalStateSrc(copy, source, parallel); 801 } 802 803 if (parent == null) { 804 scxml.addChild(parallel); 805 } else if (parent instanceof State) { 806 ((State)parent).addChild(parallel); 807 } 808 else { 809 ((Parallel)parent).addChild(parallel); 810 } 811 scxml.addTarget(parallel); 812 if (configuration.parent != null) { 813 configuration.parent.addTarget(parallel); 814 } 815 816 loop : while (reader.hasNext()) { 817 String name, nsURI; 818 switch (reader.next()) { 819 case XMLStreamConstants.START_ELEMENT: 820 pushNamespaces(reader, configuration); 821 nsURI = reader.getNamespaceURI(); 822 name = reader.getLocalName(); 823 if (XMLNS_SCXML.equals(nsURI)) { 824 if (ELEM_TRANSITION.equals(name)) { 825 parallel.addTransition(readTransition(reader, configuration)); 826 } else if (ELEM_STATE.equals(name)) { 827 readState(reader, configuration, scxml, parallel); 828 } else if (ELEM_PARALLEL.equals(name)) { 829 readParallel(reader, configuration, scxml, parallel); 830 } else if (ELEM_ONENTRY.equals(name)) { 831 readOnEntry(reader, configuration, parallel); 832 } else if (ELEM_ONEXIT.equals(name)) { 833 readOnExit(reader, configuration, parallel); 834 } else if (ELEM_DATAMODEL.equals(name)) { 835 readDatamodel(reader, configuration, null, parallel); 836 } else if (ELEM_INVOKE.equals(name)) { 837 readInvoke(reader, configuration, parallel); 838 } else if (ELEM_HISTORY.equals(name)) { 839 readHistory(reader, configuration, scxml, parallel); 840 } else { 841 reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name); 842 } 843 } else { 844 reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name); 845 } 846 break; 847 case XMLStreamConstants.END_ELEMENT: 848 popNamespaces(reader, configuration); 849 break loop; 850 default: 851 } 852 } 853 } 854 855 /** 856 * Read the contents of this <final> element. 857 * 858 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 859 * @param configuration The {@link Configuration} to use while parsing. 860 * @param scxml The root of the object model being parsed. 861 * @param parent The parent {@link State} for this final (null for top level state). 862 * 863 * @throws IOException An IO error during parsing. 864 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 865 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 866 * errors in the SCXML document that may not be identified by the schema). 867 */ 868 private static void readFinal(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml, 869 final State parent) 870 throws XMLStreamException, ModelException, IOException { 871 872 Final end = new Final(); 873 end.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_FINAL)); 874 875 if (parent == null) { 876 scxml.addChild(end); 877 } else { 878 parent.addChild(end); 879 } 880 881 scxml.addTarget(end); 882 if (configuration.parent != null) { 883 configuration.parent.addTarget(end); 884 } 885 886 loop : while (reader.hasNext()) { 887 String name, nsURI; 888 switch (reader.next()) { 889 case XMLStreamConstants.START_ELEMENT: 890 pushNamespaces(reader, configuration); 891 nsURI = reader.getNamespaceURI(); 892 name = reader.getLocalName(); 893 if (XMLNS_SCXML.equals(nsURI)) { 894 if (ELEM_ONENTRY.equals(name)) { 895 readOnEntry(reader, configuration, end); 896 } else if (ELEM_ONEXIT.equals(name)) { 897 readOnExit(reader, configuration, end); 898 } else { 899 reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name); 900 } 901 } else { 902 reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name); 903 } 904 break; 905 case XMLStreamConstants.END_ELEMENT: 906 popNamespaces(reader, configuration); 907 break loop; 908 default: 909 } 910 } 911 } 912 913 /** 914 * Parse the contents of the SCXML document that this "src" attribute value of a <state> or <parallel> 915 * element points to. Without a URL fragment, the entire state machine is imported as contents of the 916 * <state> or <parallel>. If a URL fragment is present, the fragment must specify the id of the 917 * corresponding <state> or <parallel> to import. 918 * 919 * @param configuration The {@link Configuration} to use while parsing. 920 * @param src The "src" attribute value. 921 * @param ts The parent {@link TransitionalState} that specifies this "src" attribute. 922 * 923 * @throws IOException An IO error during parsing. 924 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 925 * errors in the SCXML document that may not be identified by the schema). 926 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 927 */ 928 private static void readTransitionalStateSrc(final Configuration configuration, final String src, 929 final TransitionalState ts) 930 throws IOException, ModelException, XMLStreamException { 931 932 // Check for URI fragment 933 String[] fragments = src.split("#", 2); 934 String location = fragments[0]; 935 String fragment = null; 936 if (fragments.length > 1) { 937 fragment = fragments[1]; 938 } 939 940 // Parse external document 941 SCXML externalSCXML; 942 try { 943 externalSCXML = SCXMLReader.readInternal(configuration, new URL(location), null, null, null, null); 944 } catch (Exception e) { 945 MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC); 946 String errMsg = msgFormat.format(new Object[] {src}); 947 throw new ModelException(errMsg + " : " + e.getMessage(), e); 948 } 949 950 // Pull in the parts of the external document as needed 951 if (fragment == null) { 952 // All targets pulled in since its not a src fragment 953 if (ts instanceof State) { 954 State s = (State) ts; 955 Initial ini = new Initial(); 956 SimpleTransition t = new SimpleTransition(); 957 t.setNext(externalSCXML.getInitial()); 958 ini.setTransition(t); 959 s.setInitial(ini); 960 for (EnterableState child : externalSCXML.getChildren()) { 961 s.addChild(child); 962 } 963 s.setDatamodel(externalSCXML.getDatamodel()); 964 } else if (ts instanceof Parallel) { 965 // TODO src attribute for <parallel> 966 } 967 } else { 968 // Need to pull in only descendent targets 969 Object source = externalSCXML.getTargets().get(fragment); 970 if (source == null) { 971 MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT); 972 String errMsg = msgFormat.format(new Object[] {src}); 973 throw new ModelException(errMsg); 974 } 975 if (source instanceof State && ts instanceof State) { 976 State s = (State) ts; 977 State include = (State) source; 978 for (OnEntry onentry : include.getOnEntries()) { 979 s.addOnEntry(onentry); 980 } 981 for (OnExit onexit : include.getOnExits()) { 982 s.addOnExit(onexit); 983 } 984 s.setDatamodel(include.getDatamodel()); 985 List<History> histories = include.getHistory(); 986 for (History h : histories) { 987 s.addHistory(h); 988 configuration.parent.addTarget(h); 989 } 990 for (EnterableState child : include.getChildren()) { 991 s.addChild(child); 992 configuration.parent.addTarget(child); 993 readInExternalTargets(configuration.parent, child); 994 } 995 for (Invoke invoke : include.getInvokes()) { 996 s.addInvoke(invoke); 997 } 998 if (include.getInitial() != null) { 999 s.setInitial(include.getInitial()); 1000 } 1001 List<Transition> transitions = include.getTransitionsList(); 1002 for (Transition t : transitions) { 1003 s.addTransition(t); 1004 } 1005 } else if (ts instanceof Parallel && source instanceof Parallel) { 1006 // TODO src attribute for <parallel> 1007 } else { 1008 MessageFormat msgFormat = 1009 new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET); 1010 String errMsg = msgFormat.format(new Object[] {src}); 1011 throw new ModelException(errMsg); 1012 } 1013 } 1014 } 1015 1016 /** 1017 * Add all the nested targets from given target to given parent state machine. 1018 * 1019 * @param parent The state machine 1020 * @param es The target to import 1021 */ 1022 private static void readInExternalTargets(final SCXML parent, final EnterableState es) { 1023 if (es instanceof TransitionalState) { 1024 for (History h : ((TransitionalState)es).getHistory()) { 1025 parent.addTarget(h); 1026 } 1027 for (EnterableState child : ((TransitionalState) es).getChildren()) { 1028 parent.addTarget(child); 1029 readInExternalTargets(parent, child); 1030 } 1031 } 1032 } 1033 1034 /** 1035 * Read the contents of this <datamodel> element. 1036 * 1037 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1038 * @param configuration The {@link Configuration} to use while parsing. 1039 * @param scxml The root of the object model being parsed. 1040 * @param parent The parent {@link TransitionalState} for this datamodel (null for top level). 1041 * 1042 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1043 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1044 * errors in the SCXML document that may not be identified by the schema). 1045 */ 1046 private static void readDatamodel(final XMLStreamReader reader, final Configuration configuration, 1047 final SCXML scxml, final TransitionalState parent) 1048 throws XMLStreamException, ModelException { 1049 1050 Datamodel dm = new Datamodel(); 1051 1052 loop : while (reader.hasNext()) { 1053 String name, nsURI; 1054 switch (reader.next()) { 1055 case XMLStreamConstants.START_ELEMENT: 1056 pushNamespaces(reader, configuration); 1057 nsURI = reader.getNamespaceURI(); 1058 name = reader.getLocalName(); 1059 if (XMLNS_SCXML.equals(nsURI)) { 1060 if (ELEM_DATA.equals(name)) { 1061 readData(reader, configuration, dm); 1062 } else { 1063 reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name); 1064 } 1065 } else { 1066 reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name); 1067 } 1068 break; 1069 case XMLStreamConstants.END_ELEMENT: 1070 popNamespaces(reader, configuration); 1071 break loop; 1072 default: 1073 } 1074 } 1075 1076 if (parent == null) { 1077 scxml.setDatamodel(dm); 1078 } else { 1079 parent.setDatamodel(dm); 1080 } 1081 } 1082 1083 /** 1084 * Read the contents of this <data> element. 1085 * 1086 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1087 * @param configuration The {@link Configuration} to use while parsing. 1088 * @param dm The parent {@link Datamodel} for this data. 1089 * 1090 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1091 */ 1092 private static void readData(final XMLStreamReader reader, final Configuration configuration, final Datamodel dm) 1093 throws XMLStreamException, ModelException { 1094 1095 Data datum = new Data(); 1096 datum.setId(readRequiredAV(reader, ELEM_DATA, ATTR_ID)); 1097 datum.setExpr(readAV(reader, ATTR_EXPR)); 1098 readNamespaces(configuration, datum); 1099 datum.setNode(readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[]{"id"})); 1100 dm.addData(datum); 1101 } 1102 1103 /** 1104 * Read the contents of this <invoke> element. 1105 * 1106 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1107 * @param configuration The {@link Configuration} to use while parsing. 1108 * @param parent The parent {@link TransitionalState} for this invoke. 1109 * 1110 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1111 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1112 * errors in the SCXML document that may not be identified by the schema). 1113 */ 1114 private static void readInvoke(final XMLStreamReader reader, final Configuration configuration, 1115 final TransitionalState parent) 1116 throws XMLStreamException, ModelException { 1117 1118 Invoke invoke = new Invoke(); 1119 invoke.setId(readAV(reader, ATTR_ID)); 1120 invoke.setSrc(readAV(reader, ATTR_SRC)); 1121 invoke.setSrcexpr(readAV(reader, ATTR_SRCEXPR)); 1122 invoke.setType(readAV(reader, ATTR_TYPE)); 1123 invoke.setAutoForward(readBooleanAV(reader, ELEM_INVOKE, ATTR_AUTOFORWARD)); 1124 invoke.setPathResolver(configuration.pathResolver); 1125 readNamespaces(configuration, invoke); 1126 1127 loop : while (reader.hasNext()) { 1128 String name, nsURI; 1129 switch (reader.next()) { 1130 case XMLStreamConstants.START_ELEMENT: 1131 pushNamespaces(reader, configuration); 1132 nsURI = reader.getNamespaceURI(); 1133 name = reader.getLocalName(); 1134 if (XMLNS_SCXML.equals(nsURI)) { 1135 if (ELEM_PARAM.equals(name)) { 1136 readParam(reader, configuration, invoke); 1137 } else if (ELEM_FINALIZE.equals(name)) { 1138 readFinalize(reader, configuration, parent, invoke); 1139 } else if (ELEM_CONTENT.equals(name)) { 1140 readContent(reader, configuration, invoke); 1141 } else { 1142 reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name); 1143 } 1144 } else { 1145 reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name); 1146 } 1147 break; 1148 case XMLStreamConstants.END_ELEMENT: 1149 popNamespaces(reader, configuration); 1150 break loop; 1151 default: 1152 } 1153 } 1154 1155 parent.addInvoke(invoke); 1156 } 1157 1158 /** 1159 * Read the contents of this <param> element. 1160 * 1161 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1162 * @param configuration The {@link Configuration} to use while parsing. 1163 * @param parent The parent {@link org.apache.commons.scxml2.model.ParamsContainer} for this param. 1164 * 1165 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1166 */ 1167 private static void readParam(final XMLStreamReader reader, final Configuration configuration, 1168 final ParamsContainer parent) 1169 throws XMLStreamException, ModelException { 1170 1171 Param param = new Param(); 1172 param.setName(readRequiredAV(reader, ELEM_PARAM, ATTR_NAME)); 1173 String location = readAV(reader, ATTR_LOCATION); 1174 String expr = readAV(reader, ATTR_EXPR); 1175 if (expr != null) { 1176 if (location != null) { 1177 reportConflictingAttribute(reader, configuration, ELEM_PARAM, ATTR_LOCATION, ATTR_EXPR); 1178 } 1179 else { 1180 param.setExpr(expr); 1181 } 1182 } 1183 else if (location == null) { 1184 // force error missing required location or expr: use location attr for this 1185 param.setLocation(readRequiredAV(reader, ELEM_PARAM, ATTR_LOCATION)); 1186 } 1187 else { 1188 param.setLocation(location); 1189 } 1190 readNamespaces(configuration, param); 1191 parent.getParams().add(param); 1192 skipToEndElement(reader); 1193 } 1194 1195 /** 1196 * Read the contents of this <finalize> element. 1197 * 1198 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1199 * @param configuration The {@link Configuration} to use while parsing. 1200 * @param state The {@link TransitionalState} which contains the parent {@link Invoke}. 1201 * @param invoke The parent {@link Invoke} for this finalize. 1202 * 1203 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1204 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1205 * errors in the SCXML document that may not be identified by the schema). 1206 */ 1207 private static void readFinalize(final XMLStreamReader reader, final Configuration configuration, 1208 final TransitionalState state, final Invoke invoke) 1209 throws XMLStreamException, ModelException { 1210 1211 Finalize finalize = new Finalize(); 1212 readExecutableContext(reader, configuration, finalize, null); 1213 invoke.setFinalize(finalize); 1214 finalize.setParent(state); 1215 } 1216 1217 /** 1218 * Read the contents of this <content> element. 1219 * 1220 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1221 * @param configuration The {@link Configuration} to use while parsing. 1222 * @param contentContainer The {@link ContentContainer} for this content. 1223 * 1224 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1225 */ 1226 private static void readContent(final XMLStreamReader reader, final Configuration configuration, 1227 final ContentContainer contentContainer) 1228 throws XMLStreamException { 1229 1230 Content content = new Content(); 1231 content.setExpr(readAV(reader, ATTR_EXPR)); 1232 if (content.getExpr() != null) { 1233 skipToEndElement(reader); 1234 } 1235 else { 1236 Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_CONTENT, new String[]{}); 1237 if (body.hasChildNodes()) { 1238 NodeList children = body.getChildNodes(); 1239 if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) { 1240 content.setBody(children.item(0).getNodeValue()); 1241 } 1242 else { 1243 content.setBody(body); 1244 } 1245 } 1246 } 1247 contentContainer.setContent(content); 1248 } 1249 1250 /** 1251 * Read the contents of this <initial> element. 1252 * 1253 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1254 * @param configuration The {@link Configuration} to use while parsing. 1255 * @param state The parent composite {@link State} for this initial. 1256 * 1257 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1258 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1259 * errors in the SCXML document that may not be identified by the schema). 1260 */ 1261 private static void readInitial(final XMLStreamReader reader, final Configuration configuration, 1262 final State state) 1263 throws XMLStreamException, ModelException { 1264 1265 Initial initial = new Initial(); 1266 1267 loop : while (reader.hasNext()) { 1268 String name, nsURI; 1269 switch (reader.next()) { 1270 case XMLStreamConstants.START_ELEMENT: 1271 pushNamespaces(reader, configuration); 1272 nsURI = reader.getNamespaceURI(); 1273 name = reader.getLocalName(); 1274 if (XMLNS_SCXML.equals(nsURI)) { 1275 if (ELEM_TRANSITION.equals(name)) { 1276 initial.setTransition(readSimpleTransition(reader, configuration)); 1277 } else { 1278 reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name); 1279 } 1280 } else { 1281 reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name); 1282 } 1283 break; 1284 case XMLStreamConstants.END_ELEMENT: 1285 popNamespaces(reader, configuration); 1286 break loop; 1287 default: 1288 } 1289 } 1290 1291 state.setInitial(initial); 1292 } 1293 1294 /** 1295 * Read the contents of this <history> element. 1296 * 1297 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1298 * @param configuration The {@link Configuration} to use while parsing. 1299 * @param scxml The root of the object model being parsed. 1300 * @param ts The parent {@link org.apache.commons.scxml2.model.TransitionalState} for this history. 1301 * 1302 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1303 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1304 * errors in the SCXML document that may not be identified by the schema). 1305 */ 1306 private static void readHistory(final XMLStreamReader reader, final Configuration configuration, 1307 final SCXML scxml, final TransitionalState ts) 1308 throws XMLStreamException, ModelException { 1309 1310 History history = new History(); 1311 history.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_HISTORY)); 1312 history.setType(readAV(reader, ATTR_TYPE)); 1313 1314 ts.addHistory(history); 1315 scxml.addTarget(history); 1316 1317 loop : while (reader.hasNext()) { 1318 String name, nsURI; 1319 switch (reader.next()) { 1320 case XMLStreamConstants.START_ELEMENT: 1321 pushNamespaces(reader, configuration); 1322 nsURI = reader.getNamespaceURI(); 1323 name = reader.getLocalName(); 1324 if (XMLNS_SCXML.equals(nsURI)) { 1325 if (ELEM_TRANSITION.equals(name)) { 1326 history.setTransition(readTransition(reader, configuration)); 1327 } else { 1328 reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name); 1329 } 1330 } else { 1331 reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name); 1332 } 1333 break; 1334 case XMLStreamConstants.END_ELEMENT: 1335 popNamespaces(reader, configuration); 1336 break loop; 1337 default: 1338 } 1339 } 1340 } 1341 1342 /** 1343 * Read the contents of this <onentry> element. 1344 * 1345 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1346 * @param configuration The {@link Configuration} to use while parsing. 1347 * @param es The parent {@link EnterableState} for this onentry. 1348 * 1349 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1350 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1351 * errors in the SCXML document that may not be identified by the schema). 1352 */ 1353 private static void readOnEntry(final XMLStreamReader reader, final Configuration configuration, 1354 final EnterableState es) 1355 throws XMLStreamException, ModelException { 1356 1357 OnEntry onentry = new OnEntry(); 1358 onentry.setRaiseEvent(readBooleanAV(reader, ELEM_ONENTRY, ATTR_EVENT)); 1359 readExecutableContext(reader, configuration, onentry, null); 1360 es.addOnEntry(onentry); 1361 } 1362 1363 /** 1364 * Read the contents of this <onexit> element. 1365 * 1366 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1367 * @param configuration The {@link Configuration} to use while parsing. 1368 * @param es The parent {@link EnterableState} for this onexit. 1369 * 1370 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1371 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1372 * errors in the SCXML document that may not be identified by the schema). 1373 */ 1374 private static void readOnExit(final XMLStreamReader reader, final Configuration configuration, 1375 final EnterableState es) 1376 throws XMLStreamException, ModelException { 1377 1378 OnExit onexit = new OnExit(); 1379 onexit.setRaiseEvent(readBooleanAV(reader, ELEM_ONEXIT, ATTR_EVENT)); 1380 readExecutableContext(reader, configuration, onexit, null); 1381 es.addOnExit(onexit); 1382 } 1383 1384 /** 1385 * Read the contents of this simple <transition> element. 1386 * 1387 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1388 * @param configuration The {@link Configuration} to use while parsing. 1389 * 1390 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1391 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1392 * errors in the SCXML document that may not be identified by the schema). 1393 */ 1394 private static SimpleTransition readSimpleTransition(final XMLStreamReader reader, final Configuration configuration) 1395 throws XMLStreamException, ModelException { 1396 1397 SimpleTransition transition = new SimpleTransition(); 1398 transition.setNext(readAV(reader, ATTR_TARGET)); 1399 String type = readAV(reader, ATTR_TYPE); 1400 if (type != null) { 1401 try { 1402 transition.setType(TransitionType.valueOf(type)); 1403 } 1404 catch (IllegalArgumentException e) { 1405 MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE); 1406 String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()}); 1407 throw new ModelException(errMsg); 1408 } 1409 } 1410 1411 readNamespaces(configuration, transition); 1412 readExecutableContext(reader, configuration, transition, null); 1413 1414 return transition; 1415 } 1416 1417 /** 1418 * Read the contents of this <transition> element. 1419 * 1420 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1421 * @param configuration The {@link Configuration} to use while parsing. 1422 * 1423 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1424 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1425 * errors in the SCXML document that may not be identified by the schema). 1426 */ 1427 private static Transition readTransition(final XMLStreamReader reader, final Configuration configuration) 1428 throws XMLStreamException, ModelException { 1429 1430 Transition transition = new Transition(); 1431 transition.setCond(readAV(reader, ATTR_COND)); 1432 transition.setEvent(readAV(reader, ATTR_EVENT)); 1433 transition.setNext(readAV(reader, ATTR_TARGET)); 1434 String type = readAV(reader, ATTR_TYPE); 1435 if (type != null) { 1436 try { 1437 transition.setType(TransitionType.valueOf(type)); 1438 } 1439 catch (IllegalArgumentException e) { 1440 MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE); 1441 String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()}); 1442 throw new ModelException(errMsg); 1443 } 1444 } 1445 1446 readNamespaces(configuration, transition); 1447 readExecutableContext(reader, configuration, transition, null); 1448 1449 return transition; 1450 } 1451 1452 /** 1453 * Read this set of executable content elements. 1454 * 1455 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1456 * @param configuration The {@link Configuration} to use while parsing. 1457 * @param executable The parent {@link Executable} to which this content belongs. 1458 * @param parent The optional parent {@link ActionsContainer} if this is child content of an ActionsContainer action. 1459 * 1460 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1461 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1462 * errors in the SCXML document that may not be identified by the schema). 1463 */ 1464 private static void readExecutableContext(final XMLStreamReader reader, final Configuration configuration, 1465 final Executable executable, final ActionsContainer parent) 1466 throws XMLStreamException, ModelException { 1467 1468 String end = ""; 1469 if (parent != null) { 1470 end = parent.getContainerElementName(); 1471 } else if (executable instanceof SimpleTransition) { 1472 end = ELEM_TRANSITION; 1473 } else if (executable instanceof OnEntry) { 1474 end = ELEM_ONENTRY; 1475 } else if (executable instanceof OnExit) { 1476 end = ELEM_ONEXIT; 1477 } else if (executable instanceof Finalize) { 1478 end = ELEM_FINALIZE; 1479 } 1480 1481 loop : while (reader.hasNext()) { 1482 String name, nsURI; 1483 switch (reader.next()) { 1484 case XMLStreamConstants.START_ELEMENT: 1485 pushNamespaces(reader, configuration); 1486 nsURI = reader.getNamespaceURI(); 1487 name = reader.getLocalName(); 1488 if (XMLNS_SCXML.equals(nsURI)) { 1489 if (ELEM_RAISE.equals(name)) { 1490 readRaise(reader, configuration, executable, parent); 1491 } else if (ELEM_FOREACH.equals(name)) { 1492 readForeach(reader, configuration, executable, parent); 1493 } else if (ELEM_IF.equals(name)) { 1494 readIf(reader, configuration, executable, parent); 1495 } else if (ELEM_LOG.equals(name)) { 1496 readLog(reader, configuration, executable, parent); 1497 } else if (ELEM_ASSIGN.equals(name)) { 1498 readAssign(reader, configuration, executable, parent); 1499 } else if (ELEM_SEND.equals(name)) { 1500 readSend(reader, configuration, executable, parent); 1501 } else if (ELEM_CANCEL.equals(name)) { 1502 readCancel(reader, configuration, executable, parent); 1503 } else if (ELEM_SCRIPT.equals(name)) { 1504 readScript(reader, configuration, executable, parent); 1505 } else if (ELEM_IF.equals(end) && ELEM_ELSEIF.equals(name)) { 1506 readElseIf(reader, configuration, executable, (If) parent); 1507 } else if (ELEM_IF.equals(end) && ELEM_ELSE.equals(name)) { 1508 readElse(reader, configuration, executable, (If)parent); 1509 } else { 1510 reportIgnoredElement(reader, configuration, end, nsURI, name); 1511 } 1512 } else if (XMLNS_COMMONS_SCXML.equals(nsURI)) { 1513 if (ELEM_VAR.equals(name)) { 1514 readVar(reader, configuration, executable, parent); 1515 } else { 1516 reportIgnoredElement(reader, configuration, end, nsURI, name); 1517 } 1518 } else { // custom action 1519 CustomAction customAction = null; 1520 if (!configuration.customActions.isEmpty()) { 1521 for (CustomAction ca : configuration.customActions) { 1522 if (ca.getNamespaceURI().equals(nsURI) && ca.getLocalName().equals(name)) { 1523 customAction = ca; 1524 } 1525 } 1526 } 1527 if (customAction != null) { 1528 readCustomAction(reader, configuration, customAction, executable, parent); 1529 } else { 1530 reportIgnoredElement(reader, configuration, end, nsURI, name); 1531 } 1532 } 1533 break; 1534 case XMLStreamConstants.END_ELEMENT: 1535 popNamespaces(reader, configuration); 1536 break loop; 1537 default: 1538 } 1539 } 1540 } 1541 1542 /** 1543 * Read the contents of this <raise> element. 1544 * 1545 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1546 * @param configuration The {@link Configuration} to use while parsing. 1547 * @param executable The parent {@link Executable} for this action. 1548 * @param parent The optional parent {@link ActionsContainer} if this action is a child of one. 1549 * 1550 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1551 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1552 * errors in the SCXML document that may not be identified by the schema). 1553 */ 1554 private static void readRaise(final XMLStreamReader reader, final Configuration configuration, 1555 final Executable executable, final ActionsContainer parent) 1556 throws XMLStreamException, ModelException { 1557 1558 if (executable instanceof Finalize) { 1559 // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize 1560 // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions. 1561 // In particular, the <send> and <raise> elements MUST NOT occur. 1562 reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_RAISE); 1563 } 1564 else { 1565 Raise raise = new Raise(); 1566 raise.setEvent(readAV(reader, ATTR_EVENT)); 1567 readNamespaces(configuration, raise); 1568 raise.setParent(executable); 1569 if (parent != null) { 1570 parent.addAction(raise); 1571 } else { 1572 executable.addAction(raise); 1573 } 1574 skipToEndElement(reader); 1575 } 1576 } 1577 1578 /** 1579 * Read the contents of this <if> element. 1580 * 1581 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1582 * @param configuration The {@link Configuration} to use while parsing. 1583 * @param executable The parent {@link Executable} for this action. 1584 * @param parent The optional parent {@link ActionsContainer} if this <if> is a child of one. 1585 * 1586 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1587 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1588 * errors in the SCXML document that may not be identified by the schema). 1589 */ 1590 private static void readIf(final XMLStreamReader reader, final Configuration configuration, 1591 final Executable executable, final ActionsContainer parent) 1592 throws XMLStreamException, ModelException { 1593 1594 If iff = new If(); 1595 iff.setCond(readRequiredAV(reader, ELEM_IF, ATTR_COND)); 1596 readNamespaces(configuration, iff); 1597 iff.setParent(executable); 1598 if (parent != null) { 1599 parent.addAction(iff); 1600 } else { 1601 executable.addAction(iff); 1602 } 1603 readExecutableContext(reader, configuration, executable, iff); 1604 } 1605 1606 /** 1607 * Read the contents of this <elseif> element. 1608 * 1609 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1610 * @param configuration The {@link Configuration} to use while parsing. 1611 * @param executable The parent {@link Executable} for this action. 1612 * @param iff The parent {@link If} for this <elseif>. 1613 * 1614 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1615 */ 1616 private static void readElseIf(final XMLStreamReader reader, final Configuration configuration, 1617 final Executable executable, final If iff) 1618 throws XMLStreamException, ModelException { 1619 1620 ElseIf elseif = new ElseIf(); 1621 elseif.setCond(readRequiredAV(reader, ELEM_ELSEIF, ATTR_COND)); 1622 readNamespaces(configuration, elseif); 1623 elseif.setParent(executable); 1624 iff.addAction(elseif); 1625 skipToEndElement(reader); 1626 } 1627 1628 /** 1629 * Read the contents of this <else> element. 1630 * 1631 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1632 * @param configuration The {@link Configuration} to use while parsing. 1633 * @param executable The parent {@link Executable} for this action. 1634 * @param iff The parent {@link If} for this <else>. 1635 * 1636 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1637 */ 1638 private static void readElse(final XMLStreamReader reader, final Configuration configuration, 1639 final Executable executable, final If iff) 1640 throws XMLStreamException { 1641 1642 Else els = new Else(); 1643 readNamespaces(configuration, els); 1644 els.setParent(executable); 1645 iff.addAction(els); 1646 skipToEndElement(reader); 1647 } 1648 1649 /** 1650 * Read the contents of this <foreach> element. 1651 * 1652 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1653 * @param configuration The {@link Configuration} to use while parsing. 1654 * @param executable The parent {@link Executable} for this action. 1655 * @param parent The optional parent {@link ActionsContainer} if this <foreach> is a child of one. 1656 * 1657 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1658 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1659 * errors in the SCXML document that may not be identified by the schema). 1660 */ 1661 private static void readForeach(final XMLStreamReader reader, final Configuration configuration, 1662 final Executable executable, final ActionsContainer parent) 1663 throws XMLStreamException, ModelException { 1664 1665 Foreach fe = new Foreach(); 1666 fe.setArray(readRequiredAV(reader, ELEM_FOREACH, ATTR_ARRAY)); 1667 fe.setItem(readRequiredAV(reader, ELEM_FOREACH, ATTR_ITEM)); 1668 fe.setIndex(readAV(reader, ATTR_INDEX)); 1669 readNamespaces(configuration, fe); 1670 fe.setParent(executable); 1671 if (parent != null) { 1672 parent.addAction(fe); 1673 } else { 1674 executable.addAction(fe); 1675 } 1676 readExecutableContext(reader, configuration, executable, fe); 1677 } 1678 1679 /** 1680 * Read the contents of this <log> element. 1681 * 1682 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1683 * @param configuration The {@link Configuration} to use while parsing. 1684 * @param executable The parent {@link Executable} for this action. 1685 * @param parent The optional parent {@link ActionsContainer} if this action is a child of one. 1686 * 1687 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1688 */ 1689 private static void readLog(final XMLStreamReader reader, final Configuration configuration, 1690 final Executable executable, final ActionsContainer parent) 1691 throws XMLStreamException { 1692 1693 Log log = new Log(); 1694 log.setExpr(readAV(reader, ATTR_EXPR)); 1695 log.setLabel(readAV(reader, ATTR_LABEL)); 1696 readNamespaces(configuration, log); 1697 log.setParent(executable); 1698 if (parent != null) { 1699 parent.addAction(log); 1700 } else { 1701 executable.addAction(log); 1702 } 1703 skipToEndElement(reader); 1704 } 1705 1706 /** 1707 * Read the contents of this <assign> element. 1708 * 1709 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1710 * @param configuration The {@link Configuration} to use while parsing. 1711 * @param executable The parent {@link Executable} for this action. 1712 * @param parent The optional parent {@link ActionsContainer} if this action is a child of one. 1713 * 1714 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1715 */ 1716 private static void readAssign(final XMLStreamReader reader, final Configuration configuration, 1717 final Executable executable, final ActionsContainer parent) 1718 throws XMLStreamException, ModelException { 1719 1720 Assign assign = new Assign(); 1721 assign.setExpr(readAV(reader, ATTR_EXPR)); 1722 assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION)); 1723 String attrValue = readAV(reader, ATTR_TYPE); 1724 if (attrValue != null) { 1725 assign.setType(Evaluator.AssignType.fromValue(attrValue)); 1726 if (assign.getType() == null) { 1727 reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_TYPE, attrValue); 1728 } 1729 } 1730 attrValue = readAV(reader, ATTR_ATTR); 1731 if (attrValue != null) { 1732 if (Evaluator.AssignType.ADD_ATTRIBUTE.equals(assign.getType())) { 1733 assign.setAttr(attrValue); 1734 } 1735 else { 1736 reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_ATTR, attrValue); 1737 } 1738 } 1739 assign.setSrc(readAV(reader, ATTR_SRC)); 1740 assign.setPathResolver(configuration.pathResolver); 1741 readNamespaces(configuration, assign); 1742 assign.setParent(executable); 1743 if (parent != null) { 1744 parent.addAction(assign); 1745 } else { 1746 executable.addAction(assign); 1747 } 1748 skipToEndElement(reader); 1749 } 1750 1751 /** 1752 * Read the contents of this <send> element. 1753 * 1754 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1755 * @param configuration The {@link Configuration} to use while parsing. 1756 * @param executable The parent {@link Executable} for this action. 1757 * @param parent The optional parent {@link ActionsContainer} if this action is a child of one. 1758 * 1759 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1760 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 1761 * errors in the SCXML document that may not be identified by the schema). 1762 */ 1763 private static void readSend(final XMLStreamReader reader, final Configuration configuration, 1764 final Executable executable, final ActionsContainer parent) 1765 throws XMLStreamException, ModelException { 1766 1767 if (executable instanceof Finalize) { 1768 // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize 1769 // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions. 1770 // In particular, the <send> and <raise> elements MUST NOT occur. 1771 reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_SEND); 1772 return; 1773 } 1774 1775 Send send = new Send(); 1776 send.setId(readAV(reader, ATTR_ID)); 1777 String attrValue = readAV(reader, ATTR_IDLOCATION); 1778 if (attrValue != null) { 1779 if (send.getId() != null) { 1780 reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_ID, ATTR_IDLOCATION); 1781 } 1782 else { 1783 send.setIdlocation(attrValue); 1784 } 1785 } 1786 send.setDelay(readAV(reader, ATTR_DELAY)); 1787 attrValue = readAV(reader, ATTR_DELAYEXPR); 1788 if (attrValue != null) { 1789 if (send.getDelay() != null) { 1790 reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_DELAY, ATTR_DELAYEXPR); 1791 } 1792 else { 1793 send.setDelayexpr(attrValue); 1794 } 1795 } 1796 send.setEvent(readAV(reader, ATTR_EVENT)); 1797 attrValue = readAV(reader, ATTR_EVENTEXPR); 1798 if (attrValue != null) { 1799 if (send.getEvent() != null) { 1800 reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_EVENT, ATTR_EVENTEXPR); 1801 } 1802 else { 1803 send.setEventexpr(attrValue); 1804 } 1805 } 1806 send.setHints(readAV(reader, ATTR_HINTS)); 1807 send.setNamelist(readAV(reader, ATTR_NAMELIST)); 1808 send.setTarget(readAV(reader, ATTR_TARGET)); 1809 attrValue = readAV(reader, ATTR_TARGETEXPR); 1810 if (attrValue != null) { 1811 if (send.getTarget() != null) { 1812 reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TARGET, ATTR_TARGETEXPR); 1813 } 1814 else { 1815 send.setTargetexpr(attrValue); 1816 } 1817 } 1818 send.setType(readAV(reader, ATTR_TYPE)); 1819 attrValue = readAV(reader, ATTR_TYPEEXPR); 1820 if (attrValue != null) { 1821 if (send.getType() != null) { 1822 reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TYPE, ATTR_TYPEEXPR); 1823 } 1824 else { 1825 send.setTypeexpr(attrValue); 1826 } 1827 } 1828 readNamespaces(configuration, send); 1829 1830 loop : while (reader.hasNext()) { 1831 String name, nsURI; 1832 switch (reader.next()) { 1833 case XMLStreamConstants.START_ELEMENT: 1834 pushNamespaces(reader, configuration); 1835 nsURI = reader.getNamespaceURI(); 1836 name = reader.getLocalName(); 1837 if (XMLNS_SCXML.equals(nsURI)) { 1838 if (ELEM_PARAM.equals(name)) { 1839 if (send.getContent() == null) { 1840 readParam(reader, configuration, send); 1841 } 1842 else { 1843 reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); 1844 } 1845 } else if (ELEM_CONTENT.equals(name)) { 1846 if (send.getNamelist() == null && send.getParams().isEmpty()) { 1847 readContent(reader, configuration, send); 1848 } 1849 else { 1850 reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); 1851 } 1852 } else { 1853 reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); 1854 } 1855 } else { 1856 reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name); 1857 } 1858 break; 1859 case XMLStreamConstants.END_ELEMENT: 1860 popNamespaces(reader, configuration); 1861 break loop; 1862 default: 1863 } 1864 } 1865 1866 send.setParent(executable); 1867 if (parent != null) { 1868 parent.addAction(send); 1869 } else { 1870 executable.addAction(send); 1871 } 1872 } 1873 1874 /** 1875 * Read the contents of this <cancel> element. 1876 * 1877 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1878 * @param configuration The {@link Configuration} to use while parsing. 1879 * @param executable The parent {@link Executable} for this action. 1880 * @param parent The optional parent {@link ActionsContainer} if this action is a child of one. 1881 * 1882 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1883 */ 1884 private static void readCancel(final XMLStreamReader reader, final Configuration configuration, 1885 final Executable executable, final ActionsContainer parent) 1886 throws XMLStreamException, ModelException { 1887 1888 Cancel cancel = new Cancel(); 1889 cancel.setSendid(readAV(reader, ATTR_SENDID)); 1890 String attrValue = readAV(reader, ATTR_SENDIDEXPR); 1891 if (attrValue != null) { 1892 if (cancel.getSendid() != null) { 1893 reportConflictingAttribute(reader, configuration, ELEM_CANCEL, ATTR_SENDID, ATTR_SENDIDEXPR); 1894 } 1895 else { 1896 cancel.setSendidexpr(attrValue); 1897 } 1898 } 1899 readNamespaces(configuration, cancel); 1900 cancel.setParent(executable); 1901 if (parent != null) { 1902 parent.addAction(cancel); 1903 } else { 1904 executable.addAction(cancel); 1905 } 1906 skipToEndElement(reader); 1907 } 1908 1909 /** 1910 * Read the contents of this <script> element. 1911 * 1912 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1913 * @param configuration The {@link Configuration} to use while parsing. 1914 * @param executable The parent {@link Executable} for this action. 1915 * @param parent The optional parent {@link ActionsContainer} if this action is a child of one. 1916 * 1917 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1918 */ 1919 private static void readScript(final XMLStreamReader reader, final Configuration configuration, 1920 final Executable executable, final ActionsContainer parent) 1921 throws XMLStreamException { 1922 1923 Script script = new Script(); 1924 readNamespaces(configuration, script); 1925 script.setBody(readBody(reader)); 1926 script.setParent(executable); 1927 if (parent != null) { 1928 parent.addAction(script); 1929 } else { 1930 executable.addAction(script); 1931 } 1932 } 1933 1934 /** 1935 * Read the contents of the initial <script> element. 1936 * @see <a href="http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml"> 1937 * http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml<a> section 3.2.2 1938 * 1939 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1940 * @param configuration The {@link Configuration} to use while parsing. 1941 * @param scxml The root of the object model being parsed. 1942 * 1943 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1944 */ 1945 private static void readGlobalScript(final XMLStreamReader reader, final Configuration configuration, 1946 final SCXML scxml) 1947 throws XMLStreamException { 1948 1949 Script globalScript = new Script(); 1950 globalScript.setGlobalScript(true); 1951 readNamespaces(configuration, globalScript); 1952 globalScript.setBody(readBody(reader)); 1953 scxml.setGlobalScript(globalScript); 1954 } 1955 1956 /** 1957 * Read the contents of this <var> element. 1958 * 1959 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1960 * @param configuration The {@link Configuration} to use while parsing. 1961 * @param executable The parent {@link Executable} for this action. 1962 * @param parent The optional parent {@link ActionsContainer} if this action is a child of one. 1963 * 1964 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1965 */ 1966 private static void readVar(final XMLStreamReader reader, final Configuration configuration, 1967 final Executable executable, final ActionsContainer parent) 1968 throws XMLStreamException { 1969 1970 Var var = new Var(); 1971 var.setName(readAV(reader, ATTR_NAME)); 1972 var.setExpr(readAV(reader, ATTR_EXPR)); 1973 readNamespaces(configuration, var); 1974 var.setParent(executable); 1975 if (parent != null) { 1976 parent.addAction(var); 1977 } else { 1978 executable.addAction(var); 1979 } 1980 skipToEndElement(reader); 1981 } 1982 1983 /** 1984 * Read the contents of this custom action. 1985 * 1986 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 1987 * @param configuration The {@link Configuration} to use while parsing. 1988 * @param customAction The {@link CustomAction} to read. 1989 * @param executable The parent {@link Executable} for this custom action. 1990 * @param parent The optional parent {@link ActionsContainer} if this custom action is a child of one. 1991 * 1992 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 1993 */ 1994 private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration, 1995 final CustomAction customAction, final Executable executable, 1996 final ActionsContainer parent) 1997 throws XMLStreamException { 1998 1999 // Instantiate custom action 2000 Object actionObject; 2001 String className = customAction.getActionClass().getName(); 2002 ClassLoader cl = configuration.customActionClassLoader; 2003 if (configuration.useContextClassLoaderForCustomActions) { 2004 cl = Thread.currentThread().getContextClassLoader(); 2005 } 2006 if (cl == null) { 2007 cl = SCXMLReader.class.getClassLoader(); 2008 } 2009 Class<?> clazz; 2010 try { 2011 clazz = cl.loadClass(className); 2012 actionObject = clazz.newInstance(); 2013 } catch (ClassNotFoundException cnfe) { 2014 throw new XMLStreamException("Cannot find custom action class:" + className, cnfe); 2015 } catch (IllegalAccessException iae) { 2016 throw new XMLStreamException("Cannot access custom action class:" + className, iae); 2017 } catch (InstantiationException ie) { 2018 throw new XMLStreamException("Cannot instantiate custom action class:" + className, ie); 2019 } 2020 if (!(actionObject instanceof Action)) { 2021 throw new IllegalArgumentException(ERR_CUSTOM_ACTION_TYPE + className); 2022 } 2023 2024 // Set the attribute values as properties 2025 Action action = (Action) actionObject; 2026 for (int i = 0; i < reader.getAttributeCount(); i++) { 2027 String name = reader.getAttributeLocalName(i); 2028 String value = reader.getAttributeValue(i); 2029 String setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); 2030 Method method; 2031 try { 2032 method = clazz.getMethod(setter, String.class); 2033 method.invoke(action, value); 2034 } catch (NoSuchMethodException nsme) { 2035 throw new XMLStreamException("No setter in class:" + className + ", for string property:" + name, 2036 nsme); 2037 } catch (InvocationTargetException ite) { 2038 throw new XMLStreamException("Exception calling setter for string property:" + name + " in class:" 2039 + className, ite); 2040 } catch (IllegalAccessException iae) { 2041 throw new XMLStreamException("Cannot access setter for string property:" + name + " in class:" 2042 + className, iae); 2043 } 2044 } 2045 2046 // Add any body content if necessary 2047 if (action instanceof ExternalContent) { 2048 Node body = readNode(reader, configuration, customAction.getNamespaceURI(), 2049 customAction.getLocalName(), new String [] {}); 2050 NodeList childNodes = body.getChildNodes(); 2051 List<Node> externalNodes = ((ExternalContent) action).getExternalNodes(); 2052 for (int i = 0; i < childNodes.getLength(); i++) { 2053 externalNodes.add(childNodes.item(i)); 2054 } 2055 } 2056 else { 2057 skipToEndElement(reader); 2058 } 2059 2060 // Wire in the action and add to parent 2061 readNamespaces(configuration, action); 2062 action.setParent(executable); 2063 if (parent != null) { 2064 parent.addAction(action); 2065 } else { 2066 executable.addAction(action); 2067 } 2068 } 2069 2070 /** 2071 * Read the following contents into a DOM {@link Node}. 2072 * 2073 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2074 * @param configuration The {@link Configuration} to use while parsing. 2075 * @param namespaceURI The namespace URI of the parent element 2076 * @param localName The local name of the parent element 2077 * @param attrs The attributes that will be read into the root DOM node. 2078 * 2079 * @return The parsed content as a DOM {@link Node}. 2080 * 2081 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 2082 */ 2083 private static Node readNode(final XMLStreamReader reader, final Configuration configuration, 2084 final String namespaceURI, final String localName, final String[] attrs) 2085 throws XMLStreamException { 2086 2087 // Create a document in which to build the DOM node 2088 Document document; 2089 try { 2090 document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 2091 } catch (ParserConfigurationException pce) { 2092 throw new XMLStreamException(ERR_PARSER_CFG); 2093 } 2094 2095 // This root element will be returned, add any attributes as specified 2096 Element root = document.createElementNS(namespaceURI, localName); 2097 for (final String attr1 : attrs) { 2098 Attr attr = document.createAttributeNS(XMLNS_DEFAULT, attr1); 2099 attr.setValue(readAV(reader, attr1)); 2100 root.setAttributeNodeNS(attr); 2101 } 2102 document.appendChild(root); 2103 2104 boolean children = false; 2105 Node parent = root; 2106 2107 // Convert stream to DOM node(s) while maintaining parent child relationships 2108 loop : while (reader.hasNext()) { 2109 String name, nsURI; 2110 Node child = null; 2111 switch (reader.next()) { 2112 case XMLStreamConstants.START_ELEMENT: 2113 if (!children && root.hasChildNodes()) { 2114 // remove any children 2115 root.setTextContent(null); 2116 } 2117 children = true; 2118 pushNamespaces(reader, configuration); 2119 nsURI = reader.getNamespaceURI(); 2120 name = reader.getLocalName(); 2121 Element elem = document.createElementNS(nsURI, name); 2122 for (int i = 0; i < reader.getAttributeCount(); i++) { 2123 nsURI = reader.getAttributeNamespace(i); 2124 name = reader.getAttributeLocalName(i); 2125 String prefix = reader.getAttributePrefix(i); 2126 if (prefix != null && prefix.length() > 0) { 2127 name = prefix + ":" + name; 2128 } 2129 Attr attr = document.createAttributeNS(nsURI, name); 2130 attr.setValue(reader.getAttributeValue(i)); 2131 elem.setAttributeNodeNS(attr); 2132 } 2133 parent.appendChild(elem); 2134 parent = elem; 2135 break; 2136 case XMLStreamConstants.SPACE: 2137 case XMLStreamConstants.CHARACTERS: 2138 case XMLStreamConstants.ENTITY_REFERENCE: 2139 if (!children || parent != root) { 2140 child = document.createTextNode(reader.getText()); 2141 } 2142 break; 2143 case XMLStreamConstants.CDATA: 2144 children = true; 2145 child = document.createCDATASection(reader.getText()); 2146 break; 2147 case XMLStreamConstants.COMMENT: 2148 children = true; 2149 child = document.createComment(reader.getText()); 2150 break; 2151 case XMLStreamConstants.END_ELEMENT: 2152 popNamespaces(reader, configuration); 2153 parent = parent.getParentNode(); 2154 if (parent == document) { 2155 break loop; 2156 } 2157 break; 2158 default: // rest is ignored 2159 } 2160 if (child != null) { 2161 parent.appendChild(child); 2162 } 2163 } 2164 if (!children && root.hasChildNodes()) { 2165 root.setTextContent(root.getTextContent().trim()); 2166 } 2167 return root; 2168 } 2169 2170 /** 2171 * Read the following body contents into a String. 2172 * 2173 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2174 * 2175 * @return The body content read into a String. 2176 * 2177 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 2178 */ 2179 private static String readBody(final XMLStreamReader reader) 2180 throws XMLStreamException { 2181 2182 StringBuilder body = new StringBuilder(); 2183 org.apache.commons.logging.Log log; 2184 2185 // Add all body content to StringBuilder 2186 loop : while (reader.hasNext()) { 2187 switch (reader.next()) { 2188 case XMLStreamConstants.START_ELEMENT: 2189 log = LogFactory.getLog(SCXMLReader.class); 2190 log.warn("Ignoring XML content in <script> element, encountered element with local name: " 2191 + reader.getLocalName()); 2192 skipToEndElement(reader); 2193 break; 2194 case XMLStreamConstants.SPACE: 2195 case XMLStreamConstants.CHARACTERS: 2196 case XMLStreamConstants.ENTITY_REFERENCE: 2197 case XMLStreamConstants.CDATA: 2198 case XMLStreamConstants.COMMENT: 2199 body.append(reader.getText()); 2200 break; 2201 case XMLStreamConstants.END_ELEMENT: 2202 break loop; 2203 default: // rest is ignored 2204 } 2205 } 2206 return body.toString(); 2207 } 2208 2209 /** 2210 * @param input input string to check if null or empty after trim 2211 * @return null if input is null or empty after trim() 2212 */ 2213 private static String nullIfEmpty(String input) { 2214 return input == null || input.trim().length()==0 ? null : input.trim(); 2215 } 2216 2217 /** 2218 * Get the attribute value at the current reader location. 2219 * 2220 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2221 * @param attrLocalName The attribute name whose value is needed. 2222 * 2223 * @return The value of the attribute. 2224 */ 2225 private static String readAV(final XMLStreamReader reader, final String attrLocalName) { 2226 return nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName)); 2227 } 2228 2229 /** 2230 * Get the Boolean attribute value at the current reader location. 2231 * 2232 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2233 * @param elementName The name of the element for which the attribute value is needed. 2234 * @param attrLocalName The attribute name whose value is needed. 2235 * 2236 * @return The Boolean value of the attribute. 2237 * @throws ModelException When the attribute value is not empty but neither "true" or "false". 2238 */ 2239 private static Boolean readBooleanAV(final XMLStreamReader reader, final String elementName, 2240 final String attrLocalName) 2241 throws ModelException { 2242 String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName)); 2243 Boolean result = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null; 2244 if (result == null && value != null) { 2245 MessageFormat msgFormat = new MessageFormat(ERR_ATTRIBUTE_NOT_BOOLEAN); 2246 String errMsg = msgFormat.format(new Object[] {value, attrLocalName, elementName, reader.getLocation()}); 2247 throw new ModelException(errMsg); 2248 } 2249 return result; 2250 } 2251 2252 /** 2253 * Get a required attribute value at the current reader location, 2254 * 2255 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2256 * @param elementName The name of the element for which the attribute value is needed. 2257 * @param attrLocalName The attribute name whose value is needed. 2258 * 2259 * @return The value of the attribute. 2260 * @throws ModelException When the required attribute is missing or empty. 2261 */ 2262 private static String readRequiredAV(final XMLStreamReader reader, final String elementName, final String attrLocalName) 2263 throws ModelException { 2264 String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName)); 2265 if (value == null) { 2266 MessageFormat msgFormat = new MessageFormat(ERR_REQUIRED_ATTRIBUTE_MISSING); 2267 String errMsg = msgFormat.format(new Object[] {elementName, attrLocalName, reader.getLocation()}); 2268 throw new ModelException(errMsg); 2269 } 2270 return value; 2271 } 2272 2273 private static String readOrGeneratedTransitionTargetId(final XMLStreamReader reader, final SCXML scxml, 2274 final String elementName) 2275 throws ModelException { 2276 String id = readAV(reader, ATTR_ID); 2277 if (id == null) { 2278 id = scxml.generateTransitionTargetId(); 2279 } 2280 else if (id.startsWith(SCXML.GENERATED_TT_ID_PREFIX)) { 2281 MessageFormat msgFormat = new MessageFormat(ERR_RESERVED_ID_PREFIX); 2282 String errMsg = msgFormat.format(new Object[] {elementName, id, reader.getLocation()}); 2283 throw new ModelException(errMsg); 2284 } 2285 return id; 2286 } 2287 2288 /** 2289 * Read the current active namespace declarations into the namespace prefixes holder. 2290 * 2291 * @param configuration The {@link Configuration} to use while parsing. 2292 * @param holder The {@link NamespacePrefixesHolder} to populate. 2293 */ 2294 private static void readNamespaces(final Configuration configuration, final NamespacePrefixesHolder holder) { 2295 2296 holder.setNamespaces(configuration.getCurrentNamespaces()); 2297 } 2298 2299 /** 2300 * Report an ignored element via the {@link XMLReporter} if available and the class 2301 * {@link org.apache.commons.logging.Log}. 2302 * 2303 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2304 * @param configuration The {@link Configuration} to use while parsing. 2305 * @param parent The parent element local name in the SCXML namespace. 2306 * @param nsURI The namespace URI of the ignored element. 2307 * @param name The local name of the ignored element. 2308 * 2309 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 2310 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 2311 * errors in the SCXML document that may not be identified by the schema). 2312 */ 2313 private static void reportIgnoredElement(final XMLStreamReader reader, final Configuration configuration, 2314 final String parent, final String nsURI, final String name) 2315 throws XMLStreamException, ModelException { 2316 2317 org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class); 2318 StringBuilder sb = new StringBuilder(); 2319 sb.append("Ignoring unknown or invalid element <").append(name) 2320 .append("> in namespace \"").append(nsURI) 2321 .append("\" as child of <").append(parent) 2322 .append("> at ").append(reader.getLocation()); 2323 if (!configuration.isSilent() && log.isWarnEnabled()) { 2324 log.warn(sb.toString()); 2325 } 2326 if (configuration.isStrict()) { 2327 throw new ModelException(sb.toString()); 2328 } 2329 XMLReporter reporter = configuration.reporter; 2330 if (reporter != null) { 2331 reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); 2332 } 2333 skipToEndElement(reader); 2334 } 2335 2336 /** 2337 * Advances the XMLStreamReader until after the end of the current element: all children will be skipped as well 2338 * @param reader the reader 2339 * @throws XMLStreamException 2340 */ 2341 private static void skipToEndElement(final XMLStreamReader reader) throws XMLStreamException { 2342 int elementsToSkip = 1; 2343 while (elementsToSkip > 0 && reader.hasNext()) { 2344 int next = reader.next(); 2345 if (next == XMLStreamConstants.START_ELEMENT) { 2346 elementsToSkip++; 2347 } 2348 else if (next == XMLStreamConstants.END_ELEMENT) { 2349 elementsToSkip--; 2350 } 2351 } 2352 } 2353 2354 /** 2355 * Report an ignored attribute via the {@link XMLReporter} if available and the class 2356 * {@link org.apache.commons.logging.Log}. 2357 * 2358 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2359 * @param configuration The {@link Configuration} to use while parsing. 2360 * @param element The element name. 2361 * @param attr The attribute which is ignored. 2362 * @param value The value of the attribute which is ignored. 2363 * 2364 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 2365 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 2366 * errors in the SCXML document that may not be identified by the schema). 2367 */ 2368 private static void reportIgnoredAttribute(final XMLStreamReader reader, final Configuration configuration, 2369 final String element, final String attr, final String value) 2370 throws XMLStreamException, ModelException { 2371 2372 org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class); 2373 StringBuilder sb = new StringBuilder(); 2374 sb.append("Ignoring unknown or invalid <").append(element).append("> attribute ").append(attr) 2375 .append("=\"").append(value).append("\" at ").append(reader.getLocation()); 2376 if (!configuration.isSilent() && log.isWarnEnabled()) { 2377 log.warn(sb.toString()); 2378 } 2379 if (configuration.isStrict()) { 2380 throw new ModelException(sb.toString()); 2381 } 2382 XMLReporter reporter = configuration.reporter; 2383 if (reporter != null) { 2384 reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); 2385 } 2386 } 2387 2388 /** 2389 * Report a conflicting attribute via the {@link XMLReporter} if available and the class 2390 * {@link org.apache.commons.logging.Log}. 2391 * 2392 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2393 * @param configuration The {@link Configuration} to use while parsing. 2394 * @param element The element name. 2395 * @param attr The attribute with which a conflict is detected. 2396 * @param conflictingAttr The conflicting attribute 2397 * 2398 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 2399 * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes 2400 * errors in the SCXML document that may not be identified by the schema). 2401 */ 2402 private static void reportConflictingAttribute(final XMLStreamReader reader, final Configuration configuration, 2403 final String element, final String attr, final String conflictingAttr) 2404 throws XMLStreamException, ModelException { 2405 2406 org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class); 2407 StringBuilder sb = new StringBuilder(); 2408 sb.append("Ignoring <").append(element).append("> attribute \"").append(conflictingAttr) 2409 .append("\" which conflicts with already defined attribute \"").append(attr) 2410 .append("\" at ").append(reader.getLocation()); 2411 if (!configuration.isSilent() && log.isWarnEnabled()) { 2412 log.warn(sb.toString()); 2413 } 2414 if (configuration.isStrict()) { 2415 throw new ModelException(sb.toString()); 2416 } 2417 XMLReporter reporter = configuration.reporter; 2418 if (reporter != null) { 2419 reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation()); 2420 } 2421 } 2422 2423 /** 2424 * Push any new namespace declarations on the configuration namespaces map. 2425 * 2426 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2427 * @param configuration The {@link Configuration} to use while parsing. 2428 */ 2429 private static void pushNamespaces(final XMLStreamReader reader, final Configuration configuration) { 2430 2431 for (int i = 0; i < reader.getNamespaceCount(); i++) { 2432 Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i)); 2433 if (stack == null) { 2434 stack = new Stack<String>(); 2435 configuration.namespaces.put(reader.getNamespacePrefix(i), stack); 2436 } 2437 stack.push(reader.getNamespaceURI(i)); 2438 } 2439 } 2440 2441 /** 2442 * Pop any expiring namespace declarations from the configuration namespaces map. 2443 * 2444 * @param reader The {@link XMLStreamReader} providing the SCXML document to parse. 2445 * @param configuration The {@link Configuration} to use while parsing. 2446 * 2447 * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}. 2448 */ 2449 private static void popNamespaces(final XMLStreamReader reader, final Configuration configuration) 2450 throws XMLStreamException { 2451 2452 for (int i = 0; i < reader.getNamespaceCount(); i++) { 2453 Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i)); 2454 if (stack == null) { 2455 throw new XMLStreamException("Configuration namespaces stack null"); 2456 } 2457 try { 2458 stack.pop(); 2459 if (stack.empty()) { 2460 configuration.namespaces.remove(reader.getNamespacePrefix(i)); 2461 } 2462 } catch (EmptyStackException e) { 2463 throw new XMLStreamException("Configuration namespaces stack popped too many times"); 2464 } 2465 } 2466 } 2467 2468 /** 2469 * Use the supplied {@link Configuration} to create an appropriate {@link XMLStreamReader} for this 2470 * {@link SCXMLReader}. Exactly one of the url, path, stream, reader or source parameters must be provided. 2471 * 2472 * @param configuration The {@link Configuration} to be used. 2473 * @param url The {@link URL} to the SCXML document to read. 2474 * @param path The optional real path to the SCXML document as a string. 2475 * @param stream The optional {@link InputStream} providing the SCXML document. 2476 * @param reader The optional {@link Reader} providing the SCXML document. 2477 * @param source The optional {@link Source} providing the SCXML document. 2478 * 2479 * @return The appropriately configured {@link XMLStreamReader}. 2480 * 2481 * @throws IOException Exception with the URL IO. 2482 * @throws XMLStreamException A problem with the XML stream creation or an wrapped {@link SAXException} 2483 * thrown in trying to validate the document against the XML Schema for SCXML. 2484 */ 2485 private static XMLStreamReader getReader(final Configuration configuration, final URL url, final String path, 2486 final InputStream stream, final Reader reader, final Source source) 2487 throws IOException, XMLStreamException { 2488 2489 // Instantiate the XMLInputFactory 2490 XMLInputFactory factory = XMLInputFactory.newInstance(); 2491 if (configuration.factoryId != null && configuration.factoryClassLoader != null) { 2492 factory = XMLInputFactory.newFactory(configuration.factoryId, configuration.factoryClassLoader); 2493 } 2494 factory.setEventAllocator(configuration.allocator); 2495 for (Map.Entry<String, Object> property : configuration.properties.entrySet()) { 2496 factory.setProperty(property.getKey(), property.getValue()); 2497 } 2498 factory.setXMLReporter(configuration.reporter); 2499 factory.setXMLResolver(configuration.resolver); 2500 2501 // Consolidate InputStream options 2502 InputStream urlStream = null; 2503 if (url != null || path != null) { 2504 URL scxml = (url != null ? url : new URL(path)); 2505 URLConnection conn = scxml.openConnection(); 2506 conn.setUseCaches(false); 2507 urlStream = conn.getInputStream(); 2508 } else if (stream != null) { 2509 urlStream = stream; 2510 } 2511 2512 // Create the XMLStreamReader 2513 XMLStreamReader xsr = null; 2514 2515 if (configuration.validate) { 2516 // Validation requires us to use a Source 2517 2518 URL scxmlSchema = new URL("TODO"); // TODO, point to appropriate location 2519 SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); 2520 Schema schema; 2521 try { 2522 schema = schemaFactory.newSchema(scxmlSchema); 2523 } catch (SAXException se) { 2524 throw new XMLStreamException("Failed to create SCXML Schema for validation", se); 2525 } 2526 2527 Validator validator = schema.newValidator(); 2528 validator.setErrorHandler(new SimpleErrorHandler()); 2529 2530 Source src = null; 2531 if (urlStream != null) { 2532 // configuration.encoding is ignored 2533 if (configuration.systemId != null) { 2534 src = new StreamSource(urlStream, configuration.systemId); 2535 } else { 2536 src = new StreamSource(urlStream); 2537 } 2538 } else if (reader != null) { 2539 if (configuration.systemId != null) { 2540 src = new StreamSource(reader, configuration.systemId); 2541 } else { 2542 src = new StreamSource(reader); 2543 } 2544 } else if (source != null) { 2545 src = source; 2546 } 2547 xsr = factory.createXMLStreamReader(src); 2548 try { 2549 validator.validate(src); 2550 } catch (SAXException se) { 2551 throw new XMLStreamException("Failed to create apply SCXML Validator", se); 2552 } 2553 2554 } else { 2555 // We can use the more direct XMLInputFactory API if validation isn't needed 2556 2557 if (urlStream != null) { 2558 // systemId gets preference, then encoding if either are present 2559 if (configuration.systemId != null) { 2560 xsr = factory.createXMLStreamReader(configuration.systemId, urlStream); 2561 } else if (configuration.encoding != null) { 2562 xsr = factory.createXMLStreamReader(urlStream, configuration.encoding); 2563 } else { 2564 xsr = factory.createXMLStreamReader(urlStream); 2565 } 2566 } else if (reader != null) { 2567 if (configuration.systemId != null) { 2568 xsr = factory.createXMLStreamReader(configuration.systemId, reader); 2569 } else { 2570 xsr = factory.createXMLStreamReader(reader); 2571 } 2572 } else if (source != null) { 2573 xsr = factory.createXMLStreamReader(source); 2574 } 2575 2576 } 2577 2578 return xsr; 2579 } 2580 2581 /** 2582 * Discourage instantiation since this is a utility class. 2583 */ 2584 private SCXMLReader() { 2585 super(); 2586 } 2587 2588 //------------------------- CONFIGURATION CLASS -------------------------// 2589 /** 2590 * <p> 2591 * Configuration for the {@link SCXMLReader}. The configuration properties necessary for the following are 2592 * covered: 2593 * </p> 2594 * 2595 * <ul> 2596 * <li>{@link XMLInputFactory} configuration properties such as {@link XMLReporter}, {@link XMLResolver} and 2597 * {@link XMLEventAllocator}</li> 2598 * <li>{@link XMLStreamReader} configuration properties such as <code>systemId</code> and <code>encoding</code> 2599 * </li> 2600 * <li>Commons SCXML object model configuration properties such as the list of custom actions and the 2601 * {@link PathResolver} to use.</li> 2602 * </ul> 2603 */ 2604 public static class Configuration { 2605 2606 /* 2607 * Configuration properties for this {@link SCXMLReader}. 2608 */ 2609 // XMLInputFactory configuration properties. 2610 /** 2611 * The <code>factoryId</code> to use for the {@link XMLInputFactory}. 2612 */ 2613 final String factoryId; 2614 2615 /** 2616 * The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create. 2617 */ 2618 final ClassLoader factoryClassLoader; 2619 2620 /** 2621 * The {@link XMLEventAllocator} for the {@link XMLInputFactory}. 2622 */ 2623 final XMLEventAllocator allocator; 2624 2625 /** 2626 * The map of properties (keys are property name strings, values are object property values) for the 2627 * {@link XMLInputFactory}. 2628 */ 2629 final Map<String, Object> properties; 2630 2631 /** 2632 * The {@link XMLResolver} for the {@link XMLInputFactory}. 2633 */ 2634 final XMLResolver resolver; 2635 2636 /** 2637 * The {@link XMLReporter} for the {@link XMLInputFactory}. 2638 */ 2639 final XMLReporter reporter; 2640 2641 // XMLStreamReader configuration properties. 2642 /** 2643 * The <code>encoding</code> to use for the {@link XMLStreamReader}. 2644 */ 2645 final String encoding; 2646 2647 /** 2648 * The <code>systemId</code> to use for the {@link XMLStreamReader}. 2649 */ 2650 final String systemId; 2651 2652 /** 2653 * Whether to validate the input with the XML Schema for SCXML. 2654 */ 2655 final boolean validate; 2656 2657 // Commons SCXML object model configuration properties. 2658 /** 2659 * The list of Commons SCXML custom actions that will be available for this document. 2660 */ 2661 final List<CustomAction> customActions; 2662 2663 /** 2664 * The {@link ClassLoader} to use for loading the {@link CustomAction} instances to create. 2665 */ 2666 final ClassLoader customActionClassLoader; 2667 2668 /** 2669 * Whether to use the thread context {@link ClassLoader} for loading any {@link CustomAction} classes. 2670 */ 2671 final boolean useContextClassLoaderForCustomActions; 2672 2673 /** 2674 * The map for bookkeeping the current active namespace declarations. The keys are prefixes and the values are 2675 * {@link Stack}s containing the corresponding namespaceURIs, with the active one on top. 2676 */ 2677 final Map<String, Stack<String>> namespaces; 2678 2679 // Mutable Commons SCXML object model configuration properties. 2680 /** 2681 * The parent SCXML document if this document is src'ed in via the <state> or <parallel> element's 2682 * "src" attribute. 2683 */ 2684 SCXML parent; 2685 2686 /** 2687 * The Commons SCXML {@link PathResolver} to use for this document. 2688 */ 2689 PathResolver pathResolver; 2690 2691 /** 2692 * Whether to silently ignore any unknown or invalid elements 2693 * or to leave warning logs for those. 2694 */ 2695 boolean silent; 2696 2697 /** 2698 * Whether to strictly throw a model exception when there are any unknown or invalid elements 2699 * or to leniently allow to read the model even with those. 2700 */ 2701 boolean strict; 2702 2703 /* 2704 * Public constructors 2705 */ 2706 /** 2707 * Default constructor. 2708 */ 2709 public Configuration() { 2710 this(null, null); 2711 } 2712 2713 /** 2714 * Minimal convenience constructor. 2715 * 2716 * @param reporter The {@link XMLReporter} to use for this reading. 2717 * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading. 2718 */ 2719 public Configuration(final XMLReporter reporter, final PathResolver pathResolver) { 2720 this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, null, null, false); 2721 } 2722 2723 /** 2724 * Convenience constructor. 2725 * 2726 * @param reporter The {@link XMLReporter} to use for this reading. 2727 * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading. 2728 * @param customActions The list of Commons SCXML custom actions that will be available for this document. 2729 */ 2730 public Configuration(final XMLReporter reporter, final PathResolver pathResolver, 2731 final List<CustomAction> customActions) { 2732 this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, customActions, null, 2733 false); 2734 } 2735 2736 /** 2737 * All purpose constructor. Any of the parameters passed in can be <code>null</code> (booleans should default 2738 * to <code>false</code>). 2739 * 2740 * @param factoryId The <code>factoryId</code> to use. 2741 * @param classLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create. 2742 * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}. 2743 * @param properties The map of properties (keys are property name strings, values are object property values) 2744 * for the {@link XMLInputFactory}. 2745 * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}. 2746 * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. 2747 * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader} 2748 * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader} 2749 * @param validate Whether to validate the input with the XML Schema for SCXML. 2750 * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. 2751 * @param customActions The list of Commons SCXML custom actions that will be available for this document. 2752 * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to 2753 * create. 2754 * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the 2755 * {@link CustomAction} instances to create. 2756 */ 2757 public Configuration(final String factoryId, final ClassLoader classLoader, final XMLEventAllocator allocator, 2758 final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter, 2759 final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver, 2760 final List<CustomAction> customActions, final ClassLoader customActionClassLoader, 2761 final boolean useContextClassLoaderForCustomActions) { 2762 this(factoryId, classLoader, allocator, properties, resolver, reporter, encoding, systemId, validate, 2763 pathResolver, null, customActions, customActionClassLoader, 2764 useContextClassLoaderForCustomActions); 2765 } 2766 2767 /* 2768 * Package access constructors 2769 */ 2770 /** 2771 * Convenience package access constructor. 2772 * 2773 * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. 2774 * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. 2775 * @param parent The parent SCXML document if this document is src'ed in via the <state> or 2776 * <parallel> element's "src" attribute. 2777 */ 2778 Configuration(final XMLReporter reporter, final PathResolver pathResolver, final SCXML parent) { 2779 this(null, null, null, null, null, reporter, null, null, false, pathResolver, parent, null, null, false); 2780 } 2781 2782 /** 2783 * Package access copy constructor. 2784 * 2785 * @param source The source {@link Configuration} to replicate. 2786 */ 2787 Configuration(final Configuration source) { 2788 this(source.factoryId, source.factoryClassLoader, source.allocator, source.properties, source.resolver, 2789 source.reporter, source.encoding, source.systemId, source.validate, source.pathResolver, 2790 source.parent, source.customActions, source.customActionClassLoader, 2791 source.useContextClassLoaderForCustomActions, source.silent, source.strict); 2792 } 2793 2794 /** 2795 * All-purpose package access constructor. 2796 * 2797 * @param factoryId The <code>factoryId</code> to use. 2798 * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to 2799 * create. 2800 * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}. 2801 * @param properties The map of properties (keys are property name strings, values are object property values) 2802 * for the {@link XMLInputFactory}. 2803 * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}. 2804 * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. 2805 * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader} 2806 * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader} 2807 * @param validate Whether to validate the input with the XML Schema for SCXML. 2808 * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. 2809 * @param parent The parent SCXML document if this document is src'ed in via the <state> or 2810 * <parallel> element's "src" attribute. 2811 * @param customActions The list of Commons SCXML custom actions that will be available for this document. 2812 * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to 2813 * create. 2814 * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the 2815 * {@link CustomAction} instances to create. 2816 */ 2817 Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator, 2818 final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter, 2819 final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver, 2820 final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader, 2821 final boolean useContextClassLoaderForCustomActions) { 2822 this(factoryId, factoryClassLoader, allocator, properties, resolver, reporter, encoding, systemId, 2823 validate, pathResolver, parent, customActions, customActionClassLoader, 2824 useContextClassLoaderForCustomActions, false, false); 2825 } 2826 2827 /** 2828 * All-purpose package access constructor. 2829 * 2830 * @param factoryId The <code>factoryId</code> to use. 2831 * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to 2832 * create. 2833 * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}. 2834 * @param properties The map of properties (keys are property name strings, values are object property values) 2835 * for the {@link XMLInputFactory}. 2836 * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}. 2837 * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}. 2838 * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader} 2839 * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader} 2840 * @param validate Whether to validate the input with the XML Schema for SCXML. 2841 * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document. 2842 * @param parent The parent SCXML document if this document is src'ed in via the <state> or 2843 * <parallel> element's "src" attribute. 2844 * @param customActions The list of Commons SCXML custom actions that will be available for this document. 2845 * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to 2846 * create. 2847 * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the 2848 * {@link CustomAction} instances to create. 2849 * @param silent Whether to silently ignore any unknown or invalid elements or to leave warning logs for those. 2850 * @param strict Whether to strictly throw a model exception when there are any unknown or invalid elements 2851 * or to leniently allow to read the model even with those. 2852 */ 2853 Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator, 2854 final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter, 2855 final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver, 2856 final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader, 2857 final boolean useContextClassLoaderForCustomActions, final boolean silent, final boolean strict) { 2858 this.factoryId = factoryId; 2859 this.factoryClassLoader = factoryClassLoader; 2860 this.allocator = allocator; 2861 this.properties = (properties == null ? new HashMap<String, Object>() : properties); 2862 this.resolver = resolver; 2863 this.reporter = reporter; 2864 this.encoding = encoding; 2865 this.systemId = systemId; 2866 this.validate = validate; 2867 this.pathResolver = pathResolver; 2868 this.parent = parent; 2869 this.customActions = (customActions == null ? new ArrayList<CustomAction>() : customActions); 2870 this.customActionClassLoader = customActionClassLoader; 2871 this.useContextClassLoaderForCustomActions = useContextClassLoaderForCustomActions; 2872 this.namespaces = new HashMap<String, Stack<String>>(); 2873 this.silent = silent; 2874 this.strict = strict; 2875 } 2876 2877 /* 2878 * Package access convenience methods 2879 */ 2880 /** 2881 * Get the current namespaces at this point in the StAX reading. 2882 * 2883 * @return Map<String,String> The namespace map (keys are prefixes and values are the corresponding current 2884 * namespace URIs). 2885 */ 2886 Map<String, String> getCurrentNamespaces() { 2887 Map<String, String> currentNamespaces = new HashMap<String, String>(); 2888 for (Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet()) { 2889 currentNamespaces.put(nsEntry.getKey(), nsEntry.getValue().peek()); 2890 } 2891 return currentNamespaces; 2892 } 2893 2894 /** 2895 * Returns true if it is set to read models silently without any model error warning logs. 2896 * @return 2897 * @see {@link #silent} 2898 */ 2899 public boolean isSilent() { 2900 return silent; 2901 } 2902 2903 /** 2904 * Turn on/off silent mode (whether to read models silently without any model error warning logs) 2905 * @param silent 2906 * @see {@link #silent} 2907 */ 2908 public void setSilent(boolean silent) { 2909 this.silent = silent; 2910 } 2911 2912 /** 2913 * Returns true if it is set to check model strictly with throwing exceptions on any model error. 2914 * @return 2915 * @see {@link #strict} 2916 */ 2917 public boolean isStrict() { 2918 return strict; 2919 } 2920 2921 /** 2922 * Turn on/off strict model (whether to check model strictly with throwing exception on any model error) 2923 * @param strict 2924 * @see {@link #strict} 2925 */ 2926 public void setStrict(boolean strict) { 2927 this.strict = strict; 2928 } 2929 } 2930}