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 }