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