Java tutorial
/* * $Id$ * -------------------------------------------------------------------------------------- * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.module.xml.transformer; import org.mule.api.MuleMessage; import org.mule.api.MuleRuntimeException; import org.mule.api.lifecycle.Disposable; import org.mule.api.lifecycle.Initialisable; import org.mule.api.lifecycle.InitialisationException; import org.mule.api.transformer.TransformerException; import org.mule.config.i18n.CoreMessages; import org.mule.module.xml.i18n.XmlMessages; import org.mule.transformer.types.DataTypeFactory; import org.mule.util.IOUtils; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringReader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamReader; import net.sf.saxon.Configuration; import net.sf.saxon.javax.xml.xquery.XQCommonHandler; import net.sf.saxon.javax.xml.xquery.XQConnection; import net.sf.saxon.javax.xml.xquery.XQDataSource; import net.sf.saxon.javax.xml.xquery.XQException; import net.sf.saxon.javax.xml.xquery.XQItem; import net.sf.saxon.javax.xml.xquery.XQItemType; import net.sf.saxon.javax.xml.xquery.XQPreparedExpression; import net.sf.saxon.javax.xml.xquery.XQResultSequence; import net.sf.saxon.xqj.SaxonXQDataSource; import org.apache.commons.pool.BasePoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.dom4j.io.DOMWriter; import org.dom4j.io.DocumentSource; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.InputSource; /** * The XQuery Module gives users the ability to perform XQuery transformations on XML messages in Mule */ public class XQueryTransformer extends AbstractXmlTransformer implements Disposable { public static final String SOURCE_DOCUMENT_NAMESPACE = "document"; // keep at least 1 XSLT Transformer ready by default private static final int MIN_IDLE_TRANSFORMERS = 1; // keep max. 32 XSLT Transformers around by default private static final int MAX_IDLE_TRANSFORMERS = 32; // MAX_IDLE is also the total limit private static final int MAX_ACTIVE_TRANSFORMERS = MAX_IDLE_TRANSFORMERS; protected final GenericObjectPool transformerPool; private volatile String xqueryFile; private volatile String xquery; private volatile Map contextProperties; private volatile XQCommonHandler commonHandler; private volatile XQConnection connection; protected Configuration configuration; public XQueryTransformer() { super(); transformerPool = new GenericObjectPool(new PooledXQueryTransformerFactory()); transformerPool.setMinIdle(MIN_IDLE_TRANSFORMERS); transformerPool.setMaxIdle(MAX_IDLE_TRANSFORMERS); transformerPool.setMaxActive(MAX_ACTIVE_TRANSFORMERS); registerSourceType(DataTypeFactory.STRING); registerSourceType(DataTypeFactory.BYTE_ARRAY); registerSourceType(DataTypeFactory.create(DocumentSource.class)); registerSourceType(DataTypeFactory.create(org.dom4j.Document.class)); registerSourceType(DataTypeFactory.create(Document.class)); registerSourceType(DataTypeFactory.create(Element.class)); registerSourceType(DataTypeFactory.INPUT_STREAM); setReturnDataType(DataTypeFactory.create(Element.class)); } public XQueryTransformer(String xqueryFile) { this(); this.xqueryFile = xqueryFile; } /** * */ @Override public void initialise() throws InitialisationException { if (xquery != null && xqueryFile != null) { throw new InitialisationException(XmlMessages.canOnlySetFileOrXQuery(), this); } try { if (xqueryFile != null) { xquery = IOUtils.getResourceAsString(xqueryFile, getClass()); } if (configuration == null) { configuration = new Configuration(); } XQDataSource ds = new SaxonXQDataSource(configuration); if (commonHandler != null) { ds.setCommonHandler(commonHandler); } connection = ds.getConnection(); transformerPool.addObject(); } catch (Throwable te) { throw new InitialisationException(te, this); } } @Override public void dispose() { try { connection.close(); } catch (XQException e) { logger.warn(e.getMessage()); } } @Override public Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException { try { XQPreparedExpression transformer = null; try { transformer = (XQPreparedExpression) transformerPool.borrowObject(); bindParameters(transformer, message); bindDocument(message.getPayload(), transformer); XQResultSequence result = transformer.executeQuery(); //No support for return Arrays yet List results = new ArrayList(); while (result.next()) { XQItem item = result.getItem(); Class type = returnType.getType(); if (Node.class.isAssignableFrom(type) || Node[].class.isAssignableFrom(type)) { results.add(item.getNode()); } else if (String.class.isAssignableFrom(type) || String[].class.isAssignableFrom(type)) { results.add(item.getItemAsString()); } else if (XMLStreamReader.class.isAssignableFrom(type) || XMLStreamReader[].class.isAssignableFrom(type)) { try { results.add(item.getItemAsStream()); } catch (XQException e) { throw new TransformerException(XmlMessages.streamNotAvailble(getName())); } } else { //This can be a JAXB bound object instance depending on whether the CommonHandler has been set try { results.add(item.getObject()); } catch (XQException e) { throw new TransformerException(XmlMessages.objectNotAvailble(getName())); } } if (!type.isArray()) { break; } } if (returnType.getType().isArray()) { return results.toArray(); } if (results.size() == 1) { return results.get(0); } else if (results.size() == 0) { return null; } else { return results.toArray(); } } finally { if (transformer != null) { if (transformer.getWarnings() != null) { logger.warn(transformer.getWarnings().getMessage(), transformer.getWarnings().fillInStackTrace()); } // clear transformation parameters before returning transformer to the // pool //TODO find out what the scope is for bound variables, there doesn't seem to be a way to unbind them unbindParameters(transformer); transformerPool.returnObject(transformer); } } } catch (Exception e) { throw new TransformerException(this, e); } } protected void bindParameters(XQPreparedExpression transformer, MuleMessage message) throws XQException, TransformerException { // set transformation parameters if (contextProperties != null) { for (Iterator i = contextProperties.entrySet().iterator(); i.hasNext();) { Map.Entry parameter = (Map.Entry) i.next(); String key = (String) parameter.getKey(); Object o = evaluateTransformParameter(key, parameter.getValue(), message); if (o instanceof String) { transformer.bindAtomicValue(new QName(key), o.toString(), connection.createAtomicItemType(XQItemType.XQBASETYPE_STRING)); } else if (o instanceof Boolean) { transformer.bindBoolean(new QName(key), ((Boolean) o).booleanValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_BOOLEAN)); } else if (o instanceof Byte) { transformer.bindByte(new QName(key), ((Byte) o).byteValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_BYTE)); } else if (o instanceof Short) { transformer.bindShort(new QName(key), ((Short) o).shortValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_SHORT)); } else if (o instanceof Integer) { transformer.bindInt(new QName(key), ((Integer) o).intValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_INT)); } else if (o instanceof Long) { transformer.bindLong(new QName(key), ((Long) o).longValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_LONG)); } else if (o instanceof Float) { transformer.bindFloat(new QName(key), ((Float) o).floatValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_FLOAT)); } else if (o instanceof Double) { transformer.bindDouble(new QName(key), ((Double) o).doubleValue(), connection.createAtomicItemType(XQItemType.XQBASETYPE_DOUBLE)); } else { logger.error("Cannot bind value: " + o + " cannot be bound to the Xquery context. Not of supported type"); } } } } /** * Removes any parameter bindings from the transformer, replacing them with empty strings * * @param transformer the transformer to remove properties from */ protected void unbindParameters(XQPreparedExpression transformer) throws XQException { // Replace transformation parameters with null values if (contextProperties != null) { for (Iterator i = contextProperties.entrySet().iterator(); i.hasNext();) { Map.Entry parameter = (Map.Entry) i.next(); String key = (String) parameter.getKey(); transformer.bindAtomicValue(new QName(key), "", connection.createAtomicItemType(XQItemType.XQBASETYPE_STRING)); } } } /** * Returns the InputSource corresponding to xqueryFile or xquery * * @param src * @param transformer * @throws net.sf.saxon.javax.xml.xquery.XQException * * @throws Exception * */ protected void bindDocument(Object src, XQPreparedExpression transformer) throws Exception { if (src instanceof byte[]) { transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource(new ByteArrayInputStream((byte[]) src))); } else if (src instanceof InputStream) { transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource((InputStream) src)); } else if (src instanceof String) { transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), new InputSource(new StringReader((String) src))); } else if (src instanceof Document) { transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), (Document) src, null); } else if (src instanceof Element) { transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), (Element) src, null); } else if (src instanceof org.dom4j.Document) { DOMWriter domWriter = new DOMWriter(); Document dom = domWriter.write((org.dom4j.Document) src); transformer.bindNode(new QName(SOURCE_DOCUMENT_NAMESPACE), dom, null); } else if (src instanceof DocumentSource) { transformer.bindDocument(new QName(SOURCE_DOCUMENT_NAMESPACE), ((DocumentSource) src).getInputSource()); } else { throw new IllegalArgumentException( CoreMessages.transformUnexpectedType(src.getClass(), null).getMessage()); } } public Configuration getConfiguration() { return configuration; } public void setConfiguration(Configuration configuration) { this.configuration = configuration; } /** * @return Returns the xqueryFile. */ public String getXqueryFile() { return xqueryFile; } /** * @param xqueryFile The xqueryFile to set. */ public void setXqueryFile(String xqueryFile) { this.xqueryFile = xqueryFile; } public String getXquery() { return xquery; } public void setXquery(String xquery) { this.xquery = xquery; } public XQCommonHandler getCommonHandler() { return commonHandler; } public void setCommonHandler(XQCommonHandler commonHandler) { this.commonHandler = commonHandler; } protected class PooledXQueryTransformerFactory extends BasePoolableObjectFactory { @Override public Object makeObject() throws Exception { return connection.prepareExpression(xquery); } @Override public void destroyObject(Object o) throws Exception { ((XQPreparedExpression) o).close(); super.destroyObject(o); } } /** * @return The current maximum number of allowable active transformer objects in * the pool */ public int getMaxActiveTransformers() { return transformerPool.getMaxActive(); } /** * Sets the the current maximum number of active transformer objects allowed in the * pool * * @param maxActiveTransformers New maximum size to set */ public void setMaxActiveTransformers(int maxActiveTransformers) { transformerPool.setMaxActive(maxActiveTransformers); } /** * @return The current maximum number of allowable idle transformer objects in the * pool */ public int getMaxIdleTransformers() { return transformerPool.getMaxIdle(); } /** * Sets the the current maximum number of idle transformer objects allowed in the pool * * @param maxIdleTransformers New maximum size to set */ public void setMaxIdleTransformers(int maxIdleTransformers) { transformerPool.setMaxIdle(maxIdleTransformers); } /** * Gets the parameters to be used when applying the transformation * * @return a map of the parameter names and associated values * @see javax.xml.transform.Transformer#setParameter(java.lang.String, * java.lang.Object) */ public Map getContextProperties() { return contextProperties; } /** * Sets the parameters to be used when applying the transformation * * @param contextProperties a map of the parameter names and associated values * @see javax.xml.transform.Transformer#setParameter(java.lang.String, * java.lang.Object) */ public void setContextProperties(Map contextProperties) { this.contextProperties = contextProperties; } /** * <p> * Returns the value to be set for the parameter. This method is called for each * parameter before it is set on the transformer. The purpose of this method is to * allow dynamic parameters related to the event (usually message properties) to be * used. Any expression using the Mule expression syntax can be used. * </p> * <p> * For example: If the current event's message has a property named "myproperty", to * pass this in you would set the transform parameter's value to be * "#[mule.message:header(myproperty)]". * <p/> * <p> * This method may be overloaded by a sub class to provide a different dynamic * parameter implementation. * </p> * * @param name the name of the parameter * @param value the value of the paramter * @return the object to be set as the parameter value * @throws TransformerException */ protected Object evaluateTransformParameter(String name, Object value, MuleMessage message) throws TransformerException { if (value instanceof String) { return muleContext.getExpressionManager().parse(value.toString(), message); } return value; } @Override public Object clone() throws CloneNotSupportedException { Object clone = super.clone(); try { ((Initialisable) clone).initialise(); return clone; } catch (InitialisationException e) { throw new MuleRuntimeException(CoreMessages.failedToClone(getClass().getName()), e); } } }