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