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.File; 020 import java.io.InputStream; 021 import java.io.StringReader; 022 import java.util.HashMap; 023 import java.util.HashSet; 024 import java.util.LinkedHashMap; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Properties; 028 import java.util.Queue; 029 import java.util.concurrent.ConcurrentLinkedQueue; 030 import javax.xml.namespace.QName; 031 import javax.xml.transform.dom.DOMSource; 032 import javax.xml.xpath.XPath; 033 import javax.xml.xpath.XPathConstants; 034 import javax.xml.xpath.XPathExpression; 035 import javax.xml.xpath.XPathExpressionException; 036 import javax.xml.xpath.XPathFactory; 037 import javax.xml.xpath.XPathFactoryConfigurationException; 038 import javax.xml.xpath.XPathFunction; 039 import javax.xml.xpath.XPathFunctionException; 040 import javax.xml.xpath.XPathFunctionResolver; 041 042 import org.w3c.dom.Document; 043 import org.w3c.dom.Node; 044 import org.w3c.dom.NodeList; 045 import org.xml.sax.InputSource; 046 047 import org.apache.camel.CamelContext; 048 import org.apache.camel.Exchange; 049 import org.apache.camel.Expression; 050 import org.apache.camel.NoTypeConversionAvailableException; 051 import org.apache.camel.Predicate; 052 import org.apache.camel.RuntimeExpressionException; 053 import org.apache.camel.Service; 054 import org.apache.camel.WrappedFile; 055 import org.apache.camel.component.bean.BeanInvocation; 056 import org.apache.camel.impl.DefaultExchange; 057 import org.apache.camel.spi.Language; 058 import org.apache.camel.spi.NamespaceAware; 059 import org.apache.camel.support.SynchronizationAdapter; 060 import org.apache.camel.util.ExchangeHelper; 061 import org.apache.camel.util.IOHelper; 062 import org.apache.camel.util.MessageHelper; 063 import org.apache.camel.util.ObjectHelper; 064 import org.slf4j.Logger; 065 import org.slf4j.LoggerFactory; 066 067 import static org.apache.camel.builder.xml.Namespaces.DEFAULT_NAMESPACE; 068 import static org.apache.camel.builder.xml.Namespaces.FUNCTION_NAMESPACE; 069 import static org.apache.camel.builder.xml.Namespaces.IN_NAMESPACE; 070 import static org.apache.camel.builder.xml.Namespaces.OUT_NAMESPACE; 071 import static org.apache.camel.builder.xml.Namespaces.isMatchingNamespaceOrEmptyNamespace; 072 073 /** 074 * Creates an XPath expression builder which creates a nodeset result by default. 075 * If you want to evaluate a String expression then call {@link #stringResult()} 076 * <p/> 077 * An XPath object is not thread-safe and not reentrant. In other words, it is the application's responsibility to make 078 * sure that one XPath object is not used from more than one thread at any given time, and while the evaluate method 079 * is invoked, applications may not recursively call the evaluate method. 080 * <p/> 081 * This implementation is thread safe by using thread locals and pooling to allow concurrency 082 * 083 * @see XPathConstants#NODESET 084 */ 085 public class XPathBuilder implements Expression, Predicate, NamespaceAware, Service { 086 private static final transient Logger LOG = LoggerFactory.getLogger(XPathBuilder.class); 087 private static final String SAXON_OBJECT_MODEL_URI = "http://saxon.sf.net/jaxp/xpath/om"; 088 private static final String OBTAIN_ALL_NS_XPATH = "//*/namespace::*"; 089 090 private static XPathFactory defaultXPathFactory; 091 092 private final Queue<XPathExpression> pool = new ConcurrentLinkedQueue<XPathExpression>(); 093 private final Queue<XPathExpression> poolLogNamespaces = new ConcurrentLinkedQueue<XPathExpression>(); 094 private final String text; 095 private final ThreadLocal<Exchange> exchange = new ThreadLocal<Exchange>(); 096 private final MessageVariableResolver variableResolver = new MessageVariableResolver(exchange); 097 private XPathFactory xpathFactory; 098 private Class<?> documentType = Document.class; 099 // For some reason the default expression of "a/b" on a document such as 100 // <a><b>1</b><b>2</b></a> 101 // will evaluate as just "1" by default which is bizarre. So by default 102 // let's assume XPath expressions result in nodesets. 103 private Class<?> resultType; 104 private QName resultQName = XPathConstants.NODESET; 105 private String objectModelUri; 106 private DefaultNamespaceContext namespaceContext; 107 private boolean logNamespaces; 108 private XPathFunctionResolver functionResolver; 109 private XPathFunction bodyFunction; 110 private XPathFunction headerFunction; 111 private XPathFunction outBodyFunction; 112 private XPathFunction outHeaderFunction; 113 private XPathFunction propertiesFunction; 114 private XPathFunction simpleFunction; 115 116 public XPathBuilder(String text) { 117 this.text = text; 118 } 119 120 public static XPathBuilder xpath(String text) { 121 return new XPathBuilder(text); 122 } 123 124 public static XPathBuilder xpath(String text, Class<?> resultType) { 125 XPathBuilder builder = new XPathBuilder(text); 126 builder.setResultType(resultType); 127 return builder; 128 } 129 130 @Override 131 public String toString() { 132 return "XPath: " + text; 133 } 134 135 public boolean matches(Exchange exchange) { 136 try { 137 Object booleanResult = evaluateAs(exchange, XPathConstants.BOOLEAN); 138 return exchange.getContext().getTypeConverter().convertTo(Boolean.class, booleanResult); 139 } finally { 140 // remove the thread local after usage 141 this.exchange.remove(); 142 } 143 } 144 145 public <T> T evaluate(Exchange exchange, Class<T> type) { 146 try { 147 Object result = evaluate(exchange); 148 return exchange.getContext().getTypeConverter().convertTo(type, result); 149 } finally { 150 // remove the thread local after usage 151 this.exchange.remove(); 152 } 153 } 154 155 /** 156 * Matches the given xpath using the provided body. 157 * 158 * @param context the camel context 159 * @param body the body 160 * @return <tt>true</tt> if matches, <tt>false</tt> otherwise 161 */ 162 public boolean matches(CamelContext context, Object body) { 163 ObjectHelper.notNull(context, "CamelContext"); 164 165 // create a dummy Exchange to use during matching 166 Exchange dummy = new DefaultExchange(context); 167 dummy.getIn().setBody(body); 168 169 try { 170 return matches(dummy); 171 } finally { 172 // remove the thread local after usage 173 exchange.remove(); 174 } 175 } 176 177 /** 178 * Evaluates the given xpath using the provided body. 179 * 180 * @param context the camel context 181 * @param body the body 182 * @param type the type to return 183 * @return result of the evaluation 184 */ 185 public <T> T evaluate(CamelContext context, Object body, Class<T> type) { 186 ObjectHelper.notNull(context, "CamelContext"); 187 188 // create a dummy Exchange to use during evaluation 189 Exchange dummy = new DefaultExchange(context); 190 dummy.getIn().setBody(body); 191 192 try { 193 return evaluate(dummy, type); 194 } finally { 195 // remove the thread local after usage 196 exchange.remove(); 197 } 198 } 199 200 /** 201 * Evaluates the given xpath using the provided body as a String return type. 202 * 203 * @param context the camel context 204 * @param body the body 205 * @return result of the evaluation 206 */ 207 public String evaluate(CamelContext context, Object body) { 208 ObjectHelper.notNull(context, "CamelContext"); 209 210 // create a dummy Exchange to use during evaluation 211 Exchange dummy = new DefaultExchange(context); 212 dummy.getIn().setBody(body); 213 214 setResultQName(XPathConstants.STRING); 215 try { 216 return evaluate(dummy, String.class); 217 } finally { 218 // remove the thread local after usage 219 this.exchange.remove(); 220 } 221 } 222 223 // Builder methods 224 // ------------------------------------------------------------------------- 225 226 /** 227 * Sets the expression result type to boolean 228 * 229 * @return the current builder 230 */ 231 public XPathBuilder booleanResult() { 232 resultQName = XPathConstants.BOOLEAN; 233 return this; 234 } 235 236 /** 237 * Sets the expression result type to boolean 238 * 239 * @return the current builder 240 */ 241 public XPathBuilder nodeResult() { 242 resultQName = XPathConstants.NODE; 243 return this; 244 } 245 246 /** 247 * Sets the expression result type to boolean 248 * 249 * @return the current builder 250 */ 251 public XPathBuilder nodeSetResult() { 252 resultQName = XPathConstants.NODESET; 253 return this; 254 } 255 256 /** 257 * Sets the expression result type to boolean 258 * 259 * @return the current builder 260 */ 261 public XPathBuilder numberResult() { 262 resultQName = XPathConstants.NUMBER; 263 return this; 264 } 265 266 /** 267 * Sets the expression result type to boolean 268 * 269 * @return the current builder 270 */ 271 public XPathBuilder stringResult() { 272 resultQName = XPathConstants.STRING; 273 return this; 274 } 275 276 /** 277 * Sets the expression result type to boolean 278 * 279 * @return the current builder 280 */ 281 public XPathBuilder resultType(Class<?> resultType) { 282 setResultType(resultType); 283 return this; 284 } 285 286 /** 287 * Sets the object model URI to use 288 * 289 * @return the current builder 290 */ 291 public XPathBuilder objectModel(String uri) { 292 // TODO: Careful! Setting the Object Model URI this way will set the *Default* XPath Factory, which since is a static field, 293 // will set the XPath Factory system-wide. Decide what to do, as changing this behaviour can break compatibility. Provided the setObjectModel which changes 294 // this instance's XPath Factory rather than the static field 295 this.objectModelUri = uri; 296 return this; 297 } 298 299 /** 300 * Configures to use Saxon as the XPathFactory which allows you to use XPath 2.0 functions 301 * which may not be part of the build in JDK XPath parser. 302 * 303 * @return the current builder 304 */ 305 public XPathBuilder saxon() { 306 this.objectModelUri = SAXON_OBJECT_MODEL_URI; 307 return this; 308 } 309 310 /** 311 * Sets the {@link XPathFunctionResolver} instance to use on these XPath 312 * expressions 313 * 314 * @return the current builder 315 */ 316 public XPathBuilder functionResolver(XPathFunctionResolver functionResolver) { 317 this.functionResolver = functionResolver; 318 return this; 319 } 320 321 /** 322 * Registers the namespace prefix and URI with the builder so that the 323 * prefix can be used in XPath expressions 324 * 325 * @param prefix is the namespace prefix that can be used in the XPath 326 * expressions 327 * @param uri is the namespace URI to which the prefix refers 328 * @return the current builder 329 */ 330 public XPathBuilder namespace(String prefix, String uri) { 331 getNamespaceContext().add(prefix, uri); 332 return this; 333 } 334 335 /** 336 * Registers namespaces with the builder so that the registered 337 * prefixes can be used in XPath expressions 338 * 339 * @param namespaces is namespaces object that should be used in the 340 * XPath expression 341 * @return the current builder 342 */ 343 public XPathBuilder namespaces(Namespaces namespaces) { 344 namespaces.configure(this); 345 return this; 346 } 347 348 /** 349 * Registers a variable (in the global namespace) which can be referred to 350 * from XPath expressions 351 * 352 * @param name name of variable 353 * @param value value of variable 354 * @return the current builder 355 */ 356 public XPathBuilder variable(String name, Object value) { 357 getVariableResolver().addVariable(name, value); 358 return this; 359 } 360 361 /** 362 * Configures the document type to use. 363 * <p/> 364 * The document type controls which kind of Class Camel should convert the payload 365 * to before doing the xpath evaluation. 366 * <p/> 367 * For example you can set it to {@link InputSource} to use SAX streams. 368 * By default Camel uses {@link Document} as the type. 369 * 370 * @param documentType the document type 371 * @return the current builder 372 */ 373 public XPathBuilder documentType(Class<?> documentType) { 374 setDocumentType(documentType); 375 return this; 376 } 377 378 /** 379 * Configures to use the provided XPath factory. 380 * <p/> 381 * Can be used to use Saxon instead of the build in factory from the JDK. 382 * 383 * @param xpathFactory the xpath factory to use 384 * @return the current builder. 385 */ 386 public XPathBuilder factory(XPathFactory xpathFactory) { 387 setXPathFactory(xpathFactory); 388 return this; 389 } 390 391 /** 392 * Activates trace logging of all discovered namespaces in the message - to simplify debugging namespace-related issues 393 * <p/> 394 * Namespaces are printed in Hashmap style <code>{xmlns:prefix=[namespaceURI], xmlns:prefix=[namespaceURI]}</code>. 395 * <p/> 396 * The implicit XML namespace is omitted (http://www.w3.org/XML/1998/namespace). 397 * XML allows for namespace prefixes to be redefined/overridden due to hierarchical scoping, i.e. prefix abc can be mapped to http://abc.com, 398 * and deeper in the document it can be mapped to http://def.com. When two prefixes are detected which are equal but are mapped to different 399 * namespace URIs, Camel will show all namespaces URIs it is mapped to in an array-style. 400 * <p/> 401 * This feature is disabled by default. 402 * 403 * @return the current builder. 404 */ 405 public XPathBuilder logNamespaces() { 406 setLogNamespaces(true); 407 return this; 408 } 409 410 // Properties 411 // ------------------------------------------------------------------------- 412 public XPathFactory getXPathFactory() throws XPathFactoryConfigurationException { 413 if (xpathFactory != null) { 414 return xpathFactory; 415 } 416 417 if (objectModelUri != null) { 418 xpathFactory = XPathFactory.newInstance(objectModelUri); 419 LOG.info("Using objectModelUri " + objectModelUri + " when created XPathFactory {}", defaultXPathFactory); 420 return xpathFactory; 421 } 422 423 if (defaultXPathFactory == null) { 424 initDefaultXPathFactory(); 425 } 426 return defaultXPathFactory; 427 } 428 429 public void setXPathFactory(XPathFactory xpathFactory) { 430 this.xpathFactory = xpathFactory; 431 } 432 433 public Class<?> getDocumentType() { 434 return documentType; 435 } 436 437 public void setDocumentType(Class<?> documentType) { 438 this.documentType = documentType; 439 } 440 441 public String getText() { 442 return text; 443 } 444 445 public QName getResultQName() { 446 return resultQName; 447 } 448 449 public void setResultQName(QName resultQName) { 450 this.resultQName = resultQName; 451 } 452 453 public DefaultNamespaceContext getNamespaceContext() { 454 if (namespaceContext == null) { 455 try { 456 DefaultNamespaceContext defaultNamespaceContext = new DefaultNamespaceContext(getXPathFactory()); 457 populateDefaultNamespaces(defaultNamespaceContext); 458 namespaceContext = defaultNamespaceContext; 459 } catch (XPathFactoryConfigurationException e) { 460 throw new RuntimeExpressionException(e); 461 } 462 } 463 return namespaceContext; 464 } 465 466 public void setNamespaceContext(DefaultNamespaceContext namespaceContext) { 467 this.namespaceContext = namespaceContext; 468 } 469 470 public XPathFunctionResolver getFunctionResolver() { 471 return functionResolver; 472 } 473 474 public void setFunctionResolver(XPathFunctionResolver functionResolver) { 475 this.functionResolver = functionResolver; 476 } 477 478 public void setNamespaces(Map<String, String> namespaces) { 479 getNamespaceContext().setNamespaces(namespaces); 480 } 481 482 public XPathFunction getBodyFunction() { 483 if (bodyFunction == null) { 484 bodyFunction = new XPathFunction() { 485 @SuppressWarnings("rawtypes") 486 public Object evaluate(List list) throws XPathFunctionException { 487 if (exchange == null) { 488 return null; 489 } 490 return exchange.get().getIn().getBody(); 491 } 492 }; 493 } 494 return bodyFunction; 495 } 496 497 public void setBodyFunction(XPathFunction bodyFunction) { 498 this.bodyFunction = bodyFunction; 499 } 500 501 public XPathFunction getHeaderFunction() { 502 if (headerFunction == null) { 503 headerFunction = new XPathFunction() { 504 @SuppressWarnings("rawtypes") 505 public Object evaluate(List list) throws XPathFunctionException { 506 if (exchange != null && !list.isEmpty()) { 507 Object value = list.get(0); 508 if (value != null) { 509 String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value); 510 return exchange.get().getIn().getHeader(text); 511 } 512 } 513 return null; 514 } 515 }; 516 } 517 return headerFunction; 518 } 519 520 public void setHeaderFunction(XPathFunction headerFunction) { 521 this.headerFunction = headerFunction; 522 } 523 524 public XPathFunction getOutBodyFunction() { 525 if (outBodyFunction == null) { 526 outBodyFunction = new XPathFunction() { 527 @SuppressWarnings("rawtypes") 528 public Object evaluate(List list) throws XPathFunctionException { 529 if (exchange.get() != null && exchange.get().hasOut()) { 530 return exchange.get().getOut().getBody(); 531 } 532 return null; 533 } 534 }; 535 } 536 return outBodyFunction; 537 } 538 539 public void setOutBodyFunction(XPathFunction outBodyFunction) { 540 this.outBodyFunction = outBodyFunction; 541 } 542 543 public XPathFunction getOutHeaderFunction() { 544 if (outHeaderFunction == null) { 545 outHeaderFunction = new XPathFunction() { 546 @SuppressWarnings("rawtypes") 547 public Object evaluate(List list) throws XPathFunctionException { 548 if (exchange.get() != null && !list.isEmpty()) { 549 Object value = list.get(0); 550 if (value != null) { 551 String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value); 552 return exchange.get().getOut().getHeader(text); 553 } 554 } 555 return null; 556 } 557 }; 558 } 559 return outHeaderFunction; 560 } 561 562 public void setOutHeaderFunction(XPathFunction outHeaderFunction) { 563 this.outHeaderFunction = outHeaderFunction; 564 } 565 566 public XPathFunction getPropertiesFunction() { 567 if (propertiesFunction == null) { 568 propertiesFunction = new XPathFunction() { 569 @SuppressWarnings("rawtypes") 570 public Object evaluate(List list) throws XPathFunctionException { 571 if (exchange != null && !list.isEmpty()) { 572 Object value = list.get(0); 573 if (value != null) { 574 String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value); 575 try { 576 // use the property placeholder resolver to lookup the property for us 577 Object answer = exchange.get().getContext().resolvePropertyPlaceholders("{{" + text + "}}"); 578 return answer; 579 } catch (Exception e) { 580 throw new XPathFunctionException(e); 581 } 582 } 583 } 584 return null; 585 } 586 }; 587 } 588 return propertiesFunction; 589 } 590 591 public void setPropertiesFunction(XPathFunction propertiesFunction) { 592 this.propertiesFunction = propertiesFunction; 593 } 594 595 public XPathFunction getSimpleFunction() { 596 if (simpleFunction == null) { 597 simpleFunction = new XPathFunction() { 598 @SuppressWarnings("rawtypes") 599 public Object evaluate(List list) throws XPathFunctionException { 600 if (exchange != null && !list.isEmpty()) { 601 Object value = list.get(0); 602 if (value != null) { 603 String text = exchange.get().getContext().getTypeConverter().convertTo(String.class, value); 604 Language simple = exchange.get().getContext().resolveLanguage("simple"); 605 Expression exp = simple.createExpression(text); 606 Object answer = exp.evaluate(exchange.get(), Object.class); 607 return answer; 608 } 609 } 610 return null; 611 } 612 }; 613 } 614 return simpleFunction; 615 } 616 617 public void setSimpleFunction(XPathFunction simpleFunction) { 618 this.simpleFunction = simpleFunction; 619 } 620 621 public Class<?> getResultType() { 622 return resultType; 623 } 624 625 public void setResultType(Class<?> resultType) { 626 this.resultType = resultType; 627 if (Number.class.isAssignableFrom(resultType)) { 628 numberResult(); 629 } else if (String.class.isAssignableFrom(resultType)) { 630 stringResult(); 631 } else if (Boolean.class.isAssignableFrom(resultType)) { 632 booleanResult(); 633 } else if (Node.class.isAssignableFrom(resultType)) { 634 nodeResult(); 635 } else if (NodeList.class.isAssignableFrom(resultType)) { 636 nodeSetResult(); 637 } 638 } 639 640 public void setLogNamespaces(boolean logNamespaces) { 641 this.logNamespaces = logNamespaces; 642 } 643 644 public boolean isLogNamespaces() { 645 return logNamespaces; 646 } 647 648 public String getObjectModelUri() { 649 return objectModelUri; 650 } 651 652 /** 653 * Enables Saxon on this particular XPath expression, as {@link #saxon()} sets the default static XPathFactory which may have already been initialised 654 * by previous XPath expressions 655 */ 656 public void enableSaxon() { 657 this.setObjectModelUri(SAXON_OBJECT_MODEL_URI); 658 } 659 660 public void setObjectModelUri(String objectModelUri) { 661 this.objectModelUri = objectModelUri; 662 } 663 664 // Implementation methods 665 // ------------------------------------------------------------------------- 666 667 protected Object evaluate(Exchange exchange) { 668 Object answer = evaluateAs(exchange, resultQName); 669 if (resultType != null) { 670 return ExchangeHelper.convertToType(exchange, resultType, answer); 671 } 672 return answer; 673 } 674 675 /** 676 * Evaluates the expression as the given result type 677 */ 678 protected Object evaluateAs(Exchange exchange, QName resultQName) { 679 // pool a pre compiled expression from pool 680 XPathExpression xpathExpression = pool.poll(); 681 if (xpathExpression == null) { 682 LOG.trace("Creating new XPathExpression as none was available from pool"); 683 // no avail in pool then create one 684 try { 685 xpathExpression = createXPathExpression(); 686 } catch (XPathExpressionException e) { 687 throw new InvalidXPathExpression(getText(), e); 688 } catch (Exception e) { 689 throw new RuntimeExpressionException("Cannot create xpath expression", e); 690 } 691 } else { 692 LOG.trace("Acquired XPathExpression from pool"); 693 } 694 try { 695 if (logNamespaces && LOG.isInfoEnabled()) { 696 logNamespaces(exchange); 697 } 698 return doInEvaluateAs(xpathExpression, exchange, resultQName); 699 } finally { 700 // release it back to the pool 701 pool.add(xpathExpression); 702 LOG.trace("Released XPathExpression back to pool"); 703 } 704 } 705 706 private void logNamespaces(Exchange exchange) { 707 InputStream is = null; 708 NodeList answer = null; 709 XPathExpression xpathExpression = null; 710 711 try { 712 xpathExpression = poolLogNamespaces.poll(); 713 if (xpathExpression == null) { 714 xpathExpression = createTraceNamespaceExpression(); 715 } 716 717 // prepare the input 718 Object document; 719 if (isInputStreamNeeded(exchange)) { 720 is = exchange.getIn().getBody(InputStream.class); 721 document = getDocument(exchange, is); 722 } else { 723 Object body = exchange.getIn().getBody(); 724 document = getDocument(exchange, body); 725 } 726 // fetch all namespaces 727 if (document instanceof InputSource) { 728 InputSource inputSource = (InputSource) document; 729 answer = (NodeList) xpathExpression.evaluate(inputSource, XPathConstants.NODESET); 730 } else if (document instanceof DOMSource) { 731 DOMSource source = (DOMSource) document; 732 answer = (NodeList) xpathExpression.evaluate(source.getNode(), XPathConstants.NODESET); 733 } else { 734 answer = (NodeList) xpathExpression.evaluate(document, XPathConstants.NODESET); 735 } 736 } catch (Exception e) { 737 LOG.warn("Unable to trace discovered namespaces in XPath expression", e); 738 } finally { 739 // IOHelper can handle if is is null 740 IOHelper.close(is); 741 poolLogNamespaces.add(xpathExpression); 742 } 743 744 if (answer != null) { 745 logDiscoveredNamespaces(answer); 746 } 747 } 748 749 private void logDiscoveredNamespaces(NodeList namespaces) { 750 HashMap<String, HashSet<String>> map = new LinkedHashMap<String, HashSet<String>>(); 751 for (int i = 0; i < namespaces.getLength(); i++) { 752 Node n = namespaces.item(i); 753 if (n.getNodeName().equals("xmlns:xml")) { 754 // skip the implicit XML namespace as it provides no value 755 continue; 756 } 757 758 String prefix = namespaces.item(i).getNodeName(); 759 if (prefix.equals("xmlns")) { 760 prefix = "DEFAULT"; 761 } 762 763 // add to map 764 if (!map.containsKey(prefix)) { 765 map.put(prefix, new HashSet<String>()); 766 } 767 map.get(prefix).add(namespaces.item(i).getNodeValue()); 768 } 769 770 LOG.info("Namespaces discovered in message: {}.", map); 771 } 772 773 protected Object doInEvaluateAs(XPathExpression xpathExpression, Exchange exchange, QName resultQName) { 774 LOG.trace("Evaluating exchange: {} as: {}", exchange, resultQName); 775 776 Object answer; 777 778 // set exchange and variable resolver as thread locals for concurrency 779 this.exchange.set(exchange); 780 781 // the underlying input stream, which we need to close to avoid locking files or other resources 782 InputStream is = null; 783 try { 784 Object document; 785 // only convert to input stream if really needed 786 if (isInputStreamNeeded(exchange)) { 787 is = exchange.getIn().getBody(InputStream.class); 788 document = getDocument(exchange, is); 789 } else { 790 Object body = exchange.getIn().getBody(); 791 document = getDocument(exchange, body); 792 } 793 if (resultQName != null) { 794 if (document instanceof InputSource) { 795 InputSource inputSource = (InputSource) document; 796 answer = xpathExpression.evaluate(inputSource, resultQName); 797 } else if (document instanceof DOMSource) { 798 DOMSource source = (DOMSource) document; 799 answer = xpathExpression.evaluate(source.getNode(), resultQName); 800 } else { 801 answer = xpathExpression.evaluate(document, resultQName); 802 } 803 } else { 804 if (document instanceof InputSource) { 805 InputSource inputSource = (InputSource) document; 806 answer = xpathExpression.evaluate(inputSource); 807 } else if (document instanceof DOMSource) { 808 DOMSource source = (DOMSource) document; 809 answer = xpathExpression.evaluate(source.getNode()); 810 } else { 811 answer = xpathExpression.evaluate(document); 812 } 813 } 814 } catch (XPathExpressionException e) { 815 throw new InvalidXPathExpression(getText(), e); 816 } finally { 817 // IOHelper can handle if is is null 818 IOHelper.close(is); 819 } 820 821 if (LOG.isTraceEnabled()) { 822 LOG.trace("Done evaluating exchange: {} as: {} with result: {}", new Object[]{exchange, resultQName, answer}); 823 } 824 return answer; 825 } 826 827 protected synchronized XPathExpression createXPathExpression() throws XPathExpressionException, XPathFactoryConfigurationException { 828 // XPathFactory is not thread safe 829 XPath xPath = getXPathFactory().newXPath(); 830 831 if (!logNamespaces && LOG.isTraceEnabled()) { 832 LOG.trace("Creating new XPath expression in pool. Namespaces on XPath expression: {}", getNamespaceContext().toString()); 833 } else if (logNamespaces && LOG.isInfoEnabled()) { 834 LOG.info("Creating new XPath expression in pool. Namespaces on XPath expression: {}", getNamespaceContext().toString()); 835 } 836 xPath.setNamespaceContext(getNamespaceContext()); 837 xPath.setXPathVariableResolver(getVariableResolver()); 838 839 XPathFunctionResolver parentResolver = getFunctionResolver(); 840 if (parentResolver == null) { 841 parentResolver = xPath.getXPathFunctionResolver(); 842 } 843 xPath.setXPathFunctionResolver(createDefaultFunctionResolver(parentResolver)); 844 return xPath.compile(text); 845 } 846 847 protected synchronized XPathExpression createTraceNamespaceExpression() throws XPathFactoryConfigurationException, XPathExpressionException { 848 // XPathFactory is not thread safe 849 XPath xPath = getXPathFactory().newXPath(); 850 return xPath.compile(OBTAIN_ALL_NS_XPATH); 851 } 852 853 /** 854 * Populate a number of standard prefixes if they are not already there 855 */ 856 protected void populateDefaultNamespaces(DefaultNamespaceContext context) { 857 setNamespaceIfNotPresent(context, "in", IN_NAMESPACE); 858 setNamespaceIfNotPresent(context, "out", OUT_NAMESPACE); 859 setNamespaceIfNotPresent(context, "env", Namespaces.ENVIRONMENT_VARIABLES); 860 setNamespaceIfNotPresent(context, "system", Namespaces.SYSTEM_PROPERTIES_NAMESPACE); 861 setNamespaceIfNotPresent(context, "function", Namespaces.FUNCTION_NAMESPACE); 862 } 863 864 protected void setNamespaceIfNotPresent(DefaultNamespaceContext context, String prefix, String uri) { 865 if (context != null) { 866 String current = context.getNamespaceURI(prefix); 867 if (current == null) { 868 context.add(prefix, uri); 869 } 870 } 871 } 872 873 protected XPathFunctionResolver createDefaultFunctionResolver(final XPathFunctionResolver parent) { 874 return new XPathFunctionResolver() { 875 public XPathFunction resolveFunction(QName qName, int argumentCount) { 876 XPathFunction answer = null; 877 if (parent != null) { 878 answer = parent.resolveFunction(qName, argumentCount); 879 } 880 if (answer == null) { 881 if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), IN_NAMESPACE) 882 || isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), DEFAULT_NAMESPACE)) { 883 String localPart = qName.getLocalPart(); 884 if (localPart.equals("body") && argumentCount == 0) { 885 return getBodyFunction(); 886 } 887 if (localPart.equals("header") && argumentCount == 1) { 888 return getHeaderFunction(); 889 } 890 } 891 if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), OUT_NAMESPACE)) { 892 String localPart = qName.getLocalPart(); 893 if (localPart.equals("body") && argumentCount == 0) { 894 return getOutBodyFunction(); 895 } 896 if (localPart.equals("header") && argumentCount == 1) { 897 return getOutHeaderFunction(); 898 } 899 } 900 if (isMatchingNamespaceOrEmptyNamespace(qName.getNamespaceURI(), FUNCTION_NAMESPACE)) { 901 String localPart = qName.getLocalPart(); 902 if (localPart.equals("properties") && argumentCount == 1) { 903 return getPropertiesFunction(); 904 } 905 if (localPart.equals("simple") && argumentCount == 1) { 906 return getSimpleFunction(); 907 } 908 } 909 } 910 return answer; 911 } 912 }; 913 } 914 915 /** 916 * Checks whether we need an {@link InputStream} to access the message body. 917 * <p/> 918 * Depending on the content in the message body, we may not need to convert 919 * to {@link InputStream}. 920 * 921 * @param exchange the current exchange 922 * @return <tt>true</tt> to convert to {@link InputStream} beforehand converting afterwards. 923 */ 924 protected boolean isInputStreamNeeded(Exchange exchange) { 925 Object body = exchange.getIn().getBody(); 926 if (body == null) { 927 return false; 928 } 929 930 if (body instanceof WrappedFile) { 931 body = ((WrappedFile<?>) body).getFile(); 932 } 933 if (body instanceof File) { 934 // input stream is needed for File to avoid locking the file in case of errors etc 935 return true; 936 } 937 938 // input stream is not needed otherwise 939 return false; 940 } 941 942 /** 943 * Strategy method to extract the document from the exchange. 944 */ 945 protected Object getDocument(Exchange exchange, Object body) { 946 try { 947 return doGetDocument(exchange, body); 948 } catch (Exception e) { 949 throw ObjectHelper.wrapRuntimeCamelException(e); 950 } finally { 951 // call the reset if the in message body is StreamCache 952 MessageHelper.resetStreamCache(exchange.getIn()); 953 } 954 } 955 956 protected Object doGetDocument(Exchange exchange, Object body) throws Exception { 957 if (body == null) { 958 return null; 959 } 960 961 Object answer = null; 962 963 Class<?> type = getDocumentType(); 964 Exception cause = null; 965 if (type != null) { 966 // try to get the body as the desired type 967 try { 968 answer = exchange.getContext().getTypeConverter().convertTo(type, exchange, body); 969 } catch (Exception e) { 970 // we want to store the caused exception, if we could not convert 971 cause = e; 972 } 973 } 974 975 // okay we can try to remedy the failed conversion by some special types 976 if (answer == null) { 977 // let's try coercing some common types into something JAXP work with the best for special types 978 if (body instanceof WrappedFile) { 979 // special for files so we can work with them out of the box 980 InputStream is = exchange.getContext().getTypeConverter().convertTo(InputStream.class, body); 981 answer = new InputSource(is); 982 } else if (body instanceof BeanInvocation) { 983 // if its a null bean invocation then handle that specially 984 BeanInvocation bi = exchange.getContext().getTypeConverter().convertTo(BeanInvocation.class, body); 985 if (bi.getArgs() != null && bi.getArgs().length == 1 && bi.getArgs()[0] == null) { 986 // its a null argument from the bean invocation so use null as answer 987 answer = null; 988 } 989 } else if (body instanceof String) { 990 answer = new InputSource(new StringReader((String) body)); 991 } 992 } 993 994 if (type == null && answer == null) { 995 // fallback to get the body as is 996 answer = body; 997 } else if (answer == null) { 998 // there was a type, and we could not convert to it, then fail 999 if (cause != null) { 1000 throw cause; 1001 } else { 1002 throw new NoTypeConversionAvailableException(body, type); 1003 } 1004 } 1005 1006 return answer; 1007 } 1008 1009 private MessageVariableResolver getVariableResolver() { 1010 return variableResolver; 1011 } 1012 1013 public void start() throws Exception { 1014 if (xpathFactory == null) { 1015 initDefaultXPathFactory(); 1016 } 1017 } 1018 1019 public void stop() throws Exception { 1020 pool.clear(); 1021 poolLogNamespaces.clear(); 1022 } 1023 1024 protected synchronized void initDefaultXPathFactory() throws XPathFactoryConfigurationException { 1025 if (defaultXPathFactory == null) { 1026 if (objectModelUri != null) { 1027 defaultXPathFactory = XPathFactory.newInstance(objectModelUri); 1028 LOG.info("Using objectModelUri " + objectModelUri + " when created XPathFactory {}", defaultXPathFactory); 1029 } 1030 1031 if (defaultXPathFactory == null) { 1032 // read system property and see if there is a factory set 1033 Properties properties = System.getProperties(); 1034 for (Map.Entry<Object, Object> prop : properties.entrySet()) { 1035 String key = (String) prop.getKey(); 1036 if (key.startsWith(XPathFactory.DEFAULT_PROPERTY_NAME)) { 1037 String uri = ObjectHelper.after(key, ":"); 1038 if (uri != null) { 1039 defaultXPathFactory = XPathFactory.newInstance(uri); 1040 LOG.info("Using system property {} with value {} when created XPathFactory {}", new Object[]{key, uri, defaultXPathFactory}); 1041 } 1042 } 1043 } 1044 } 1045 1046 defaultXPathFactory = XPathFactory.newInstance(); 1047 LOG.info("Created default XPathFactory {}", defaultXPathFactory); 1048 } 1049 } 1050 1051 }