View Javadoc

1   /*
2    * $Id: XQueryTransformer.java 19250 2010-08-30 16:53:14Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  package org.mule.module.xml.transformer;
11  
12  import org.mule.api.MuleMessage;
13  import org.mule.api.MuleRuntimeException;
14  import org.mule.api.lifecycle.Disposable;
15  import org.mule.api.lifecycle.Initialisable;
16  import org.mule.api.lifecycle.InitialisationException;
17  import org.mule.api.transformer.TransformerException;
18  import org.mule.config.i18n.CoreMessages;
19  import org.mule.module.xml.i18n.XmlMessages;
20  import org.mule.transformer.types.DataTypeFactory;
21  import org.mule.util.IOUtils;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.InputStream;
25  import java.io.StringReader;
26  import java.util.ArrayList;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  
31  import javax.xml.namespace.QName;
32  import javax.xml.stream.XMLStreamReader;
33  
34  import net.sf.saxon.Configuration;
35  import net.sf.saxon.javax.xml.xquery.XQCommonHandler;
36  import net.sf.saxon.javax.xml.xquery.XQConnection;
37  import net.sf.saxon.javax.xml.xquery.XQDataSource;
38  import net.sf.saxon.javax.xml.xquery.XQException;
39  import net.sf.saxon.javax.xml.xquery.XQItem;
40  import net.sf.saxon.javax.xml.xquery.XQItemType;
41  import net.sf.saxon.javax.xml.xquery.XQPreparedExpression;
42  import net.sf.saxon.javax.xml.xquery.XQResultSequence;
43  import net.sf.saxon.xqj.SaxonXQDataSource;
44  
45  import org.apache.commons.pool.BasePoolableObjectFactory;
46  import org.apache.commons.pool.impl.GenericObjectPool;
47  import org.dom4j.io.DOMWriter;
48  import org.dom4j.io.DocumentSource;
49  import org.w3c.dom.Document;
50  import org.w3c.dom.Element;
51  import org.w3c.dom.Node;
52  import org.xml.sax.InputSource;
53  
54  /**
55   * The XQuery Module gives users the ability to perform XQuery transformations on XML messages in Mule
56   */
57  public class XQueryTransformer extends AbstractXmlTransformer implements Disposable
58  {
59      public static final String SOURCE_DOCUMENT_NAMESPACE = "document";
60  
61      // keep at least 1 XSLT Transformer ready by default
62      private static final int MIN_IDLE_TRANSFORMERS = 1;
63      // keep max. 32 XSLT Transformers around by default
64      private static final int MAX_IDLE_TRANSFORMERS = 32;
65      // MAX_IDLE is also the total limit
66      private static final int MAX_ACTIVE_TRANSFORMERS = MAX_IDLE_TRANSFORMERS;
67  
68      protected final GenericObjectPool transformerPool;
69  
70      private volatile String xqueryFile;
71      private volatile String xquery;
72      private volatile Map contextProperties;
73      private volatile XQCommonHandler commonHandler;
74      private volatile XQConnection connection;
75      protected Configuration configuration;
76  
77      public XQueryTransformer()
78      {
79          super();
80          transformerPool = new GenericObjectPool(new PooledXQueryTransformerFactory());
81          transformerPool.setMinIdle(MIN_IDLE_TRANSFORMERS);
82          transformerPool.setMaxIdle(MAX_IDLE_TRANSFORMERS);
83          transformerPool.setMaxActive(MAX_ACTIVE_TRANSFORMERS);
84  
85          registerSourceType(DataTypeFactory.STRING);
86          registerSourceType(DataTypeFactory.BYTE_ARRAY);
87          registerSourceType(DataTypeFactory.create(DocumentSource.class));
88          registerSourceType(DataTypeFactory.create(org.dom4j.Document.class));
89          registerSourceType(DataTypeFactory.create(Document.class));
90          registerSourceType(DataTypeFactory.create(Element.class));
91          registerSourceType(DataTypeFactory.INPUT_STREAM);
92          setReturnDataType(DataTypeFactory.create(Element.class));
93      }
94  
95      public XQueryTransformer(String xqueryFile)
96      {
97          this();
98          this.xqueryFile = xqueryFile;
99      }
100 
101     /**
102      *
103      */
104     @Override
105     public void initialise() throws InitialisationException
106     {
107 
108         if (xquery != null && xqueryFile != null)
109         {
110             throw new InitialisationException(XmlMessages.canOnlySetFileOrXQuery(), this);
111         }
112 
113         try
114         {
115             if (xqueryFile != null)
116             {
117                 xquery = IOUtils.getResourceAsString(xqueryFile, getClass());
118             }
119             if (configuration == null)
120             {
121                 configuration = new Configuration();
122             }
123 
124             XQDataSource ds = new SaxonXQDataSource(configuration);
125             if (commonHandler != null)
126             {
127                 ds.setCommonHandler(commonHandler);
128             }
129             connection = ds.getConnection();
130 
131             transformerPool.addObject();
132 
133         }
134         catch (Throwable te)
135         {
136             throw new InitialisationException(te, this);
137         }
138     }
139 
140     @Override
141     public void dispose()
142     {
143         try
144         {
145             connection.close();
146         }
147         catch (XQException e)
148         {
149             logger.warn(e.getMessage());
150         }
151     }
152 
153     @Override
154     public Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException
155     {
156         try
157         {
158             XQPreparedExpression transformer = null;
159             try
160             {
161                 transformer = (XQPreparedExpression) transformerPool.borrowObject();
162 
163                 bindParameters(transformer, message);
164 
165                 bindDocument(message.getPayload(), transformer);
166 
167                 XQResultSequence result = transformer.executeQuery();
168                 //No support for return Arrays yet
169                 List results = new ArrayList();
170                 while (result.next())
171                 {
172                     XQItem item = result.getItem();
173 
174                     Class type = returnType.getType();
175                     if (Node.class.isAssignableFrom(type) || Node[].class.isAssignableFrom(type))
176                     {
177                         results.add(item.getNode());
178                     }
179                     else if (String.class.isAssignableFrom(type) || String[].class.isAssignableFrom(type))
180                     {
181                         results.add(item.getItemAsString());
182                     }
183                     else if (XMLStreamReader.class.isAssignableFrom(type) || XMLStreamReader[].class.isAssignableFrom(type))
184                     {
185                         try
186                         {
187                             results.add(item.getItemAsStream());
188                         }
189                         catch (XQException e)
190                         {
191                             throw new TransformerException(XmlMessages.streamNotAvailble(getName()));
192                         }
193                     }
194                     else
195                     {
196                         //This can be a JAXB bound  object instance depending on whether the CommonHandler has been set
197                         try
198                         {
199                             results.add(item.getObject());
200                         }
201                         catch (XQException e)
202                         {
203                             throw new TransformerException(XmlMessages.objectNotAvailble(getName()));
204 
205                         }
206                     }
207                     if (!type.isArray())
208                     {
209                         break;
210                     }
211                 }
212                 if (returnType.getType().isArray())
213                 {
214                     return results.toArray();
215                 }
216                 if (results.size() == 1)
217                 {
218                     return results.get(0);
219                 }
220                 else if (results.size() == 0)
221                 {
222                     return null;
223                 }
224                 else
225                 {
226                     return results.toArray();
227                 }
228 
229             }
230             finally
231             {
232                 if (transformer != null)
233                 {
234                     if (transformer.getWarnings() != null)
235                     {
236                         logger.warn(transformer.getWarnings().getMessage(), transformer.getWarnings().fillInStackTrace());
237                     }
238                     // clear transformation parameters before returning transformer to the
239                     // pool
240                     //TODO find out what the scope is for bound variables, there doesn't seem to be a way to unbind them
241 
242                     transformerPool.returnObject(transformer);
243                 }
244             }
245 
246         }
247         catch (Exception e)
248         {
249             throw new TransformerException(this, e);
250         }
251     }
252 
253     protected void bindParameters(XQPreparedExpression transformer, MuleMessage message) throws XQException, TransformerException
254     {
255         // set transformation parameters
256         if (contextProperties != null)
257         {
258             for (Iterator i = contextProperties.entrySet().iterator(); i.hasNext();)
259             {
260                 Map.Entry parameter = (Map.Entry) i.next();
261                 String key = (String) parameter.getKey();
262                 Object o = evaluateTransformParameter(key, parameter.getValue(), message);
263 
264                 if (o instanceof String)
265                 {
266                     transformer.bindAtomicValue(new QName(key), o.toString(), connection.createAtomicItemType(XQItemType.XQBASETYPE_STRING));
267                 }
268                 else if (o instanceof Boolean)
269                 {
270                     transformer.bindBoolean(new QName(key), ((Boolean) o).booleanValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_BOOLEAN));
271                 }
272                 else if (o instanceof Byte)
273                 {
274                     transformer.bindByte(new QName(key), ((Byte) o).byteValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_BYTE));
275                 }
276                 else if (o instanceof Short)
277                 {
278                     transformer.bindShort(new QName(key), ((Short) o).shortValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_SHORT));
279                 }
280                 else if (o instanceof Integer)
281                 {
282                     transformer.bindInt(new QName(key), ((Integer) o).intValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_INT));
283                 }
284                 else if (o instanceof Long)
285                 {
286                     transformer.bindLong(new QName(key), ((Long) o).longValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_LONG));
287                 }
288                 else if (o instanceof Float)
289                 {
290                     transformer.bindFloat(new QName(key), ((Float) o).floatValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_FLOAT));
291                 }
292                 else if (o instanceof Double)
293                 {
294                     transformer.bindDouble(new QName(key), ((Double) o).doubleValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_DOUBLE));
295                 }
296                 else
297                 {
298                     logger.error("Cannot bind value: " + o + " cannot be bound to the Xquery context. Not of supported type");
299                 }
300             }
301         }
302     }
303 
304     /**
305      * Returns the InputSource corresponding to xqueryFile or xquery
306      *
307      * @param src
308      * @param transformer
309      * @throws net.sf.saxon.javax.xml.xquery.XQException
310      *
311      * @throws org.mule.umo.transformer.TransformerException
312      *
313      */
314     protected void bindDocument(Object src, XQPreparedExpression transformer) throws Exception
315     {
316         if (src instanceof byte[])
317         {
318             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource(new ByteArrayInputStream((byte[]) src)));
319 
320         }
321         else if (src instanceof InputStream)
322         {
323             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource((InputStream) src));
324 
325         }
326         else if (src instanceof String)
327         {
328             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource(new StringReader((String) src)));
329 
330         }
331         else if (src instanceof Document)
332         {
333             transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), (Document) src, null);
334 
335         }
336         else if (src instanceof Element)
337         {
338             transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), (Element) src, null);
339 
340         }
341         else if (src instanceof org.dom4j.Document)
342         {
343             DOMWriter domWriter = new DOMWriter();
344             Document dom = domWriter.write((org.dom4j.Document) src);
345             transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), dom, null);
346 
347         }
348         else if (src instanceof DocumentSource)
349         {
350             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), ((DocumentSource) src).getInputSource());
351 
352         }
353         else
354         {
355             throw new IllegalArgumentException(CoreMessages.transformUnexpectedType(src.getClass(), null).getMessage());
356         }
357     }
358 
359     public Configuration getConfiguration()
360     {
361         return configuration;
362     }
363 
364     public void setConfiguration(Configuration configuration)
365     {
366         this.configuration = configuration;
367     }
368 
369     /**
370      * @return Returns the xqueryFile.
371      */
372     public String getXqueryFile()
373     {
374         return xqueryFile;
375     }
376 
377     /**
378      * @param xqueryFile The xqueryFile to set.
379      */
380     public void setXqueryFile(String xqueryFile)
381     {
382         this.xqueryFile = xqueryFile;
383     }
384 
385     public String getXquery()
386     {
387         return xquery;
388     }
389 
390     public void setXquery(String xquery)
391     {
392         this.xquery = xquery;
393     }
394 
395     public XQCommonHandler getCommonHandler()
396     {
397         return commonHandler;
398     }
399 
400     public void setCommonHandler(XQCommonHandler commonHandler)
401     {
402         this.commonHandler = commonHandler;
403     }
404 
405 
406     protected class PooledXQueryTransformerFactory extends BasePoolableObjectFactory
407     {
408         @Override
409         public Object makeObject() throws Exception
410         {
411             return connection.prepareExpression(xquery);
412         }
413 
414         @Override
415         public void destroyObject(Object o) throws Exception
416         {
417             ((XQPreparedExpression) o).close();
418             super.destroyObject(o);
419         }
420     }
421 
422 
423     /**
424      * @return The current maximum number of allowable active transformer objects in
425      *         the pool
426      */
427     public int getMaxActiveTransformers()
428     {
429         return transformerPool.getMaxActive();
430     }
431 
432     /**
433      * Sets the the current maximum number of active transformer objects allowed in the
434      * pool
435      *
436      * @param maxActiveTransformers New maximum size to set
437      */
438     public void setMaxActiveTransformers(int maxActiveTransformers)
439     {
440         transformerPool.setMaxActive(maxActiveTransformers);
441     }
442 
443     /**
444      * @return The current maximum number of allowable idle transformer objects in the
445      *         pool
446      */
447     public int getMaxIdleTransformers()
448     {
449         return transformerPool.getMaxIdle();
450     }
451 
452     /**
453      * Sets the the current maximum number of idle transformer objects allowed in the pool
454      *
455      * @param maxIdleTransformers New maximum size to set
456      */
457     public void setMaxIdleTransformers(int maxIdleTransformers)
458     {
459         transformerPool.setMaxIdle(maxIdleTransformers);
460     }
461 
462     /**
463      * Gets the parameters to be used when applying the transformation
464      *
465      * @return a map of the parameter names and associated values
466      * @see javax.xml.transform.Transformer#setParameter(java.lang.String,
467      *      java.lang.Object)
468      */
469     public Map getContextProperties()
470     {
471         return contextProperties;
472     }
473 
474     /**
475      * Sets the parameters to be used when applying the transformation
476      *
477      * @param contextProperties a map of the parameter names and associated values
478      * @see javax.xml.transform.Transformer#setParameter(java.lang.String,
479      *      java.lang.Object)
480      */
481     public void setContextProperties(Map contextProperties)
482     {
483         this.contextProperties = contextProperties;
484     }
485 
486     /**
487      * <p>
488      * Returns the value to be set for the parameter. This method is called for each
489      * parameter before it is set on the transformer. The purpose of this method is to
490      * allow dynamic parameters related to the event (usually message properties) to be
491      * used. Any expression using the Mule expression syntax can be used.
492      * </p>
493      * <p>
494      * For example: If the current event's message has a property named "myproperty", to
495      * pass this in you would set the transform parameter's value to be
496      * "#[mule.message:header(myproperty)]".
497      * <p/>
498      * <p>
499      * This method may be overloaded by a sub class to provide a different dynamic
500      * parameter implementation.
501      * </p>
502      *
503      * @param name  the name of the parameter
504      * @param value the value of the paramter
505      * @return the object to be set as the parameter value
506      * @throws TransformerException
507      */
508     protected Object evaluateTransformParameter(String name, Object value, MuleMessage message) throws TransformerException
509     {
510         if (value instanceof String)
511         {
512             return muleContext.getExpressionManager().parse(value.toString(), message);
513         }
514         return value;
515     }
516 
517     @Override
518     public Object clone() throws CloneNotSupportedException
519     {
520         Object clone = super.clone();
521         try
522         {
523             ((Initialisable) clone).initialise();
524             return clone;
525         }
526         catch (InitialisationException e)
527         {
528             throw new MuleRuntimeException(CoreMessages.failedToClone(getClass().getName()), e);
529         }
530     }
531 }