content: /*
   * 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.velocity;
  
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.Reader;
  import java.io.StringReader;
  import java.io.StringWriter;
  import java.util.Map;
  import java.util.Properties;
  
  import org.apache.camel.Category;
  import org.apache.camel.Exchange;
  import org.apache.camel.ExchangePattern;
  import org.apache.camel.component.ResourceEndpoint;
  import org.apache.camel.spi.UriEndpoint;
  import org.apache.camel.spi.UriParam;
  import org.apache.camel.support.ExchangeHelper;
  import org.apache.camel.support.ResourceHelper;
  import org.apache.camel.util.IOHelper;
  import org.apache.camel.util.ObjectHelper;
  import org.apache.velocity.VelocityContext;
  import org.apache.velocity.app.VelocityEngine;
  import org.apache.velocity.context.Context;
  import org.apache.velocity.runtime.RuntimeConstants;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Transform messages using a Velocity template.
   */
  @UriEndpoint(firstVersion = "1.2.0", scheme = "velocity", title = "Velocity", syntax = "velocity:resourceUri",
               remote = false, producerOnly = true, category = { Category.TRANSFORMATION },
               headersClass = VelocityConstants.class)
  public class VelocityEndpoint extends ResourceEndpoint {
  
      private VelocityEngine velocityEngine;
  
      @UriParam
      private boolean allowTemplateFromHeader;
      @UriParam(defaultValue = "true")
      private boolean loaderCache = true;
      @UriParam
      private String encoding;
      @UriParam
      private String propertiesFile;
  
      public VelocityEndpoint() {
      }
  
      public VelocityEndpoint(String uri, VelocityComponent component, String resourceUri) {
          super(uri, component, resourceUri);
      }
  
      @Override
      public boolean isRemote() {
          return false;
      }
  
      @Override
      public ExchangePattern getExchangePattern() {
          return ExchangePattern.InOut;
      }
  
      @Override
      protected String createEndpointUri() {
          return "velocity:" + getResourceUri();
      }
  
      private VelocityEngine getVelocityEngine() throws Exception {
          getInternalLock().lock();
          try {
              if (velocityEngine == null) {
                  velocityEngine = new VelocityEngine();
  
                  // set the class resolver as a property so we can access it from CamelVelocityClasspathResourceLoader
                  velocityEngine.addProperty("CamelClassResolver", getCamelContext().getClassResolver());
  
                  // set default properties
                  Properties properties = new Properties();
                  properties.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, isLoaderCache() ? "true" : "false");
                  properties.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file, class");
                  properties.setProperty("resource.loader.class.description", "Camel Velocity Classpath Resource Loader");
                  properties.setProperty("resource.loader.class.class", CamelVelocityClasspathResourceLoader.class.getName());
                  final Logger velocityLogger = LoggerFactory.getLogger("org.apache.camel.maven.Velocity");
                  properties.setProperty(RuntimeConstants.RUNTIME_LOG_NAME, velocityLogger.getName());
  
                  // load the velocity properties from property file which may overrides the default ones
                  if (ObjectHelper.isNotEmpty(getPropertiesFile())) {
                      InputStream reader
                              = ResourceHelper.resolveMandatoryResourceAsInputStream(getCamelContext(), getPropertiesFile());
                      try {
                          properties.load(reader);
                          log.info("Loaded the velocity configuration file {}", getPropertiesFile());
                      } finally {
                          IOHelper.close(reader, getPropertiesFile(), log);
                      }
                  }
  
                  log.debug("Initializing VelocityEngine with properties {}", properties);
                  // help the velocityEngine to load the CamelVelocityClasspathResourceLoader
                  ClassLoader old = Thread.currentThread().getContextClassLoader();
                  try {
                      ClassLoader delegate = new CamelVelocityDelegateClassLoader(old);
                      Thread.currentThread().setContextClassLoader(delegate);
                      velocityEngine.init(properties);
                  } finally {
                      Thread.currentThread().setContextClassLoader(old);
                  }
              }
              return velocityEngine;
          } finally {
              getInternalLock().unlock();
          }
      }
  
      public void setVelocityEngine(VelocityEngine velocityEngine) {
          this.velocityEngine = velocityEngine;
      }
  
      public boolean isAllowTemplateFromHeader() {
          return allowTemplateFromHeader;
      }
  
      /**
       * Whether to allow to use resource template from header or not (default false).
       *
       * Enabling this allows to specify dynamic templates via message header. However this can be seen as a potential
       * security vulnerability if the header is coming from a malicious user, so use this with care.
       */
      public void setAllowTemplateFromHeader(boolean allowTemplateFromHeader) {
          this.allowTemplateFromHeader = allowTemplateFromHeader;
      }
  
      public boolean isLoaderCache() {
          return loaderCache;
      }
  
      /**
       * Enables / disables the velocity resource loader cache which is enabled by default
       */
      public void setLoaderCache(boolean loaderCache) {
          this.loaderCache = loaderCache;
      }
  
      /**
       * Character encoding of the resource content.
       */
      public void setEncoding(String encoding) {
          this.encoding = encoding;
      }
  
      public String getEncoding() {
          return encoding;
      }
  
      /**
       * The URI of the properties file which is used for VelocityEngine initialization.
       */
      public void setPropertiesFile(String file) {
          propertiesFile = file;
      }
  
      public String getPropertiesFile() {
          return propertiesFile;
      }
  
      public VelocityEndpoint findOrCreateEndpoint(String uri, String newResourceUri) {
          String newUri = uri.replace(getResourceUri(), newResourceUri);
          log.debug("Getting endpoint with URI: {}", newUri);
          return getCamelContext().getEndpoint(newUri, VelocityEndpoint.class);
      }
  
      @Override
      protected void onExchange(Exchange exchange) throws Exception {
          String path = getResourceUri();
          ObjectHelper.notNull(path, "resourceUri");
  
          if (allowTemplateFromHeader) {
              String newResourceUri = exchange.getIn().getHeader(VelocityConstants.VELOCITY_RESOURCE_URI, String.class);
              if (newResourceUri != null) {
                  exchange.getIn().removeHeader(VelocityConstants.VELOCITY_RESOURCE_URI);
  
                  log.debug("{} set to {} creating new endpoint to handle exchange", VelocityConstants.VELOCITY_RESOURCE_URI,
                          newResourceUri);
                  VelocityEndpoint newEndpoint = findOrCreateEndpoint(getEndpointUri(), newResourceUri);
                  newEndpoint.onExchange(exchange);
                  return;
              }
          }
  
          Reader reader;
          String content = null;
          if (allowTemplateFromHeader) {
              content = exchange.getIn().getHeader(VelocityConstants.VELOCITY_TEMPLATE, String.class);
          }
          if (content != null) {
              // use content from header
              reader = new StringReader(content);
              if (log.isDebugEnabled()) {
                  log.debug("Velocity content read from header {} for endpoint {}", VelocityConstants.VELOCITY_TEMPLATE,
                          getEndpointUri());
              }
              // remove the header to avoid it being propagated in the routing
              exchange.getIn().removeHeader(VelocityConstants.VELOCITY_TEMPLATE);
          } else {
              if (log.isDebugEnabled()) {
                  log.debug("Velocity content read from resource {} with resourceUri: {} for endpoint {}", getResourceUri(), path,
                          getEndpointUri());
              }
              reader = getEncoding() != null
                      ? new InputStreamReader(getResourceAsInputStream(), getEncoding())
                      : new InputStreamReader(getResourceAsInputStream());
          }
  
          // getResourceAsInputStream also considers the content cache
          StringWriter buffer = new StringWriter();
          String logTag = getClass().getName();
          Context velocityContext = null;
          if (allowTemplateFromHeader) {
              velocityContext = exchange.getIn().getHeader(VelocityConstants.VELOCITY_CONTEXT, Context.class);
          }
          if (velocityContext == null) {
              Map<String, Object> variableMap = ExchangeHelper.createVariableMap(exchange, isAllowContextMapAll());
              if (allowTemplateFromHeader) {
                  @SuppressWarnings("unchecked")
                  Map<String, Object> supplementalMap
                          = exchange.getIn().getHeader(VelocityConstants.VELOCITY_SUPPLEMENTAL_CONTEXT, Map.class);
                  if (supplementalMap != null) {
                      variableMap.putAll(supplementalMap);
                  }
              }
              velocityContext = new VelocityContext(variableMap);
          }
  
          // let velocity parse and generate the result in buffer
          VelocityEngine engine = getVelocityEngine();
          log.debug("Velocity is evaluating using velocity context: {}", velocityContext);
          engine.evaluate(velocityContext, buffer, logTag, reader);
  
          // now lets output the results to the exchange
          ExchangeHelper.setInOutBodyPatternAware(exchange, buffer.toString());
      }
  }
  