blob: f2a5cf10f000f2d8fd68d610a669c6fc17683757 [file] [log] [blame]
/*
* 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.component.xslt;
import java.io.IOException;
import java.util.concurrent.RejectedExecutionException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import org.w3c.dom.Document;
import org.apache.camel.AggregationStrategy;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Exchange;
import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The XSLT Aggregation Strategy enables you to use XSL stylesheets to aggregate messages.
* <p>
* Since XSLT does not directly support providing multiple XML payloads as an input, this aggregator injects
* the new incoming XML document (<tt>newExchange</tt>) into the <tt>oldExchange</tt> as an exchange property of
* type {@link Document}. The old exchange therefore remains accessible as the root context.
* This exchange property can then be accessed from your XSLT by declaring an {@code <xsl:param />} at the top
* of your stylesheet:
*
* <code>
* <xsl:param name="new-exchange" />
*
* <xsl:template match="/">
* <abc>
* <xsl:copy-of select="/ElementFromOldExchange" />
* <xsl:copy-of select="$new-exchange/ElementFromNewExchange" />
* </abc>
* </xsl:template>
* </code>
*
* The exchange property name defaults to <tt>new-exchange</tt> but can be
* changed through {@link #setPropertyName(String)}.
* <p>
* Some code bits have been copied from the {@link org.apache.camel.component.xslt.XsltEndpoint}.
*/
public class XsltAggregationStrategy extends ServiceSupport implements AggregationStrategy, CamelContextAware {
private static final Logger LOG = LoggerFactory.getLogger(XsltAggregationStrategy.class);
private static final String DEFAULT_PROPERTY_NAME = "new-exchange";
private volatile XsltBuilder xslt;
private volatile URIResolver uriResolver;
private CamelContext camelContext;
private String propertyName;
private String xslFile;
private String transformerFactoryClass;
private XsltOutput output = XsltOutput.string;
/**
* Constructor.
*
* @param xslFileLocation location of the XSL transformation
*/
public XsltAggregationStrategy(String xslFileLocation) {
this.xslFile = xslFileLocation;
}
@Override
public CamelContext getCamelContext() {
return camelContext;
}
@Override
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}
@Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
// guard against unlikely NPE
if (newExchange == null) {
return oldExchange;
}
// in the first call to this aggregation, do not call the XSLT but instead store the
// incoming exchange
if (oldExchange == null) {
return newExchange;
}
if (!isRunAllowed()) {
throw new RejectedExecutionException();
}
try {
oldExchange.setProperty(propertyName, newExchange.getIn().getBody(Document.class));
xslt.process(oldExchange);
return oldExchange;
} catch (Throwable e) {
oldExchange.setException(e);
}
return oldExchange;
}
public void setOutput(XsltOutput output) {
this.output = output;
}
public void setXslt(XsltBuilder xslt) {
this.xslt = xslt;
}
public void setUriResolver(URIResolver uriResolver) {
this.uriResolver = uriResolver;
}
public void setTransformerFactoryClass(String transformerFactoryClass) {
this.transformerFactoryClass = transformerFactoryClass;
}
public String getPropertyName() {
return propertyName;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
protected void configureOutput(XsltBuilder xslt, String output) throws Exception {
if (ObjectHelper.isEmpty(output)) {
return;
}
if ("string".equalsIgnoreCase(output)) {
xslt.outputString();
} else if ("bytes".equalsIgnoreCase(output)) {
xslt.outputBytes();
} else if ("DOM".equalsIgnoreCase(output)) {
xslt.outputDOM();
} else if ("file".equalsIgnoreCase(output)) {
xslt.outputFile();
} else {
throw new IllegalArgumentException("Unknown output type: " + output);
}
}
/**
* Loads the resource.
*
* @param resourceUri the resource to load
* @throws TransformerException is thrown if error loading resource
* @throws IOException is thrown if error loading resource
*/
protected void loadResource(String resourceUri) throws TransformerException, IOException {
LOG.trace("{} loading schema resource: {}", this, resourceUri);
Source source = xslt.getUriResolver().resolve(resourceUri, null);
if (source == null) {
throw new IOException("Cannot load schema resource " + resourceUri);
} else {
xslt.setTransformerSource(source);
}
}
// --- fluent builders ---
public static XsltAggregationStrategy create(String xslFile) {
return new XsltAggregationStrategy(xslFile);
}
public XsltAggregationStrategy withPropertyName(String propertyName) {
setPropertyName(propertyName);
return this;
}
public XsltAggregationStrategy withOutput(XsltOutput output) {
setOutput(output);
return this;
}
public XsltAggregationStrategy withUriResolver(URIResolver resolver) {
setUriResolver(resolver);
return this;
}
public XsltAggregationStrategy withTransformerFactoryClass(String clazz) {
setTransformerFactoryClass(clazz);
return this;
}
public XsltAggregationStrategy withSaxon() {
setTransformerFactoryClass(XsltEndpoint.SAXON_TRANSFORMER_FACTORY_CLASS_NAME);
return this;
}
@Override
protected void doStart() throws Exception {
ObjectHelper.notNull(camelContext, "CamelContext", this);
// set the default property name if not set
this.propertyName = ObjectHelper.isNotEmpty(propertyName) ? propertyName : DEFAULT_PROPERTY_NAME;
// initialize the XsltBuilder
this.xslt = camelContext.getInjector().newInstance(XsltBuilder.class);
if (transformerFactoryClass != null) {
Class<?> factoryClass = camelContext.getClassResolver().resolveMandatoryClass(transformerFactoryClass,
XsltAggregationStrategy.class.getClassLoader());
TransformerFactory factory = (TransformerFactory) camelContext.getInjector().newInstance(factoryClass);
xslt.setTransformerFactory(factory);
}
if (uriResolver == null) {
uriResolver = new XsltUriResolver(camelContext, xslFile);
}
xslt.setUriResolver(uriResolver);
xslt.setFailOnNullBody(true);
xslt.transformerCacheSize(0);
xslt.setAllowStAX(true);
configureOutput(xslt, output.name());
loadResource(xslFile);
}
@Override
protected void doStop() throws Exception {
// noop
}
}