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 "in" 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 <assign> 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 <assign> 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 <data> 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