All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.builder.xml.XsltBuilder Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.builder.xml;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Node;

import org.xml.sax.EntityResolver;

import org.apache.camel.Exchange;
import org.apache.camel.ExpectedBodyTypeException;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.RuntimeTransformException;
import org.apache.camel.TypeConverter;
import org.apache.camel.converter.jaxp.StAX2SAXSource;
import org.apache.camel.converter.jaxp.XmlConverter;
import org.apache.camel.support.SynchronizationAdapter;
import org.apache.camel.util.ExchangeHelper;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.camel.util.ObjectHelper.notNull;

/**
 * Creates a Processor
 * which performs an XSLT transformation of the IN message body.
 * 

* Will by default output the result as a String. You can chose which kind of output * you want using the outputXXX methods. * * @version */ public class XsltBuilder implements Processor { private static final Logger LOG = LoggerFactory.getLogger(XsltBuilder.class); private Map parameters = new HashMap(); private XmlConverter converter = new XmlConverter(); private Templates template; private volatile BlockingQueue transformers; private ResultHandlerFactory resultHandlerFactory = new StringResultHandlerFactory(); private boolean failOnNullBody = true; private URIResolver uriResolver; private boolean deleteOutputFile; private ErrorListener errorListener; private boolean allowStAX = true; private EntityResolver entityResolver; public XsltBuilder() { } public XsltBuilder(Templates templates) { this.template = templates; } @Override public String toString() { return "XSLT[" + template + "]"; } public void process(Exchange exchange) throws Exception { notNull(getTemplate(), "template"); if (isDeleteOutputFile()) { // add on completion so we can delete the file when the Exchange is done String fileName = ExchangeHelper.getMandatoryHeader(exchange, Exchange.XSLT_FILE_NAME, String.class); exchange.addOnCompletion(new XsltBuilderOnCompletion(fileName)); } Transformer transformer = getTransformer(); configureTransformer(transformer, exchange); ResultHandler resultHandler = resultHandlerFactory.createResult(exchange); Result result = resultHandler.getResult(); // let's copy the headers before we invoke the transform in case they modify them Message out = exchange.getOut(); out.copyFrom(exchange.getIn()); // the underlying input stream, which we need to close to avoid locking files or other resources InputStream is = null; try { Source source; // only convert to input stream if really needed if (isInputStreamNeeded(exchange)) { is = exchange.getIn().getBody(InputStream.class); source = getSource(exchange, is); } else { Object body = exchange.getIn().getBody(); source = getSource(exchange, body); } if (source instanceof StAXSource) { // Always convert StAXSource to SAXSource. // * Xalan and Saxon-B don't support StAXSource. // * The JDK default implementation (XSLTC) doesn't handle CDATA events // (see com.sun.org.apache.xalan.internal.xsltc.trax.StAXStream2SAX). // * Saxon-HE/PE/EE seem to support StAXSource, but don't advertise this // officially (via TransformerFactory.getFeature(StAXSource.FEATURE)) source = new StAX2SAXSource(((StAXSource) source).getXMLStreamReader()); } LOG.trace("Using {} as source", source); transformer.transform(source, result); LOG.trace("Transform complete with result {}", result); resultHandler.setBody(out); } finally { releaseTransformer(transformer); // IOHelper can handle if is is null IOHelper.close(is); } } // Builder methods // ------------------------------------------------------------------------- /** * Creates an XSLT processor using the given templates instance */ public static XsltBuilder xslt(Templates templates) { return new XsltBuilder(templates); } /** * Creates an XSLT processor using the given XSLT source */ public static XsltBuilder xslt(Source xslt) throws TransformerConfigurationException { notNull(xslt, "xslt"); XsltBuilder answer = new XsltBuilder(); answer.setTransformerSource(xslt); return answer; } /** * Creates an XSLT processor using the given XSLT source */ public static XsltBuilder xslt(File xslt) throws TransformerConfigurationException { notNull(xslt, "xslt"); return xslt(new StreamSource(xslt)); } /** * Creates an XSLT processor using the given XSLT source */ public static XsltBuilder xslt(URL xslt) throws TransformerConfigurationException, IOException { notNull(xslt, "xslt"); return xslt(xslt.openStream()); } /** * Creates an XSLT processor using the given XSLT source */ public static XsltBuilder xslt(InputStream xslt) throws TransformerConfigurationException, IOException { notNull(xslt, "xslt"); return xslt(new StreamSource(xslt)); } /** * Sets the output as being a byte[] */ public XsltBuilder outputBytes() { setResultHandlerFactory(new StreamResultHandlerFactory()); return this; } /** * Sets the output as being a String */ public XsltBuilder outputString() { setResultHandlerFactory(new StringResultHandlerFactory()); return this; } /** * Sets the output as being a DOM */ public XsltBuilder outputDOM() { setResultHandlerFactory(new DomResultHandlerFactory()); return this; } /** * Sets the output as being a File where the filename * must be provided in the {@link Exchange#XSLT_FILE_NAME} header. */ public XsltBuilder outputFile() { setResultHandlerFactory(new FileResultHandlerFactory()); return this; } /** * Should the output file be deleted when the {@link Exchange} is done. *

* This option should only be used if you use {@link #outputFile()} as well. */ public XsltBuilder deleteOutputFile() { this.deleteOutputFile = true; return this; } public XsltBuilder parameter(String name, Object value) { parameters.put(name, value); return this; } /** * Sets a custom URI resolver to be used */ public XsltBuilder uriResolver(URIResolver uriResolver) { setUriResolver(uriResolver); return this; } /** * Enables to allow using StAX. *

* When enabled StAX is preferred as the first choice as {@link Source}. */ public XsltBuilder allowStAX() { setAllowStAX(true); return this; } /** * Used for caching {@link Transformer}s. *

* By default no caching is in use. * * @param numberToCache the maximum number of transformers to cache */ public XsltBuilder transformerCacheSize(int numberToCache) { if (numberToCache > 0) { transformers = new ArrayBlockingQueue(numberToCache); } else { transformers = null; } return this; } /** * Uses a custom {@link javax.xml.transform.ErrorListener}. */ public XsltBuilder errorListener(ErrorListener errorListener) { setErrorListener(errorListener); return this; } // Properties // ------------------------------------------------------------------------- public Map getParameters() { return parameters; } public void setParameters(Map parameters) { this.parameters = parameters; } public void setTemplate(Templates template) { this.template = template; if (transformers != null) { transformers.clear(); } } public Templates getTemplate() { return template; } public boolean isFailOnNullBody() { return failOnNullBody; } public void setFailOnNullBody(boolean failOnNullBody) { this.failOnNullBody = failOnNullBody; } public ResultHandlerFactory getResultHandlerFactory() { return resultHandlerFactory; } public void setResultHandlerFactory(ResultHandlerFactory resultHandlerFactory) { this.resultHandlerFactory = resultHandlerFactory; } public boolean isAllowStAX() { return allowStAX; } public void setAllowStAX(boolean allowStAX) { this.allowStAX = allowStAX; } /** * Sets the XSLT transformer from a Source * * @param source the source * @throws TransformerConfigurationException is thrown if creating a XSLT transformer failed. */ public void setTransformerSource(Source source) throws TransformerConfigurationException { TransformerFactory factory = converter.getTransformerFactory(); if (errorListener != null) { factory.setErrorListener(errorListener); } else { // use a logger error listener so users can see from the logs what the error may be factory.setErrorListener(new XsltErrorListener()); } if (getUriResolver() != null) { factory.setURIResolver(getUriResolver()); } // Check that the call to newTemplates() returns a valid template instance. // In case of an xslt parse error, it will return null and we should stop the // deployment and raise an exception as the route will not be setup properly. Templates templates = factory.newTemplates(source); if (templates != null) { setTemplate(templates); } else { throw new TransformerConfigurationException("Error creating XSLT template. " + "This is most likely be caused by a XML parse error. " + "Please verify your XSLT file configured."); } } /** * Sets the XSLT transformer from a File */ public void setTransformerFile(File xslt) throws TransformerConfigurationException { setTransformerSource(new StreamSource(xslt)); } /** * Sets the XSLT transformer from a URL */ public void setTransformerURL(URL url) throws TransformerConfigurationException, IOException { notNull(url, "url"); setTransformerInputStream(url.openStream()); } /** * Sets the XSLT transformer from the given input stream */ public void setTransformerInputStream(InputStream in) throws TransformerConfigurationException, IOException { notNull(in, "InputStream"); setTransformerSource(new StreamSource(in)); } public XmlConverter getConverter() { return converter; } public void setConverter(XmlConverter converter) { this.converter = converter; } public URIResolver getUriResolver() { return uriResolver; } public void setUriResolver(URIResolver uriResolver) { this.uriResolver = uriResolver; } public void setEntityResolver(EntityResolver entityResolver) { this.entityResolver = entityResolver; } public boolean isDeleteOutputFile() { return deleteOutputFile; } public void setDeleteOutputFile(boolean deleteOutputFile) { this.deleteOutputFile = deleteOutputFile; } public ErrorListener getErrorListener() { return errorListener; } public void setErrorListener(ErrorListener errorListener) { this.errorListener = errorListener; } // Implementation methods // ------------------------------------------------------------------------- private void releaseTransformer(Transformer transformer) { if (transformers != null) { transformer.reset(); transformers.offer(transformer); } } private Transformer getTransformer() throws Exception { Transformer t = null; if (transformers != null) { t = transformers.poll(); } if (t == null) { t = createTransformer(); } return t; } protected Transformer createTransformer() throws Exception { return getTemplate().newTransformer(); } /** * Checks whether we need an {@link InputStream} to access the message body. *

* Depending on the content in the message body, we may not need to convert * to {@link InputStream}. * * @param exchange the current exchange * @return true to convert to {@link InputStream} beforehand converting to {@link Source} afterwards. */ protected boolean isInputStreamNeeded(Exchange exchange) { Object body = exchange.getIn().getBody(); if (body == null) { return false; } if (body instanceof InputStream) { return true; } else if (body instanceof Source) { return false; } else if (body instanceof String) { return false; } else if (body instanceof byte[]) { return false; } else if (body instanceof Node) { return false; } else if (exchange.getContext().getTypeConverterRegistry().lookup(Source.class, body.getClass()) != null) { //there is a direct and hopefully optimized converter to Source return false; } // yes an input stream is needed return true; } /** * Converts the inbound body to a {@link Source}, if the body is not already a {@link Source}. *

* This implementation will prefer to source in the following order: *

    *
  • StAX - If StAX is allowed
  • *
  • SAX - SAX as 2nd choice
  • *
  • Stream - Stream as 3rd choice
  • *
  • DOM - DOM as 4th choice
  • *
*/ protected Source getSource(Exchange exchange, Object body) { // body may already be a source if (body instanceof Source) { return (Source) body; } Source source = null; if (body != null) { if (isAllowStAX()) { // try StAX if enabled source = exchange.getContext().getTypeConverter().tryConvertTo(StAXSource.class, exchange, body); } if (source == null) { // then try SAX source = exchange.getContext().getTypeConverter().tryConvertTo(SAXSource.class, exchange, body); tryAddEntityResolver((SAXSource)source); } if (source == null) { // then try stream source = exchange.getContext().getTypeConverter().tryConvertTo(StreamSource.class, exchange, body); } if (source == null) { // and fallback to DOM source = exchange.getContext().getTypeConverter().tryConvertTo(DOMSource.class, exchange, body); } // as the TypeConverterRegistry will look up source the converter differently if the type converter is loaded different // now we just put the call of source converter at last if (source == null) { TypeConverter tc = exchange.getContext().getTypeConverterRegistry().lookup(Source.class, body.getClass()); if (tc != null) { source = tc.convertTo(Source.class, exchange, body); } } } if (source == null) { if (isFailOnNullBody()) { throw new ExpectedBodyTypeException(exchange, Source.class); } else { try { source = converter.toDOMSource(converter.createDocument()); } catch (ParserConfigurationException e) { throw new RuntimeTransformException(e); } } } return source; } private void tryAddEntityResolver(SAXSource source) { //expecting source to have not null XMLReader if (this.entityResolver != null && source != null) { source.getXMLReader().setEntityResolver(this.entityResolver); } } /** * Configures the transformer with exchange specific parameters */ protected void configureTransformer(Transformer transformer, Exchange exchange) throws Exception { if (uriResolver == null) { uriResolver = new XsltUriResolver(exchange.getContext(), null); } transformer.setURIResolver(uriResolver); if (errorListener == null) { // set our error listener so we can capture errors and report them back on the exchange transformer.setErrorListener(new DefaultTransformErrorHandler(exchange)); } else { // use custom error listener transformer.setErrorListener(errorListener); } transformer.clearParameters(); addParameters(transformer, exchange.getProperties()); addParameters(transformer, exchange.getIn().getHeaders()); addParameters(transformer, getParameters()); transformer.setParameter("exchange", exchange); transformer.setParameter("in", exchange.getIn()); transformer.setParameter("out", exchange.getOut()); } protected void addParameters(Transformer transformer, Map map) { Set> propertyEntries = map.entrySet(); for (Map.Entry entry : propertyEntries) { String key = entry.getKey(); Object value = entry.getValue(); if (value != null) { LOG.trace("Transformer set parameter {} -> {}", key, value); transformer.setParameter(key, value); } } } private static final class XsltBuilderOnCompletion extends SynchronizationAdapter { private final String fileName; private XsltBuilderOnCompletion(String fileName) { this.fileName = fileName; } @Override public void onDone(Exchange exchange) { FileUtil.deleteFile(new File(fileName)); } @Override public String toString() { return "XsltBuilderOnCompletion"; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy