001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.scxml;
018
019 import java.io.Serializable;
020 import java.util.Iterator;
021 import java.util.Map;
022 import java.util.Set;
023
024 import javax.xml.transform.TransformerException;
025
026 import org.apache.commons.logging.Log;
027 import org.apache.commons.logging.LogFactory;
028 import org.apache.commons.scxml.model.TransitionTarget;
029 import org.apache.xml.utils.PrefixResolver;
030 import org.apache.xpath.XPath;
031 import org.apache.xpath.XPathAPI;
032 import org.apache.xpath.XPathContext;
033 import org.w3c.dom.Node;
034 import org.w3c.dom.NodeList;
035
036 /**
037 * Implementations of builtin functions defined by the SCXML
038 * specification.
039 *
040 * The current version of the specification defines one builtin
041 * predicate In()
042 */
043 public class Builtin implements Serializable {
044
045 /**
046 * Serial version UID.
047 */
048 private static final long serialVersionUID = 1L;
049
050 /**
051 * Implements the In() predicate for SCXML documents. The method
052 * name chosen is different since "in" is a reserved token
053 * in some expression languages.
054 *
055 * Does this state belong to the given Set of States.
056 * Simple ID based comparator, assumes IDs are unique.
057 *
058 * @param allStates The Set of State objects to look in
059 * @param state The State ID to compare with
060 * @return Whether this State belongs to this Set
061 */
062 public static boolean isMember(final Set allStates,
063 final String state) {
064 for (Iterator i = allStates.iterator(); i.hasNext();) {
065 TransitionTarget tt = (TransitionTarget) i.next();
066 if (state.equals(tt.getId())) {
067 return true;
068 }
069 }
070 return false;
071 }
072
073 /**
074 * Implements the Data() function for Commons SCXML documents, that
075 * can be used to obtain a node from one of the XML data trees.
076 * Manifests within "location" attribute of <assign> element,
077 * for Commons JEXL and Commons EL based documents.
078 *
079 * @param namespaces The current document namespaces map at XPath location
080 * @param data The context Node, though the method accepts an Object
081 * so error is reported by Commons SCXML, rather
082 * than the underlying expression language.
083 * @param path The XPath expression.
084 * @return The first node matching the path, or null if no nodes match.
085 */
086 public static Node dataNode(final Map namespaces, final Object data,
087 final String path) {
088 if (data == null || !(data instanceof Node)) {
089 Log log = LogFactory.getLog(Builtin.class);
090 log.error("Data(): Cannot evaluate an XPath expression"
091 + " in the absence of a context Node, null returned");
092 return null;
093 }
094 Node dataNode = (Node) data;
095 NodeList result = null;
096 try {
097 if (namespaces == null || namespaces.size() == 0) {
098 Log log = LogFactory.getLog(Builtin.class);
099 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