1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.scxml2.io;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.Reader;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.net.URL;
25 import java.net.URLConnection;
26 import java.text.MessageFormat;
27 import java.util.ArrayList;
28 import java.util.EmptyStackException;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Stack;
33
34 import javax.xml.parsers.DocumentBuilderFactory;
35 import javax.xml.parsers.ParserConfigurationException;
36 import javax.xml.stream.XMLInputFactory;
37 import javax.xml.stream.XMLReporter;
38 import javax.xml.stream.XMLResolver;
39 import javax.xml.stream.XMLStreamConstants;
40 import javax.xml.stream.XMLStreamException;
41 import javax.xml.stream.XMLStreamReader;
42 import javax.xml.stream.util.XMLEventAllocator;
43 import javax.xml.transform.Source;
44 import javax.xml.transform.stream.StreamSource;
45 import javax.xml.validation.Schema;
46 import javax.xml.validation.SchemaFactory;
47 import javax.xml.validation.Validator;
48
49 import org.apache.commons.logging.LogFactory;
50 import org.apache.commons.scxml2.Evaluator;
51 import org.apache.commons.scxml2.PathResolver;
52 import org.apache.commons.scxml2.env.SimpleErrorHandler;
53 import org.apache.commons.scxml2.env.URLResolver;
54 import org.apache.commons.scxml2.model.Action;
55 import org.apache.commons.scxml2.model.ActionsContainer;
56 import org.apache.commons.scxml2.model.Assign;
57 import org.apache.commons.scxml2.model.Cancel;
58 import org.apache.commons.scxml2.model.Content;
59 import org.apache.commons.scxml2.model.ContentContainer;
60 import org.apache.commons.scxml2.model.CustomAction;
61 import org.apache.commons.scxml2.model.Data;
62 import org.apache.commons.scxml2.model.Datamodel;
63 import org.apache.commons.scxml2.model.Else;
64 import org.apache.commons.scxml2.model.ElseIf;
65 import org.apache.commons.scxml2.model.EnterableState;
66 import org.apache.commons.scxml2.model.Executable;
67 import org.apache.commons.scxml2.model.ExternalContent;
68 import org.apache.commons.scxml2.model.Final;
69 import org.apache.commons.scxml2.model.Finalize;
70 import org.apache.commons.scxml2.model.Foreach;
71 import org.apache.commons.scxml2.model.History;
72 import org.apache.commons.scxml2.model.If;
73 import org.apache.commons.scxml2.model.Initial;
74 import org.apache.commons.scxml2.model.Invoke;
75 import org.apache.commons.scxml2.model.Log;
76 import org.apache.commons.scxml2.model.ModelException;
77 import org.apache.commons.scxml2.model.NamespacePrefixesHolder;
78 import org.apache.commons.scxml2.model.OnEntry;
79 import org.apache.commons.scxml2.model.OnExit;
80 import org.apache.commons.scxml2.model.Parallel;
81 import org.apache.commons.scxml2.model.Param;
82 import org.apache.commons.scxml2.model.ParamsContainer;
83 import org.apache.commons.scxml2.model.Raise;
84 import org.apache.commons.scxml2.model.SCXML;
85 import org.apache.commons.scxml2.model.Script;
86 import org.apache.commons.scxml2.model.Send;
87 import org.apache.commons.scxml2.model.SimpleTransition;
88 import org.apache.commons.scxml2.model.State;
89 import org.apache.commons.scxml2.model.Transition;
90 import org.apache.commons.scxml2.model.TransitionType;
91 import org.apache.commons.scxml2.model.TransitionalState;
92 import org.apache.commons.scxml2.model.Var;
93 import org.w3c.dom.Attr;
94 import org.w3c.dom.Document;
95 import org.w3c.dom.Element;
96 import org.w3c.dom.Node;
97 import org.w3c.dom.NodeList;
98 import org.xml.sax.SAXException;
99
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 */
112 public 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 }