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     */
017    package org.apache.camel.component.xslt;
018    
019    import java.io.File;
020    import java.io.FileNotFoundException;
021    import java.util.Map;
022    import javax.xml.transform.TransformerConfigurationException;
023    import javax.xml.transform.TransformerFactory;
024    import javax.xml.transform.URIResolver;
025    
026    import org.apache.camel.Endpoint;
027    import org.apache.camel.Exchange;
028    import org.apache.camel.builder.xml.ResultHandlerFactory;
029    import org.apache.camel.builder.xml.XsltBuilder;
030    import org.apache.camel.builder.xml.XsltUriResolver;
031    import org.apache.camel.component.ResourceBasedComponent;
032    import org.apache.camel.converter.jaxp.XmlConverter;
033    import org.apache.camel.impl.ProcessorEndpoint;
034    import org.apache.camel.util.ObjectHelper;
035    import org.springframework.core.io.Resource;
036    import org.springframework.core.io.UrlResource;
037    
038    /**
039     * An <a href="http://camel.apache.org/xslt.html">XSLT Component</a>
040     * for performing XSLT transforms of messages
041     *
042     * @version 
043     */
044    public class XsltComponent extends ResourceBasedComponent {
045        private XmlConverter xmlConverter;
046        private URIResolver uriResolver;
047        private boolean contentCache = true;
048    
049        public XmlConverter getXmlConverter() {
050            return xmlConverter;
051        }
052    
053        public void setXmlConverter(XmlConverter xmlConverter) {
054            this.xmlConverter = xmlConverter;
055        }
056    
057        public URIResolver getUriResolver() {
058            return uriResolver;
059        }
060    
061        public void setUriResolver(URIResolver uriResolver) {
062            this.uriResolver = uriResolver;
063        }
064    
065        public boolean isContentCache() {
066            return contentCache;
067        }
068    
069        public void setContentCache(boolean contentCache) {
070            this.contentCache = contentCache;
071        }
072    
073        protected Endpoint createEndpoint(String uri, final String remaining, Map<String, Object> parameters) throws Exception {
074            final Resource resource = resolveMandatoryResource(remaining);
075            log.debug("{} using schema resource: {}", this, resource);
076            final XsltBuilder xslt = getCamelContext().getInjector().newInstance(XsltBuilder.class);
077    
078            // lets allow the converter to be configured
079            XmlConverter converter = resolveAndRemoveReferenceParameter(parameters, "converter", XmlConverter.class);
080            if (converter == null) {
081                converter = getXmlConverter();
082            }
083            if (converter != null) {
084                xslt.setConverter(converter);
085            }
086            
087            String transformerFactoryClassName = getAndRemoveParameter(parameters, "transformerFactoryClass", String.class);
088            TransformerFactory factory = null;
089            if (transformerFactoryClassName != null) {
090                // provide the class loader of this component to work in OSGi environments
091                Class<?> factoryClass = getCamelContext().getClassResolver().resolveClass(transformerFactoryClassName, XsltComponent.class.getClassLoader());
092                if (factoryClass != null) {
093                    factory = (TransformerFactory) getCamelContext().getInjector().newInstance(factoryClass);
094                } else {
095                    log.warn("Cannot find the TransformerFactoryClass with the class name: " + transformerFactoryClassName);
096                }
097            }
098            
099            if (parameters.get("transformerFactory") != null) {
100                factory = resolveAndRemoveReferenceParameter(parameters, "transformerFactory", TransformerFactory.class);
101            }
102            
103            if (factory != null) {
104                xslt.getConverter().setTransformerFactory(factory);
105            }
106    
107            // lookup custom resolver to use
108            URIResolver resolver = resolveAndRemoveReferenceParameter(parameters, "uriResolver", URIResolver.class);
109            if (resolver == null) {
110                // not in endpoint then use component specific resolver
111                resolver = getUriResolver();
112            }
113            if (resolver == null) {
114                // fallback to use a Camel specific resolver
115                resolver = new XsltUriResolver(getCamelContext().getClassResolver(), remaining);
116            }
117            // set resolver before input stream as resolver is used when loading the input stream
118            xslt.setUriResolver(resolver);
119    
120            ResultHandlerFactory resultHandlerFactory = resolveAndRemoveReferenceParameter(parameters, "resultHandlerFactory", ResultHandlerFactory.class);
121            if (resultHandlerFactory != null) {
122                xslt.setResultHandlerFactory(resultHandlerFactory);
123            }
124    
125            Boolean failOnNullBody = getAndRemoveParameter(parameters, "failOnNullBody", Boolean.class);
126            if (failOnNullBody != null) {
127                xslt.setFailOnNullBody(failOnNullBody);
128            }
129            String output = getAndRemoveParameter(parameters, "output", String.class);
130            configureOutput(xslt, output);
131    
132            configureXslt(xslt, uri, remaining, parameters);
133            loadResource(xslt, resource);
134    
135            // default to use the cache option from the component if the endpoint did not have the contentCache parameter
136            boolean cache = getAndRemoveParameter(parameters, "contentCache", Boolean.class, contentCache);
137            if (!cache) {
138                return new ProcessorEndpoint(uri, this, xslt) {
139                    @Override
140                    protected void onExchange(Exchange exchange) throws Exception {
141                        // force to load the resource on each exchange as we are not cached
142                        loadResource(xslt, resource);
143                        super.onExchange(exchange);
144                    }
145                };
146            } else {
147                // we have already loaded xslt so we are cached
148                return new ProcessorEndpoint(uri, this, xslt);
149            }
150        }
151    
152        private void loadResource(XsltBuilder xslt, Resource resource) throws TransformerConfigurationException {
153            log.trace("{} loading schema resource: {}", this, resource);
154            try {
155                if (resource instanceof UrlResource) {
156                    // prefer to use file when a file based url
157                    File file = resource.getFile();
158                    if (file != null) {
159                        // check if the file exists and report a better error as the XSLT
160                        // will just say it cannot compile the stylesheet file
161                        if (!file.exists()) {
162                            throw new FileNotFoundException("File: " + file + " not found.");
163                        }
164                        xslt.setTransformerFile(file);
165                    } else {
166                        xslt.setTransformerURL(resource.getURL());
167                    }
168                } else {
169                    // fallback and use input stream
170                    xslt.setTransformerInputStream(resource.getInputStream());
171                }
172            } catch (Exception e) {
173                // include information about the resource in the caused exception, so its easier for
174                // end users to know which resource failed
175                throw new TransformerConfigurationException(e.getMessage() + " " + resource.toString(), e);
176            }
177        }
178    
179        protected void configureXslt(XsltBuilder xslt, String uri, String remaining, Map<String, Object> parameters) throws Exception {
180            setProperties(xslt, parameters);
181        }
182    
183        protected void configureOutput(XsltBuilder xslt, String output) throws Exception {
184            if (ObjectHelper.isEmpty(output)) {
185                return;
186            }
187    
188            if ("string".equalsIgnoreCase(output)) {
189                xslt.outputString();
190            } else if ("bytes".equalsIgnoreCase(output)) {
191                xslt.outputBytes();
192            } else if ("DOM".equalsIgnoreCase(output)) {
193                xslt.outputDOM();
194            } else if ("file".equalsIgnoreCase(output)) {
195                xslt.outputFile();
196            } else {
197                throw new IllegalArgumentException("Unknown output type: " + output);
198            }
199        }
200    
201    }