1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
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
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 <target> 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
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 <exit/>
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
580
581 /***
582 * Discourage instantiation since this is a utility class.
583 */
584 private SCXMLSerializer() {
585 super();
586 }
587
588 }
589