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 }