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.scxml.io;
18  
19  import java.io.StringWriter;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Properties;
24  import java.util.Set;
25  
26  import javax.xml.transform.OutputKeys;
27  import javax.xml.transform.Result;
28  import javax.xml.transform.Source;
29  import javax.xml.transform.Transformer;
30  import javax.xml.transform.TransformerException;
31  import javax.xml.transform.TransformerFactory;
32  import javax.xml.transform.dom.DOMSource;
33  import javax.xml.transform.stream.StreamResult;
34  
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.commons.scxml.SCXMLHelper;
37  import org.apache.commons.scxml.model.Action;
38  import org.apache.commons.scxml.model.Assign;
39  import org.apache.commons.scxml.model.Cancel;
40  import org.apache.commons.scxml.model.Data;
41  import org.apache.commons.scxml.model.Datamodel;
42  import org.apache.commons.scxml.model.Else;
43  import org.apache.commons.scxml.model.ElseIf;
44  import org.apache.commons.scxml.model.Exit;
45  import org.apache.commons.scxml.model.ExternalContent;
46  import org.apache.commons.scxml.model.Finalize;
47  import org.apache.commons.scxml.model.History;
48  import org.apache.commons.scxml.model.If;
49  import org.apache.commons.scxml.model.Initial;
50  import org.apache.commons.scxml.model.Invoke;
51  import org.apache.commons.scxml.model.Log;
52  import org.apache.commons.scxml.model.OnEntry;
53  import org.apache.commons.scxml.model.OnExit;
54  import org.apache.commons.scxml.model.Parallel;
55  import org.apache.commons.scxml.model.Param;
56  import org.apache.commons.scxml.model.SCXML;
57  import org.apache.commons.scxml.model.Send;
58  import org.apache.commons.scxml.model.State;
59  import org.apache.commons.scxml.model.Transition;
60  import org.apache.commons.scxml.model.TransitionTarget;
61  import org.apache.commons.scxml.model.Var;
62  import org.w3c.dom.Node;
63  
64  /***
65   * Utility class for serializing the Commons SCXML Java object
66   * model. Class uses the visitor pattern to trace through the
67   * object heirarchy. Used primarily for testing, debugging and
68   * visual verification.
69   *
70   */
71  public class SCXMLSerializer {
72  
73      /*** The indent to be used while serializing an SCXML object. */
74      private static final String INDENT = " ";
75      /*** The JAXP transformer. */
76      private static final Transformer XFORMER = getTransformer();
77  
78      /***
79       * Serialize this SCXML object (primarily for debugging).
80       *
81       * @param scxml
82       *            The SCXML to be serialized
83       * @return String The serialized SCXML
84       */
85      public static String serialize(final SCXML scxml) {
86          StringBuffer b =
87              new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n").
88                  append("<scxml xmlns=\"").append(scxml.getXmlns()).
89                  append("\" version=\"").append(scxml.getVersion()).
90                  append("\" initialstate=\"").append(scxml.getInitialstate()).
91                  append("\">\n");
92          if (XFORMER == null) {
93              org.apache.commons.logging.Log log = LogFactory.
94                  getLog(SCXMLSerializer.class);
95              log.warn("SCXMLSerializer: DOM serialization pertinent to"
96                  + " the document will be skipped since a suitable"
97                  + " JAXP Transformer could not be instantiated.");
98          }
99          Datamodel dm = scxml.getDatamodel();
100         if (dm != null) {
101             serializeDatamodel(b, dm, INDENT);
102         }
103         Map c = scxml.getChildren();
104         Iterator i = c.keySet().iterator();
105         while (i.hasNext()) {
106             TransitionTarget tt = (TransitionTarget) c.get(i.next());
107             if (tt instanceof State) {
108                 serializeState(b, (State) tt, INDENT);
109             } else {
110                 serializeParallel(b, (Parallel) tt, INDENT);
111             }
112         }
113         b.append("</scxml>\n");
114         return b.toString();
115     }
116 
117     /***
118      * Serialize this State object.
119      *
120      * @param b The buffer to append the serialization to
121      * @param s The State to serialize
122      * @param indent The indent for this XML element
123      */
124     public static void serializeState(final StringBuffer b,
125             final State s, final String indent) {
126         b.append(indent).append("<state");
127         serializeTransitionTargetAttributes(b, s);
128         boolean f = s.isFinal();
129         if (f) {
130             b.append(" final=\"true\"");
131         }
132         b.append(">\n");
133         Initial ini = s.getInitial();
134         if (ini != null) {
135             serializeInitial(b, ini, indent + INDENT);
136         }
137         List h = s.getHistory();
138         if (h != null) {
139             serializeHistory(b, h, indent + INDENT);
140         }
141         Datamodel dm = s.getDatamodel();
142         if (dm != null) {
143             serializeDatamodel(b, dm, indent + INDENT);
144         }
145         serializeOnEntry(b, s, indent + INDENT);
146         List t = s.getTransitionsList();
147         for (int i = 0; i < t.size(); i++) {
148             serializeTransition(b, (Transition) t.get(i), indent + INDENT);
149         }
150         Parallel p = s.getParallel(); //TODO: Remove in v1.0
151         Invoke inv = s.getInvoke();
152         if (p != null) {
153             serializeParallel(b, p, indent + INDENT);
154         } else if (inv != null) {
155             serializeInvoke(b , inv, indent + INDENT);
156         } else {
157             Map c = s.getChildren();
158             Iterator j = c.keySet().iterator();
159             while (j.hasNext()) {
160                 State cs = (State) c.get(j.next());
161                 serializeState(b, cs, indent + INDENT);
162             }
163         }
164         serializeOnExit(b, s, indent + INDENT);
165         b.append(indent).append("</state>\n");
166     }
167 
168     /***
169      * Serialize this Parallel object.
170      *
171      * @param b The buffer to append the serialization to
172      * @param p The Parallel to serialize
173      * @param indent The indent for this XML element
174      */
175     public static void serializeParallel(final StringBuffer b,
176             final Parallel p, final String indent) {
177         b.append(indent).append("<parallel");
178         serializeTransitionTargetAttributes(b, p);
179         b.append(">\n");
180         serializeOnEntry(b, p, indent + INDENT);
181         Set s = p.getChildren();
182         Iterator i = s.iterator();
183         while (i.hasNext()) {
184             serializeState(b, (State) i.next(), indent + INDENT);
185         }
186         serializeOnExit(b, p, indent + INDENT);
187         b.append(indent).append("</parallel>\n");
188     }
189 
190     /***
191      * Serialize this Invoke object.
192      *
193      * @param b The buffer to append the serialization to
194      * @param i The Invoke to serialize
195      * @param indent The indent for this XML element
196      */
197     public static void serializeInvoke(final StringBuffer b,
198             final Invoke i, final String indent) {
199         b.append(indent).append("<invoke");
200         String ttype = i.getTargettype();
201         String src = i.getSrc();
202         String srcexpr = i.getSrcexpr();
203         if (ttype != null) {
204             b.append(" targettype=\"").append(ttype).append("\"");
205         }
206         // Prefer src
207         if (src != null) {
208             b.append(" src=\"").append(src).append("\"");
209         } else if (srcexpr != null) {
210             b.append(" srcexpr=\"").append(srcexpr).append("\"");
211         }
212         b.append(">\n");
213         List params = i.params();
214         for (Iterator iter = params.iterator(); iter.hasNext();) {
215             Param p = (Param) iter.next();
216             b.append(indent).append(INDENT).append("<param name=\"").
217                 append(p.getName()).append("\" expr=\"").
218                 append(p.getExpr()).append("\"/>\n");
219         }
220         Finalize f = i.getFinalize();
221         if (f != null) {
222             b.append(indent).append(INDENT).append("<finalize>\n");
223             serializeActions(b, f.getActions(), indent + INDENT + INDENT);
224             b.append(indent).append(INDENT).append("</finalize>\n");
225         }
226         b.append(indent).append("</invoke>\n");
227     }
228 
229     /***
230      * Serialize this Initial object.
231      *
232      * @param b The buffer to append the serialization to
233      * @param i The Initial to serialize
234      * @param indent The indent for this XML element
235      */
236     public static void serializeInitial(final StringBuffer b, final Initial i,
237             final String indent) {
238         b.append(indent).append("<initial");
239         serializeTransitionTargetAttributes(b, i);
240         b.append(">\n");
241         serializeTransition(b, i.getTransition(), indent + INDENT);
242         b.append(indent).append("</initial>\n");
243     }
244 
245     /***
246      * Serialize the History.
247      *
248      * @param b The buffer to append the serialization to
249      * @param l The List of History objects to serialize
250      * @param indent The indent for this XML element
251      */
252     public static void serializeHistory(final StringBuffer b, final List l,
253             final String indent) {
254         if (l.size() > 0) {
255             for (int i = 0; i < l.size(); i++) {
256                 History h = (History) l.get(i);
257                 b.append(indent).append("<history");
258                 serializeTransitionTargetAttributes(b, h);
259                  if (h.isDeep()) {
260                      b.append(" type=\"deep\"");
261                  } else {
262                      b.append(" type=\"shallow\"");
263                  }
264                 b.append(">\n");
265                 serializeTransition(b, h.getTransition(), indent + INDENT);
266                 b.append(indent).append("</history>\n");
267             }
268         }
269     }
270 
271     /***
272      * Serialize this Transition object.
273      *
274      * @param b The buffer to append the serialization to
275      * @param t The Transition to serialize
276      * @param indent The indent for this XML element
277      */
278     public static void serializeTransition(final StringBuffer b,
279             final Transition t, final String indent) {
280         b.append(indent).append("<transition event=\"").append(t.getEvent())
281             .append("\" cond=\"").append(t.getCond()).append("\">\n");
282         boolean exit = serializeActions(b, t.getActions(), indent + INDENT);
283         if (!exit) {
284             serializeTarget(b, t, indent + INDENT);
285         }
286         b.append(indent).append("</transition>\n");
287     }
288 
289     /***
290      * Serialize this Transition's Target.
291      *
292      *
293      * @param b The buffer to append the serialization to
294      * @param t The Transition whose Target needs to be serialized
295      * @param indent The indent for this XML element
296      *
297      * @deprecated Inline &lt;target&gt; element has been deprecated
298      *             in the SCXML WD
299      */
300     public static void serializeTarget(final StringBuffer b,
301             final Transition t, final String indent) {
302         b.append(indent).append("<target");
303         String n = t.getNext();
304         if (n != null) {
305             b.append(" next=\"" + n + "\">\n");
306         } else {
307             b.append(">\n");
308             if (t.getTarget() != null) {
309                 // The inline transition target can only be a state
310                 serializeState(b, (State) t.getTarget(), indent + INDENT);
311             }
312         }
313         b.append(indent).append("</target>\n");
314     }
315 
316     /***
317      * Serialize this Datamodel object.
318      *
319      * @param b The buffer to append the serialization to
320      * @param dm The Datamodel to be serialized
321      * @param indent The indent for this XML element
322      */
323     public static void serializeDatamodel(final StringBuffer b,
324             final Datamodel dm, final String indent) {
325         List data = dm.getData();
326         if (data != null && data.size() > 0) {
327             b.append(indent).append("<datamodel>\n");
328             if (XFORMER == null) {
329                 b.append(indent).append(INDENT).
330                     append("<!-- Body content was not serialized -->\n");
331                 b.append(indent).append("</datamodel>\n");
332                 return;
333             }
334             for (Iterator iter = data.iterator(); iter.hasNext();) {
335                 Data datum = (Data) iter.next();
336                 Node dataNode = datum.getNode();
337                 if (dataNode != null) {
338                     StringWriter out = new StringWriter();
339                     try {
340                         Source input = new DOMSource(dataNode);
341                         Result output = new StreamResult(out);
342                         XFORMER.transform(input, output);
343                     } catch (TransformerException te) {
344                         org.apache.commons.logging.Log log = LogFactory.
345                             getLog(SCXMLSerializer.class);
346                         log.error(te.getMessage(), te);
347                         b.append(indent).append(INDENT).
348                             append("<!-- Data content not serialized -->\n");
349                     }
350                     b.append(indent).append(INDENT).append(out.toString());
351                 } else {
352                     b.append(indent).append(INDENT).append("<data name=\"").
353                         append(datum.getName()).append("\" expr=\"").
354                         append(datum.getExpr()).append("\" />\n");
355                 }
356             }
357             b.append(indent).append("</datamodel>\n");
358         }
359     }
360 
361     /***
362      * Serialize this OnEntry object.
363      *
364      * @param b The buffer to append the serialization to
365      * @param t The TransitionTarget whose OnEntry is to be serialized
366      * @param indent The indent for this XML element
367      */
368     public static void serializeOnEntry(final StringBuffer b,
369             final TransitionTarget t, final String indent) {
370         OnEntry e = t.getOnEntry();
371         if (e != null && e.getActions().size() > 0) {
372             b.append(indent).append("<onentry>\n");
373             serializeActions(b, e.getActions(), indent + INDENT);
374             b.append(indent).append("</onentry>\n");
375         }
376     }
377 
378     /***
379      * Serialize this OnExit object.
380      *
381      * @param b The buffer to append the serialization to
382      * @param t The TransitionTarget whose OnExit is to be serialized
383      * @param indent The indent for this XML element
384      */
385     public static void serializeOnExit(final StringBuffer b,
386             final TransitionTarget t, final String indent) {
387         OnExit x = t.getOnExit();
388         if (x != null && x.getActions().size() > 0) {
389             b.append(indent).append("<onexit>\n");
390             serializeActions(b, x.getActions(), indent + INDENT);
391             b.append(indent).append("</onexit>\n");
392         }
393     }
394 
395     /***
396      * Serialize this List of actions.
397      *
398      * @param b The buffer to append the serialization to
399      * @param l The List of actions to serialize
400      * @param indent The indent for this XML element
401      * @return boolean true if the list of actions contains an &lt;exit/&gt;
402      */
403     public static boolean serializeActions(final StringBuffer b, final List l,
404             final String indent) {
405         if (l == null) {
406             return false;
407         }
408         boolean exit = false;
409         Iterator i = l.iterator();
410         while (i.hasNext()) {
411             Action a = (Action) i.next();
412             if (a instanceof Var) {
413                 Var v = (Var) a;
414                 b.append(indent).append("<var name=\"").append(v.getName())
415                         .append("\" expr=\"").append(v.getExpr()).append(
416                                 "\"/>\n");
417             } else if (a instanceof Assign) {
418                 Assign asn = (Assign) a;
419                 b.append(indent).append("<assign");
420                 if (!SCXMLHelper.isStringEmpty(asn.getLocation())) {
421                     b.append(" location=\"").append(asn.getLocation());
422                     if (!SCXMLHelper.isStringEmpty(asn.getSrc())) {
423                         b.append("\" src=\"").append(asn.getSrc());
424                     } else {
425                         b.append("\" expr=\"").append(asn.getExpr());
426                     }
427                 } else {
428                     b.append(" name=\"").append(asn.getName()).
429                         append("\" expr=\"").append(asn.getExpr());
430                 }
431                 b.append("\"/>\n");
432             } else if (a instanceof Send) {
433                 serializeSend(b, (Send) a, indent);
434             } else if (a instanceof Cancel) {
435                 Cancel c = (Cancel) a;
436                 b.append(indent).append("<cancel sendid=\"")
437                     .append(c.getSendid()).append("\"/>\n");
438             } else if (a instanceof Log) {
439                 Log lg = (Log) a;
440                 b.append(indent).append("<log expr=\"").append(lg.getExpr())
441                         .append("\"/>\n");
442             } else if (a instanceof Exit) {
443                 Exit e = (Exit) a;
444                 b.append(indent).append("<exit");
445                 String expr = e.getExpr();
446                 String nl = e.getNamelist();
447                 if (expr != null) {
448                     b.append(" expr=\"" + expr + "\"");
449                 }
450                 if (nl != null) {
451                     b.append(" namelist=\"" + nl + "\"");
452                 }
453                 b.append("/>\n");
454                 exit = true;
455             } else if (a instanceof If) {
456                 If iff = (If) a;
457                 serializeIf(b, iff, indent);
458             } else if (a instanceof Else) {
459                 b.append(indent).append("<else/>\n");
460             } else if (a instanceof ElseIf) {
461                 ElseIf eif = (ElseIf) a;
462                 b.append(indent).append("<elseif cond=\"")
463                         .append(eif.getCond()).append("\" />\n");
464             }
465         }
466         return exit;
467     }
468 
469     /***
470      * Serialize this Send object.
471      *
472      * @param b The buffer to append the serialization to
473      * @param send The Send object to serialize
474      * @param indent The indent for this XML element
475      */
476     public static void serializeSend(final StringBuffer b,
477             final Send send, final String indent) {
478         b.append(indent).append("<send sendid=\"")
479             .append(send.getSendid()).append("\" target=\"")
480             .append(send.getTarget()).append("\" targetType=\"")
481             .append(send.getTargettype()).append("\" namelist=\"")
482             .append(send.getNamelist()).append("\" delay=\"")
483             .append(send.getDelay()).append("\" events=\"")
484             .append(send.getEvent()).append("\" hints=\"")
485             .append(send.getHints()).append("\">\n")
486             .append(getBodyContent(send))
487             .append(indent).append("</send>\n");
488     }
489 
490     /***
491      * Return serialized body of <code>ExternalContent</code>.
492      *
493      * @param externalContent The model element containing the body content
494      * @return String The serialized body content
495      */
496     public static final String getBodyContent(
497             final ExternalContent externalContent) {
498         StringBuffer buf = new StringBuffer();
499         List externalNodes = externalContent.getExternalNodes();
500         if (externalNodes.size() > 0 && XFORMER == null) {
501             buf.append("<!-- Body content was not serialized -->\n");
502             return buf.toString();
503         }
504         for (int i = 0; i < externalNodes.size(); i++) {
505             Source input = new DOMSource((Node) externalNodes.get(i));
506             StringWriter out = new StringWriter();
507             Result output = new StreamResult(out);
508             try {
509                 XFORMER.transform(input, output);
510             } catch (TransformerException te) {
511                 org.apache.commons.logging.Log log = LogFactory.
512                     getLog(SCXMLSerializer.class);
513                 log.error(te.getMessage(), te);
514                 buf.append("<!-- Not all body content was serialized -->");
515             }
516             buf.append(out.toString()).append("\n");
517         }
518         return buf.toString();
519     }
520 
521     /***
522      * Serialize this If object.
523      *
524      * @param b The buffer to append the serialization to
525      * @param iff The If object to serialize
526      * @param indent The indent for this XML element
527      */
528     public static void serializeIf(final StringBuffer b,
529             final If iff, final String indent) {
530         b.append(indent).append("<if cond=\"").append(iff.getCond()).append(
531                 "\">\n");
532         serializeActions(b, iff.getActions(), indent + INDENT);
533         b.append(indent).append("</if>\n");
534     }
535 
536     /***
537      * Serialize properties of TransitionTarget which are element attributes.
538      *
539      * @param b The buffer to append the serialization to
540      * @param t The TransitionTarget
541      */
542     private static void serializeTransitionTargetAttributes(
543             final StringBuffer b, final TransitionTarget t) {
544         String id = t.getId();
545         if (id != null) {
546             b.append(" id=\"").append(id).append("\"");
547         }
548         TransitionTarget pt = t.getParent();
549         if (pt != null) {
550             String pid = pt.getId();
551             if (pid != null) {
552                 b.append(" parentid=\"").append(pid).append("\"");
553             }
554         }
555     }
556 
557     /***
558      * Get a <code>Transformer</code> instance.
559      *
560      * @return Transformer The <code>Transformer</code> instance.
561      */
562     private static Transformer getTransformer() {
563         Transformer transformer = null;
564         Properties outputProps = new Properties();
565         outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
566         outputProps.put(OutputKeys.STANDALONE, "no");
567         outputProps.put(OutputKeys.INDENT, "yes");
568         try {
569             TransformerFactory tfFactory = TransformerFactory.newInstance();
570             transformer = tfFactory.newTransformer();
571             transformer.setOutputProperties(outputProps);
572         } catch (Throwable t) {
573             return null;
574         }
575         return transformer;
576     }
577 
578     /*
579      * Private methods.
580      */
581     /***
582      * Discourage instantiation since this is a utility class.
583      */
584     private SCXMLSerializer() {
585         super();
586     }
587 
588 }
589