All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mybatis.scripting.velocity.VelocityLanguageDriverConfig Maven / Gradle / Ivy

The newest version!
/*
 *    Copyright 2012-2022 the original author or authors.
 *
 *    Licensed 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
 *
 *       https://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.mybatis.scripting.velocity;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;

import org.apache.commons.text.WordUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.scripting.ScriptingException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeInstance;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

/**
 * Configuration class for {@link Driver}.
 *
 * @author Kazuki Shimizu
 *
 * @since 2.1.0
 */
public class VelocityLanguageDriverConfig {

  private static final String PROPERTY_KEY_CONFIG_FILE = "mybatis-velocity.config.file";
  private static final String PROPERTY_KEY_CONFIG_ENCODING = "mybatis-velocity.config.encoding";
  private static final String DEFAULT_PROPERTIES_FILE = "mybatis-velocity.properties";
  private static final String PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE = "additional.context.attributes";
  private static final String[] BUILT_IN_DIRECTIVES = { TrimDirective.class.getName(), WhereDirective.class.getName(),
      SetDirective.class.getName(), InDirective.class.getName(), RepeatDirective.class.getName() };

  private static final Map, Function> TYPE_CONVERTERS;
  static {
    Map, Function> converters = new HashMap<>();
    converters.put(String.class, String::trim);
    converters.put(Charset.class, v -> Charset.forName(v.trim()));
    converters.put(String[].class, v -> Stream.of(v.split(",")).map(String::trim).toArray(String[]::new));
    converters.put(Object.class, v -> v);
    TYPE_CONVERTERS = Collections.unmodifiableMap(converters);
  }

  private static final Log log = LogFactory.getLog(VelocityLanguageDriverConfig.class);

  /**
   * The Velocity settings.
   */
  private final Map velocitySettings = new HashMap<>();
  {
    velocitySettings.put(RuntimeConstants.RESOURCE_LOADERS, "class");
    velocitySettings.put(RuntimeConstants.RESOURCE_LOADER + ".class.class", ClasspathResourceLoader.class.getName());
  }

  /**
   * The base directory for reading template resources.
   */
  private String[] userDirectives = {};

  /**
   * The additional context attribute.
   */
  private final Map additionalContextAttributes = new HashMap<>();

  /**
   * Get Velocity settings.
   *
   * @return Velocity settings
   */
  public Map getVelocitySettings() {
    return velocitySettings;
  }

  /**
   * Get user define directives.
   *
   * @return user define directives.
   *
   * @deprecated Recommend to use the 'velocity-settings.runtime.custom_directives' or 'runtime.custom_directives'
   *             because this method defined for keeping backward compatibility (There is possibility that this method
   *             removed at a future version)
   */
  @Deprecated
  public String[] getUserdirective() {
    return userDirectives;
  }

  /**
   * Set user define directives.
   *
   * @param userDirectives
   *          user define directives
   *
   * @deprecated Recommend to use the 'velocity-settings.runtime.custom_directives' or 'runtime.custom_directives'
   *             because this method defined for keeping backward compatibility (There is possibility that this method
   *             removed at a future version)
   */
  @Deprecated
  public void setUserdirective(String... userDirectives) {
    log.warn(
        "The 'userdirective' has been deprecated since 2.1.0. Please use the 'velocity-settings.runtime.custom_directives' or 'runtime.custom_directives'.");
    this.userDirectives = userDirectives;
  }

  /**
   * Get additional context attributes.
   *
   * @return additional context attributes
   */
  public Map getAdditionalContextAttributes() {
    return additionalContextAttributes;
  }

  /**
   * Generate a custom directives string.
   *
   * @return a custom directives string
   */
  public String generateCustomDirectivesString() {
    StringJoiner customDirectivesJoiner = new StringJoiner(",");
    Optional.ofNullable(velocitySettings.get(RuntimeConstants.CUSTOM_DIRECTIVES))
        .ifPresent(customDirectivesJoiner::add);
    Stream.of(userDirectives).forEach(customDirectivesJoiner::add);
    Stream.of(BUILT_IN_DIRECTIVES).forEach(customDirectivesJoiner::add);
    return customDirectivesJoiner.toString();
  }

