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