001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util.toolbox;
018
019import java.io.IOException;
020
021import javax.xml.transform.Source;
022import javax.xml.transform.TransformerException;
023import javax.xml.transform.TransformerFactory;
024import javax.xml.transform.URIResolver;
025
026import org.w3c.dom.Document;
027
028import org.apache.camel.CamelContext;
029import org.apache.camel.Exchange;
030import org.apache.camel.builder.xml.XsltBuilder;
031import org.apache.camel.builder.xml.XsltUriResolver;
032import org.apache.camel.component.xslt.XsltEndpoint;
033import org.apache.camel.component.xslt.XsltOutput;
034import org.apache.camel.processor.aggregate.AggregationStrategy;
035import org.apache.camel.util.ObjectHelper;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * The XSLT Aggregation Strategy enables you to use XSL stylesheets to aggregate messages.
041 * <p>
042 * Since XSLT does not directly support providing multiple XML payloads as an input, this aggregator injects
043 * the new incoming XML document (<tt>newExchange</tt>) into the <tt>oldExchange</tt> as an exchange property of
044 * type {@link Document}. The old exchange therefore remains accessible as the root context.
045 * This exchange property can then be accessed from your XSLT by declaring an {@code <xsl:param />} at the top
046 * of your stylesheet:
047 *
048 * <code>
049 *     <xsl:param name="new-exchange" />
050 *
051 *     <xsl:template match="/">
052 *         <abc>
053 *             <xsl:copy-of select="/ElementFromOldExchange" />
054 *             <xsl:copy-of select="$new-exchange/ElementFromNewExchange" />
055 *         </abc>
056 *     </xsl:template>
057 * </code>
058 *
059 * The exchange property name defaults to <tt>new-exchange</tt> but can be
060 * changed through {@link #setPropertyName(String)}.
061 * <p>
062 * Some code bits have been copied from the {@link org.apache.camel.component.xslt.XsltEndpoint}.
063 *
064 */
065public class XsltAggregationStrategy implements AggregationStrategy {
066
067    private static final Logger LOG = LoggerFactory.getLogger(XsltAggregationStrategy.class);
068    private static final String DEFAULT_PROPERTY_NAME = "new-exchange";
069
070    private volatile XsltBuilder xslt;
071    private volatile URIResolver uriResolver;
072
073    private String propertyName;
074    private String xslFile;
075    private String transformerFactoryClass;
076    private XsltOutput output = XsltOutput.string;
077
078    /**
079     * Constructor.
080     *
081     * @param xslFileLocation location of the XSL transformation
082     */
083    public XsltAggregationStrategy(String xslFileLocation) {
084        this.xslFile = xslFileLocation;
085    }
086
087    @Override
088    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
089        // guard against unlikely NPE
090        if (newExchange == null) {
091            return oldExchange;
092        }
093
094        // in the first call to this aggregation, do not call the XSLT but instead store the
095        // incoming exchange
096        if (oldExchange == null) {
097            return newExchange;
098        }
099
100        try {
101            // initialize if this is the first call
102            if (xslt == null) {
103                initialize(oldExchange.getContext());
104            }
105
106            oldExchange.setProperty(propertyName, newExchange.getIn().getBody(Document.class));
107            xslt.process(oldExchange);
108
109            return oldExchange;
110
111        } catch (Throwable e) {
112            oldExchange.setException(e);
113        }
114
115        return oldExchange;
116    }
117
118    public void setOutput(XsltOutput output) {
119        this.output = output;
120    }
121
122    public void setXslt(XsltBuilder xslt) {
123        this.xslt = xslt;
124    }
125
126    public void setUriResolver(URIResolver uriResolver) {
127        this.uriResolver = uriResolver;
128    }
129
130    public void setTransformerFactoryClass(String transformerFactoryClass) {
131        this.transformerFactoryClass = transformerFactoryClass;
132    }
133
134    public String getPropertyName() {
135        return propertyName;
136    }
137
138    public void setPropertyName(String propertyName) {
139        this.propertyName = propertyName;
140    }
141
142    protected void initialize(CamelContext context) throws Exception {
143        // set the default property name if not set
144        this.propertyName = ObjectHelper.isNotEmpty(propertyName) ? propertyName : DEFAULT_PROPERTY_NAME;
145
146        // initialize the XsltBuilder
147        this.xslt = context.getInjector().newInstance(XsltBuilder.class);
148
149        if (transformerFactoryClass != null) {
150            Class<?> factoryClass = context.getClassResolver().resolveMandatoryClass(transformerFactoryClass,
151                    XsltAggregationStrategy.class.getClassLoader());
152            TransformerFactory factory = (TransformerFactory) context.getInjector().newInstance(factoryClass);
153            xslt.getConverter().setTransformerFactory(factory);
154        }
155
156        if (uriResolver == null) {
157            uriResolver = new XsltUriResolver(context, xslFile);
158        }
159
160        xslt.setUriResolver(uriResolver);
161        xslt.setFailOnNullBody(true);
162        xslt.transformerCacheSize(0);
163        xslt.setAllowStAX(true);
164
165        configureOutput(xslt, output.name());
166        loadResource(xslFile);
167    }
168
169    protected void configureOutput(XsltBuilder xslt, String output) throws Exception {
170        if (ObjectHelper.isEmpty(output)) {
171            return;
172        }
173
174        if ("string".equalsIgnoreCase(output)) {
175            xslt.outputString();
176        } else if ("bytes".equalsIgnoreCase(output)) {
177            xslt.outputBytes();
178        } else if ("DOM".equalsIgnoreCase(output)) {
179            xslt.outputDOM();
180        } else if ("file".equalsIgnoreCase(output)) {
181            xslt.outputFile();
182        } else {
183            throw new IllegalArgumentException("Unknown output type: " + output);
184        }
185    }
186
187    /**
188     * Loads the resource.
189     *
190     * @param resourceUri the resource to load
191     * @throws TransformerException is thrown if error loading resource
192     * @throws IOException          is thrown if error loading resource
193     */
194    protected void loadResource(String resourceUri) throws TransformerException, IOException {
195        LOG.trace("{} loading schema resource: {}", this, resourceUri);
196        Source source = xslt.getUriResolver().resolve(resourceUri, null);
197        if (source == null) {
198            throw new IOException("Cannot load schema resource " + resourceUri);
199        } else {
200            xslt.setTransformerSource(source);
201        }
202    }
203
204    // --- fluent builders ---
205    public static XsltAggregationStrategy create(String xslFile) {
206        return new XsltAggregationStrategy(xslFile);
207    }
208
209    public XsltAggregationStrategy withPropertyName(String propertyName) {
210        setPropertyName(propertyName);
211        return this;
212    }
213
214    public XsltAggregationStrategy withOutput(XsltOutput output) {
215        setOutput(output);
216        return this;
217    }
218
219    public XsltAggregationStrategy withUriResolver(URIResolver resolver) {
220        setUriResolver(resolver);
221        return this;
222    }
223
224    public XsltAggregationStrategy withTransformerFactoryClass(String clazz) {
225        setTransformerFactoryClass(clazz);
226        return this;
227    }
228
229    public XsltAggregationStrategy withSaxon() {
230        setTransformerFactoryClass(XsltEndpoint.SAXON_TRANSFORMER_FACTORY_CLASS_NAME);
231        return this;
232    }
233
234}