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 s = scxml.getStates();
104         Iterator i = s.keySet().iterator();
105         while (i.hasNext()) {
106             serializeState(b, (State) s.get(i.next()), INDENT);
107         }
108         b.append("</scxml>\n");
109         return b.toString();
110     }
111 
112     /***
113      * Serialize this State object.
114      *
115      * @param b The buffer to append the serialization to
116      * @param s The State to serialize
117      * @param indent The indent for this XML element
118      */
119     public static void serializeState(final StringBuffer b,
120             final State s, final String indent) {
121         b.append(indent).append("<state");
122         serializeTransitionTargetAttributes(b, s);
123         boolean f = s.getIsFinal();
124         if (f) {
125             b.append(" final=\"true\"");
126         }
127         b.append(">\n");
128         Initial ini = s.getInitial();
129         if (ini != null) {
130             serializeInitial(b, ini, indent + INDENT);
131         }
132         List h = s.getHistory();
133         if (h != null) {
134             serializeHistory(b, h, indent + INDENT);
135         }
136         Datamodel dm = s.getDatamodel();
137         if (dm != null) {
138             serializeDatamodel(b, dm, indent + INDENT);
139         }
140         serializeOnEntry(b, s, indent + INDENT);
141         Map t = s.getTransitions();
142         Iterator i = t.keySet().iterator();
143         while (i.hasNext()) {
144             List et = (List) t.get(i.next());
145             for (int len = 0; len < et.size(); len++) {
146                 serializeTransition(b, (Transition) et.get(len), indent
147                     + INDENT);
148             }
149         }
150         Parallel p = s.getParallel();
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.getStates();
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     public static void serializeTarget(final StringBuffer b,
298             final Transition t, final String indent) {
299         b.append(indent).append("<target");
300         String n = t.getNext();
301         if (n != null) {
302             b.append(" next=\"" + n + "\">\n");
303         } else {
304             b.append(">\n");
305             if (t.getTarget() != null) {
306                 // The inline transition target can only be a state
307                 serializeState(b, (State) t.getTarget(), indent + INDENT);
308             }
309         }
310         b.append(indent).append("</target>\n");
311     }
312 
313     /***
314      * Serialize this Datamodel object.
315      *
316      * @param b The buffer to append the serialization to
317      * @param dm The Datamodel to be serialized
318      * @param indent The indent for this XML element
319      */
320     public static void serializeDatamodel(final StringBuffer b,
321             final Datamodel dm, final String indent) {
322         List data = dm.getData();
323         if (data != null && data.size() > 0) {
324             b.append(indent).append("<datamodel>\n");
325             if (XFORMER == null) {
326                 b.append(indent).append(INDENT).
327                     append("<!-- Body content was not serialized -->\n");
328                 b.append(indent).append("</datamodel>\n");
329                 return;
330             }
331             for (Iterator iter = data.iterator(); iter.hasNext();) {
332                 Data datum = (Data) iter.next();
333                 Node dataNode = datum.getNode();
334                 if (dataNode != null) {
335                     StringWriter out = new StringWriter();
336                     try {
337                         Source input = new DOMSource(dataNode);
338                         Result output = new StreamResult(out);
339                         XFORMER.transform(input, output);
340                     } catch (TransformerException te) {
341                         org.apache.commons.logging.Log log = LogFactory.
342                             getLog(SCXMLSerializer.class);
343                         log.error(te.getMessage(), te);
344                         b.append(indent).append(INDENT).
345                             append("<!-- Data content not serialized -->\n");
346                     }
347                     b.append(indent).append(INDENT).append(out.toString());
348                 } else {
349                     b.append(indent).append(INDENT).append("<data name=\"").
350                         append(datum.getName()).append("\" expr=\"").
351                         append(datum.getExpr()).append("\" />\n");
352                 }
353             }
354             b.append(indent).append("</datamodel>\n");
355         }
356     }
357 
358     /***
359      * Serialize this OnEntry object.
360      *
361      * @param b The buffer to append the serialization to
362      * @param t The TransitionTarget whose OnEntry is to be serialized
363      * @param indent The indent for this XML element
364      */
365     public static void serializeOnEntry(final StringBuffer b,
366             final TransitionTarget t, final String indent) {
367         OnEntry e = t.getOnEntry();
368         if (e != null && e.getActions().size() > 0) {
369             b.append(indent).append("<onentry>\n");
370             serializeActions(b, e.getActions(), indent + INDENT);
371             b.append(indent).append("</onentry>\n");
372         }
373     }
374 
375     /***
376      * Serialize this OnExit object.
377      *
378      * @param b The buffer to append the serialization to
379      * @param t The TransitionTarget whose OnExit is to be serialized
380      * @param indent The indent for this XML element
381      */
382     public static void serializeOnExit(final StringBuffer b,
383             final TransitionTarget t, final String indent) {
384         OnExit x = t.getOnExit();
385         if (x != null && x.getActions().size() > 0) {
386             b.append(indent).append("<onexit>\n");
387             serializeActions(b, x.getActions(), indent + INDENT);
388             b.append(indent).append("</onexit>\n");
389         }
390     }
391 
392     /***
393      * Serialize this List of actions.
394      *
395      * @param b The buffer to append the serialization to
396      * @param l The List of actions to serialize
397      * @param indent The indent for this XML element
398      * @return boolean true if the list of actions contains an &lt;exit/&gt;
399      */
400     public static boolean serializeActions(final StringBuffer b, final List l,
401             final String indent) {
402         if (l == null) {
403             return false;
404         }
405         boolean exit = false;
406         Iterator i = l.iterator();
407         while (i.hasNext()) {
408             Action a = (Action) i.next();
409             if (a instanceof Var) {
410                 Var v = (Var) a;
411                 b.append(indent).append("<var name=\"").append(v.getName())
412                         .append("\" expr=\"").append(v.getExpr()).append(
413                                 "\"/>\n");
414             } else if (a instanceof Assign) {
415                 Assign asn = (Assign) a;
416                 b.append(indent).append("<assign");
417                 if (!SCXMLHelper.isStringEmpty(asn.getLocation())) {
418                     b.append(" location=\"").append(asn.getLocation());
419                     if (!SCXMLHelper.isStringEmpty(asn.getSrc())) {
420                         b.append("\" src=\"").append(asn.getSrc());
421                     } else {
422                         b.append("\" expr=\"").append(asn.getExpr());
423                     }
424                 } else {
425                     b.append(" name=\"").append(asn.getName()).
426                         append("\" expr=\"").append(asn.getExpr());
427                 }
428                 b.append("\"/>\n");
429             } else if (a instanceof Send) {
430                 serializeSend(b, (Send) a, indent);
431             } else if (a instanceof Cancel) {
432                 Cancel c = (Cancel) a;
433                 b.append(indent).append("<cancel sendid=\"")
434                     .append(c.getSendid()).append("\"/>\n");
435             } else if (a instanceof Log) {
436                 Log lg = (Log) a;
437                 b.append(indent).append("<log expr=\"").append(lg.getExpr())
438                         .append("\"/>\n");
439             } else if (a instanceof Exit) {
440                 Exit e = (Exit) a;
441                 b.append(indent).append("<exit");
442                 String expr = e.getExpr();
443                 String nl = e.getNamelist();
444                 if (expr != null) {
445                     b.append(" expr=\"" + expr + "\"");
446                 }
447                 if (nl != null) {
448                     b.append(" namelist=\"" + nl + "\"");
449                 }
450                 b.append("/>\n");
451                 exit = true;
452             } else if (a instanceof If) {
453                 If iff = (If) a;
454                 serializeIf(b, iff, indent);
455             } else if (a instanceof Else) {
456                 b.append(indent).append("<else/>\n");
457             } else if (a instanceof ElseIf) {
458                 ElseIf eif = (ElseIf) a;
459                 b.append(indent).append("<elseif cond=\"")
460                         .append(eif.getCond()).append("\" />\n");
461             }
462         }
463         return exit;
464     }
465 
466     /***
467      * Serialize this Send object.
468      *
469      * @param b The buffer to append the serialization to
470      * @param send The Send object to serialize
471      * @param indent The indent for this XML element
472      */
473     public static void serializeSend(final StringBuffer b,
474             final Send send, final String indent) {
475         b.append(indent).append("<send sendid=\"")
476             .append(send.getSendid()).append("\" target=\"")
477             .append(send.getTarget()).append("\" targetType=\"")
478             .append(send.getTargettype()).append("\" namelist=\"")
479             .append(send.getNamelist()).append("\" delay=\"")
480             .append(send.getDelay()).append("\" events=\"")
481             .append(send.getEvent()).append("\" hints=\"")
482             .append(send.getHints()).append("\">\n")
483             .append(getBodyContent(send))
484             .append(indent).append("</send>\n");
485     }
486 
487     /***
488      * Return serialized body of <code>ExternalContent</code>.
489      *
490      * @param externalContent The model element containing the body content
491      * @return String The serialized body content
492      */
493     public static final String getBodyContent(
494             final ExternalContent externalContent) {
495         StringBuffer buf = new StringBuffer();
496         List externalNodes = externalContent.getExternalNodes();
497         if (externalNodes.size() > 0 && XFORMER == null) {
498             buf.append("<!-- Body content was not serialized -->\n");
499             return buf.toString();
500         }
501         for (int i = 0; i < externalNodes.size(); i++) {
502             Source input = new DOMSource((Node) externalNodes.get(i));
503             StringWriter out = new StringWriter();
504             Result output = new StreamResult(out);
505             try {
506                 XFORMER.transform(input, output);
507             } catch (TransformerException te) {
508                 org.apache.commons.logging.Log log = LogFactory.
509                     getLog(SCXMLSerializer.class);
510                 log.error(te.getMessage(), te);
511                 buf.append("<!-- Not all body content was serialized -->");
512             }
513             buf.append(out.toString()).append("\n");
514         }
515         return buf.toString();
516     }
517 
518     /***
519      * Serialize this If object.
520      *
521      * @param b The buffer to append the serialization to
522      * @param iff The If object to serialize
523      * @param indent The indent for this XML element
524      */
525     public static void serializeIf(final StringBuffer b,
526             final If iff, final String indent) {
527         b.append(indent).append("<if cond=\"").append(iff.getCond()).append(
528                 "\">\n");
529         serializeActions(b, iff.getActions(), indent + INDENT);
530         b.append(indent).append("</if>\n");
531     }
532 
533     /***
534      * Serialize properties of TransitionTarget which are element attributes.
535      *
536      * @param b The buffer to append the serialization to
537      * @param t The TransitionTarget
538      */
539     private static void serializeTransitionTargetAttributes(
540             final StringBuffer b, final TransitionTarget t) {
541         String id = t.getId();
542         if (id != null) {
543             b.append(" id=\"").append(id).append("\"");
544         }
545         TransitionTarget pt = t.getParent();
546         if (pt != null) {
547             String pid = pt.getId();
548             if (pid != null) {
549                 b.append(" parentid=\"").append(pid).append("\"");
550             }
551         }
552     }
553 
554     /***
555      * Get a <code>Transformer</code> instance.
556      *
557      * @return Transformer The <code>Transformer</code> instance.
558      */
559     private static Transformer getTransformer() {
560         Transformer transformer = null;
561         Properties outputProps = new Properties();
562         outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
563         outputProps.put(OutputKeys.STANDALONE, "no");
564         outputProps.put(OutputKeys.INDENT, "yes");
565         try {
566             TransformerFactory tfFactory = TransformerFactory.newInstance();
567             transformer = tfFactory.newTransformer();
568             transformer.setOutputProperties(outputProps);
569         } catch (Throwable t) {
570             return null;
571         }
572         return transformer;
573     }
574 
575     /*
576      * Private methods.
577      */
578     /***
579      * Discourage instantiation since this is a utility class.
580      */
581     private SCXMLSerializer() {
582         super();
583     }
584 
585 }
586