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,
087: final Object data, final String path) {
088: if (data == null || !(data instanceof Node)) {
089: Log log = LogFactory.getLog(Builtin.class);
090: log
091: .error("Data(): Cannot evaluate an XPath expression"
092: + " in the absence of a context Node, null returned");
093: return null;
094: }
095: Node dataNode = (Node) data;
096: NodeList result = null;
097: try {
098: if (namespaces == null || namespaces.size() == 0) {
099: Log log = LogFactory.getLog(Builtin.class);
100: if (log.isDebugEnabled()) {
101: log
102: .debug("Turning off namespaced XPath evaluation since "
103: + "no namespace information is available for path: "
104: + path);
105: }
106: result = XPathAPI.selectNodeList(dataNode, path);
107: } else {
108: XPathContext xpathSupport = new XPathContext();
109: PrefixResolver prefixResolver = new DataPrefixResolver(
110: namespaces);
111: XPath xpath = new XPath(path, null, prefixResolver,
112: XPath.SELECT);
113: int ctxtNode = xpathSupport
114: .getDTMHandleFromNode(dataNode);
115: result = xpath.execute(xpathSupport, ctxtNode,
116: prefixResolver).nodelist();
117: }
118: } catch (TransformerException te) {
119: Log log = LogFactory.getLog(Builtin.class);
120: log.error(te.getMessage(), te);
121: return null;
122: }
123: int length = result.getLength();
124: if (length == 0) {
125: Log log = LogFactory.getLog(Builtin.class);
126: log
127: .warn("Data(): No nodes matching the XPath expression \""
128: + path + "\", returning null");
129: return null;
130: } else {
131: if (length > 1) {
132: Log log = LogFactory.getLog(Builtin.class);
133: log
134: .warn("Data(): Multiple nodes matching XPath expression"
135: + path + "\", returning first");
136: }
137: return result.item(0);
138: }
139: }
140:
141: /**
142: * A variant of the Data() function for Commons SCXML documents,
143: * coerced to a Double, a Long or a String, whichever succeeds,
144: * in that order.
145: * Manifests within rvalue expressions in the document,
146: * for Commons JEXL and Commons EL based documents..
147: *
148: * @param namespaces The current document namespaces map at XPath location
149: * @param data The context Node, though the method accepts an Object
150: * so error is reported by Commons SCXML, rather
151: * than the underlying expression language.
152: * @param path The XPath expression.
153: * @return The first node matching the path, coerced to a String, or null
154: * if no nodes match.
155: */
156: public static Object data(final Map namespaces, final Object data,
157: final String path) {
158: Object retVal = null;
159: String strVal = SCXMLHelper.getNodeValue(dataNode(namespaces,
160: data, path));
161: // try as a double
162: try {
163: double d = Double.parseDouble(strVal);
164: retVal = new Double(d);
165: } catch (NumberFormatException notADouble) {
166: // else as a long
167: try {
168: long l = Long.parseLong(strVal);
169: retVal = new Long(l);
170: } catch (NumberFormatException notALong) {
171: // fallback to string
172: retVal = strVal;
173: }
174: }
175: return retVal;
176: }
177:
178: /**
179: * Implements the Data() function for Commons SCXML documents, that
180: * can be used to obtain a node from one of the XML data trees.
181: * Manifests within "location" attribute of <assign> element,
182: * for Commons JEXL and Commons EL based documents.
183: *
184: * @param data The context Node, though the method accepts an Object
185: * so error is reported by Commons SCXML, rather
186: * than the underlying expression language.
187: * @param path The XPath expression.
188: * @return The first node matching the path, or null if no nodes match.
189: *
190: * @deprecated Use {@link #dataNode(Map,Object,String)} instead
191: */
192: public static Node dataNode(final Object data, final String path) {
193: if (data == null || !(data instanceof Node)) {
194: Log log = LogFactory.getLog(Builtin.class);
195: log
196: .error("Data(): Cannot evaluate an XPath expression"
197: + " in the absence of a context Node, null returned");
198: return null;
199: }
200: Node dataNode = (Node) data;
201: NodeList result = null;
202: try {
203: result = XPathAPI.selectNodeList(dataNode, path);
204: } catch (TransformerException te) {
205: Log log = LogFactory.getLog(Builtin.class);
206: log.error(te.getMessage(), te);
207: return null;
208: }
209: int length = result.getLength();
210: if (length == 0) {
211: Log log = LogFactory.getLog(Builtin.class);
212: log
213: .warn("Data(): No nodes matching the XPath expression \""
214: + path + "\", returning null");
215: return null;
216: } else {
217: if (length > 1) {
218: Log log = LogFactory.getLog(Builtin.class);
219: log
220: .warn("Data(): Multiple nodes matching XPath expression"
221: + path + "\", returning first");
222: }
223: return result.item(0);
224: }
225: }
226:
227: /**
228: * A variant of the Data() function for Commons SCXML documents,
229: * coerced to a Double, a Long or a String, whichever succeeds,
230: * in that order.
231: * Manifests within rvalue expressions in the document,
232: * for Commons JEXL and Commons EL based documents..
233: *
234: * @param data The context Node, though the method accepts an Object
235: * so error is reported by Commons SCXML, rather
236: * than the underlying expression language.
237: * @param path The XPath expression.
238: * @return The first node matching the path, coerced to a String, or null
239: * if no nodes match.
240: *
241: * @deprecated Use {@link #data(Map,Object,String)} instead
242: */
243: public static Object data(final Object data, final String path) {
244: Object retVal = null;
245: String strVal = SCXMLHelper.getNodeValue(dataNode(data, path));
246: // try as a double
247: try {
248: double d = Double.parseDouble(strVal);
249: retVal = new Double(d);
250: } catch (NumberFormatException notADouble) {
251: // else as a long
252: try {
253: long l = Long.parseLong(strVal);
254: retVal = new Long(l);
255: } catch (NumberFormatException notALong) {
256: // fallback to string
257: retVal = strVal;
258: }
259: }
260: return retVal;
261: }
262:
263: /**
264: * Prefix resolver for XPaths pointing to <data> nodes.
265: */
266: private static class DataPrefixResolver implements PrefixResolver {
267:
268: /** Cached namespaces. */
269: private Map namespaces;
270:
271: /**
272: * Constructor.
273: * @param namespaces The prefix to namespace URI map.
274: */
275: private DataPrefixResolver(final Map namespaces) {
276: this .namespaces = namespaces;
277: }
278:
279: /** {@inheritDoc} */
280: public String getNamespaceForPrefix(final String prefix) {
281: return (String) namespaces.get(prefix);
282: }
283:
284: /** {@inheritDoc} */
285: public String getNamespaceForPrefix(final String prefix,
286: final Node nsContext) {
287: return (String) namespaces.get(prefix);
288: }
289:
290: /** {@inheritDoc} */
291: public String getBaseIdentifier() {
292: return null;
293: }
294:
295: /** {@inheritDoc} */
296: public boolean handlesNullPrefixes() {
297: return false;
298: }
299:
300: }
301:
302: }
|