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