1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
157 try {
158 double d = Double.parseDouble(strVal);
159 retVal = new Double(d);
160 } catch (NumberFormatException notADouble) {
161
162 try {
163 long l = Long.parseLong(strVal);
164 retVal = new Long(l);
165 } catch (NumberFormatException notALong) {
166
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
239 try {
240 double d = Double.parseDouble(strVal);
241 retVal = new Double(d);
242 } catch (NumberFormatException notADouble) {
243
244 try {
245 long l = Long.parseLong(strVal);
246 retVal = new Long(l);
247 } catch (NumberFormatException notALong) {
248
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