diff --git a/pom.xml b/pom.xml index 497b864..02da9f3 100644 --- a/pom.xml +++ b/pom.xml @@ -79,8 +79,8 @@ org.apache.commons - commons-lang3 - 3.8.1 + commons-text + 1.6 diff --git a/src/main/java/org/mybatis/scripting/velocity/Driver.java b/src/main/java/org/mybatis/scripting/velocity/Driver.java index 87e0101..2490bdf 100644 --- a/src/main/java/org/mybatis/scripting/velocity/Driver.java +++ b/src/main/java/org/mybatis/scripting/velocity/Driver.java @@ -1,5 +1,5 @@ /** - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -26,6 +26,24 @@ public class Driver implements LanguageDriver { + /** + * Default constructor. + */ + public Driver() { + this(VelocityLanguageDriverConfig.newInstance()); + } + + /** + * Constructor. + * + * @param driverConfig + * a language driver configuration + * @since 2.1.0 + */ + public Driver(VelocityLanguageDriverConfig driverConfig) { + VelocityFacade.initialize(driverConfig); + } + @Override public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { diff --git a/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java b/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java index f9645ac..878b944 100644 --- a/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java +++ b/src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java @@ -1,5 +1,5 @@ /** - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -17,41 +17,59 @@ import java.io.StringReader; import java.io.StringWriter; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.builder.BuilderException; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.scripting.ScriptingException; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; +import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.RuntimeInstance; import org.apache.velocity.runtime.parser.node.SimpleNode; public class VelocityFacade { - private static final String ADDITIONAL_CTX_ATTRIBUTES_KEY = "additional.context.attributes"; - private static final String EXTERNAL_PROPERTIES = "mybatis-velocity.properties"; - private static final String DIRECTIVES = TrimDirective.class.getName() + "," + WhereDirective.class.getName() + "," - + SetDirective.class.getName() + "," + InDirective.class.getName() + "," + RepeatDirective.class.getName(); + private static final RuntimeInstance engine = new RuntimeInstance(); + private static final Map additionalCtxAttributes = new HashMap<>(); - private static final RuntimeInstance engine; - - /** Contains thread safe objects to be set in the velocity context. */ - private static final Map additionalCtxAttributes; - private static final Properties settings; - - static { + private VelocityFacade() { + // Prevent instantiation + } - settings = loadProperties(); - additionalCtxAttributes = Collections.unmodifiableMap(loadAdditionalCtxAttributes()); - engine = new RuntimeInstance(); - engine.init(settings); + /** + * Initialize a template engine. + * + * @param driverConfig + * a language driver configuration + * @since 2.1.0 + */ + public static void initialize(VelocityLanguageDriverConfig driverConfig) { + Properties properties = new Properties(); + driverConfig.getVelocitySettings().forEach(properties::setProperty); + properties.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, driverConfig.generateCustomDirectivesString()); + engine.init(properties); + additionalCtxAttributes.putAll(driverConfig.getAdditionalContextAttributes().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, v -> { + try { + return Resources.classForName(v.getValue()).getConstructor().newInstance(); + } catch (Exception e) { + throw new ScriptingException("Cannot load additional context attribute class.", e); + } + }))); } - private VelocityFacade() { - // Prevent instantiation + /** + * Destroy a template engine. + * + * @since 2.1.0 + */ + public static void destroy() { + engine.reset(); + additionalCtxAttributes.clear(); } public static Object compile(String script, String name) { @@ -76,48 +94,4 @@ public static String apply(Object template, Map context) { return out.toString(); } - private static Properties loadProperties() { - final Properties props = new Properties(); - // Defaults - props.setProperty("resource.loader", "class"); - props.setProperty("class.resource.loader.class", - "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - - try { - // External properties - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - props.load(cl.getResourceAsStream(EXTERNAL_PROPERTIES)); - } catch (Exception ex) { - // No custom properties - } - - // Append the user defined directives if provided - String userDirective = StringUtils.trim(props.getProperty("userdirective")); - if (userDirective == null) { - userDirective = DIRECTIVES; - } else { - userDirective += "," + DIRECTIVES; - } - props.setProperty("userdirective", userDirective); - return props; - } - - private static Map loadAdditionalCtxAttributes() { - Map attributes = new HashMap<>(); - String additionalContextAttributes = settings.getProperty(ADDITIONAL_CTX_ATTRIBUTES_KEY); - if (additionalContextAttributes == null) { - return attributes; - } - - try { - String[] entries = additionalContextAttributes.split(","); - for (String str : entries) { - String[] entry = str.trim().split(":"); - attributes.put(entry[0].trim(), Class.forName(entry[1].trim()).newInstance()); - } - } catch (Exception ex) { - throw new BuilderException("Error parsing velocity property '" + ADDITIONAL_CTX_ATTRIBUTES_KEY + "'", ex); - } - return attributes; - } } diff --git a/src/main/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfig.java b/src/main/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfig.java new file mode 100644 index 0000000..5d6750e --- /dev/null +++ b/src/main/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfig.java @@ -0,0 +1,314 @@ +/** + * Copyright 2012-2019 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 + * + * 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.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.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 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); + } + + /** + * 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) { + 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 (metaObject.getGetterType(pt.getName()).isAssignableFrom(Map.class)) { + @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)) { + 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; + } + +} diff --git a/src/test/java/org/mybatis/scripting/velocity/InDirectiveTest.java b/src/test/java/org/mybatis/scripting/velocity/InDirectiveTest.java index c8c8fb0..646a202 100644 --- a/src/test/java/org/mybatis/scripting/velocity/InDirectiveTest.java +++ b/src/test/java/org/mybatis/scripting/velocity/InDirectiveTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -27,6 +27,7 @@ import org.apache.ibatis.session.Configuration; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.RuntimeConstants; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -38,7 +39,7 @@ public class InDirectiveTest { @BeforeAll public static void setUpClass() throws Exception { Properties p = new Properties(); - p.setProperty("userdirective", InDirective.class.getName()); + p.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, InDirective.class.getName()); velocity = new VelocityEngine(); velocity.setProperty("runtime.log", "target/velocity.log"); velocity.init(p); diff --git a/src/test/java/org/mybatis/scripting/velocity/TrimDirectiveTest.java b/src/test/java/org/mybatis/scripting/velocity/TrimDirectiveTest.java index 5c0f86c..7bfb803 100644 --- a/src/test/java/org/mybatis/scripting/velocity/TrimDirectiveTest.java +++ b/src/test/java/org/mybatis/scripting/velocity/TrimDirectiveTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -20,6 +20,8 @@ import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.velocity.runtime.RuntimeConstants; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -31,7 +33,7 @@ public class TrimDirectiveTest { @BeforeAll public static void setUpClass() throws Exception { Properties p = new Properties(); - p.setProperty("userdirective", TrimDirective.class.getName()); + p.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, TrimDirective.class.getName()); velocity = new VelocityEngine(); velocity.setProperty("runtime.log", "target/velocity.log"); velocity.init(p); diff --git a/src/test/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfigTest.java b/src/test/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfigTest.java new file mode 100644 index 0000000..649b220 --- /dev/null +++ b/src/test/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfigTest.java @@ -0,0 +1,217 @@ +/** + * Copyright 2012-2019 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 + * + * 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.mybatis.scripting.velocity; + +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +import org.apache.ibatis.scripting.ScriptingException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class VelocityLanguageDriverConfigTest { + + private String currentConfigFile; + private String currentConfigEncoding; + + @BeforeEach + void saveCurrentConfig() { + currentConfigFile = System.getProperty("mybatis-velocity.config"); + currentConfigEncoding = System.getProperty("mybatis-velocity.config.encoding"); + } + + @AfterEach + void restoreConfig() { + if (currentConfigFile == null) { + System.clearProperty("mybatis-velocity.config.file"); + } else { + System.setProperty("mybatis-velocity.config.file", currentConfigFile); + } + if (currentConfigEncoding == null) { + System.clearProperty("mybatis-velocity.config.encoding"); + } else { + System.setProperty("mybatis-velocity.config.encoding", currentConfigEncoding); + } + } + + @Test + void newInstanceWithEmptyPropertiesFile() { + System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-empty.properties"); + VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); + Assertions.assertEquals(0, config.getUserdirective().length); + Assertions.assertEquals(0, config.getAdditionalContextAttributes().size()); + Assertions.assertEquals(2, config.getVelocitySettings().size()); + Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); + Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", + config.getVelocitySettings().get("resource.loader.class.class")); + Assertions.assertEquals( + "org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", + config.generateCustomDirectivesString()); + } + + @Test + void newInstanceWithPropertiesFileNotFound() { + System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-notfound.properties"); + VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); + Assertions.assertEquals(0, config.getUserdirective().length); + Assertions.assertEquals(0, config.getAdditionalContextAttributes().size()); + Assertions.assertEquals(2, config.getVelocitySettings().size()); + Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); + Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", + config.getVelocitySettings().get("resource.loader.class.class")); + Assertions.assertEquals( + "org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", + config.generateCustomDirectivesString()); + } + + @Test + void newInstanceWithCustomPropertiesFile() { + System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-custom.properties"); + VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); + Assertions.assertEquals(0, config.getUserdirective().length); + Assertions.assertEquals(4, config.getAdditionalContextAttributes().size()); + Assertions.assertEquals("org.mybatis.scripting.velocity.use.TrailingWildCardFormatter", + config.getAdditionalContextAttributes().get("trailingWildCardFormatter")); + Assertions.assertEquals("org.mybatis.scripting.velocity.use.EnumBinder", + config.getAdditionalContextAttributes().get("enumBinder")); + Assertions.assertEquals("attribute1Value", config.getAdditionalContextAttributes().get("attribute1")); + Assertions.assertEquals("attribute2Value", config.getAdditionalContextAttributes().get("attribute2")); + Assertions.assertEquals(7, config.getVelocitySettings().size()); + Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); + Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", + config.getVelocitySettings().get("resource.loader.class.class")); + Assertions.assertEquals("Windows-31J", config.getVelocitySettings().get("resource.default_encoding")); + Assertions.assertEquals("100", config.getVelocitySettings().get("resource.manager.cache.default_size")); + Assertions.assertEquals("20", config.getVelocitySettings().get("directive.foreach.max_loops")); + Assertions.assertEquals("org.apache.velocity", config.getVelocitySettings().get("runtime.log.name")); + Assertions.assertEquals( + "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.use.CustomUserDirective2,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", + config.generateCustomDirectivesString()); + } + + @Test + void newInstanceWithCustomProperties() { + Properties properties = new Properties(); + properties.setProperty("additional-context-attributes.trailingWildCardFormatter", + "org.mybatis.scripting.velocity.use.TrailingWildCardFormatter"); + properties.setProperty("additional-context-attributes.enumBinder", "org.mybatis.scripting.velocity.use.EnumBinder"); + properties.setProperty("velocity-settings.resource.default_encoding", StandardCharsets.ISO_8859_1.name()); + properties.setProperty("velocity-settings.resource.manager.cache.default_size", "200"); + properties.setProperty("additional.context.attributes", + "attribute1 : attribute1Value , attribute2 : attribute2Value"); + properties.setProperty("directive.foreach.max_loops", "30"); + properties.setProperty("runtime.log.name", "org.apache.velocity"); + VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(properties); + Assertions.assertEquals(0, config.getUserdirective().length); + Assertions.assertEquals(4, config.getAdditionalContextAttributes().size()); + Assertions.assertEquals("org.mybatis.scripting.velocity.use.TrailingWildCardFormatter", + config.getAdditionalContextAttributes().get("trailingWildCardFormatter")); + Assertions.assertEquals("org.mybatis.scripting.velocity.use.EnumBinder", + config.getAdditionalContextAttributes().get("enumBinder")); + Assertions.assertEquals("attribute1Value", config.getAdditionalContextAttributes().get("attribute1")); + Assertions.assertEquals("attribute2Value", config.getAdditionalContextAttributes().get("attribute2")); + Assertions.assertEquals(7, config.getVelocitySettings().size()); + Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); + Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", + config.getVelocitySettings().get("resource.loader.class.class")); + Assertions.assertEquals(StandardCharsets.ISO_8859_1.name(), + config.getVelocitySettings().get("resource.default_encoding")); + Assertions.assertEquals("200", config.getVelocitySettings().get("resource.manager.cache.default_size")); + Assertions.assertEquals("30", config.getVelocitySettings().get("directive.foreach.max_loops")); + Assertions.assertEquals("org.apache.velocity", config.getVelocitySettings().get("runtime.log.name")); + Assertions.assertEquals( + "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", + config.generateCustomDirectivesString()); + } + + @Test + void newInstanceWithLegacyPropertiesFile() { + System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-legacy.properties"); + VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); + Assertions.assertEquals(1, config.getUserdirective().length); + Assertions.assertEquals(2, config.getAdditionalContextAttributes().size()); + Assertions.assertEquals("org.mybatis.scripting.velocity.use.TrailingWildCardFormatter", + config.getAdditionalContextAttributes().get("trailingWildCardFormatter")); + Assertions.assertEquals("org.mybatis.scripting.velocity.use.EnumBinder", + config.getAdditionalContextAttributes().get("enumBinder")); + Assertions.assertEquals(4, config.getVelocitySettings().size()); + Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); + Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", + config.getVelocitySettings().get("resource.loader.class.class")); + Assertions.assertEquals("20", config.getVelocitySettings().get("directive.foreach.max_loops")); + Assertions.assertEquals("org.apache.velocity", config.getVelocitySettings().get("runtime.log.name")); + Assertions.assertEquals( + "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", + config.generateCustomDirectivesString()); + } + + @Test + void newInstanceWithConsumer() { + VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig + .newInstance(c -> c.getVelocitySettings().put("resource.default_encoding", "Windows-31J")); + Assertions.assertEquals(4, config.getVelocitySettings().size()); + Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); + Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", + config.getVelocitySettings().get("resource.loader.class.class")); + Assertions.assertEquals("Windows-31J", config.getVelocitySettings().get("resource.default_encoding")); + Assertions.assertEquals( + "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", + config.generateCustomDirectivesString()); + } + + @Test + void invalidAdditionalContextAttributeValue() { + { + Properties properties = new Properties(); + properties.setProperty("additional.context.attributes", ""); + try { + VelocityLanguageDriverConfig.newInstance(properties); + Assertions.fail(); + } catch (ScriptingException e) { + Assertions.assertEquals( + "Invalid additional context property '' on 'additional.context.attributes'. Must be specify by 'key:value' format.", + e.getMessage()); + } + } + { + Properties properties = new Properties(); + properties.setProperty("additional.context.attributes", "key"); + try { + VelocityLanguageDriverConfig.newInstance(properties); + Assertions.fail(); + } catch (ScriptingException e) { + Assertions.assertEquals( + "Invalid additional context property 'key' on 'additional.context.attributes'. Must be specify by 'key:value' format.", + e.getMessage()); + } + } + { + Properties properties = new Properties(); + properties.setProperty("additional.context.attributes", "key:value:note"); + try { + VelocityLanguageDriverConfig.newInstance(properties); + Assertions.fail(); + } catch (ScriptingException e) { + Assertions.assertEquals( + "Invalid additional context property 'key:value:note' on 'additional.context.attributes'. Must be specify by 'key:value' format.", + e.getMessage()); + } + } + } + +} diff --git a/src/test/java/org/mybatis/scripting/velocity/WhereDirectiveTest.java b/src/test/java/org/mybatis/scripting/velocity/WhereDirectiveTest.java index e00c62a..8a5162d 100644 --- a/src/test/java/org/mybatis/scripting/velocity/WhereDirectiveTest.java +++ b/src/test/java/org/mybatis/scripting/velocity/WhereDirectiveTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -20,6 +20,8 @@ import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.velocity.runtime.RuntimeConstants; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -31,7 +33,7 @@ public class WhereDirectiveTest { @BeforeAll public static void setUpClass() throws Exception { Properties p = new Properties(); - p.setProperty("userdirective", WhereDirective.class.getName()); + p.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, WhereDirective.class.getName()); velocity = new VelocityEngine(); velocity.setProperty("runtime.log", "target/velocity.log"); velocity.init(p); diff --git a/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java b/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java index 5604a27..2473328 100644 --- a/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java +++ b/src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -29,8 +29,11 @@ import org.apache.ibatis.session.SqlSessionFactoryBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.mybatis.scripting.velocity.VelocityFacade; /** * Just a test case. Not a real Velocity implementation. @@ -75,6 +78,11 @@ public static void setUp() throws Exception { } } + @AfterAll + static void cleanup() { + VelocityFacade.destroy(); + } + @Test public void testDynamicSelectWithPropertyParams() { SqlSession sqlSession = sqlSessionFactory.openSession(); @@ -403,4 +411,16 @@ public void testDynamicSelectWithInDirectiveForOneItem() { } } + @Test + void testAdditionalContextAttributes() { + Object template = VelocityFacade.compile("SELECT * FROM users WHERE id = ${id}", "test"); + Map context = new HashMap<>(); + context.put("id", 123); + String sql = VelocityFacade.apply(template, context); + assertEquals(3, context.size()); + assertEquals(TrailingWildCardFormatter.class, context.get("trailingWildCardFormatter").getClass()); + assertEquals(EnumBinder.class, context.get("enumBinder").getClass()); + assertEquals("SELECT * FROM users WHERE id = 123", sql); + } + } diff --git a/src/test/resources/mybatis-velocity-custom.properties b/src/test/resources/mybatis-velocity-custom.properties new file mode 100644 index 0000000..483f5e6 --- /dev/null +++ b/src/test/resources/mybatis-velocity-custom.properties @@ -0,0 +1,28 @@ +# +# Copyright 2012-2019 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 +# +# 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. +# + +additional-context-attributes.trailingWildCardFormatter=org.mybatis.scripting.velocity.use.TrailingWildCardFormatter +additional-context-attributes.enumBinder=org.mybatis.scripting.velocity.use.EnumBinder +velocity-settings.resource.default_encoding = Windows-31J +velocity-settings.resource.manager.cache.default_size = 100 + +# For legacy style configuration +runtime.custom_directives=org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.use.CustomUserDirective2 +additional.context.attributes=\ + attribute1 : attribute1Value,\ + attribute2 : attribute2Value +directive.foreach.max_loops = 20 +runtime.log.name = org.apache.velocity diff --git a/src/test/resources/mybatis-velocity-empty.properties b/src/test/resources/mybatis-velocity-empty.properties new file mode 100644 index 0000000..9e17bf2 --- /dev/null +++ b/src/test/resources/mybatis-velocity-empty.properties @@ -0,0 +1,17 @@ +# +# Copyright 2012-2019 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 +# +# 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. +# + +# diff --git a/src/test/resources/mybatis-velocity-legacy.properties b/src/test/resources/mybatis-velocity-legacy.properties new file mode 100644 index 0000000..7993e51 --- /dev/null +++ b/src/test/resources/mybatis-velocity-legacy.properties @@ -0,0 +1,21 @@ +# +# Copyright 2012-2019 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 +# +# 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. +# + +additional.context.attributes=trailingWildCardFormatter:org.mybatis.scripting.velocity.use.TrailingWildCardFormatter,enumBinder:org.mybatis.scripting.velocity.use.EnumBinder +userdirective=org.mybatis.scripting.velocity.use.CustomUserDirective + +directive.foreach.max_loops = 20 +runtime.log.name = org.apache.velocity diff --git a/src/test/resources/mybatis-velocity.properties b/src/test/resources/mybatis-velocity.properties index 2f61a8b..a7a192e 100644 --- a/src/test/resources/mybatis-velocity.properties +++ b/src/test/resources/mybatis-velocity.properties @@ -1,5 +1,5 @@ # -# Copyright 2012-2017 the original author or authors. +# Copyright 2012-2019 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. @@ -14,5 +14,6 @@ # limitations under the License. # -additional.context.attributes=trailingWildCardFormatter:org.mybatis.scripting.velocity.use.TrailingWildCardFormatter,enumBinder:org.mybatis.scripting.velocity.use.EnumBinder -userdirective=org.mybatis.scripting.velocity.use.CustomUserDirective +additional-context-attributes.trailingWildCardFormatter=org.mybatis.scripting.velocity.use.TrailingWildCardFormatter +additional-context-attributes.enumBinder=org.mybatis.scripting.velocity.use.EnumBinder +velocity-settings.runtime.custom_directives=org.mybatis.scripting.velocity.use.CustomUserDirective