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