Coverage Report - org.mule.module.xml.transformer.XQueryTransformer
 
Classes in this File Line Coverage Branch Coverage Complexity
XQueryTransformer
0%
0/148
0%
0/72
0
XQueryTransformer$PooledXQueryTransformerFactory
0%
0/5
N/A
0
 
 1  
 /*
 2  
  * $Id: XQueryTransformer.java 20321 2010-11-24 15:21:24Z dfeist $
 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  0
 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  0
         super();
 80  0
         transformerPool = new GenericObjectPool(new PooledXQueryTransformerFactory());
 81  0
         transformerPool.setMinIdle(MIN_IDLE_TRANSFORMERS);
 82  0
         transformerPool.setMaxIdle(MAX_IDLE_TRANSFORMERS);
 83  0
         transformerPool.setMaxActive(MAX_ACTIVE_TRANSFORMERS);
 84  
 
 85  0
         registerSourceType(DataTypeFactory.STRING);
 86  0
         registerSourceType(DataTypeFactory.BYTE_ARRAY);
 87  0
         registerSourceType(DataTypeFactory.create(DocumentSource.class));
 88  0
         registerSourceType(DataTypeFactory.create(org.dom4j.Document.class));
 89  0
         registerSourceType(DataTypeFactory.create(Document.class));
 90  0
         registerSourceType(DataTypeFactory.create(Element.class));
 91  0
         registerSourceType(DataTypeFactory.INPUT_STREAM);
 92  0
         setReturnDataType(DataTypeFactory.create(Element.class));
 93  0
     }
 94  
 
 95  
     public XQueryTransformer(String xqueryFile)
 96  
     {
 97  0
         this();
 98  0
         this.xqueryFile = xqueryFile;
 99  0
     }
 100  
 
 101  
     /**
 102  
      *
 103  
      */
 104  
     @Override
 105  
     public void initialise() throws InitialisationException
 106  
     {
 107  
 
 108  0
         if (xquery != null && xqueryFile != null)
 109  
         {
 110  0
             throw new InitialisationException(XmlMessages.canOnlySetFileOrXQuery(), this);
 111  
         }
 112  
 
 113  
         try
 114  
         {
 115  0
             if (xqueryFile != null)
 116  
             {
 117  0
                 xquery = IOUtils.getResourceAsString(xqueryFile, getClass());
 118  
             }
 119  0
             if (configuration == null)
 120  
             {
 121  0
                 configuration = new Configuration();
 122  
             }
 123  
 
 124  0
             XQDataSource ds = new SaxonXQDataSource(configuration);
 125  0
             if (commonHandler != null)
 126  
             {
 127  0
                 ds.setCommonHandler(commonHandler);
 128  
             }
 129  0
             connection = ds.getConnection();
 130  
 
 131  0
             transformerPool.addObject();
 132  
 
 133  
         }
 134  0
         catch (Throwable te)
 135  
         {
 136  0
             throw new InitialisationException(te, this);
 137  0
         }
 138  0
     }
 139  
 
 140  
     @Override
 141  
     public void dispose()
 142  
     {
 143  
         try
 144  
         {
 145  0
             connection.close();
 146  
         }
 147  0
         catch (XQException e)
 148  
         {
 149  0
             logger.warn(e.getMessage());
 150  0
         }
 151  0
     }
 152  
 
 153  
     @Override
 154  
     public Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException
 155  
     {
 156  
         try
 157  
         {
 158  0
             XQPreparedExpression transformer = null;
 159  
             try
 160  
             {
 161  0
                 transformer = (XQPreparedExpression) transformerPool.borrowObject();
 162  
 
 163  0
                 bindParameters(transformer, message);
 164  
 
 165  0
                 bindDocument(message.getPayload(), transformer);
 166  
 
 167  0
                 XQResultSequence result = transformer.executeQuery();
 168  
                 //No support for return Arrays yet
 169  0
                 List results = new ArrayList();
 170  0
                 while (result.next())
 171  
                 {
 172  0
                     XQItem item = result.getItem();
 173  
 
 174  0
                     Class type = returnType.getType();
 175  0
                     if (Node.class.isAssignableFrom(type) || Node[].class.isAssignableFrom(type))
 176  
                     {
 177  0
                         results.add(item.getNode());
 178  
                     }
 179  0
                     else if (String.class.isAssignableFrom(type) || String[].class.isAssignableFrom(type))
 180  
                     {
 181  0
                         results.add(item.getItemAsString());
 182  
                     }
 183  0
                     else if (XMLStreamReader.class.isAssignableFrom(type) || XMLStreamReader[].class.isAssignableFrom(type))
 184  
                     {
 185  
                         try
 186  
                         {
 187  0
                             results.add(item.getItemAsStream());
 188  
                         }
 189  0
                         catch (XQException e)
 190  
                         {
 191  0
                             throw new TransformerException(XmlMessages.streamNotAvailble(getName()));
 192  0
                         }
 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  0
                             results.add(item.getObject());
 200  
                         }
 201  0
                         catch (XQException e)
 202  
                         {
 203  0
                             throw new TransformerException(XmlMessages.objectNotAvailble(getName()));
 204  
 
 205  0
                         }
 206  
                     }
 207  0
                     if (!type.isArray())
 208  
                     {
 209  0
                         break;
 210  
                     }
 211  0
                 }
 212  0
                 if (returnType.getType().isArray())
 213  
                 {
 214  0
                     return results.toArray();
 215  
                 }
 216  0
                 if (results.size() == 1)
 217  
                 {
 218  0
                     return results.get(0);
 219  
                 }
 220  0
                 else if (results.size() == 0)
 221  
                 {
 222  0
                     return null;
 223  
                 }
 224  
                 else
 225  
                 {
 226  0
                     return results.toArray();
 227  
                 }
 228  
 
 229  
             }
 230  
             finally
 231  
             {
 232  0
                 if (transformer != null)
 233  
                 {
 234  0
                     if (transformer.getWarnings() != null)
 235  
                     {
 236  0
                         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  0
                     transformerPool.returnObject(transformer);
 243  
                 }
 244  
             }
 245  
 
 246  
         }
 247  0
         catch (Exception e)
 248  
         {
 249  0
             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  0
         if (contextProperties != null)
 257  
         {
 258  0
             for (Iterator i = contextProperties.entrySet().iterator(); i.hasNext();)
 259  
             {
 260  0
                 Map.Entry parameter = (Map.Entry) i.next();
 261  0
                 String key = (String) parameter.getKey();
 262  0
                 Object o = evaluateTransformParameter(key, parameter.getValue(), message);
 263  
 
 264  0
                 if (o instanceof String)
 265  
                 {
 266  0
                     transformer.bindAtomicValue(new QName(key), o.toString(), connection.createAtomicItemType(XQItemType.XQBASETYPE_STRING));
 267  
                 }
 268  0
                 else if (o instanceof Boolean)
 269  
                 {
 270  0
                     transformer.bindBoolean(new QName(key), ((Boolean) o).booleanValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_BOOLEAN));
 271  
                 }
 272  0
                 else if (o instanceof Byte)
 273  
                 {
 274  0
                     transformer.bindByte(new QName(key), ((Byte) o).byteValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_BYTE));
 275  
                 }
 276  0
                 else if (o instanceof Short)
 277  
                 {
 278  0
                     transformer.bindShort(new QName(key), ((Short) o).shortValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_SHORT));
 279  
                 }
 280  0
                 else if (o instanceof Integer)
 281  
                 {
 282  0
                     transformer.bindInt(new QName(key), ((Integer) o).intValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_INT));
 283  
                 }
 284  0
                 else if (o instanceof Long)
 285  
                 {
 286  0
                     transformer.bindLong(new QName(key), ((Long) o).longValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_LONG));
 287  
                 }
 288  0
                 else if (o instanceof Float)
 289  
                 {
 290  0
                     transformer.bindFloat(new QName(key), ((Float) o).floatValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_FLOAT));
 291  
                 }
 292  0
                 else if (o instanceof Double)
 293  
                 {
 294  0
                     transformer.bindDouble(new QName(key), ((Double) o).doubleValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_DOUBLE));
 295  
                 }
 296  
                 else
 297  
                 {
 298  0
                     logger.error("Cannot bind value: " + o + " cannot be bound to the Xquery context. Not of supported type");
 299  
                 }
 300  0
             }
 301  
         }
 302  0
     }
 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  0
         if (src instanceof byte[])
 317  
         {
 318  0
             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource(new ByteArrayInputStream((byte[]) src)));
 319  
 
 320  
         }
 321  0
         else if (src instanceof InputStream)
 322  
         {
 323  0
             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource((InputStream) src));
 324  
 
 325  
         }
 326  0
         else if (src instanceof String)
 327  
         {
 328  0
             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource(new StringReader((String) src)));
 329  
 
 330  
         }
 331  0
         else if (src instanceof Document)
 332  
         {
 333  0
             transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), (Document) src, null);
 334  
 
 335  
         }
 336  0
         else if (src instanceof Element)
 337  
         {
 338  0
             transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), (Element) src, null);
 339  
 
 340  
         }
 341  0
         else if (src instanceof org.dom4j.Document)
 342  
         {
 343  0
             DOMWriter domWriter = new DOMWriter();
 344  0
             Document dom = domWriter.write((org.dom4j.Document) src);
 345  0
             transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), dom, null);
 346  
 
 347  0
         }
 348  0
         else if (src instanceof DocumentSource)
 349  
         {
 350  0
             transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), ((DocumentSource) src).getInputSource());
 351  
 
 352  
         }
 353  
         else
 354  
         {
 355  0
             throw new IllegalArgumentException(CoreMessages.transformUnexpectedType(src.getClass(), null).getMessage());
 356  
         }
 357  0
     }
 358  
 
 359  
     public Configuration getConfiguration()
 360  
     {
 361  0
         return configuration;
 362  
     }
 363  
 
 364  
     public void setConfiguration(Configuration configuration)
 365  
     {
 366  0
         this.configuration = configuration;
 367  0
     }
 368  
 
 369  
     /**
 370  
      * @return Returns the xqueryFile.
 371  
      */
 372  
     public String getXqueryFile()
 373  
     {
 374  0
         return xqueryFile;
 375  
     }
 376  
 
 377  
     /**
 378  
      * @param xqueryFile The xqueryFile to set.
 379  
      */
 380  
     public void setXqueryFile(String xqueryFile)
 381  
     {
 382  0
         this.xqueryFile = xqueryFile;
 383  0
     }
 384  
 
 385  
     public String getXquery()
 386  
     {
 387  0
         return xquery;
 388  
     }
 389  
 
 390  
     public void setXquery(String xquery)
 391  
     {
 392  0
         this.xquery = xquery;
 393  0
     }
 394  
 
 395  
     public XQCommonHandler getCommonHandler()
 396  
     {
 397  0
         return commonHandler;
 398  
     }
 399  
 
 400  
     public void setCommonHandler(XQCommonHandler commonHandler)
 401  
     {
 402  0
         this.commonHandler = commonHandler;
 403  0
     }
 404  
 
 405  
 
 406  0
     protected class PooledXQueryTransformerFactory extends BasePoolableObjectFactory
 407  
     {
 408  
         @Override
 409  
         public Object makeObject() throws Exception
 410  
         {
 411  0
             return connection.prepareExpression(xquery);
 412  
         }
 413  
 
 414  
         @Override
 415  
         public void destroyObject(Object o) throws Exception
 416  
         {
 417  0
             ((XQPreparedExpression) o).close();
 418  0
             super.destroyObject(o);
 419  0
         }
 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  0
         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  0
         transformerPool.setMaxActive(maxActiveTransformers);
 441  0
     }
 442  
 
 443  
     /**
 444  
      * @return The current maximum number of allowable idle transformer objects in the
 445  
      *         pool
 446  
      */
 447  
     public int getMaxIdleTransformers()
 448  
     {
 449  0
         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  0
         transformerPool.setMaxIdle(maxIdleTransformers);
 460  0
     }
 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  0
         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  0
         this.contextProperties = contextProperties;
 484  0
     }
 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  0
         if (value instanceof String)
 511  
         {
 512  0
             return muleContext.getExpressionManager().parse(value.toString(), message);
 513  
         }
 514  0
         return value;
 515  
     }
 516  
 
 517  
     @Override
 518  
     public Object clone() throws CloneNotSupportedException
 519  
     {
 520  0
         Object clone = super.clone();
 521  
         try
 522  
         {
 523  0
             ((Initialisable) clone).initialise();
 524  0
             return clone;
 525  
         }
 526  0
         catch (InitialisationException e)
 527  
         {
 528  0
             throw new MuleRuntimeException(CoreMessages.failedToClone(getClass().getName()), e);
 529  
         }
 530  
     }
 531  
 }