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;
    18
    19  import java.io.Serializable;
    20  import java.util.Iterator;
    21  import java.util.Map;
    22  import java.util.Set;
    23
    24  import javax.xml.transform.TransformerException;
    25
    26  import org.apache.commons.logging.Log;
    27  import org.apache.commons.logging.LogFactory;
    28  import org.apache.commons.scxml.model.TransitionTarget;
    29  import org.apache.xml.utils.PrefixResolver;
    30  import org.apache.xpath.XPath;
    31  import org.apache.xpath.XPathAPI;
    32  import org.apache.xpath.XPathContext;
    33  import org.w3c.dom.Node;
    34  import org.w3c.dom.NodeList;
    35
    36  /***
    37   * Implementations of builtin functions defined by the SCXML
    38   * specification.
    39   *
    40   * The current version of the specification defines one builtin
    41   * predicate In()
    42   */
    43  public class Builtin implements Serializable {
    44
    45      /***
    46       * Serial version UID.
    47       */
    48      private static final long serialVersionUID = 1L;
    49
    50      /***
    51       * Implements the In() predicate for SCXML documents. The method
    52       * name chosen is different since &quot;in&quot; is a reserved token
    53       * in some expression languages.
    54       *
    55       * Does this state belong to the given Set of States.
    56       * Simple ID based comparator, assumes IDs are unique.
    57       *
    58       * @param allStates The Set of State objects to look in
    59       * @param state The State ID to compare with
    60       * @return Whether this State belongs to this Set
    61       */
    62      public static boolean isMember(final Set allStates,
    63              final String state) {
    64          for (Iterator i = allStates.iterator(); i.hasNext();) {
    65              TransitionTarget tt = (TransitionTarget) i.next();
    66              if (state.equals(tt.getId())) {
    67                  return true;
    68              }
    69          }
    70          return false;
    71      }
    72
    73      /***
    74       * Implements the Data() function for Commons SCXML documents, that
    75       * can be used to obtain a node from one of the XML data trees.
    76       * Manifests within "location" attribute of &lt;assign&gt; element,
    77       * for Commons JEXL and Commons EL based documents.
    78       *
    79       * @param namespaces The current document namespaces map at XPath location
    80       * @param data The context Node, though the method accepts an Object
    81       *             so error is reported by Commons SCXML, rather
    82       *             than the underlying expression language.
    83       * @param path The XPath expression.
    84       * @return The first node matching the path, or null if no nodes match.
    85       */
    86      public static Node dataNode(final Map namespaces, final Object data,
    87              final String path) {
    88          if (data == null || !(data instanceof Node)) {
    89              Log log = LogFactory.getLog(Builtin.class);
    90              log.error("Data(): Cannot evaluate an XPath expression"
    91                  + " in the absence of a context Node, null returned");
    92              return null;
    93          }
    94          Node dataNode = (Node) data;
    95          NodeList result = null;
    96          try {
    97              if (namespaces == null || namespaces.size() == 0) {
    98                  Log log = LogFactory.getLog(Builtin.class);
    99                  if (log.isDebugEnabled()) {
    100                     log.debug("Turning off namespaced XPath evaluation since "
    101                         + "no namespace information is available for path: "
    102                         + path);
    103                 }
    104                 result = XPathAPI.selectNodeList(dataNode, path);
    105             } else {
    106                 XPathContext xpathSupport = new XPathContext();
    107                 PrefixResolver prefixResolver =
    108                     new DataPrefixResolver(namespaces);
    109                 XPath xpath = new XPath(path, null, prefixResolver,
    110                     XPath.SELECT);
    111                 int ctxtNode = xpathSupport.getDTMHandleFromNode(dataNode);
    112                 result = xpath.execute(xpathSupport, ctxtNode,
    113                     prefixResolver).nodelist();
    114             }
    115         } catch (TransformerException te) {
    116             Log log = LogFactory.getLog(Builtin.class);
    117             log.error(te.getMessage(), te);
    118             return null;
    119         }
    120         int length = result.getLength();
    121         if (length == 0) {
    122             Log log = LogFactory.getLog(Builtin.class);
    123             log.warn("Data(): No nodes matching the XPath expression \""
    124                 + path + "\", returning null");
    125             return null;
    126         } else {
    127             if (length > 1) {
    128                 Log log = LogFactory.getLog(Builtin.class);
    129                 log.warn("Data(): Multiple nodes matching XPath expression"
    130                     + path + "\", returning first");
    131             }
    132             return result.item(0);
    133         }
    134     }
    135
    136     /***
    137      * A variant of the Data() function for Commons SCXML documents,
    138      * coerced to a Double, a Long or a String, whichever succeeds,
    139      * in that order.
    140      * Manifests within rvalue expressions in the document,
    141      * for Commons JEXL and Commons EL based documents..
    142      *
    143      * @param namespaces The current document namespaces map at XPath location
    144      * @param data The context Node, though the method accepts an Object
    145      *             so error is reported by Commons SCXML, rather
    146      *             than the underlying expression language.
    147      * @param path The XPath expression.
    148      * @return The first node matching the path, coerced to a String, or null
    149      *         if no nodes match.
    150      */
    151     public static Object data(final Map namespaces, final Object data,
    152             final String path) {
    153         Object retVal = null;
    154         String strVal = SCXMLHelper.getNodeValue(dataNode(namespaces,
    155             data, path));
    156         // try as a double
    157         try {
    158             double d = Double.parseDouble(strVal);
    159             retVal = new Double(d);
    160         } catch (NumberFormatException notADouble) {
    161             // else as a long
    162             try {
    163                 long l = Long.parseLong(strVal);
    164                 retVal = new Long(l);
    165             } catch (NumberFormatException notALong) {
    166                 // fallback to string
    167                 retVal = strVal;
    168             }
    169         }
    170         return retVal;
    171     }
    172
    173     /***
    174      * Implements the Data() function for Commons SCXML documents, that
    175      * can be used to obtain a node from one of the XML data trees.
    176      * Manifests within "location" attribute of &lt;assign&gt; element,
    177      * for Commons JEXL and Commons EL based documents.
    178      *
    179      * @param data The context Node, though the method accepts an Object
    180      *             so error is reported by Commons SCXML, rather
    181      *             than the underlying expression language.
    182      * @param path The XPath expression.
    183      * @return The first node matching the path, or null if no nodes match.
    184      *
    185      * @deprecated Use {@link #dataNode(Map,Object,String)} instead
    186      */
    187     public static Node dataNode(final Object data, final String path) {
    188         if (data == null || !(data instanceof Node)) {
    189             Log log = LogFactory.getLog(Builtin.class);
    190             log.error("Data(): Cannot evaluate an XPath expression"
    191                 + " in the absence of a context Node, null returned");
    192             return null;
    193         }
    194         Node dataNode = (Node) data;
    195         NodeList result = null;
    196         try {
    197             result = XPathAPI.selectNodeList(dataNode, path);
    198         } catch (TransformerException te) {
    199             Log log = LogFactory.getLog(Builtin.class);
    200             log.error(te.getMessage(), te);
    201             return null;
    202         }
    203         int length = result.getLength();
    204         if (length == 0) {
    205             Log log = LogFactory.getLog(Builtin.class);
    206             log.warn("Data(): No nodes matching the XPath expression \""
    207                 + path + "\", returning null");
    208             return null;
    209         } else {
    210             if (length > 1) {
    211                 Log log = LogFactory.getLog(Builtin.class);
    212                 log.warn("Data(): Multiple nodes matching XPath expression"
    213                     + path + "\", returning first");
    214             }
    215             return result.item(0);
    216         }
    217     }
    218
    219     /***
    220      * A variant of the Data() function for Commons SCXML documents,
    221      * coerced to a Double, a Long or a String, whichever succeeds,
    222      * in that order.
    223      * Manifests within rvalue expressions in the document,
    224      * for Commons JEXL and Commons EL based documents..
    225      *
    226      * @param data The context Node, though the method accepts an Object
    227      *             so error is reported by Commons SCXML, rather
    228      *             than the underlying expression language.
    229      * @param path The XPath expression.
    230      * @return The first node matching the path, coerced to a String, or null
    231      *         if no nodes match.
    232      *
    233      * @deprecated Use {@link #data(Map,Object,String)} instead
    234      */
    235     public static Object data(final Object data, final String path) {
    236         Object retVal = null;
    237         String strVal = SCXMLHelper.getNodeValue(dataNode(data, path));
    238         // try as a double
    239         try {
    240             double d = Double.parseDouble(strVal);
    241             retVal = new Double(d);
    242         } catch (NumberFormatException notADouble) {
    243             // else as a long
    244             try {
    245                 long l = Long.parseLong(strVal);
    246                 retVal = new Long(l);
    247             } catch (NumberFormatException notALong) {
    248                 // fallback to string
    249                 retVal = strVal;
    250             }
    251         }
    252         return retVal;
    253     }
    254
    255     /***
    256      * Prefix resolver for XPaths pointing to &lt;data&gt; nodes.
    257      */
    258     private static class DataPrefixResolver implements PrefixResolver {
    259
    260         /*** Cached namespaces. */
    261         private Map namespaces;
    262
    263         /***
    264          * Constructor.
    265          * @param namespaces The prefix to namespace URI map.
    266          */
    267         private DataPrefixResolver(final Map namespaces) {
    268             this.namespaces = namespaces;
    269         }
    270
    271         /*** {@inheritDoc} */
    272         public String getNamespaceForPrefix(final String prefix) {
    273             return (String) namespaces.get(prefix);
    274         }
    275
    276         /*** {@inheritDoc} */
    277         public String getNamespaceForPrefix(final String prefix,
    278                 final Node nsContext) {
    279             return (String) namespaces.get(prefix);
    280         }
    281
    282         /*** {@inheritDoc} */
    283         public String getBaseIdentifier() {
    284             return null;
    285         }
    286
    287         /*** {@inheritDoc} */
    288         public boolean handlesNullPrefixes() {
    289             return false;
    290         }
    291
    292     }
    293
    294 }
    295