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.camel.builder.xml; 018 019 import java.io.StringReader; 020 import java.util.List; 021 022 import javax.xml.namespace.QName; 023 import javax.xml.xpath.XPath; 024 import javax.xml.xpath.XPathConstants; 025 import javax.xml.xpath.XPathExpression; 026 import javax.xml.xpath.XPathExpressionException; 027 import javax.xml.xpath.XPathFactory; 028 import javax.xml.xpath.XPathFactoryConfigurationException; 029 import javax.xml.xpath.XPathFunction; 030 import javax.xml.xpath.XPathFunctionException; 031 import javax.xml.xpath.XPathFunctionResolver; 032 033 import org.w3c.dom.Document; 034 import org.w3c.dom.Element; 035 036 import org.xml.sax.InputSource; 037 038 import org.apache.camel.Exchange; 039 import org.apache.camel.Expression; 040 import org.apache.camel.Message; 041 import org.apache.camel.Predicate; 042 import org.apache.camel.RuntimeExpressionException; 043 044 import static org.apache.camel.builder.xml.Namespaces.DEFAULT_NAMESPACE; 045 import static org.apache.camel.builder.xml.Namespaces.IN_NAMESPACE; 046 import static org.apache.camel.builder.xml.Namespaces.OUT_NAMESPACE; 047 import static org.apache.camel.builder.xml.Namespaces.isMatchingNamespaceOrEmptyNamespace; 048 import static org.apache.camel.converter.ObjectConverter.toBoolean; 049 050 /** 051 * Creates an XPath expression builder 052 * 053 * @version $Revision: 531854 $ 054 */ 055 public class XPathBuilder<E extends Exchange> implements Expression<E>, Predicate<E> { 056 private final String text; 057 private XPathFactory xpathFactory; 058 private Class documentType = Document.class; 059 private QName resultType; 060 private String objectModelUri; 061 private DefaultNamespaceContext namespaceContext; 062 private XPathFunctionResolver functionResolver; 063 private XPathExpression expression; 064 private MessageVariableResolver variableResolver = new MessageVariableResolver(); 065 private E exchange; 066 private XPathFunction bodyFunction; 067 private XPathFunction headerFunction; 068 private XPathFunction outBodyFunction; 069 private XPathFunction outHeaderFunction; 070 071 public XPathBuilder(String text) { 072 this.text = text; 073 } 074 075 public static XPathBuilder xpath(String text) { 076 return new XPathBuilder(text); 077 } 078 079 @Override 080 public String toString() { 081 return "XPath: " + text; 082 } 083 084 public boolean matches(E exchange) { 085 Object booleanResult = evaluateAs(exchange, XPathConstants.BOOLEAN); 086 return toBoolean(booleanResult); 087 } 088 089 public void assertMatches(String text, E exchange) throws AssertionError { 090 Object booleanResult = evaluateAs(exchange, XPathConstants.BOOLEAN); 091 if (!toBoolean(booleanResult)) { 092 throw new AssertionError(this + " failed on " + exchange + " as returned <" + booleanResult + ">"); 093 } 094 } 095 096 public Object evaluate(E exchange) { 097 return evaluateAs(exchange, resultType); 098 } 099 100 // Builder methods 101 // ------------------------------------------------------------------------- 102 103 /** 104 * Sets the expression result type to boolean 105 * 106 * @return the current builder 107 */ 108 public XPathBuilder<E> booleanResult() { 109 resultType = XPathConstants.BOOLEAN; 110 return this; 111 } 112 113 /** 114 * Sets the expression result type to boolean 115 * 116 * @return the current builder 117 */ 118 public XPathBuilder<E> nodeResult() { 119 resultType = XPathConstants.NODE; 120 return this; 121 } 122 123 /** 124 * Sets the expression result type to boolean 125 * 126 * @return the current builder 127 */ 128 public XPathBuilder<E> nodeSetResult() { 129 resultType = XPathConstants.NODESET; 130 return this; 131 } 132 133 /** 134 * Sets the expression result type to boolean 135 * 136 * @return the current builder 137 */ 138 public XPathBuilder<E> numberResult() { 139 resultType = XPathConstants.NUMBER; 140 return this; 141 } 142 143 /** 144 * Sets the expression result type to boolean 145 * 146 * @return the current builder 147 */ 148 public XPathBuilder<E> stringResult() { 149 resultType = XPathConstants.STRING; 150 return this; 151 } 152 153 /** 154 * Sets the object model URI to use 155 * 156 * @return the current builder 157 */ 158 public XPathBuilder<E> objectModel(String uri) { 159 this.objectModelUri = uri; 160 return this; 161 } 162 163 /** 164 * Sets the {@link XPathFunctionResolver} instance to use on these XPath 165 * expressions 166 * 167 * @return the current builder 168 */ 169 public XPathBuilder<E> functionResolver(XPathFunctionResolver functionResolver) { 170 this.functionResolver = functionResolver; 171 return this; 172 } 173 174 /** 175 * Registers the namespace prefix and URI with the builder so that the 176 * prefix can be used in XPath expressions 177 * 178 * @param prefix is the namespace prefix that can be used in the XPath 179 * expressions 180 * @param uri is the namespace URI to which the prefix refers 181 * @return the current builder 182 */ 183 public XPathBuilder<E> namespace(String prefix, String uri) { 184 getNamespaceContext().add(prefix, uri); 185 return this; 186 } 187 188 /** 189 * Registers a variable (in the global namespace) which can be referred to 190 * from XPath expressions 191 */ 192 public XPathBuilder<E> variable(String name, Object value) { 193 variableResolver.addVariable(name, value); 194 return this; 195 } 196 197 // Properties 198 // ------------------------------------------------------------------------- 199 public XPathFactory getXPathFactory() throws XPathFactoryConfigurationException { 200 if (xpathFactory == null) { 201 if (objectModelUri != null) { 202 xpathFactory = XPathFactory.newInstance(objectModelUri); 203 } 204 xpathFactory = XPathFactory.newInstance(); 205 } 206 return xpathFactory; 207 } 208 209 public void setXPathFactory(XPathFactory xpathFactory) { 210 this.xpathFactory = xpathFactory; 211 } 212 213 public Class getDocumentType() { 214 return documentType; 215 } 216 217 public void setDocumentType(Class documentType) { 218 this.documentType = documentType; 219 } 220 221 public String getText() { 222 return text; 223 } 224 225 public QName getResultType() { 226 return resultType; 227 } 228 229 public DefaultNamespaceContext getNamespaceContext() { 230 if (namespaceContext == null) { 231 try { 232 DefaultNamespaceContext defaultNamespaceContext = new DefaultNamespaceContext( 233 getXPathFactory()); 234 populateDefaultNamespaces(defaultNamespaceContext); 235 namespaceContext = defaultNamespaceContext; 236 } catch (XPathFactoryConfigurationException e) { 237 throw new RuntimeExpressionException(e); 238 } 239 } 240 return namespaceContext; 241 } 242 243 public void setNamespaceContext(DefaultNamespaceContext namespaceContext) { 244 this.namespaceContext = namespaceContext; 245 } 246 247 public XPathFunctionResolver getFunctionResolver() { 248 return functionResolver; 249 } 250 251 public void setFunctionResolver(XPathFunctionResolver functionResolver) { 252 this.functionResolver = functionResolver; 253 } 254 255 public XPathExpression getExpression() throws XPathFactoryConfigurationException, 256 XPathExpressionException { 257 if (expression == null) { 258 expression = createXPathExpression(); 259 } 260 return expression; 261 } 262 263 public void setNamespacesFromDom(Element node) { 264 getNamespaceContext().setNamespacesFromDom(node); 265 } 266 267 public XPathFunction getBodyFunction() { 268 if (bodyFunction == null) { 269 bodyFunction = new XPathFunction() { 270 public Object evaluate(List list) throws XPathFunctionException { 271 if (exchange == null) { 272 return null; 273 } 274 return exchange.getIn().getBody(); 275 } 276 }; 277 } 278 return bodyFunction; 279 } 280 281 public void setBodyFunction(XPathFunction bodyFunction) { 282 this.bodyFunction = bodyFunction; 283 } 284 285 public XPathFunction getHeaderFunction() { 286 if (headerFunction == null) { 287 headerFunction = new XPathFunction() { 288 public Object evaluate(List list) throws XPathFunctionException { 289 if (exchange != null && !list.isEmpty()) { 290 Object value = list.get(0); 291 if (value != null) { 292 return exchange.getIn().getHeader(value.toString()); 293 } 294 } 295 return null; 296 } 297 }; 298 } 299 return headerFunction; 300 } 301 302 public void setHeaderFunction(XPathFunction headerFunction) { 303 this.headerFunction = headerFunction; 304 } 305 306 public XPathFunction getOutBodyFunction() { 307 if (outBodyFunction == null) { 308 outBodyFunction = new XPathFunction() { 309 public Object evaluate(List list) throws XPathFunctionException { 310 if (exchange == null) { 311 return null; 312 } 313 return exchange.getOut().getBody(); 314 } 315 }; 316 } 317 return outBodyFunction; 318 } 319 320 public void setOutBodyFunction(XPathFunction outBodyFunction) { 321 this.outBodyFunction = outBodyFunction; 322 } 323 324 public XPathFunction getOutHeaderFunction() { 325 if (outHeaderFunction == null) { 326 outHeaderFunction = new XPathFunction() { 327 public Object evaluate(List list) throws XPathFunctionException { 328 if (exchange != null && !list.isEmpty()) { 329 Object value = list.get(0); 330 if (value != null) { 331 return exchange.getOut().getHeader(value.toString()); 332 } 333 } 334 return null; 335 } 336 }; 337 } 338 return outHeaderFunction; 339 } 340 341 public void setOutHeaderFunction(XPathFunction outHeaderFunction) { 342 this.outHeaderFunction = outHeaderFunction; 343 } 344 345 // Implementation methods 346 // ------------------------------------------------------------------------- 347 348 /** 349 * Evaluates the expression as the given result type 350 */ 351 protected synchronized Object evaluateAs(E exchange, QName resultType) { 352 this.exchange = exchange; 353 variableResolver.setExchange(exchange); 354 try { 355 Object document = getDocument(exchange); 356 if (resultType != null) { 357 if (document instanceof InputSource) { 358 InputSource inputSource = (InputSource)document; 359 return getExpression().evaluate(inputSource, resultType); 360 } else { 361 return getExpression().evaluate(document, resultType); 362 } 363 } else { 364 if (document instanceof InputSource) { 365 InputSource inputSource = (InputSource)document; 366 return getExpression().evaluate(inputSource); 367 } else { 368 return getExpression().evaluate(document); 369 } 370 } 371 } catch (XPathExpressionException e) { 372 throw new InvalidXPathExpression(getText(), e); 373 } catch (XPathFactoryConfigurationException e) { 374 throw new InvalidXPathExpression(getText(), e); 375 } 376 } 377 378 protected XPathExpression createXPathExpression() throws XPathExpressionException, 379 XPathFactoryConfigurationException { 380 XPath xPath = getXPathFactory().newXPath(); 381 382 // lets now clear any factory references to avoid keeping them around 383 xpathFactory = null; 384 385 xPath.setNamespaceContext(getNamespaceContext()); 386 387 xPath.setXPathVariableResolver(variableResolver); 388 389 XPathFunctionResolver parentResolver = getFunctionResolver(); 390 if (parentResolver == null) { 391 parentResolver = xPath.getXPathFunctionResolver(); 392 } 393 xPath.setXPathFunctionResolver(createDefaultFunctionResolver(parentResolver)); 394 return xPath.compile(text); 395 } 396 397 /** 398 * Lets populate a number of standard prefixes if they are not already there 399 */ 400 protected void populateDefaultNamespaces(DefaultNamespaceContext context) { 401 setNamespaceIfNotPresent(context, "in", IN_NAMESPACE); 402 setNamespaceIfNotPresent(context, "out", OUT_NAMESPACE); 403 setNamespaceIfNotPresent(context, "env", Namespaces.ENVIRONMENT_VARIABLES); 404 setNamespaceIfNotPresent(context, "system", Namespaces.SYSTEM_PROPERTIES_NAMESPACE); 405 } 406 407 protected void setNamespaceIfNotPresent(DefaultNamespaceContext context, String prefix, String uri) { 408 if (context != null) { 409 String current = context.getNamespaceURI(prefix); 410 if (current == null) { 411 context.add(prefix, uri); 412 } 413 } 414 } 415 416 protected XPathFunctionResolver createDefaultFunctionResolver(final XPathFunctionResolver parent) { 417 return new XPathFunctionResolver() { 418 public XPathFunction resolveFunction(QName qName, int argumentCount) { 419 XPathFunction answer = null; 420 if (parent != null) { 421 answer = parent.resolveFunction(qName, argumentCount); 422 } 423 if (answer == null) { 424 if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), IN_NAMESPACE) 425 || isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), DEFAULT_NAMESPACE)) { 426 String localPart = qName.getLocalPart(); 427 if (localPart.equals("body") && argumentCount == 0) { 428 return getBodyFunction(); 429 } 430 if (localPart.equals("header") && argumentCount == 1) { 431 return getHeaderFunction(); 432 } 433 } 434 if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), OUT_NAMESPACE)) { 435 String localPart = qName.getLocalPart(); 436 if (localPart.equals("body") && argumentCount == 0) { 437 return getOutBodyFunction(); 438 } 439 if (localPart.equals("header") && argumentCount == 1) { 440 return getOutHeaderFunction(); 441 } 442 } 443 if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), DEFAULT_NAMESPACE)) { 444 String localPart = qName.getLocalPart(); 445 if (localPart.equals("out-body") && argumentCount == 0) { 446 return getOutBodyFunction(); 447 } 448 if (localPart.equals("out-header") && argumentCount == 1) { 449 return getOutHeaderFunction(); 450 } 451 } 452 } 453 return answer; 454 } 455 }; 456 } 457 458 /** 459 * Strategy method to extract the document from the exchange 460 */ 461 protected Object getDocument(E exchange) { 462 Message in = exchange.getIn(); 463 Class type = getDocumentType(); 464 Object answer = null; 465 if (type != null) { 466 answer = in.getBody(type); 467 } 468 if (answer == null) { 469 answer = in.getBody(); 470 } 471 472 // lets try coerce some common types into something JAXP can deal with 473 if (answer instanceof String) { 474 answer = new InputSource(new StringReader(answer.toString())); 475 } 476 return answer; 477 } 478 479 }