diff --git a/pom.xml b/pom.xml index 1debf38..f7b2ad7 100644 --- a/pom.xml +++ b/pom.xml @@ -90,6 +90,11 @@ freemarker 2.3.28 + + org.apache.commons + commons-text + 1.6 + diff --git a/src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriver.java b/src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriver.java index fd31ad1..df9489c 100644 --- a/src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriver.java +++ b/src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriver.java @@ -1,5 +1,5 @@ /** - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-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,10 +20,7 @@ import freemarker.template.Template; import java.io.IOException; -import java.io.InputStream; import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.util.Properties; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.mapping.BoundSql; @@ -39,36 +36,32 @@ * configuration, use can inherit from this class and override {@link #createFreeMarkerConfiguration()} method. * * @author elwood + * @author Kazuki Shimizu */ public class FreeMarkerLanguageDriver implements LanguageDriver { + + protected final FreeMarkerLanguageDriverConfig driverConfig; + protected final freemarker.template.Configuration freemarkerCfg; + /** - * Base package for all FreeMarker templates. + * Constructor. + * + * @see FreeMarkerLanguageDriverConfig#newInstance() */ - public static final String basePackage; - - public static final String DEFAULT_BASE_PACKAGE = ""; - - static { - Properties properties = new Properties(); - try { - try (InputStream stream = FreeMarkerLanguageDriver.class.getClassLoader() - .getResourceAsStream("mybatis-freemarker.properties")) { - if (stream != null) { - properties.load(stream); - basePackage = properties.getProperty("basePackage", DEFAULT_BASE_PACKAGE); - } else { - basePackage = DEFAULT_BASE_PACKAGE; - } - } - } catch (IOException e) { - throw new IllegalStateException(e); - } + public FreeMarkerLanguageDriver() { + this(FreeMarkerLanguageDriverConfig.newInstance()); } - protected freemarker.template.Configuration freemarkerCfg; - - public FreeMarkerLanguageDriver() { - freemarkerCfg = createFreeMarkerConfiguration(); + /** + * Constructor. + * + * @param driverConfig + * a language driver configuration + * @since 1.2.0 + */ + public FreeMarkerLanguageDriver(FreeMarkerLanguageDriverConfig driverConfig) { + this.driverConfig = driverConfig; + this.freemarkerCfg = createFreeMarkerConfiguration(); } /** @@ -77,16 +70,17 @@ public FreeMarkerLanguageDriver() { */ protected freemarker.template.Configuration createFreeMarkerConfiguration() { freemarker.template.Configuration cfg = new freemarker.template.Configuration( - freemarker.template.Configuration.VERSION_2_3_22); + driverConfig.getIncompatibleImprovementsVersion()); - TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass().getClassLoader(), basePackage); + TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass().getClassLoader(), + driverConfig.getBasePackage()); cfg.setTemplateLoader(templateLoader); // To avoid formatting numbers using spaces and commas in SQL cfg.setNumberFormat("computer"); // Because it defaults to default system encoding, we should set it always explicitly - cfg.setDefaultEncoding(StandardCharsets.UTF_8.name()); + cfg.setDefaultEncoding(driverConfig.getDefaultEncoding().name()); return cfg; } diff --git a/src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriverConfig.java b/src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriverConfig.java new file mode 100644 index 0000000..d230e2a --- /dev/null +++ b/src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriverConfig.java @@ -0,0 +1,252 @@ +/** + * Copyright 2015-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.freemarker; + +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.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.function.Consumer; +import java.util.function.Function; + +import freemarker.template.Configuration; +import freemarker.template.Version; +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.wrapper.DefaultObjectWrapperFactory; + +/** + * Configuration class for {@link FreeMarkerLanguageDriver}. + * + * @author Kazuki Shimizu + * @since 1.2.0 + */ +public class FreeMarkerLanguageDriverConfig { + + private static final String PROPERTY_KEY_CONFIG_FILE = "mybatis-freemarker.config.file"; + private static final String PROPERTY_KEY_CONFIG_ENCODING = "mybatis-freemarker.config.encoding"; + private static final String DEFAULT_PROPERTIES_FILE = "mybatis-freemarker.properties"; + private static Map, Function> TYPE_CONVERTERS; + + static { + Map, Function> converters = new HashMap<>(); + converters.put(String.class, String::trim); + converters.put(Version.class, v -> new Version(v.trim())); + converters.put(Charset.class, v -> Charset.forName(v.trim())); + TYPE_CONVERTERS = Collections.unmodifiableMap(converters); + } + + /** + * The base directory for reading template resources. + */ + private String basePackage = ""; + + /** + * The default encoding for reading template resources. + */ + private Charset defaultEncoding = StandardCharsets.UTF_8; + + /** + * The incompatible improvements version of freemarker. + */ + private Version incompatibleImprovementsVersion = Configuration.VERSION_2_3_22; + + /** + * Get a base directory for reading template resources. + *

+ * Default is none (just under classpath). + *

+ * + * @return a base directory for reading template resources + */ + public String getBasePackage() { + return basePackage; + } + + /** + * Set a base directory for reading template resources. + * + * @param basePackage + * a base directory for reading template resources + */ + public void setBasePackage(String basePackage) { + this.basePackage = basePackage; + } + + /** + * Get a default encoding for reading template resources. + *

+ * Default is {@code UTF-8}. + *

+ * + * @return a default encoding for reading template resources + */ + public Charset getDefaultEncoding() { + return defaultEncoding; + } + + /** + * Set a default encoding for reading template resources. + * + * @param defaultEncoding + * a default encoding for reading template resources + */ + public void setDefaultEncoding(Charset defaultEncoding) { + this.defaultEncoding = defaultEncoding; + } + + /** + * Get an incompatible improvements version of freemarker. + *

+ * Default is 2.3.22. + *

+ * + * @return an incompatible improvements version of freemarker + */ + public Version getIncompatibleImprovementsVersion() { + return incompatibleImprovementsVersion; + } + + /** + * Set an incompatible improvements version of freemarker. + * + * @param incompatibleImprovementsVersion + * an incompatible improvements version of freemarker + */ + public void setIncompatibleImprovementsVersion(Version incompatibleImprovementsVersion) { + this.incompatibleImprovementsVersion = incompatibleImprovementsVersion; + } + + /** + * Create an instance from default properties file.
+ * If you want to customize a default {@code TemplateEngine}, you can configure some property using + * mybatis-freemarker.properties that encoded by UTF-8. Also, you can change the properties file that will read using + * system property (-Dmybatis-freemarker.config.file=... -Dmybatis-freemarker.config.encoding=...).
+ * Supported properties are as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported properties
Property KeyDescriptionDefault
General configuration
base-packageThe base directory for reading template resourcesNone(just under classpath)
default-encodingThe default encoding for reading template resourcesUTF-8
freemarker-incompatible-improvements-versionThe incompatible improvements version of freemarker2.3.22
+ * + * @return a configuration instance + */ + public static FreeMarkerLanguageDriverConfig newInstance() { + return newInstance(loadDefaultProperties()); + } + + /** + * Create an instance from specified properties. + * + * @param customProperties + * custom configuration properties + * @return a configuration instance + * @see #newInstance() + */ + public static FreeMarkerLanguageDriverConfig newInstance(Properties customProperties) { + FreeMarkerLanguageDriverConfig config = new FreeMarkerLanguageDriverConfig(); + Properties properties = loadDefaultProperties(); + Optional.ofNullable(customProperties).ifPresent(properties::putAll); + override(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 FreeMarkerLanguageDriverConfig newInstance(Consumer customizer) { + FreeMarkerLanguageDriverConfig config = new FreeMarkerLanguageDriverConfig(); + customizer.accept(config); + override(config, loadDefaultProperties()); + return config; + } + + private static void override(FreeMarkerLanguageDriverConfig config, Properties properties) { + MetaObject metaObject = MetaObject.forObject(config, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), + new DefaultReflectorFactory()); + properties.forEach((key, value) -> { + String propertyPath = WordUtils + .uncapitalize(WordUtils.capitalize(Objects.toString(key), '-').replaceAll("-", "")); + Optional.ofNullable(value).ifPresent(v -> { + Object convertedValue = TYPE_CONVERTERS.get(metaObject.getSetterType(propertyPath)).apply(value.toString()); + metaObject.setValue(propertyPath, convertedValue); + }); + }); + } + + 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/freemarker/FreeMarkerLanguageDriverConfigTest.java b/src/test/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriverConfigTest.java new file mode 100644 index 0000000..5a3661f --- /dev/null +++ b/src/test/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriverConfigTest.java @@ -0,0 +1,103 @@ +/** + * Copyright 2015-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.freemarker; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +import freemarker.template.Configuration; +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 FreeMarkerLanguageDriverConfigTest { + + private String currentConfigFile; + private String currentConfigEncoding; + + @BeforeEach + void saveCurrentConfig() { + currentConfigFile = System.getProperty("mybatis-freemarker.config"); + currentConfigEncoding = System.getProperty("mybatis-freemarker.config.encoding"); + } + + @AfterEach + void restoreConfig() { + if (currentConfigFile == null) { + System.clearProperty("mybatis-freemarker.config.file"); + } else { + System.setProperty("mybatis-freemarker.config.file", currentConfigFile); + } + if (currentConfigEncoding == null) { + System.clearProperty("mybatis-freemarker.config.encoding"); + } else { + System.setProperty("mybatis-freemarker.config.encoding", currentConfigEncoding); + } + } + + @Test + void newInstanceWithEmptyPropertiesFile() { + System.setProperty("mybatis-freemarker.config.file", "mybatis-freemarker-empty.properties"); + FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(); + Assertions.assertEquals("", config.getBasePackage()); + Assertions.assertEquals(StandardCharsets.UTF_8, config.getDefaultEncoding()); + Assertions.assertEquals(Configuration.VERSION_2_3_22, config.getIncompatibleImprovementsVersion()); + } + + @Test + void newInstanceWithPropertiesFileNotFound() { + System.setProperty("mybatis-freemarker.config.file", "mybatis-freemarker-notfound.properties"); + FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(); + Assertions.assertEquals("", config.getBasePackage()); + Assertions.assertEquals(StandardCharsets.UTF_8, config.getDefaultEncoding()); + Assertions.assertEquals(Configuration.VERSION_2_3_22, config.getIncompatibleImprovementsVersion()); + } + + @Test + void newInstanceWithCustomPropertiesFile() { + System.setProperty("mybatis-freemarker.config.file", "mybatis-freemarker-custom.properties"); + FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(); + Assertions.assertEquals("sqls", config.getBasePackage()); + Assertions.assertEquals(Charset.forName("Windows-31J"), config.getDefaultEncoding()); + Assertions.assertEquals(Configuration.VERSION_2_3_28, config.getIncompatibleImprovementsVersion()); + } + + @Test + void newInstanceWithCustomProperties() { + Properties properties = new Properties(); + properties.setProperty("defaultEncoding", " " + StandardCharsets.ISO_8859_1.name() + " "); + properties.setProperty("incompatibleImprovementsVersion", " 2.3.27 "); + FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(properties); + Assertions.assertEquals("sql", config.getBasePackage()); + Assertions.assertEquals(StandardCharsets.ISO_8859_1, config.getDefaultEncoding()); + Assertions.assertEquals(Configuration.VERSION_2_3_27, config.getIncompatibleImprovementsVersion()); + } + + @Test + void newInstanceWithConsumer() { + FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(c -> { + c.setBasePackage("sqls"); + c.setDefaultEncoding(StandardCharsets.UTF_16); + c.setIncompatibleImprovementsVersion(Configuration.VERSION_2_3_26); + }); + Assertions.assertEquals("sql", config.getBasePackage()); + Assertions.assertEquals(StandardCharsets.UTF_16, config.getDefaultEncoding()); + Assertions.assertEquals(Configuration.VERSION_2_3_26, config.getIncompatibleImprovementsVersion()); + } + +} diff --git a/src/test/resources/mybatis-freemarker-custom.properties b/src/test/resources/mybatis-freemarker-custom.properties new file mode 100644 index 0000000..4ba41eb --- /dev/null +++ b/src/test/resources/mybatis-freemarker-custom.properties @@ -0,0 +1,19 @@ +# +# Copyright 2015-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. +# + +base-package=sqls +default-encoding=Windows-31J +incompatible-improvements-version=2.3.28 \ No newline at end of file diff --git a/src/test/resources/mybatis-freemarker-empty.properties b/src/test/resources/mybatis-freemarker-empty.properties new file mode 100644 index 0000000..006c5da --- /dev/null +++ b/src/test/resources/mybatis-freemarker-empty.properties @@ -0,0 +1,17 @@ +# +# Copyright 2015-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-freemarker.properties b/src/test/resources/mybatis-freemarker.properties index accbc39..a3dfa2e 100644 --- a/src/test/resources/mybatis-freemarker.properties +++ b/src/test/resources/mybatis-freemarker.properties @@ -1,5 +1,5 @@ # -# Copyright 2015-2016 the original author or authors. +# Copyright 2015-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,4 +14,4 @@ # limitations under the License. # -basePackage=sql \ No newline at end of file +basePackage=sql