/* * Copyright 2002-2016 the original author or authors. * * Licensed 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.springframework.integration.xml.transformer; import java.io.IOException; import java.util.Arrays; import java.util.Map; import java.util.Map.Entry; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.io.Resource; import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.xml.result.DomResultFactory; import org.springframework.integration.xml.result.ResultFactory; import org.springframework.integration.xml.source.DomSourceFactory; import org.springframework.integration.xml.source.SourceFactory; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; import org.springframework.xml.transform.StringResult; import org.springframework.xml.transform.StringSource; /** * Thread safe XSLT transformer implementation which returns a transformed * {@link Source}, {@link Document}, or {@link String}. If * alwaysUseSourceResultFactories is false (default) the following logic occurs * <p> * {@link String} payload in results in {@link String} payload out * <p> * {@link Document} payload in results in {@link Document} payload out * <p> * {@link Source} payload in results in {@link Result} payload out, type will be * determined by the {@link ResultFactory}, {@link DomResultFactory} by default. * If an instance of {@link ResultTransformer} is registered this will be used * to convert the result. * <p> * If alwaysUseSourceResultFactories is true then the ResultFactory and * {@link SourceFactory} will be used to create the {@link Source} from the * payload and the {@link Result} to pass into the transformer. An instance of * {@link ResultTransformer} can also be provided to convert the Result prior to * returning. * * @author Jonas Partner * @author Mark Fisher * @author Oleg Zhurakousky * @author Artem Bilan * @author Mike Bazos * @author Gary Russell */ public class XsltPayloadTransformer extends AbstractXmlTransformer implements BeanClassLoaderAware { private final ResultTransformer resultTransformer; private volatile Resource xslResource; private volatile Templates templates; private String transformerFactoryClassName; private volatile StandardEvaluationContext evaluationContext; private Map<String, Expression> xslParameterMappings; private volatile SourceFactory sourceFactory = new DomSourceFactory(); private volatile boolean resultFactoryExplicitlySet; private volatile boolean alwaysUseSourceFactory = false; private volatile boolean alwaysUseResultFactory = false; private volatile String[] xsltParamHeaders; private ClassLoader classLoader; public XsltPayloadTransformer(Templates templates) { this(templates, null); } public XsltPayloadTransformer(Resource xslResource) { this(xslResource, null, null); } public XsltPayloadTransformer(Resource xslResource, ResultTransformer resultTransformer) { this(xslResource, resultTransformer, null); } public XsltPayloadTransformer(Resource xslResource, String transformerFactoryClassName) { Assert.notNull(xslResource, "'xslResource' must not be null."); Assert.hasText(transformerFactoryClassName, "'transformerFactoryClassName' must not be empty String."); this.xslResource = xslResource; this.transformerFactoryClassName = transformerFactoryClassName; this.resultTransformer = null; } public XsltPayloadTransformer(Resource xslResource, ResultTransformer resultTransformer, String transformerFactoryClassName) { Assert.notNull(xslResource, "'xslResource' must not be null."); this.xslResource = xslResource; this.resultTransformer = resultTransformer; this.transformerFactoryClassName = transformerFactoryClassName; } public XsltPayloadTransformer(Templates templates, ResultTransformer resultTransformer) { Assert.notNull(templates, "'templates' must not be null."); this.templates = templates; this.resultTransformer = resultTransformer; } /** * Sets the SourceFactory. * * @param sourceFactory The source factory. */ public void setSourceFactory(SourceFactory sourceFactory) { Assert.notNull(sourceFactory, "SourceFactory must not be null"); this.sourceFactory = sourceFactory; } /** * Sets the ResultFactory. * * @param resultFactory The result factory. */ @Override public void setResultFactory(ResultFactory resultFactory) { super.setResultFactory(resultFactory); this.resultFactoryExplicitlySet = true; } /** * Specify whether to always use source factory even for directly supported payload types. * * @param alwaysUseSourceFactory true to always use the source factory. */ public void setAlwaysUseSourceFactory(boolean alwaysUseSourceFactory) { this.alwaysUseSourceFactory = alwaysUseSourceFactory; } /** * Specify whether to always use result factory even for directly supported payload types * * @param alwaysUseResultFactory true to always use the result factory. */ public void setAlwaysUseResultFactory(boolean alwaysUseResultFactory) { this.alwaysUseResultFactory = alwaysUseResultFactory; } public void setXslParameterMappings(Map<String, Expression> xslParameterMappings) { this.xslParameterMappings = xslParameterMappings; } public void setXsltParamHeaders(String[] xsltParamHeaders) { this.xsltParamHeaders = Arrays.copyOf(xsltParamHeaders, xsltParamHeaders.length); } @Override public void setBeanClassLoader(ClassLoader classLoader) { Assert.notNull(classLoader, "'beanClassLoader' must not be null."); this.classLoader = classLoader; } @Override public void setResultType(String resultType) { super.setResultType(resultType); if (StringUtils.hasText(resultType)) { this.alwaysUseResultFactory = true; } } @Override public void setResultFactoryName(String resultFactoryName) { super.setResultFactoryName(resultFactoryName); if (StringUtils.hasText(resultFactoryName)) { this.alwaysUseResultFactory = true; } } @Override public String getComponentType() { return "xml:xslt-transformer"; } @Override protected void onInit() throws Exception { super.onInit(); this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); if (this.templates == null) { TransformerFactory transformerFactory; if (this.transformerFactoryClassName != null) { transformerFactory = TransformerFactory.newInstance(this.transformerFactoryClassName, this.classLoader); } else { transformerFactory = TransformerFactory.newInstance(); } this.templates = transformerFactory.newTemplates(createStreamSourceOnResource(this.xslResource)); } } @Override protected Object doTransform(Message<?> message) throws Exception { Transformer transformer = buildTransformer(message); Object payload; if (this.alwaysUseSourceFactory) { payload = this.sourceFactory.createSource(message.getPayload()); } else { payload = message.getPayload(); } Object transformedPayload = null; if (this.alwaysUseResultFactory) { transformedPayload = transformUsingResultFactory(payload, transformer); } else if (payload instanceof String) { transformedPayload = transformString((String) payload, transformer); } else if (payload instanceof Document) { transformedPayload = transformDocument((Document) payload, transformer); } else if (payload instanceof Source) { transformedPayload = transformSource((Source) payload, payload, transformer); } else { // fall back to trying factories transformedPayload = transformUsingResultFactory(payload, transformer); } return transformedPayload; } private Object transformUsingResultFactory(Object payload, Transformer transformer) throws TransformerException { Source source; if (this.alwaysUseSourceFactory) { source = this.sourceFactory.createSource(payload); } else if (payload instanceof String) { source = new StringSource((String) payload); } else if (payload instanceof Document) { source = new DOMSource((Document) payload); } else if (payload instanceof Source) { source = (Source) payload; } else { source = this.sourceFactory.createSource(payload); } return transformSource(source, payload, transformer); } private Object transformSource(Source source, Object payload, Transformer transformer) throws TransformerException { Result result; if (!this.resultFactoryExplicitlySet && "text".equals(transformer.getOutputProperties().getProperty("method"))) { result = new StringResult(); } else { result = this.getResultFactory().createResult(payload); } transformer.transform(source, result); if (this.resultTransformer != null) { return this.resultTransformer.transformResult(result); } return result; } private String transformString(String stringPayload, Transformer transformer) throws TransformerException { StringResult result = new StringResult(); Source source; if (this.alwaysUseSourceFactory) { source = this.sourceFactory.createSource(stringPayload); } else { source = new StringSource(stringPayload); } transformer.transform(source, result); return result.toString(); } private Document transformDocument(Document documentPayload, Transformer transformer) throws TransformerException { Source source; if (this.alwaysUseSourceFactory) { source = this.sourceFactory.createSource(documentPayload); } else { source = new DOMSource(documentPayload); } Result result = this.getResultFactory().createResult(documentPayload); if (!DOMResult.class.isAssignableFrom(result.getClass())) { throw new MessagingException( "Document to Document conversion requires a DOMResult-producing ResultFactory implementation."); } DOMResult domResult = (DOMResult) result; transformer.transform(source, domResult); return (Document) domResult.getNode(); } private Transformer buildTransformer(Message<?> message) throws TransformerException { // process individual mappings Transformer transformer = this.templates.newTransformer(); if (this.xslParameterMappings != null) { for (Entry<String, Expression> entry : this.xslParameterMappings.entrySet()) { String parameterName = entry.getKey(); Expression expression = entry.getValue(); try { Object value = expression.getValue(this.evaluationContext, message); transformer.setParameter(parameterName, value); } catch (Exception e) { if (logger.isWarnEnabled()) { logger.warn("Evaluation of header expression '" + expression.getExpressionString() + "' failed. The XSLT parameter '" + parameterName + "' will be skipped."); } } } } // process xslt-parameter-headers if (!ObjectUtils.isEmpty(this.xsltParamHeaders)) { for (Entry<String, Object> entry : message.getHeaders().entrySet()) { String headerName = entry.getKey(); if (PatternMatchUtils.simpleMatch(this.xsltParamHeaders, headerName)) { transformer.setParameter(headerName, entry.getValue()); } } } return transformer; } /** * Compensate for the fact that a Resource <i>may</i> not be a File or even * addressable through a URI. If it is, we want the created StreamSource to * read other resources relative to the provided one. If it isn't, it loads * from the default path. */ private static StreamSource createStreamSourceOnResource(Resource xslResource) throws IOException { try { String systemId = xslResource.getURI().toString(); return new StreamSource(xslResource.getInputStream(), systemId); } catch (IOException e) { return new StreamSource(xslResource.getInputStream()); } } }