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