View Javadoc
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.OutputStream;
21  import java.io.StringReader;
22  import java.io.StringWriter;
23  import java.io.Writer;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Properties;
28  
29  import javax.xml.stream.XMLOutputFactory;
30  import javax.xml.stream.XMLStreamException;
31  import javax.xml.stream.XMLStreamWriter;
32  import javax.xml.transform.OutputKeys;
33  import javax.xml.transform.Result;
34  import javax.xml.transform.Source;
35  import javax.xml.transform.Transformer;
36  import javax.xml.transform.TransformerConfigurationException;
37  import javax.xml.transform.TransformerException;
38  import javax.xml.transform.TransformerFactory;
39  import javax.xml.transform.TransformerFactoryConfigurationError;
40  import javax.xml.transform.dom.DOMSource;
41  import javax.xml.transform.stream.StreamResult;
42  import javax.xml.transform.stream.StreamSource;
43  
44  import org.apache.commons.logging.LogFactory;
45  import org.apache.commons.scxml2.model.Action;
46  import org.apache.commons.scxml2.model.Assign;
47  import org.apache.commons.scxml2.model.Cancel;
48  import org.apache.commons.scxml2.model.Content;
49  import org.apache.commons.scxml2.model.Data;
50  import org.apache.commons.scxml2.model.Datamodel;
51  import org.apache.commons.scxml2.model.Else;
52  import org.apache.commons.scxml2.model.ElseIf;
53  import org.apache.commons.scxml2.model.EnterableState;
54  import org.apache.commons.scxml2.model.Raise;
55  import org.apache.commons.scxml2.model.ExternalContent;
56  import org.apache.commons.scxml2.model.Final;
57  import org.apache.commons.scxml2.model.Finalize;
58  import org.apache.commons.scxml2.model.Foreach;
59  import org.apache.commons.scxml2.model.History;
60  import org.apache.commons.scxml2.model.If;
61  import org.apache.commons.scxml2.model.Initial;
62  import org.apache.commons.scxml2.model.Invoke;
63  import org.apache.commons.scxml2.model.Log;
64  import org.apache.commons.scxml2.model.OnEntry;
65  import org.apache.commons.scxml2.model.OnExit;
66  import org.apache.commons.scxml2.model.Parallel;
67  import org.apache.commons.scxml2.model.Param;
68  import org.apache.commons.scxml2.model.SCXML;
69  import org.apache.commons.scxml2.model.Script;
70  import org.apache.commons.scxml2.model.Send;
71  import org.apache.commons.scxml2.model.SimpleTransition;
72  import org.apache.commons.scxml2.model.State;
73  import org.apache.commons.scxml2.model.Transition;
74  import org.apache.commons.scxml2.model.TransitionTarget;
75  import org.apache.commons.scxml2.model.Var;
76  import org.w3c.dom.Node;
77  import org.w3c.dom.NodeList;
78  
79  /**
80   * <p>Utility class for serializing the Commons SCXML Java object
81   * model. Class uses the visitor pattern to trace through the
82   * object heirarchy. Used primarily for testing, debugging and
83   * visual verification.</p>
84   *
85   * <b>NOTE:</b> This writer makes the following assumptions about the
86   * original SCXML document(s) parsed to create the object model:
87   * <ul>
88   *  <li>The default document namespace is the SCXML namespace:
89   *      <i>http://www.w3.org/2005/07/scxml</i></li>;
90   *  <li>The Commons SCXML namespace
91   *      ( <i>http://commons.apache.org/scxml</i> ), if needed, uses the
92   *      &quot;<i>cs</i>&quot; prefix</li>
93   *  <li>All namespace prefixes needed throughout the document are
94   *      declared on the document root element (&lt;scxml&gt;)</li>
95   * </ul>
96   *
97   * @since 1.0
98   */
99  public class SCXMLWriter {
100 
101     //---------------------- PRIVATE CONSTANTS ----------------------//
102     //---- NAMESPACES ----//
103     /**
104      * The SCXML namespace.
105      */
106     private static final String XMLNS_SCXML = "http://www.w3.org/2005/07/scxml";
107 
108     /**
109      * The Commons SCXML namespace.
110      */
111     private static final String XMLNS_COMMONS_SCXML = "http://commons.apache.org/scxml";
112 
113     //---- ERROR MESSAGES ----//
114     /**
115      * Null OutputStream passed as argument.
116      */
117     private static final String ERR_NULL_OSTR = "Cannot write to null OutputStream";
118 
119     /**
120      * Null Writer passed as argument.
121      */
122     private static final String ERR_NULL_WRIT = "Cannot write to null Writer";
123 
124     /**
125      * Null Result passed as argument.
126      */
127     private static final String ERR_NULL_RES = "Cannot parse null Result";
128 
129     //--------------------------- XML VOCABULARY ---------------------------//
130     //---- ELEMENT NAMES ----//
131     private static final String ELEM_ASSIGN = "assign";
132     private static final String ELEM_CANCEL = "cancel";
133     private static final String ELEM_CONTENT = "content";
134     private static final String ELEM_DATA = "data";
135     private static final String ELEM_DATAMODEL = "datamodel";
136     private static final String ELEM_ELSE = "else";
137     private static final String ELEM_ELSEIF = "elseif";
138     private static final String ELEM_RAISE = "raise";
139     private static final String ELEM_FINAL = "final";
140     private static final String ELEM_FINALIZE = "finalize";
141     private static final String ELEM_HISTORY = "history";
142     private static final String ELEM_IF = "if";
143     private static final String ELEM_INITIAL = "initial";
144     private static final String ELEM_INVOKE = "invoke";
145     private static final String ELEM_FOREACH = "foreach";
146     private static final String ELEM_LOG = "log";
147     private static final String ELEM_ONENTRY = "onentry";
148     private static final String ELEM_ONEXIT = "onexit";
149     private static final String ELEM_PARALLEL = "parallel";
150     private static final String ELEM_PARAM = "param";
151     private static final String ELEM_SCRIPT = "script";
152     private static final String ELEM_SCXML = "scxml";
153     private static final String ELEM_SEND = "send";
154     private static final String ELEM_STATE = "state";
155     private static final String ELEM_TRANSITION = "transition";
156     private static final String ELEM_VAR = "var";
157 
158     //---- ATTRIBUTE NAMES ----//
159     private static final String ATTR_ARRAY = "array";
160     private static final String ATTR_ATTR = "attr";
161     private static final String ATTR_AUTOFORWARD = "autoforward";
162     private static final String ATTR_COND = "cond";
163     private static final String ATTR_DATAMODEL = "datamodel";
164     private static final String ATTR_DELAY = "delay";
165     private static final String ATTR_DELAYEXPR = "delayexpr";
166     private static final String ATTR_EVENT = "event";
167     private static final String ATTR_EVENTEXPR = "eventexpr";
168     private static final String ATTR_EXMODE = "exmode";
169     private static final String ATTR_EXPR = "expr";
170     private static final String ATTR_HINTS = "hints";
171     private static final String ATTR_ID = "id";
172     private static final String ATTR_IDLOCATION = "idlocation";
173     private static final String ATTR_INDEX = "index";
174     private static final String ATTR_INITIAL = "initial";
175     private static final String ATTR_ITEM = "item";
176     private static final String ATTR_LABEL = "label";
177     private static final String ATTR_LOCATION = "location";
178     private static final String ATTR_NAME = "name";
179     private static final String ATTR_NAMELIST = "namelist";
180     private static final String ATTR_PROFILE = "profile";
181     private static final String ATTR_SENDID = "sendid";
182     private static final String ATTR_SRC = "src";
183     private static final String ATTR_SRCEXPR = "srcexpr";
184     private static final String ATTR_TARGET = "target";
185     private static final String ATTR_TARGETEXPR = "targetexpr";
186     private static final String ATTR_TYPE = "type";
187     private static final String ATTR_TYPEEXPR = "typeexpr";
188     private static final String ATTR_VERSION = "version";
189 
190     //------------------------- STATIC MEMBERS -------------------------//
191     /**
192      * The JAXP transformer.
193      */
194     private static final Transformer XFORMER = getTransformer();
195 
196     //------------------------- PUBLIC API METHODS -------------------------//
197     /**
198      * Write out the Commons SCXML object model as an SCXML document (used
199      * primarily for testing, debugging and visual verification), returned as
200      * a string.
201      *
202      * @param scxml The object model to serialize.
203      *
204      * @return The corresponding SCXML document as a string.
205      *
206      * @throws IOException An IO error during serialization.
207      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
208      */
209     public static String write(final SCXML scxml)
210             throws IOException, XMLStreamException {
211 
212         return write(scxml, new Configuration(true, true));
213     }
214 
215     /**
216      * Write out the Commons SCXML object model as an SCXML document (used
217      * primarily for testing, debugging and visual verification) using the
218      * supplied {@link Configuration}, and return as a string.
219      *
220      * @param scxml The object model to serialize.
221      * @param configuration The {@link Configuration} to use while serializing.
222      *
223      * @return The corresponding SCXML document as a string.
224      *
225      * @throws IOException An IO error during serialization.
226      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
227      */
228     public static String write(final SCXML scxml, final Configuration configuration)
229             throws IOException, XMLStreamException {
230 
231         // Must be true since we want to return a string
232         configuration.writeToString = true;
233         writeInternal(scxml, configuration, null, null, null);
234         if (configuration.usePrettyPrint) {
235             return configuration.prettyPrintOutput;
236         } else {
237             configuration.internalWriter.flush();
238             return configuration.internalWriter.toString();
239         }
240     }
241 
242     /**
243      * Write out the Commons SCXML object model as an SCXML document to the
244      * supplied {@link OutputStream}.
245      *
246      * @param scxml The object model to write out.
247      * @param scxmlStream The {@link OutputStream} to write to.
248      *
249      * @throws IOException An IO error during serialization.
250      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
251      */
252     public static void write(final SCXML scxml, final OutputStream scxmlStream)
253             throws IOException, XMLStreamException {
254 
255         write(scxml, scxmlStream, new Configuration());
256     }
257 
258     /**
259      * Write out the Commons SCXML object model as an SCXML document to the
260      * supplied {@link OutputStream} using the given {@link Configuration}.
261      *
262      * @param scxml The object model to write out.
263      * @param scxmlStream The {@link OutputStream} to write to.
264      * @param configuration The {@link Configuration} to use.
265      *
266      * @throws IOException An IO error during serialization.
267      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
268      */
269     public static void write(final SCXML scxml, final OutputStream scxmlStream, final Configuration configuration)
270             throws IOException, XMLStreamException {
271 
272         if (scxmlStream == null) {
273             throw new IllegalArgumentException(ERR_NULL_OSTR);
274         }
275         writeInternal(scxml, configuration, scxmlStream, null, null);
276         if (configuration.closeUnderlyingWhenDone) {
277             scxmlStream.flush();
278             scxmlStream.close();
279         }
280     }
281 
282     /**
283      * Write out the Commons SCXML object model as an SCXML document to the
284      * supplied {@link Writer}.
285      *
286      * @param scxml The object model to write out.
287      * @param scxmlWriter The {@link Writer} to write to.
288      *
289      * @throws IOException An IO error during serialization.
290      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
291      */
292     public static void write(final SCXML scxml, final Writer scxmlWriter)
293             throws IOException, XMLStreamException {
294 
295         write(scxml, scxmlWriter, new Configuration());
296     }
297 
298     /**
299      * Write out the Commons SCXML object model as an SCXML document to the
300      * supplied {@link Writer} using the given {@link Configuration}.
301      *
302      * @param scxml The object model to write out.
303      * @param scxmlWriter The {@link Writer} to write to.
304      * @param configuration The {@link Configuration} to use.
305      *
306      * @throws IOException An IO error during serialization.
307      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
308      */
309     public static void write(final SCXML scxml, final Writer scxmlWriter, final Configuration configuration)
310             throws IOException, XMLStreamException {
311 
312         if (scxmlWriter == null) {
313             throw new IllegalArgumentException(ERR_NULL_WRIT);
314         }
315         writeInternal(scxml, configuration, null, scxmlWriter, null);
316         if (configuration.closeUnderlyingWhenDone) {
317             scxmlWriter.flush();
318             scxmlWriter.close();
319         }
320     }
321 
322     /**
323      * Write out the Commons SCXML object model as an SCXML document to the
324      * supplied {@link Result}.
325      *
326      * @param scxml The object model to write out.
327      * @param scxmlResult The {@link Result} to write to.
328      *
329      * @throws IOException An IO error during serialization.
330      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
331      */
332     public static void write(final SCXML scxml, final Result scxmlResult)
333             throws IOException, XMLStreamException {
334 
335         write(scxml, scxmlResult, new Configuration());
336     }
337 
338     /**
339      * Write out the Commons SCXML object model as an SCXML document to the
340      * supplied {@link Result} using the given {@link Configuration}.
341      *
342      * @param scxml The object model to write out.
343      * @param scxmlResult The {@link Result} to write to.
344      * @param configuration The {@link Configuration} to use.
345      *
346      * @throws IOException An IO error during serialization.
347      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
348      */
349     public static void write(final SCXML scxml, final Result scxmlResult, final Configuration configuration)
350             throws IOException, XMLStreamException {
351 
352         if (scxmlResult == null) {
353             throw new IllegalArgumentException(ERR_NULL_RES);
354         }
355         writeInternal(scxml, configuration, null, null, scxmlResult);
356     }
357 
358     //---------------------- PRIVATE UTILITY METHODS ----------------------//
359 
360     /**
361      * Escape XML strings for serialization.
362      * The basic algorithm is taken from Commons Lang (see oacl.Entities.java)
363      *
364      * @param str A string to be escaped
365      * @return The escaped string
366      */
367     private static String escapeXML(final String str) {
368         if (str == null) {
369             return null;
370         }
371 
372         // Make the writer an arbitrary bit larger than the source string
373         int len = str.length();
374         StringWriter stringWriter = new StringWriter(len + 8);
375 
376         for (int i = 0; i < len; i++) {
377             char c = str.charAt(i);
378             String entityName = null; // Look for XML 1.0 predefined entities
379             switch (c) {
380                 case '"':
381                     entityName = "quot";
382                     break;
383                 case '&':
384                     entityName = "amp";
385                     break;
386                 case '<':
387                     entityName = "lt";
388                     break;
389                 case '>':
390                     entityName = "gt";
391                     break;
392                 default:
393             }
394             if (entityName == null) {
395                 if (c > 0x7F) {
396                     stringWriter.write("&#");
397                     stringWriter.write(Integer.toString(c));
398                     stringWriter.write(';');
399                 } else {
400                     stringWriter.write(c);
401                 }
402             } else {
403                 stringWriter.write('&');
404                 stringWriter.write(entityName);
405                 stringWriter.write(';');
406             }
407         }
408 
409         return stringWriter.toString();
410     }
411 
412     /**
413      * Write out the Commons SCXML object model using the supplied {@link Configuration}.
414      * Exactly one of the stream, writer or result parameters must be provided.
415      *
416      * @param scxml The object model to write out.
417      * @param configuration The {@link Configuration} to use.
418      * @param scxmlStream The optional {@link OutputStream} to write to.
419      * @param scxmlWriter The optional {@link Writer} to write to.
420      * @param scxmlResult The optional {@link Result} to write to.
421      *
422      * @throws IOException An IO error during serialization.
423      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
424      */
425     private static void writeInternal(final SCXML scxml, final Configuration configuration,
426                                       final OutputStream scxmlStream, final Writer scxmlWriter, final Result scxmlResult)
427             throws IOException, XMLStreamException {
428 
429         XMLStreamWriter writer = getWriter(configuration, scxmlStream, scxmlWriter, scxmlResult);
430         writeDocument(writer, configuration, scxml);
431         writer.flush();
432         writer.close();
433         if (configuration.internalWriter != null) {
434             configuration.internalWriter.flush();
435         }
436         if (configuration.usePrettyPrint) {
437             Writer prettyPrintWriter = (scxmlWriter != null ? scxmlWriter : new StringWriter());
438             writePretty(configuration, scxmlStream, prettyPrintWriter, scxmlResult);
439             if (configuration.writeToString) {
440                 prettyPrintWriter.flush();
441                 configuration.prettyPrintOutput = prettyPrintWriter.toString();
442             }
443         }
444     }
445 
446     /**
447      * Write out the Commons SCXML object model as an SCXML document using the supplied {@link Configuration}.
448      * This method tackles the XML document level concerns.
449      *
450      * @param writer The {@link XMLStreamWriter} in use for the serialization.
451      * @param configuration The {@link Configuration} in use.
452      * @param scxml The root of the object model to write out.
453      *
454      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
455      */
456     private static void writeDocument(final XMLStreamWriter writer, final Configuration configuration,
457                                       final SCXML scxml)
458             throws XMLStreamException {
459 
460         String encoding = "UTF-8";
461         if (configuration.encoding != null) {
462             encoding = configuration.encoding;
463         }
464         writer.writeStartDocument(encoding, "1.0");
465         writeSCXML(writer, scxml);
466         writer.writeEndDocument();
467     }
468 
469     /**
470      * Write out this {@link SCXML} object into its serialization as the corresponding &lt;scxml&gt; element.
471      *
472      * @param writer The {@link XMLStreamWriter} in use for the serialization.
473      * @param scxml The root of the object model to write out.
474      *
475      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
476      */
477     private static void writeSCXML(final XMLStreamWriter writer, final SCXML scxml)
478             throws XMLStreamException {
479 
480         // Start
481         writer.writeStartElement(ELEM_SCXML);
482 
483         // Namespaces
484         writer.writeNamespace(null, XMLNS_SCXML);
485         writer.writeNamespace("cs", XMLNS_COMMONS_SCXML);
486         for (Map.Entry<String, String> entry : scxml.getNamespaces().entrySet()) {
487             String key = entry.getKey();
488             if (key != null && key.trim().length() > 0 && !key.equals("cs")) { // TODO Remove reserved prefixes
489                 writer.writeNamespace(key, entry.getValue());
490             }
491         }
492 
493         // Attributes
494         writeAV(writer, ATTR_VERSION, scxml.getVersion());
495         writeAV(writer, ATTR_INITIAL, scxml.getInitial());
496         writeAV(writer, ATTR_DATAMODEL, scxml.getDatamodelName());
497         writeAV(writer, ATTR_NAME, scxml.getName());
498         writeAV(writer, ATTR_PROFILE, scxml.getProfile());
499         writeAV(writer, ATTR_EXMODE, scxml.getExmode());
500 
501         // Marker to indicate generated document
502         writer.writeComment(XMLNS_COMMONS_SCXML);
503 
504         // Write global script if defined
505         if (scxml.getGlobalScript() != null) {
506             Script s = scxml.getGlobalScript();
507             writer.writeStartElement(XMLNS_SCXML, ELEM_SCRIPT);
508             writer.writeCData(s.getScript());
509             writer.writeEndElement();
510         }
511 
512         // Children
513         writeDatamodel(writer, scxml.getDatamodel());
514         for (EnterableState es : scxml.getChildren()) {
515             if (es instanceof Final) {
516                 writeFinal(writer, (Final) es);
517             } else if (es instanceof State) {
518                 writeState(writer, (State) es);
519             } else if (es instanceof Parallel) {
520                 writeParallel(writer, (Parallel) es);
521             }
522         }
523 
524         // End
525         writer.writeEndElement();
526     }
527 
528     /**
529      * Write out this {@link Datamodel} object into its serialization as the corresponding &lt;datamodel&gt; element.
530      *
531      * @param writer The {@link XMLStreamWriter} in use for the serialization.
532      * @param datamodel The {@link Datamodel} to serialize.
533      *
534      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
535      */
536     private static void writeDatamodel(final XMLStreamWriter writer, final Datamodel datamodel)
537             throws XMLStreamException {
538 
539         if (datamodel == null) {
540             return;
541         }
542 
543         writer.writeStartElement(ELEM_DATAMODEL);
544         if (datamodel.getData().size() > 0 && XFORMER == null) {
545             writer.writeComment("Datamodel was not serialized");
546         } else {
547             for (Data d : datamodel.getData()) {
548                 Node n = d.getNode();
549                 if (n != null) {
550                     writeNode(writer, n);
551                 } else {
552                     writer.writeStartElement(ELEM_DATA);
553                     writeAV(writer, ATTR_ID, d.getId());
554                     writeAV(writer, ATTR_SRC, escapeXML(d.getSrc()));
555                     writeAV(writer, ATTR_EXPR, escapeXML(d.getExpr()));
556                     writer.writeEndElement();
557                 }
558             }
559         }
560         writer.writeEndElement();
561     }
562 
563     /**
564      * Write out the TransitionTarget id attribute unless it was auto-generated
565      * @param writer The {@link XMLStreamWriter} in use for the serialization.
566      * @param tt The {@link TransitionTarget} for which to write the id attribute.
567      * @throws XMLStreamException
568      */
569     private static void writeTransitionTargetId(final XMLStreamWriter writer, final TransitionTarget tt)
570             throws XMLStreamException {
571         if (!tt.getId().startsWith(SCXML.GENERATED_TT_ID_PREFIX)) {
572             writeAV(writer, ATTR_ID, tt.getId());
573         }
574     }
575 
576     /**
577      * Write out this {@link State} object into its serialization as the corresponding &lt;state&gt; element.
578      *
579      * @param writer The {@link XMLStreamWriter} in use for the serialization.
580      * @param state The {@link State} to serialize.
581      *
582      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
583      */
584     private static void writeState(final XMLStreamWriter writer, final State state)
585             throws XMLStreamException {
586 
587         writer.writeStartElement(ELEM_STATE);
588         writeTransitionTargetId(writer, state);
589         writeAV(writer, ATTR_INITIAL, state.getFirst());
590         writeInitial(writer, state.getInitial());
591         writeDatamodel(writer, state.getDatamodel());
592         writeHistory(writer, state.getHistory());
593         for (OnEntry onentry : state.getOnEntries()) {
594             writeOnEntry(writer, onentry);
595         }
596 
597         for (Transition t : state.getTransitionsList()) {
598             writeTransition(writer, t);
599         }
600 
601         for (Invoke inv : state.getInvokes()) {
602             writeInvoke(writer, inv);
603         }
604 
605         for (EnterableState es : state.getChildren()) {
606             if (es instanceof Final) {
607                 writeFinal(writer, (Final) es);
608             } else if (es instanceof State) {
609                 writeState(writer, (State) es);
610             } else if (es instanceof Parallel) {
611                 writeParallel(writer, (Parallel) es);
612             }
613         }
614 
615         for (OnExit onexit : state.getOnExits()) {
616             writeOnExit(writer, onexit);
617         }
618         writer.writeEndElement();
619     }
620 
621     /**
622      * Write out this {@link Parallel} object into its serialization as the corresponding &lt;parallel&gt; element.
623      *
624      * @param writer The {@link XMLStreamWriter} in use for the serialization.
625      * @param parallel The {@link Parallel} to serialize.
626      *
627      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
628      */
629     private static void writeParallel(final XMLStreamWriter writer, final Parallel parallel)
630             throws XMLStreamException {
631 
632         writer.writeStartElement(ELEM_PARALLEL);
633         writeTransitionTargetId(writer, parallel);
634 
635         writeDatamodel(writer, parallel.getDatamodel());
636         writeHistory(writer, parallel.getHistory());
637         for (OnEntry onentry : parallel.getOnEntries()) {
638             writeOnEntry(writer, onentry);
639         }
640 
641         for (Transition t : parallel.getTransitionsList()) {
642             writeTransition(writer, t);
643         }
644 
645         for (Invoke inv : parallel.getInvokes()) {
646             writeInvoke(writer, inv);
647         }
648 
649         for (EnterableState es : parallel.getChildren()) {
650             if (es instanceof Final) {
651                 writeFinal(writer, (Final) es);
652             } else if (es instanceof State) {
653                 writeState(writer, (State) es);
654             } else if (es instanceof Parallel) {
655                 writeParallel(writer, (Parallel) es);
656             }
657         }
658 
659         for (OnExit onexit : parallel.getOnExits()) {
660             writeOnExit(writer, onexit);
661         }
662         writer.writeEndElement();
663     }
664 
665     /**
666      * Write out this {@link Final} object into its serialization as the corresponding &lt;final&gt; element.
667      *
668      * @param writer The {@link XMLStreamWriter} in use for the serialization.
669      * @param end The {@link Final} to serialize.
670      *
671      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
672      */
673     private static void writeFinal(final XMLStreamWriter writer, final Final end)
674             throws XMLStreamException {
675 
676         writer.writeStartElement(ELEM_FINAL);
677         writeTransitionTargetId(writer, end);
678         for (OnEntry onentry : end.getOnEntries()) {
679             writeOnEntry(writer, onentry);
680         }
681         for (OnExit onexit : end.getOnExits()) {
682             writeOnExit(writer, onexit);
683         }
684         writer.writeEndElement();
685     }
686 
687     /**
688      * Write out this {@link Initial} object into its serialization as the corresponding &lt;initial&gt; element.
689      *
690      * @param writer The {@link XMLStreamWriter} in use for the serialization.
691      * @param initial The {@link Initial} to serialize.
692      *
693      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
694      */
695     private static void writeInitial(final XMLStreamWriter writer, final Initial initial)
696             throws XMLStreamException {
697 
698         if (initial == null || initial.isGenerated()) {
699             return;
700         }
701 
702         writer.writeStartElement(ELEM_INITIAL);
703         writeTransition(writer, initial.getTransition());
704         writer.writeEndElement();
705     }
706 
707     /**
708      * Write out this {@link History} list into its serialization as the corresponding set of &lt;history&gt;
709      * elements.
710      *
711      * @param writer The {@link XMLStreamWriter} in use for the serialization.
712      * @param history The {@link History} list to serialize.
713      *
714      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
715      */
716     private static void writeHistory(final XMLStreamWriter writer, final List<History> history)
717             throws XMLStreamException {
718 
719         if (history == null) {
720             return;
721         }
722 
723         for (History h : history) {
724             writer.writeStartElement(ELEM_HISTORY);
725             writeTransitionTargetId(writer, h);
726             if (h.isDeep()) {
727                 writeAV(writer, ATTR_TYPE, "deep");
728             } else {
729                 writeAV(writer, ATTR_TYPE, "shallow");
730             }
731             writeTransition(writer, h.getTransition());
732             writer.writeEndElement();
733         }
734     }
735 
736     /**
737      * Write out this {@link OnEntry} object into its serialization as the corresponding &lt;onentry&gt; element.
738      *
739      * @param writer The {@link XMLStreamWriter} in use for the serialization.
740      * @param onentry The {@link OnEntry} to serialize.
741      *
742      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
743      */
744     private static void writeOnEntry(final XMLStreamWriter writer, final OnEntry onentry)
745             throws XMLStreamException {
746 
747         if (onentry != null && (onentry.isRaiseEvent() || onentry.getActions().size() > 0 )) {
748             writer.writeStartElement(ELEM_ONENTRY);
749             writeAV(writer, ATTR_EVENT, onentry.getRaiseEvent());
750             writeExecutableContent(writer, onentry.getActions());
751             writer.writeEndElement();
752         }
753     }
754 
755     /**
756      * Write out this {@link OnExit} object into its serialization as the corresponding &lt;onexit&gt; element.
757      *
758      * @param writer The {@link XMLStreamWriter} in use for the serialization.
759      * @param onexit The {@link OnExit} to serialize.
760      *
761      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
762      */
763     private static void writeOnExit(final XMLStreamWriter writer, final OnExit onexit)
764             throws XMLStreamException {
765 
766         if (onexit != null && (onexit.isRaiseEvent() || onexit.getActions().size() > 0)) {
767             writer.writeStartElement(ELEM_ONEXIT);
768             writeAV(writer, ATTR_EVENT, onexit.getRaiseEvent());
769             writeExecutableContent(writer, onexit.getActions());
770             writer.writeEndElement();
771         }
772     }
773 
774     /**
775      * Write out this {@link Transition} object into its serialization as the corresponding &lt;transition&gt; element.
776      *
777      * @param writer The {@link XMLStreamWriter} in use for the serialization.
778      * @param transition The {@link Transition} to serialize.
779      *
780      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
781      */
782     private static void writeTransition(final XMLStreamWriter writer, final SimpleTransition transition)
783             throws XMLStreamException {
784 
785         writer.writeStartElement(ELEM_TRANSITION);
786         if (transition instanceof Transition) {
787             writeAV(writer, ATTR_EVENT, ((Transition)transition).getEvent());
788             writeAV(writer, ATTR_COND, escapeXML(((Transition)transition).getCond()));
789         }
790 
791         writeAV(writer, ATTR_TARGET, transition.getNext());
792         if (transition.getType() != null) {
793             writeAV(writer, ATTR_TYPE, transition.getType().name());
794         }
795         writeExecutableContent(writer, transition.getActions());
796         writer.writeEndElement();
797     }
798 
799     /**
800      * Write out this {@link Invoke} object into its serialization as the corresponding &lt;invoke&gt; element.
801      *
802      * @param writer The {@link XMLStreamWriter} in use for the serialization.
803      * @param invoke The {@link Invoke} to serialize.
804      *
805      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
806      */
807     private static void writeInvoke(final XMLStreamWriter writer, final Invoke invoke)
808             throws XMLStreamException {
809 
810         writer.writeStartElement(ELEM_INVOKE);
811         writeAV(writer, ATTR_ID, invoke.getId());
812         writeAV(writer, ATTR_SRC, invoke.getSrc());
813         writeAV(writer, ATTR_SRCEXPR, invoke.getSrcexpr());
814         writeAV(writer, ATTR_TYPE, invoke.getType());
815         writeAV(writer, ATTR_AUTOFORWARD, invoke.getAutoForward());
816 
817         for (Param p : invoke.getParams()) {
818             writer.writeStartElement(ELEM_PARAM);
819             writeAV(writer, ATTR_NAME, p.getName());
820             writeAV(writer, ATTR_LOCATION, p.getLocation());
821             writeAV(writer, ATTR_EXPR, escapeXML(p.getExpr()));
822             writer.writeEndElement();
823         }
824         writeFinalize(writer, invoke.getFinalize());
825         writeContent(writer, invoke.getContent());
826 
827         writer.writeEndElement();
828     }
829 
830     /**
831      * Write out this {@link Finalize} object into its serialization as the corresponding &lt;finalize&gt; element.
832      *
833      * @param writer The {@link XMLStreamWriter} in use for the serialization.
834      * @param finalize The {@link Finalize} to serialize.
835      *
836      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
837      */
838     private static void writeFinalize(final XMLStreamWriter writer, final Finalize finalize)
839             throws XMLStreamException {
840 
841         if (finalize != null && finalize.getActions().size() > 0) {
842             writer.writeStartElement(ELEM_FINALIZE);
843             writeExecutableContent(writer, finalize.getActions());
844             writer.writeEndElement();
845         }
846     }
847 
848     /**
849      * Write out this executable content (list of actions) into its serialization as the corresponding set of action
850      * elements. Custom actions aren't serialized.
851      *
852      * @param writer The {@link XMLStreamWriter} in use for the serialization.
853      * @param actions The list of actions to serialize.
854      *
855      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
856      */
857     private static void writeExecutableContent(final XMLStreamWriter writer, final List<Action> actions)
858             throws XMLStreamException {
859 
860         if (actions == null) {
861             return;
862         }
863         for (Action a : actions) {
864             if (a instanceof Assign) {
865                 Assign asn = (Assign) a;
866                 writer.writeStartElement(XMLNS_SCXML, ELEM_ASSIGN);
867                 writeAV(writer, ATTR_LOCATION, asn.getLocation());
868                 if (asn.getType() != null) {
869                     writeAV(writer, ATTR_TYPE, asn.getType().value());
870                 }
871                 writeAV(writer, ATTR_ATTR, asn.getAttr());
872                 writeAV(writer, ATTR_SRC, asn.getSrc());
873                 writeAV(writer, ATTR_EXPR, escapeXML(asn.getExpr()));
874                 writer.writeEndElement();
875             } else if (a instanceof Send) {
876                 writeSend(writer, (Send) a);
877             } else if (a instanceof Cancel) {
878                 Cancel c = (Cancel) a;
879                 writer.writeStartElement(XMLNS_SCXML, ELEM_CANCEL);
880                 writeAV(writer, ATTR_SENDID, c.getSendid());
881                 writer.writeEndElement();
882             } else if (a instanceof Foreach) {
883                 writeForeach(writer, (Foreach) a);
884             } else if (a instanceof Log) {
885                 Log lg = (Log) a;
886                 writer.writeStartElement(XMLNS_SCXML, ELEM_LOG);
887                 writeAV(writer, ATTR_LABEL, lg.getLabel());
888                 writeAV(writer, ATTR_EXPR, escapeXML(lg.getExpr()));
889                 writer.writeEndElement();
890             } else if (a instanceof Raise) {
891                 Raise e = (Raise) a;
892                 writer.writeStartElement(XMLNS_SCXML, ELEM_RAISE);
893                 writeAV(writer, ATTR_EVENT, e.getEvent());
894                 writer.writeEndElement();
895             } else if (a instanceof Script) {
896                 Script s = (Script) a;
897                 writer.writeStartElement(XMLNS_SCXML, ELEM_SCRIPT);
898                 writer.writeCData(s.getScript());
899                 writer.writeEndElement();
900             } else if (a instanceof If) {
901                 writeIf(writer, (If) a);
902             } else if (a instanceof Else) {
903                 writer.writeEmptyElement(ELEM_ELSE);
904             } else if (a instanceof ElseIf) {
905                 ElseIf eif = (ElseIf) a;
906                 writer.writeStartElement(XMLNS_SCXML, ELEM_ELSEIF);
907                 writeAV(writer, ATTR_COND, escapeXML(eif.getCond()));
908                 writer.writeEndElement();
909             } else if (a instanceof Var) {
910                 Var v = (Var) a;
911                 writer.writeStartElement(XMLNS_COMMONS_SCXML, ELEM_VAR);
912                 writeAV(writer, ATTR_NAME, v.getName());
913                 writeAV(writer, ATTR_EXPR, escapeXML(v.getExpr()));
914                 writer.writeEndElement();
915             } else {
916                 writer.writeComment("Custom action with class name '" + a.getClass().getName() + "' not serialized");
917             }
918         }
919     }
920 
921     /**
922      * Write out this {@link Send} object into its serialization as the corresponding &lt;send&gt; element.
923      *
924      * @param writer The {@link XMLStreamWriter} in use for the serialization.
925      * @param send The {@link Send} to serialize.
926      *
927      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
928      */
929     private static void writeSend(final XMLStreamWriter writer, final Send send)
930             throws XMLStreamException {
931 
932         writer.writeStartElement(XMLNS_SCXML, ELEM_SEND);
933         writeAV(writer, ATTR_ID, send.getId());
934         writeAV(writer, ATTR_IDLOCATION, send.getIdlocation());
935         writeAV(writer, ATTR_EVENT, send.getEvent());
936         writeAV(writer, ATTR_EVENTEXPR, send.getEventexpr());
937         writeAV(writer, ATTR_TARGET, send.getTarget());
938         writeAV(writer, ATTR_TARGETEXPR, send.getTargetexpr());
939         writeAV(writer, ATTR_TYPE, send.getType());
940         writeAV(writer, ATTR_TYPEEXPR, send.getTypeexpr());
941         writeAV(writer, ATTR_DELAY, send.getDelay());
942         writeAV(writer, ATTR_DELAYEXPR, send.getDelayexpr());
943         writeAV(writer, ATTR_NAMELIST, send.getNamelist());
944         writeAV(writer, ATTR_HINTS, send.getHints());
945 
946         for (Param p : send.getParams()) {
947             writer.writeStartElement(ELEM_PARAM);
948             writeAV(writer, ATTR_NAME, p.getName());
949             writeAV(writer, ATTR_LOCATION, p.getLocation());
950             writeAV(writer, ATTR_EXPR, escapeXML(p.getExpr()));
951             writer.writeEndElement();
952         }
953         writeContent(writer, send.getContent());
954 
955         writer.writeEndElement();
956     }
957 
958     /**
959      * Write out this {@link If} object into its serialization as the corresponding &lt;if&gt; element.
960      *
961      * @param writer The {@link XMLStreamWriter} in use for the serialization.
962      * @param iff The {@link If} to serialize.
963      *
964      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
965      */
966     private static void writeIf(final XMLStreamWriter writer, final If iff)
967             throws XMLStreamException {
968 
969         writer.writeStartElement(ELEM_IF);
970         writeAV(writer, ATTR_COND, escapeXML(iff.getCond()));
971         writeExecutableContent(writer, iff.getActions());
972         writer.writeEndElement();
973     }
974 
975     /**
976      * Write out this {@link Foreach} object into its serialization as the corresponding &lt;foreach&gt; element.
977      *
978      * @param writer The {@link XMLStreamWriter} in use for the serialization.
979      * @param foreach The {@link If} to serialize.
980      *
981      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
982      */
983     private static void writeForeach(final XMLStreamWriter writer, final Foreach foreach)
984             throws XMLStreamException {
985 
986         writer.writeStartElement(ELEM_FOREACH);
987         writeAV(writer, ATTR_ITEM, foreach.getItem());
988         writeAV(writer, ATTR_INDEX, foreach.getIndex());
989         writeAV(writer, ATTR_ARRAY, escapeXML(foreach.getArray()));
990         writeExecutableContent(writer, foreach.getActions());
991         writer.writeEndElement();
992     }
993 
994     /**
995      * Write the {@link Content} element.
996      *
997      * @param writer The {@link XMLStreamWriter} in use for the serialization.
998      * @param content The content element to write.
999      *
1000      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1001      */
1002     private static void writeContent(final XMLStreamWriter writer, final Content content)
1003             throws XMLStreamException {
1004 
1005         if (content != null) {
1006             writer.writeStartElement(ELEM_CONTENT);
1007             writeAV(writer, ATTR_EXPR, content.getExpr());
1008             if (content.getBody() != null) {
1009                 if (content.getBody() instanceof Node) {
1010                     NodeList nodeList = ((Node)content.getBody()).getChildNodes();
1011                     if (nodeList.getLength() > 0 && XFORMER == null) {
1012                         writer.writeComment("External content was not serialized");
1013                     }
1014                     else {
1015                         for (int i = 0, size = nodeList.getLength(); i < size; i++) {
1016                             writeNode(writer, nodeList.item(i));
1017                         }
1018                     }
1019                 }
1020                 else {
1021                     writer.writeCharacters(content.getBody().toString());
1022                 }
1023             }
1024             writer.writeEndElement();
1025         }
1026     }
1027 
1028     /**
1029      * Write the serialized body of this {@link ExternalContent} element.
1030      *
1031      * @param writer The {@link XMLStreamWriter} in use for the serialization.
1032      * @param externalContent The model element containing the external body content.
1033      *
1034      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1035      */
1036     private static void writeExternalContent(final XMLStreamWriter writer,
1037                                              final ExternalContent externalContent)
1038             throws XMLStreamException {
1039 
1040         List<Node> externalNodes = externalContent.getExternalNodes();
1041 
1042         if (externalNodes.size() > 0 && XFORMER == null) {
1043             writer.writeComment("External content was not serialized");
1044         } else {
1045             for (Node n : externalNodes) {
1046                 writeNode(writer, n);
1047             }
1048         }
1049     }
1050 
1051     /**
1052      * Write out this {@link Node} object into its serialization.
1053      *
1054      * @param writer The {@link XMLStreamWriter} in use for the serialization.
1055      * @param node The {@link Node} to serialize.
1056      *
1057      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1058      */
1059     private static void writeNode(final XMLStreamWriter writer, final Node node)
1060             throws XMLStreamException {
1061 
1062         Source input = new DOMSource(node);
1063         StringWriter out = new StringWriter();
1064         Result output = new StreamResult(out);
1065         try {
1066             XFORMER.transform(input, output);
1067         } catch (TransformerException te) {
1068             org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLWriter.class);
1069             log.error(te.getMessage(), te);
1070             writer.writeComment("TransformerException: Node was not serialized");
1071         }
1072         writer.writeCharacters(out.toString());
1073     }
1074 
1075     /**
1076      * Write out this attribute, if the value is not <code>null</code>.
1077      *
1078      * @param writer The {@link XMLStreamWriter} in use for the serialization.
1079      * @param localName The local name of the attribute.
1080      * @param value The attribute value.
1081      *
1082      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1083      */
1084     private static void writeAV(final XMLStreamWriter writer, final String localName, final String value)
1085             throws XMLStreamException {
1086         if (value != null) {
1087             writer.writeAttribute(localName, value);
1088         }
1089     }
1090 
1091     /**
1092      * Write out this attribute, if the value is not <code>null</code>.
1093      *
1094      * @param writer The {@link XMLStreamWriter} in use for the serialization.
1095      * @param localName The local name of the attribute.
1096      * @param value The attribute value.
1097      *
1098      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1099      */
1100     private static void writeAV(final XMLStreamWriter writer, final String localName, final Boolean value)
1101             throws XMLStreamException {
1102         if (value != null) {
1103             writer.writeAttribute(localName, value.toString());
1104         }
1105     }
1106 
1107     /**
1108      * Write the serialized SCXML document while making attempts to make the serialization human readable. This
1109      * includes using new-lines and indentation as appropriate, where possible. Exactly one of the stream, writer
1110      * or result parameters must be provided.
1111      *
1112      * @param configuration The {@link Configuration} to use.
1113      * @param scxmlStream The optional {@link OutputStream} to write to.
1114      * @param scxmlWriter The optional {@link Writer} to write to.
1115      * @param scxmlResult The optional {@link Result} to write to.
1116      *
1117      * @throws IOException An IO error during serialization.
1118      * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1119      */
1120     private static void writePretty(final Configuration configuration, final OutputStream scxmlStream,
1121                                     final Writer scxmlWriter, final Result scxmlResult)
1122             throws IOException, XMLStreamException {
1123 
1124         // There isn't any portable way to write pretty using the JDK 1.6 StAX API
1125         configuration.internalWriter.flush();
1126         Source prettyPrintSource = new StreamSource(new StringReader(configuration.internalWriter.toString()));
1127         Result prettyPrintResult = null;
1128         if (scxmlStream != null) {
1129             prettyPrintResult = new StreamResult(scxmlStream);
1130         } else if (scxmlWriter != null) {
1131             prettyPrintResult = new StreamResult(scxmlWriter);
1132         } else if (scxmlResult != null) {
1133             prettyPrintResult = scxmlResult;
1134         }
1135 
1136         TransformerFactory factory = TransformerFactory.newInstance();
1137         try {
1138             Transformer transformer = factory.newTransformer();
1139             if (configuration.encoding != null) {
1140                 transformer.setOutputProperty(OutputKeys.ENCODING, configuration.encoding);
1141             }
1142             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1143             transformer.transform(prettyPrintSource, prettyPrintResult);
1144         } catch (TransformerException te) {
1145             throw new XMLStreamException("TransformerException while pretty printing SCXML", te);
1146         }
1147     }
1148 
1149     /**
1150      * Use the supplied {@link Configuration} to create an appropriate {@link XMLStreamWriter} for this
1151      * {@link SCXMLWriter}. Exactly one of the stream, writer or result parameters must be provided.
1152      *
1153      * @param configuration The {@link Configuration} to use.
1154      * @param stream The optional {@link OutputStream} to write to.
1155      * @param writer The optional {@link Writer} to write to.
1156      * @param result The optional {@link Result} to write to.
1157      *
1158      * @return The appropriately configured {@link XMLStreamWriter}.
1159      *
1160      * @throws XMLStreamException A problem with the XML stream creation.
1161      */
1162     private static XMLStreamWriter getWriter(final Configuration configuration, final OutputStream stream,
1163                                              final Writer writer, final Result result)
1164             throws XMLStreamException {
1165 
1166         // Instantiate the XMLOutputFactory
1167         XMLOutputFactory factory = XMLOutputFactory.newInstance();
1168         /*
1169         if (configuration.factoryId != null && configuration.factoryClassLoader != null) {
1170             // TODO StAX API bug means we can't use custom factories yet
1171             //factory = XMLOutputFactory.newInstance(configuration.factoryId, configuration.factoryClassLoader);
1172         }
1173         */
1174         for (Map.Entry<String, Object> property : configuration.properties.entrySet()) {
1175             factory.setProperty(property.getKey(), property.getValue());
1176         }
1177 
1178         XMLStreamWriter xsw = null;
1179         if (configuration.usePrettyPrint || configuration.writeToString) {
1180             xsw = factory.createXMLStreamWriter(configuration.internalWriter);
1181         } else if (stream != null) {
1182             if (configuration.encoding != null) {
1183                 xsw = factory.createXMLStreamWriter(stream, configuration.encoding);
1184             } else {
1185                 xsw = factory.createXMLStreamWriter(stream);
1186             }
1187         } else if (writer != null) {
1188             xsw = factory.createXMLStreamWriter(writer);
1189         } else if (result != null) {
1190             xsw = factory.createXMLStreamWriter(result);
1191         }
1192         return xsw;
1193     }
1194 
1195     /**
1196      * Get a {@link Transformer} instance that pretty prints the output.
1197      *
1198      * @return Transformer The indenting {@link Transformer} instance.
1199      */
1200     private static Transformer getTransformer() {
1201         Transformer transformer;
1202         Properties outputProps = new Properties();
1203         outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
1204         outputProps.put(OutputKeys.STANDALONE, "no");
1205         outputProps.put(OutputKeys.INDENT, "yes");
1206         try {
1207             TransformerFactory tfFactory = TransformerFactory.newInstance();
1208             transformer = tfFactory.newTransformer();
1209             transformer.setOutputProperties(outputProps);
1210         } catch (TransformerFactoryConfigurationError t) {
1211             org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLWriter.class);
1212             log.error(t.getMessage(), t);
1213             return null;
1214         } catch (TransformerConfigurationException e) {
1215             org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLWriter.class);
1216             log.error(e.getMessage(), e);
1217             return null;
1218         }
1219         return transformer;
1220     }
1221 
1222     /**
1223      * Discourage instantiation since this is a utility class.
1224      */
1225     private SCXMLWriter() {
1226         super();
1227     }
1228 
1229     //------------------------- CONFIGURATION CLASS -------------------------//
1230     /**
1231      * <p>
1232      * Configuration for the {@link SCXMLWriter}. The configuration properties necessary for the following are
1233      * covered:
1234      * </p>
1235      *
1236      * <ul>
1237      *   <li>{@link XMLOutputFactory} configuration properties such as <code>factoryId</code> or any properties</li>
1238      *   <li>{@link XMLStreamWriter} configuration properties such as target {@link Writer} or {@link OutputStream}
1239      *   and the <code>encoding</code></li>
1240      * </ul>
1241      */
1242     public static class Configuration {
1243 
1244         /*
1245          * Configuration properties for this {@link SCXMLWriter}.
1246          */
1247         // XMLOutputFactory configuration properties.
1248         /**
1249          * The <code>factoryId</code> to use for the {@link XMLOutputFactory}.
1250          */
1251         final String factoryId;
1252 
1253         /**
1254          * The {@link ClassLoader} to use for the {@link XMLOutputFactory} instance to create.
1255          */
1256         final ClassLoader factoryClassLoader;
1257 
1258         /**
1259          * The map of properties (keys are property name strings, values are object property values) for the
1260          * {@link XMLOutputFactory}.
1261          */
1262         final Map<String, Object> properties;
1263 
1264         // XMLStreamWriter configuration properties.
1265         /**
1266          * The <code>encoding</code> to use for the {@link XMLStreamWriter}.
1267          */
1268         final String encoding;
1269 
1270         /**
1271          * Whether to use a pretty print style that makes the output much more human readable.
1272          */
1273         final boolean usePrettyPrint;
1274 
1275         /**
1276          * The intermediate writer that will hold the output to be pretty printed, given the lack of a standard
1277          * StAX property for the {@link XMLOutputFactory} in this regard. The contents will get transformed using
1278          * the transformation API.
1279          */
1280         final Writer internalWriter;
1281 
1282         // Underlying stream or writer close
1283         /**
1284          * Whether to close the underlying stream or writer passed by the caller.
1285          */
1286         final boolean closeUnderlyingWhenDone;
1287 
1288         /**
1289          * Whether to maintain an internal writer to return the serialization as a string.
1290          */
1291         boolean writeToString;
1292 
1293         /**
1294          * The pretty print output as a string.
1295          */
1296         String prettyPrintOutput;
1297 
1298         /*
1299          * Public constructors
1300          */
1301         /**
1302          * Default constructor.
1303          */
1304         public Configuration() {
1305 
1306             this(null, null, null, null, false, false, false);
1307         }
1308 
1309         /**
1310          * All-purpose constructor. Any of the parameters passed in can be <code>null</code> (booleans should default
1311          * to <code>false</code>). At the moment, the <code>factoryId</code> and <code>factoryClassLoader</code>
1312          * arguments are effectively ignored due to a bug in the underlying StAX {@link XMLOutputFactory} API.
1313          *
1314          * @param factoryId The <code>factoryId</code> to use.
1315          * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLOutputFactory} instance to
1316          *                           create.
1317          * @param properties The map of properties (keys are property name strings, values are object property values)
1318          *                   for the {@link XMLOutputFactory}.
1319          * @param encoding The <code>encoding</code> to use for the {@link XMLStreamWriter}
1320          * @param usePrettyPrint Whether to make the output human readable as far as possible. Since StAX does not
1321          *                       provide a portable way to do this in JDK 1.6, choosing the pretty print option
1322          *                       is currently not very efficient.
1323          * @param closeUnderlyingWhenDone Whether to close the underlying stream or writer passed by the caller.
1324          */
1325         public Configuration(final String factoryId, final ClassLoader factoryClassLoader,
1326                              final Map<String, Object> properties, final String encoding, final boolean usePrettyPrint,
1327                              final boolean closeUnderlyingWhenDone) {
1328 
1329             this(factoryId, factoryClassLoader, properties, encoding, usePrettyPrint, closeUnderlyingWhenDone, false);
1330         }
1331 
1332         /*
1333          * Package access constructors
1334          */
1335         /**
1336          * Convenience package access constructor.
1337          *
1338          * @param writeToString Whether we will be returning the serialization as a string.
1339          * @param usePrettyPrint Whether we will attempt to make the output human readable as far as possible.
1340          */
1341         Configuration(final boolean writeToString, final boolean usePrettyPrint) {
1342 
1343             this(null, null, null, null, usePrettyPrint, false, writeToString);
1344         }
1345 
1346         /**
1347          * All-purpose package access constructor.
1348          *
1349          * @param factoryId The <code>factoryId</code> to use.
1350          * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLOutputFactory} instance to
1351          *                           create.
1352          * @param properties The map of properties (keys are property name strings, values are object property values)
1353          *                   for the {@link XMLOutputFactory}.
1354          * @param encoding The <code>encoding</code> to use for the {@link XMLStreamWriter}
1355          * @param usePrettyPrint Whether to make the output human readable as far as possible. Since StAX does not
1356          *                       provide a portable way to do this in JDK 1.6, choosing the pretty print option
1357          *                       is currently not very efficient.
1358          * @param closeUnderlyingWhenDone Whether to close the underlying stream or writer passed by the caller.
1359          * @param writeToString Whether to maintain an internal writer to return the serialization as a string.
1360          */
1361         Configuration(final String factoryId, final ClassLoader factoryClassLoader,
1362                       final Map<String, Object> properties, final String encoding, final boolean usePrettyPrint,
1363                       final boolean closeUnderlyingWhenDone, final boolean writeToString) {
1364 
1365             this.factoryId = factoryId;
1366             this.factoryClassLoader = factoryClassLoader;
1367             this.properties = (properties == null ? new HashMap<String, Object>() : properties);
1368             this.encoding = encoding;
1369             this.usePrettyPrint = usePrettyPrint;
1370             this.closeUnderlyingWhenDone = closeUnderlyingWhenDone;
1371             this.writeToString = writeToString;
1372             if (this.usePrettyPrint || this.writeToString) {
1373                 this.internalWriter = new StringWriter();
1374             } else {
1375                 this.internalWriter = null;
1376             }
1377         }
1378     }
1379 }