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.component.xquery;
018
019 import java.io.ByteArrayOutputStream;
020 import java.io.File;
021 import java.io.FileNotFoundException;
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.io.Reader;
025 import java.io.StringWriter;
026 import java.net.URL;
027 import java.util.Collection;
028 import java.util.HashMap;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.Properties;
032 import java.util.Set;
033 import java.util.concurrent.atomic.AtomicBoolean;
034
035 import javax.xml.transform.Result;
036 import javax.xml.transform.Source;
037 import javax.xml.transform.dom.DOMResult;
038 import javax.xml.transform.stream.StreamResult;
039
040 import org.w3c.dom.Node;
041
042 import net.sf.saxon.Configuration;
043 import net.sf.saxon.om.DocumentInfo;
044 import net.sf.saxon.om.Item;
045 import net.sf.saxon.om.SequenceIterator;
046 import net.sf.saxon.query.DynamicQueryContext;
047 import net.sf.saxon.query.StaticQueryContext;
048 import net.sf.saxon.query.XQueryExpression;
049 import net.sf.saxon.trans.XPathException;
050
051 import org.apache.camel.Exchange;
052 import org.apache.camel.Expression;
053 import org.apache.camel.Message;
054 import org.apache.camel.Predicate;
055 import org.apache.camel.Processor;
056 import org.apache.camel.RuntimeExpressionException;
057 import org.apache.camel.converter.IOConverter;
058 import org.apache.camel.converter.jaxp.BytesSource;
059 import org.apache.camel.converter.jaxp.StringSource;
060 import org.apache.camel.converter.jaxp.XmlConverter;
061 import org.apache.camel.spi.NamespaceAware;
062 import org.apache.camel.util.MessageHelper;
063 import org.apache.camel.util.ObjectHelper;
064 import org.apache.commons.logging.Log;
065 import org.apache.commons.logging.LogFactory;
066
067 /**
068 * Creates an XQuery builder
069 *
070 * @version $Revision: 769448 $
071 */
072 public abstract class XQueryBuilder implements Expression, Predicate, NamespaceAware, Processor {
073 private static final transient Log LOG = LogFactory.getLog(XQueryBuilder.class);
074 private Configuration configuration;
075 private XQueryExpression expression;
076 private StaticQueryContext staticQueryContext;
077 private Map<String, Object> parameters = new HashMap<String, Object>();
078 private Map<String, String> namespacePrefixes = new HashMap<String, String>();
079 private XmlConverter converter = new XmlConverter();
080 private ResultFormat resultsFormat = ResultFormat.DOM;
081 private Properties properties = new Properties();
082 private Class resultType;
083 private final AtomicBoolean initialized = new AtomicBoolean(false);
084
085 @Override
086 public String toString() {
087 return "XQuery[" + expression + "]";
088 }
089
090 public void process(Exchange exchange) throws Exception {
091 Object body = evaluate(exchange);
092 exchange.getOut().setBody(body);
093
094 // propogate headers
095 exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
096 }
097
098 @SuppressWarnings("unchecked")
099 public <T> T evaluate(Exchange exchange, Class<T> type) {
100 Object result = evaluate(exchange);
101 return exchange.getContext().getTypeConverter().convertTo(type, result);
102 }
103
104 public Object evaluate(Exchange exchange) {
105 try {
106 if (LOG.isDebugEnabled()) {
107 LOG.debug("Evaluation: " + expression + " for exchange: " + exchange);
108 }
109
110 if (resultType != null) {
111 if (resultType.equals(String.class)) {
112 return evaluateAsString(exchange);
113 } else if (resultType.isAssignableFrom(Collection.class)) {
114 return evaluateAsList(exchange);
115 } else if (resultType.isAssignableFrom(Node.class)) {
116 return evaluateAsDOM(exchange);
117 } else {
118 throw new IllegalArgumentException("ResultType: " + resultType.getCanonicalName() + " not supported");
119 }
120 }
121 switch (resultsFormat) {
122 case Bytes:
123 return evaluateAsBytes(exchange);
124 case BytesSource:
125 return evaluateAsBytesSource(exchange);
126 case DOM:
127 return evaluateAsDOM(exchange);
128 case List:
129 return evaluateAsList(exchange);
130 case StringSource:
131 return evaluateAsStringSource(exchange);
132 case String:
133 default:
134 return evaluateAsString(exchange);
135 }
136 } catch (Exception e) {
137 throw new RuntimeExpressionException(e);
138 }
139 }
140
141 public List evaluateAsList(Exchange exchange) throws Exception {
142 initialize(exchange);
143
144 return getExpression().evaluate(createDynamicContext(exchange));
145 }
146
147 public Object evaluateAsStringSource(Exchange exchange) throws Exception {
148 initialize(exchange);
149
150 String text = evaluateAsString(exchange);
151 return new StringSource(text);
152 }
153
154 public Object evaluateAsBytesSource(Exchange exchange) throws Exception {
155 initialize(exchange);
156
157 byte[] bytes = evaluateAsBytes(exchange);
158 return new BytesSource(bytes);
159 }
160
161 public Node evaluateAsDOM(Exchange exchange) throws Exception {
162 initialize(exchange);
163
164 DOMResult result = new DOMResult();
165 DynamicQueryContext context = createDynamicContext(exchange);
166 XQueryExpression expression = getExpression();
167 expression.pull(context, result, properties);
168 return result.getNode();
169 }
170
171 public byte[] evaluateAsBytes(Exchange exchange) throws Exception {
172 initialize(exchange);
173
174 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
175 Result result = new StreamResult(buffer);
176 getExpression().pull(createDynamicContext(exchange), result, properties);
177 byte[] bytes = buffer.toByteArray();
178 return bytes;
179 }
180
181 public String evaluateAsString(Exchange exchange) throws Exception {
182 initialize(exchange);
183
184 StringWriter buffer = new StringWriter();
185 SequenceIterator iter = getExpression().iterator(createDynamicContext(exchange));
186 for (Item item = iter.next(); item != null; item = iter.next()) {
187 buffer.append(item.getStringValueCS());
188 }
189 return buffer.toString();
190 }
191
192 public boolean matches(Exchange exchange) {
193 try {
194 List list = evaluateAsList(exchange);
195 return matches(exchange, list);
196 } catch (Exception e) {
197 throw new RuntimeExpressionException(e);
198 }
199 }
200
201 public void assertMatches(String text, Exchange exchange) throws AssertionError {
202 List list;
203
204 try {
205 list = evaluateAsList(exchange);
206 } catch (Exception e) {
207 throw new AssertionError(e);
208 }
209
210 if (!matches(exchange, list)) {
211 throw new AssertionError(this + " failed on " + exchange + " as evaluated: " + list);
212 }
213 }
214
215 // Static helper methods
216 //-------------------------------------------------------------------------
217 public static XQueryBuilder xquery(final String queryText) {
218 return new XQueryBuilder() {
219 protected XQueryExpression createQueryExpression(StaticQueryContext staticQueryContext)
220 throws XPathException {
221 return staticQueryContext.compileQuery(queryText);
222 }
223 };
224 }
225
226 public static XQueryBuilder xquery(final Reader reader) {
227 return new XQueryBuilder() {
228 protected XQueryExpression createQueryExpression(StaticQueryContext staticQueryContext)
229 throws XPathException, IOException {
230 return staticQueryContext.compileQuery(reader);
231 }
232 };
233 }
234
235 public static XQueryBuilder xquery(final InputStream in, final String characterSet) {
236 return new XQueryBuilder() {
237 protected XQueryExpression createQueryExpression(StaticQueryContext staticQueryContext)
238 throws XPathException, IOException {
239 return staticQueryContext.compileQuery(in, characterSet);
240 }
241 };
242 }
243
244 public static XQueryBuilder xquery(File file, String characterSet) throws FileNotFoundException {
245 return xquery(IOConverter.toInputStream(file), characterSet);
246 }
247
248 public static XQueryBuilder xquery(URL url, String characterSet) throws IOException {
249 return xquery(IOConverter.toInputStream(url), characterSet);
250 }
251
252 public static XQueryBuilder xquery(File file) throws FileNotFoundException {
253 return xquery(IOConverter.toInputStream(file), ObjectHelper.getDefaultCharacterSet());
254 }
255
256 public static XQueryBuilder xquery(URL url) throws IOException {
257 return xquery(IOConverter.toInputStream(url), ObjectHelper.getDefaultCharacterSet());
258 }
259
260 // Fluent API
261 // -------------------------------------------------------------------------
262 public XQueryBuilder parameter(String name, Object value) {
263 parameters.put(name, value);
264 return this;
265 }
266
267 public XQueryBuilder namespace(String prefix, String uri) {
268 namespacePrefixes.put(prefix, uri);
269 // more namespace, we must re initialize
270 initialized.set(false);
271 return this;
272 }
273
274 public XQueryBuilder resultType(Class resultType) {
275 setResultType(resultType);
276 return this;
277 }
278
279 public XQueryBuilder asBytes() {
280 setResultsFormat(ResultFormat.Bytes);
281 return this;
282 }
283
284 public XQueryBuilder asBytesSource() {
285 setResultsFormat(ResultFormat.BytesSource);
286 return this;
287 }
288
289 public XQueryBuilder asDOM() {
290 setResultsFormat(ResultFormat.DOM);
291 return this;
292 }
293
294 public XQueryBuilder asDOMSource() {
295 setResultsFormat(ResultFormat.DOMSource);
296 return this;
297 }
298
299 public XQueryBuilder asList() {
300 setResultsFormat(ResultFormat.List);
301 return this;
302 }
303
304 public XQueryBuilder asString() {
305 setResultsFormat(ResultFormat.String);
306 return this;
307 }
308
309 public XQueryBuilder asStringSource() {
310 setResultsFormat(ResultFormat.StringSource);
311 return this;
312 }
313
314 // Properties
315 // -------------------------------------------------------------------------
316
317 /**
318 * Configures the namespace context from the given DOM element
319 */
320 public void setNamespaces(Map<String, String> namespaces) {
321 namespacePrefixes.putAll(namespaces);
322 // more namespace, we must re initialize
323 initialized.set(false);
324 }
325
326 public XQueryExpression getExpression() throws IOException, XPathException {
327 return expression;
328 }
329
330 public Configuration getConfiguration() {
331 return configuration;
332 }
333
334 public void setConfiguration(Configuration configuration) {
335 this.configuration = configuration;
336 // change configuration, we must re intialize
337 initialized.set(false);
338 }
339
340 public StaticQueryContext getStaticQueryContext() {
341 return staticQueryContext;
342 }
343
344 public void setStaticQueryContext(StaticQueryContext staticQueryContext) {
345 this.staticQueryContext = staticQueryContext;
346 // change context, we must re intialize
347 initialized.set(false);
348 }
349
350 public Map<String, Object> getParameters() {
351 return parameters;
352 }
353
354 public void setParameters(Map<String, Object> parameters) {
355 this.parameters = parameters;
356 }
357
358 public Properties getProperties() {
359 return properties;
360 }
361
362 public void setProperties(Properties properties) {
363 this.properties = properties;
364 }
365
366 public ResultFormat getResultsFormat() {
367 return resultsFormat;
368 }
369
370 public void setResultsFormat(ResultFormat resultsFormat) {
371 this.resultsFormat = resultsFormat;
372 }
373
374 public Class getResultType() {
375 return resultType;
376 }
377
378 public void setResultType(Class resultType) {
379 this.resultType = resultType;
380 }
381
382 // Implementation methods
383 // -------------------------------------------------------------------------
384
385 /**
386 * A factory method to create the XQuery expression
387 */
388 protected abstract XQueryExpression createQueryExpression(StaticQueryContext staticQueryContext)
389 throws XPathException, IOException;
390
391 /**
392 * Creates a dynamic context for the given exchange
393 */
394 protected DynamicQueryContext createDynamicContext(Exchange exchange) throws Exception {
395 Configuration config = getConfiguration();
396 DynamicQueryContext dynamicQueryContext = new DynamicQueryContext(config);
397
398 Message in = exchange.getIn();
399
400 Item item = in.getBody(Item.class);
401 if (item != null) {
402 dynamicQueryContext.setContextItem(item);
403 } else {
404 Source source = in.getBody(Source.class);
405 if (source == null) {
406 source = converter.toSource(converter.createDocument());
407 }
408 DocumentInfo doc = getStaticQueryContext().buildDocument(source);
409 dynamicQueryContext.setContextItem(doc);
410 }
411
412 configureQuery(dynamicQueryContext, exchange);
413 // call the reset if the in message body is StreamCache
414 MessageHelper.resetStreamCache(exchange.getIn());
415 return dynamicQueryContext;
416 }
417
418 /**
419 * Configures the dynamic context with exchange specific parameters
420 */
421 protected void configureQuery(DynamicQueryContext dynamicQueryContext, Exchange exchange)
422 throws Exception {
423 addParameters(dynamicQueryContext, exchange.getProperties());
424 addParameters(dynamicQueryContext, exchange.getIn().getHeaders(), "in.headers.");
425 dynamicQueryContext.setParameter("in.body", exchange.getIn().getBody());
426 addParameters(dynamicQueryContext, getParameters());
427
428 dynamicQueryContext.setParameter("exchange", exchange);
429 if (exchange.hasOut() && exchange.getPattern().isOutCapable()) {
430 dynamicQueryContext.setParameter("out.body", exchange.getOut().getBody());
431 addParameters(dynamicQueryContext, exchange.getOut().getHeaders(), "out.headers.");
432 }
433 }
434
435 protected void addParameters(DynamicQueryContext dynamicQueryContext, Map<String, Object> map) {
436 addParameters(dynamicQueryContext, map, "");
437 }
438
439 protected void addParameters(DynamicQueryContext dynamicQueryContext, Map<String, Object> map, String parameterPrefix) {
440 Set<Map.Entry<String, Object>> propertyEntries = map.entrySet();
441 for (Map.Entry<String, Object> entry : propertyEntries) {
442 dynamicQueryContext.setParameter(parameterPrefix + entry.getKey(), entry.getValue());
443 }
444 }
445
446 protected boolean matches(Exchange exchange, List results) {
447 return ObjectHelper.matches(results);
448 }
449
450 /**
451 * Initializes this builder - <b>Must be invoked before evaluation</b>.
452 */
453 protected synchronized void initialize(Exchange exchange) throws XPathException, IOException {
454 // must use synchronized for concurrency issues and only let it initialize once
455 if (!initialized.get()) {
456 if (LOG.isDebugEnabled()) {
457 LOG.debug("Initializing XQueryBuilder " + this);
458 }
459 configuration = new Configuration();
460 configuration.setHostLanguage(Configuration.XQUERY);
461
462 staticQueryContext = new StaticQueryContext(getConfiguration());
463 Set<Map.Entry<String, String>> entries = namespacePrefixes.entrySet();
464 for (Map.Entry<String, String> entry : entries) {
465 String prefix = entry.getKey();
466 String uri = entry.getValue();
467 staticQueryContext.declareNamespace(prefix, uri);
468 staticQueryContext.setInheritNamespaces(true);
469 }
470
471 expression = createQueryExpression(staticQueryContext);
472
473 initialized.set(true);
474 }
475
476 // let the configuration be accessible on the exchange as its shared for this evaluation
477 // and can be needed by 3rd party type converters or other situations (camel-artixds)
478 exchange.setProperty("CamelSaxonConfiguration", configuration);
479 }
480
481 }