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.component.xslt;
018
019import java.io.IOException;
020import java.util.HashMap;
021import java.util.Map;
022import javax.xml.transform.ErrorListener;
023import javax.xml.transform.Source;
024import javax.xml.transform.TransformerException;
025import javax.xml.transform.TransformerFactory;
026import javax.xml.transform.URIResolver;
027
028import org.apache.camel.Component;
029import org.apache.camel.Exchange;
030import org.apache.camel.api.management.ManagedAttribute;
031import org.apache.camel.api.management.ManagedOperation;
032import org.apache.camel.api.management.ManagedResource;
033import org.apache.camel.builder.xml.ResultHandlerFactory;
034import org.apache.camel.builder.xml.XsltBuilder;
035import org.apache.camel.converter.jaxp.XmlConverter;
036import org.apache.camel.impl.ProcessorEndpoint;
037import org.apache.camel.spi.Metadata;
038import org.apache.camel.spi.UriEndpoint;
039import org.apache.camel.spi.UriParam;
040import org.apache.camel.spi.UriPath;
041import org.apache.camel.util.ObjectHelper;
042import org.apache.camel.util.ServiceHelper;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046@ManagedResource(description = "Managed XsltEndpoint")
047@UriEndpoint(scheme = "xslt", syntax = "xslt:resourceUri", producerOnly = true, label = "core,transformation")
048public class XsltEndpoint extends ProcessorEndpoint {
049
050    private static final Logger LOG = LoggerFactory.getLogger(XsltEndpoint.class);
051    private static final String SAXON_TRANSFORMER_FACTORY_CLASS_NAME = "net.sf.saxon.TransformerFactoryImpl";
052
053    private volatile boolean cacheCleared;
054    private volatile XsltBuilder xslt;
055    private Map<String, Object> parameters;
056
057    @UriPath @Metadata(required = "true")
058    private String resourceUri;
059    @UriParam(defaultValue = "true")
060    private boolean contentCache = true;
061    @UriParam
062    private XmlConverter converter;
063    @UriParam
064    private String transformerFactoryClass;
065    @UriParam
066    private TransformerFactory transformerFactory;
067    @UriParam
068    private boolean saxon;
069    @UriParam
070    private ResultHandlerFactory resultHandlerFactory;
071    @UriParam(defaultValue = "true")
072    private boolean failOnNullBody = true;
073    @UriParam(defaultValue = "string")
074    private XsltOutput output = XsltOutput.string;
075    @UriParam(defaultValue = "0")
076    private int transformerCacheSize;
077    @UriParam
078    private ErrorListener errorListener;
079    @UriParam
080    private URIResolver uriResolver;
081    @UriParam(defaultValue = "true")
082    private boolean allowStAX = true;
083    @UriParam
084    private boolean deleteOutputFile;
085
086    @Deprecated
087    public XsltEndpoint(String endpointUri, Component component, XsltBuilder xslt, String resourceUri,
088            boolean cacheStylesheet) throws Exception {
089        super(endpointUri, component, xslt);
090        this.xslt = xslt;
091        this.resourceUri = resourceUri;
092        this.contentCache = cacheStylesheet;
093    }
094
095    public XsltEndpoint(String endpointUri, Component component) {
096        super(endpointUri, component);
097    }
098
099    @ManagedOperation(description = "Clears the cached XSLT stylesheet, forcing to re-load the stylesheet on next request")
100    public void clearCachedStylesheet() {
101        this.cacheCleared = true;
102    }
103
104    @ManagedAttribute(description = "Whether the XSLT stylesheet is cached")
105    public boolean isCacheStylesheet() {
106        return contentCache;
107    }
108
109    @ManagedAttribute(description = "Endpoint State")
110    public String getState() {
111        return getStatus().name();
112    }
113
114    @ManagedAttribute(description = "Camel ID")
115    public String getCamelId() {
116        return getCamelContext().getName();
117    }
118
119    @ManagedAttribute(description = "Camel ManagementName")
120    public String getCamelManagementName() {
121        return getCamelContext().getManagementName();
122    }
123
124    public XsltEndpoint findOrCreateEndpoint(String uri, String newResourceUri) {
125        String newUri = uri.replace(resourceUri, newResourceUri);
126        LOG.trace("Getting endpoint with URI: {}", newUri);
127        return getCamelContext().getEndpoint(newUri, XsltEndpoint.class);
128    }
129
130    @Override
131    protected void onExchange(Exchange exchange) throws Exception {
132        if (!contentCache || cacheCleared) {
133            loadResource(resourceUri);
134        }
135        super.onExchange(exchange);
136    }
137
138    public boolean isCacheCleared() {
139        return cacheCleared;
140    }
141
142    public void setCacheCleared(boolean cacheCleared) {
143        this.cacheCleared = cacheCleared;
144    }
145
146    public XsltBuilder getXslt() {
147        return xslt;
148    }
149
150    public void setXslt(XsltBuilder xslt) {
151        this.xslt = xslt;
152    }
153
154    public String getResourceUri() {
155        return resourceUri;
156    }
157
158    /**
159     * The name of the template to load from classpath or file system
160     */
161    public void setResourceUri(String resourceUri) {
162        this.resourceUri = resourceUri;
163    }
164
165    public XmlConverter getConverter() {
166        return converter;
167    }
168
169    /**
170     * To use a custom implementation of {@link org.apache.camel.converter.jaxp.XmlConverter}
171     */
172    public void setConverter(XmlConverter converter) {
173        this.converter = converter;
174    }
175
176    public String getTransformerFactoryClass() {
177        return transformerFactoryClass;
178    }
179
180    /**
181     * To use a custom XSLT transformer factory, specified as a FQN class name
182     */
183    public void setTransformerFactoryClass(String transformerFactoryClass) {
184        this.transformerFactoryClass = transformerFactoryClass;
185    }
186
187    public TransformerFactory getTransformerFactory() {
188        return transformerFactory;
189    }
190
191    /**
192     * To use a custom XSLT transformer factory
193     */
194    public void setTransformerFactory(TransformerFactory transformerFactory) {
195        this.transformerFactory = transformerFactory;
196    }
197
198    public boolean isSaxon() {
199        return saxon;
200    }
201
202    /**
203     * Whether to use Saxon as the transformerFactoryClass.
204     * If enabled then the class net.sf.saxon.TransformerFactoryImpl. You would need to add Saxon to the classpath.
205     */
206    public void setSaxon(boolean saxon) {
207        this.saxon = saxon;
208    }
209
210    public ResultHandlerFactory getResultHandlerFactory() {
211        return resultHandlerFactory;
212    }
213
214    /**
215     * Allows you to use a custom org.apache.camel.builder.xml.ResultHandlerFactory which is capable of
216     * using custom org.apache.camel.builder.xml.ResultHandler types.
217     */
218    public void setResultHandlerFactory(ResultHandlerFactory resultHandlerFactory) {
219        this.resultHandlerFactory = resultHandlerFactory;
220    }
221
222    public boolean isFailOnNullBody() {
223        return failOnNullBody;
224    }
225
226    /**
227     * Whether or not to throw an exception if the input body is null.
228     */
229    public void setFailOnNullBody(boolean failOnNullBody) {
230        this.failOnNullBody = failOnNullBody;
231    }
232
233    public XsltOutput getOutput() {
234        return output;
235    }
236
237    /**
238     * Option to specify which output type to use.
239     * Possible values are: string, bytes, DOM, file. The first three options are all in memory based, where as file is streamed directly to a java.io.File.
240     * For file you must specify the filename in the IN header with the key Exchange.XSLT_FILE_NAME which is also CamelXsltFileName.
241     * Also any paths leading to the filename must be created beforehand, otherwise an exception is thrown at runtime.
242     */
243    public void setOutput(XsltOutput output) {
244        this.output = output;
245    }
246
247    public int getTransformerCacheSize() {
248        return transformerCacheSize;
249    }
250
251    /**
252     * The number of javax.xml.transform.Transformer object that are cached for reuse to avoid calls to Template.newTransformer().
253     */
254    public void setTransformerCacheSize(int transformerCacheSize) {
255        this.transformerCacheSize = transformerCacheSize;
256    }
257
258    public ErrorListener getErrorListener() {
259        return errorListener;
260    }
261
262    /**
263     *  Allows to configure to use a custom javax.xml.transform.ErrorListener. Beware when doing this then the default error
264     *  listener which captures any errors or fatal errors and store information on the Exchange as properties is not in use.
265     *  So only use this option for special use-cases.
266     */
267    public void setErrorListener(ErrorListener errorListener) {
268        this.errorListener = errorListener;
269    }
270
271    public boolean isContentCache() {
272        return contentCache;
273    }
274
275    /**
276     * Cache for the resource content (the stylesheet file) when it is loaded.
277     * If set to false Camel will reload the stylesheet file on each message processing. This is good for development.
278     * A cached stylesheet can be forced to reload at runtime via JMX using the clearCachedStylesheet operation.
279     */
280    public void setContentCache(boolean contentCache) {
281        this.contentCache = contentCache;
282    }
283
284    public URIResolver getUriResolver() {
285        return uriResolver;
286    }
287
288    /**
289     * To use a custom javax.xml.transform.URIResolver
290     */
291    public void setUriResolver(URIResolver uriResolver) {
292        this.uriResolver = uriResolver;
293    }
294
295    public boolean isAllowStAX() {
296        return allowStAX;
297    }
298
299    /**
300     * Whether to allow using StAX as the javax.xml.transform.Source.
301     */
302    public void setAllowStAX(boolean allowStAX) {
303        this.allowStAX = allowStAX;
304    }
305
306    public boolean isDeleteOutputFile() {
307        return deleteOutputFile;
308    }
309
310    /**
311     * If you have output=file then this option dictates whether or not the output file should be deleted when the Exchange
312     * is done processing. For example suppose the output file is a temporary file, then it can be a good idea to delete it after use.
313     */
314    public void setDeleteOutputFile(boolean deleteOutputFile) {
315        this.deleteOutputFile = deleteOutputFile;
316    }
317
318    public Map<String, Object> getParameters() {
319        return parameters;
320    }
321
322    /**
323     * Additional parameters to configure on the javax.xml.transform.Transformer.
324     */
325    public void setParameters(Map<String, Object> parameters) {
326        this.parameters = parameters;
327    }
328
329    /**
330     * Loads the resource.
331     *
332     * @param resourceUri  the resource to load
333     * @throws TransformerException is thrown if error loading resource
334     * @throws IOException is thrown if error loading resource
335     */
336    protected void loadResource(String resourceUri) throws TransformerException, IOException {
337        LOG.trace("{} loading schema resource: {}", this, resourceUri);
338        Source source = xslt.getUriResolver().resolve(resourceUri, null);
339        if (source == null) {
340            throw new IOException("Cannot load schema resource " + resourceUri);
341        } else {
342            xslt.setTransformerSource(source);
343        }
344        // now loaded so clear flag
345        cacheCleared = false;
346    }
347
348    @Override
349    protected void doStart() throws Exception {
350        super.doStart();
351
352        LOG.debug("{} using schema resource: {}", this, resourceUri);
353
354        this.xslt = getCamelContext().getInjector().newInstance(XsltBuilder.class);
355        if (converter != null) {
356            xslt.setConverter(converter);
357        }
358
359        if (transformerFactoryClass == null && saxon) {
360            transformerFactoryClass = SAXON_TRANSFORMER_FACTORY_CLASS_NAME;
361        }
362
363        TransformerFactory factory = transformerFactory;
364        if (factory == null && transformerFactoryClass != null) {
365            // provide the class loader of this component to work in OSGi environments
366            Class<?> factoryClass = getCamelContext().getClassResolver().resolveMandatoryClass(transformerFactoryClass, XsltComponent.class.getClassLoader());
367            LOG.debug("Using TransformerFactoryClass {}", factoryClass);
368            factory = (TransformerFactory) getCamelContext().getInjector().newInstance(factoryClass);
369        }
370
371        if (factory != null) {
372            LOG.debug("Using TransformerFactory {}", factory);
373            xslt.getConverter().setTransformerFactory(factory);
374        }
375        if (resultHandlerFactory != null) {
376            xslt.setResultHandlerFactory(resultHandlerFactory);
377        }
378        if (errorListener != null) {
379            xslt.errorListener(errorListener);
380        }
381        xslt.setFailOnNullBody(failOnNullBody);
382        xslt.transformerCacheSize(transformerCacheSize);
383        xslt.setUriResolver(uriResolver);
384        xslt.setAllowStAX(allowStAX);
385        xslt.setDeleteOutputFile(deleteOutputFile);
386
387        configureOutput(xslt, output.name());
388
389        // any additional transformer parameters then make a copy to avoid side-effects
390        if (parameters != null) {
391            Map<String, Object> copy = new HashMap<String, Object>(parameters);
392            xslt.setParameters(copy);
393        }
394
395        // must load resource first which sets a template and do a stylesheet compilation to catch errors early
396        loadResource(resourceUri);
397
398        // and then inject camel context and start service
399        xslt.setCamelContext(getCamelContext());
400
401        // the processor is the xslt builder
402        setProcessor(xslt);
403
404        ServiceHelper.startService(xslt);
405    }
406
407    protected void configureOutput(XsltBuilder xslt, String output) throws Exception {
408        if (ObjectHelper.isEmpty(output)) {
409            return;
410        }
411
412        if ("string".equalsIgnoreCase(output)) {
413            xslt.outputString();
414        } else if ("bytes".equalsIgnoreCase(output)) {
415            xslt.outputBytes();
416        } else if ("DOM".equalsIgnoreCase(output)) {
417            xslt.outputDOM();
418        } else if ("file".equalsIgnoreCase(output)) {
419            xslt.outputFile();
420        } else {
421            throw new IllegalArgumentException("Unknown output type: " + output);
422        }
423    }
424
425    @Override
426    protected void doStop() throws Exception {
427        super.doStop();
428        ServiceHelper.stopService(xslt);
429    }
430}