  /**
   * Create an instance from default properties file. 
* If you want to customize a default {@link RuntimeInstance}, you can configure some property using * mybatis-velocity.properties that encoded by UTF-8. Also, you can change the properties file that will read using * system property (-Dmybatis-velocity.config.file=... -Dmybatis-velocity.config.encoding=...).
* Supported properties are as follows: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Supported properties
Property KeyDescriptionDefault
Directive configuration
userdirectiveThe user defined directives (Recommend to use the 'velocity-settings.runtime.custom_directives' property * because this property defined for keeping backward compatibility)None(empty)
Additional context attribute configuration
additional.context.attributesThe user defined additional context attribute values(Recommend to use the * 'additional-context-attributes.{name}' because this property defined for keeping backward compatibility)None(empty)
additional-context-attributes.{name}The user defined additional context attributes value(FQCN)-
Velocity settings configuration
velocity-settings.{name}The settings of Velocity's {@link RuntimeInstance#setProperty(String, Object)}-
{name}The settings of Velocity's {@link RuntimeInstance#setProperty(String, Object)} (Recommend to use the * 'velocity-settings.{name}' because this property defined for keeping backward compatibility)-
* * @return a configuration instance */ public static VelocityLanguageDriverConfig newInstance() { return newInstance(loadDefaultProperties()); } /** * Create an instance from specified properties. * * @param customProperties * custom configuration properties * * @return a configuration instance * * @see #newInstance() */ public static VelocityLanguageDriverConfig newInstance(Properties customProperties) { VelocityLanguageDriverConfig config = new VelocityLanguageDriverConfig(); Properties properties = loadDefaultProperties(); Optional.ofNullable(customProperties).ifPresent(properties::putAll); override(config, properties); configureVelocitySettings(config, properties); return config; } /** * Create an instance using specified customizer and override using a default properties file. * * @param customizer * baseline customizer * * @return a configuration instance * * @see #newInstance() */ public static VelocityLanguageDriverConfig newInstance(Consumer customizer) { VelocityLanguageDriverConfig config = new VelocityLanguageDriverConfig(); Properties properties = loadDefaultProperties(); customizer.accept(config); override(config, properties); configureVelocitySettings(config, properties); return config; } private static void override(VelocityLanguageDriverConfig config, Properties properties) { enableLegacyAdditionalContextAttributes(properties); MetaObject metaObject = MetaObject.forObject(config, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory()); Set consumedKeys = new HashSet<>(); properties.forEach((key, value) -> { String propertyPath = WordUtils .uncapitalize(WordUtils.capitalize(Objects.toString(key), '-').replaceAll("-", "")); if (metaObject.hasSetter(propertyPath)) { PropertyTokenizer pt = new PropertyTokenizer(propertyPath); if (Map.class.isAssignableFrom(metaObject.getGetterType(pt.getName()))) { @SuppressWarnings("unchecked") Map map = (Map) metaObject.getValue(pt.getName()); map.put(pt.getChildren(), value); } else { Optional.ofNullable(value).ifPresent(v -> { Object convertedValue = TYPE_CONVERTERS.get(metaObject.getSetterType(propertyPath)).apply(value.toString()); metaObject.setValue(propertyPath, convertedValue); }); } consumedKeys.add(key); } }); consumedKeys.forEach(properties::remove); } private static void enableLegacyAdditionalContextAttributes(Properties properties) { String additionalContextAttributes = properties.getProperty(PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE); if (Objects.nonNull(additionalContextAttributes)) { log.warn(String.format( "The '%s' has been deprecated since 2.1.0. Please use the 'additionalContextAttributes.{name}={value}'.", PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE)); Stream.of(additionalContextAttributes.split(",")).forEach(pair -> { String[] keyValue = pair.split(":"); if (keyValue.length != 2) { throw new ScriptingException("Invalid additional context property '" + pair + "' on '" + PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE + "'. Must be specify by 'key:value' format."); } properties.setProperty("additional-context-attributes." + keyValue[0].trim(), keyValue[1].trim()); }); properties.remove(PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE); } } private static void configureVelocitySettings(VelocityLanguageDriverConfig config, Properties properties) { properties.forEach((name, value) -> config.getVelocitySettings().put((String) name, (String) value)); } private static Properties loadDefaultProperties() { return loadProperties(System.getProperty(PROPERTY_KEY_CONFIG_FILE, DEFAULT_PROPERTIES_FILE)); } private static Properties loadProperties(String resourcePath) { Properties properties = new Properties(); InputStream in; try { in = Resources.getResourceAsStream(resourcePath); } catch (IOException e) { in = null; } if (in != null) { Charset encoding = Optional.ofNullable(System.getProperty(PROPERTY_KEY_CONFIG_ENCODING)).map(Charset::forName) .orElse(StandardCharsets.UTF_8); try (InputStreamReader inReader = new InputStreamReader(in, encoding); BufferedReader bufReader = new BufferedReader(inReader)) { properties.load(bufReader); } catch (IOException e) { throw new IllegalStateException(e); } } return properties; } }