Skip to content

Add FreeMarkerLanguageDriverConfig #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.6</version>
</dependency>

<!-- TEST -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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();
}

/**
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Class<?>, Function<String, Object>> TYPE_CONVERTERS;

static {
Map<Class<?>, Function<String, Object>> 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.
* <p>
* Default is none (just under classpath).
* </p>
*
* @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.
* <p>
* Default is {@code UTF-8}.
* </p>
*
* @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.
* <p>
* Default is 2.3.22.
* </p>
*
* @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. <br>
* 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=...). <br>
* Supported properties are as follows:
* <table border="1">
* <caption>Supported properties</caption>
* <tr>
* <th>Property Key</th>
* <th>Description</th>
* <th>Default</th>
* </tr>
* <tr>
* <th colspan="3">General configuration</th>
* </tr>
* <tr>
* <td>base-package</td>
* <td>The base directory for reading template resources</td>
* <td>None(just under classpath)</td>
* </tr>
* <tr>
* <td>default-encoding</td>
* <td>The default encoding for reading template resources</td>
* <td>UTF-8</td>
* </tr>
* <tr>
* <td>freemarker-incompatible-improvements-version</td>
* <td>The incompatible improvements version of freemarker</td>
* <td>2.3.22</td>
* </tr>
* </table>
*
* @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<FreeMarkerLanguageDriverConfig> 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;
}

}
Loading