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.velocity;
018    
019    import java.io.InputStream;
020    import java.io.InputStreamReader;
021    import java.io.Reader;
022    import java.io.StringReader;
023    import java.io.StringWriter;
024    import java.util.Map;
025    import java.util.Map.Entry;
026    import java.util.Properties;
027    
028    import org.apache.camel.Exchange;
029    import org.apache.camel.ExchangePattern;
030    import org.apache.camel.Message;
031    import org.apache.camel.component.ResourceBasedEndpoint;
032    import org.apache.camel.util.ExchangeHelper;
033    import org.apache.camel.util.ObjectHelper;
034    import org.apache.velocity.VelocityContext;
035    import org.apache.velocity.app.Velocity;
036    import org.apache.velocity.app.VelocityEngine;
037    import org.apache.velocity.context.Context;
038    import org.apache.velocity.runtime.log.CommonsLogLogChute;
039    import org.springframework.core.io.Resource;
040    
041    /**
042     * @version 
043     */
044    public class VelocityEndpoint extends ResourceBasedEndpoint {
045        private VelocityEngine velocityEngine;
046        private boolean loaderCache = true;
047        private String encoding;
048        private String propertiesFile;
049    
050        public VelocityEndpoint() {
051        }
052    
053        public VelocityEndpoint(String uri, VelocityComponent component, String resourceUri) {
054            super(uri, component, resourceUri, null);
055        }
056    
057        @Override
058        public boolean isSingleton() {
059            return true;
060        }
061    
062        @Override
063        public ExchangePattern getExchangePattern() {
064            return ExchangePattern.InOut;
065        }
066    
067        @Override
068        protected String createEndpointUri() {
069            return "velocity:" + getResourceUri();
070        }
071    
072        private synchronized VelocityEngine getVelocityEngine() throws Exception {
073            if (velocityEngine == null) {
074                velocityEngine = new VelocityEngine();
075                Properties properties = new Properties();
076                // load the velocity properties from property file
077                if (ObjectHelper.isNotEmpty(getPropertiesFile())) {
078                    Resource resource = getResourceLoader().getResource(getPropertiesFile());
079                    InputStream reader = resource.getInputStream();
080                    properties.load(reader);
081                    log.info("Loaded the velocity configuration file " + getPropertiesFile());
082                }
083    
084                // set the class resolver as a property so we can access it from CamelVelocityClasspathResourceLoader
085                velocityEngine.addProperty("CamelClassResolver", getCamelContext().getClassResolver());
086    
087                // set regular properties
088                properties.setProperty(Velocity.FILE_RESOURCE_LOADER_CACHE, isLoaderCache() ? "true" : "false");
089                properties.setProperty(Velocity.RESOURCE_LOADER, "file, class");
090                properties.setProperty("class.resource.loader.description", "Camel Velocity Classpath Resource Loader");
091                properties.setProperty("class.resource.loader.class", CamelVelocityClasspathResourceLoader.class.getName());
092                properties.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS, CommonsLogLogChute.class.getName());
093                properties.setProperty(CommonsLogLogChute.LOGCHUTE_COMMONS_LOG_NAME, VelocityEndpoint.class.getName());
094    
095                log.debug("Initializing VelocityEngine with properties {}", properties);
096                // help the velocityEngine to load the CamelVelocityClasspathResourceLoader 
097                ClassLoader old = Thread.currentThread().getContextClassLoader();
098                try {
099                    ClassLoader delegate = new CamelVelocityDelegateClassLoader(old);
100                    Thread.currentThread().setContextClassLoader(delegate);
101                    velocityEngine.init(properties);
102                } finally {
103                    Thread.currentThread().setContextClassLoader(old);
104                }
105            }
106            return velocityEngine;
107        }
108    
109        public void setVelocityEngine(VelocityEngine velocityEngine) {
110            this.velocityEngine = velocityEngine;
111        }
112    
113        public boolean isLoaderCache() {
114            return loaderCache;
115        }
116    
117        /**
118         * Enables / disables the velocity resource loader cache which is enabled by default
119         *
120         * @param loaderCache a flag to enable/disable the cache
121         */
122        public void setLoaderCache(boolean loaderCache) {
123            this.loaderCache = loaderCache;
124        }
125    
126        public void setEncoding(String encoding) {
127            this.encoding = encoding;
128        }
129    
130        public String getEncoding() {
131            return encoding;
132        }
133    
134        public void setPropertiesFile(String file) {
135            propertiesFile = file;
136        }
137    
138        public String getPropertiesFile() {
139            return propertiesFile;
140        }
141    
142        public VelocityEndpoint findOrCreateEndpoint(String uri, String newResourceUri) {
143            String newUri = uri.replace(getResourceUri(), newResourceUri);
144            log.debug("Getting endpoint with URI: {}", newUri);
145            return (VelocityEndpoint) getCamelContext().getEndpoint(newUri);
146        }
147    
148        @SuppressWarnings("unchecked")
149        @Override
150        protected void onExchange(Exchange exchange) throws Exception {
151            String path = getResourceUri();
152            ObjectHelper.notNull(path, "resourceUri");
153    
154            String newResourceUri = exchange.getIn().getHeader(VelocityConstants.VELOCITY_RESOURCE_URI, String.class);
155            if (newResourceUri != null) {
156                exchange.getIn().removeHeader(VelocityConstants.VELOCITY_RESOURCE_URI);
157    
158                log.debug("{} set to {} creating new endpoint to handle exchange", VelocityConstants.VELOCITY_RESOURCE_URI, newResourceUri);
159                VelocityEndpoint newEndpoint = findOrCreateEndpoint(getEndpointUri(), newResourceUri);
160                newEndpoint.onExchange(exchange);
161                return;
162            }
163    
164            Resource resource;
165            Reader reader;
166            String content = exchange.getIn().getHeader(VelocityConstants.VELOCITY_TEMPLATE, String.class);
167            if (content != null) {
168                // use content from header
169                reader = new StringReader(content);
170                if (log.isDebugEnabled()) {
171                    log.debug("Velocity content read from header {} for endpoint {}", VelocityConstants.VELOCITY_TEMPLATE, getEndpointUri());
172                }
173                // remove the header to avoid it being propagated in the routing
174                exchange.getIn().removeHeader(VelocityConstants.VELOCITY_TEMPLATE);
175            } else {
176                // use resource from endpoint configuration
177                resource = getResource();
178                ObjectHelper.notNull(resource, "resource");
179                if (log.isDebugEnabled()) {
180                    log.debug("Velocity content read from resource {} with resourceUri: {} for endpoint {}", new Object[]{resource, path, getEndpointUri()});
181                }
182                reader = getEncoding() != null ? new InputStreamReader(getResourceAsInputStream(), getEncoding()) : new InputStreamReader(getResourceAsInputStream());
183            }
184    
185            // getResourceAsInputStream also considers the content cache
186            StringWriter buffer = new StringWriter();
187            String logTag = getClass().getName();
188            Map variableMap = ExchangeHelper.createVariableMap(exchange);
189            Context velocityContext = new VelocityContext(variableMap);
190    
191            // let velocity parse and generate the result in buffer
192            VelocityEngine engine = getVelocityEngine();
193            log.debug("Velocity is evaluating using velocity context: {}", variableMap);
194            engine.evaluate(velocityContext, buffer, logTag, reader);
195    
196            // now lets output the results to the exchange
197            Message out = exchange.getOut();
198            out.setBody(buffer.toString());
199    
200            Map<String, Object> headers = (Map<String, Object>) velocityContext.get("headers");
201            for (Entry<String, Object> entry : headers.entrySet()) {
202                out.setHeader(entry.getKey(), entry.getValue());
203            }
204        }
205    
206    }