Skip to content

Commit c66cf99

Browse files
authored
Merge pull request #49 from kazuki43zoo/gh-44
Add FreeMarkerLanguageDriverConfig
2 parents 8f4621e + 1951975 commit c66cf99

File tree

7 files changed

+423
-33
lines changed

7 files changed

+423
-33
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@
9090
<artifactId>freemarker</artifactId>
9191
<version>2.3.28</version>
9292
</dependency>
93+
<dependency>
94+
<groupId>org.apache.commons</groupId>
95+
<artifactId>commons-text</artifactId>
96+
<version>1.6</version>
97+
</dependency>
9398

9499
<!-- TEST -->
95100
<dependency>

src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriver.java

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2015-2018 the original author or authors.
2+
* Copyright 2015-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,10 +20,7 @@
2020
import freemarker.template.Template;
2121

2222
import java.io.IOException;
23-
import java.io.InputStream;
2423
import java.io.StringReader;
25-
import java.nio.charset.StandardCharsets;
26-
import java.util.Properties;
2724

2825
import org.apache.ibatis.executor.parameter.ParameterHandler;
2926
import org.apache.ibatis.mapping.BoundSql;
@@ -39,36 +36,32 @@
3936
* configuration, use can inherit from this class and override {@link #createFreeMarkerConfiguration()} method.
4037
*
4138
* @author elwood
39+
* @author Kazuki Shimizu
4240
*/
4341
public class FreeMarkerLanguageDriver implements LanguageDriver {
42+
43+
protected final FreeMarkerLanguageDriverConfig driverConfig;
44+
protected final freemarker.template.Configuration freemarkerCfg;
45+
4446
/**
45-
* Base package for all FreeMarker templates.
47+
* Constructor.
48+
*
49+
* @see FreeMarkerLanguageDriverConfig#newInstance()
4650
*/
47-
public static final String basePackage;
48-
49-
public static final String DEFAULT_BASE_PACKAGE = "";
50-
51-
static {
52-
Properties properties = new Properties();
53-
try {
54-
try (InputStream stream = FreeMarkerLanguageDriver.class.getClassLoader()
55-
.getResourceAsStream("mybatis-freemarker.properties")) {
56-
if (stream != null) {
57-
properties.load(stream);
58-
basePackage = properties.getProperty("basePackage", DEFAULT_BASE_PACKAGE);
59-
} else {
60-
basePackage = DEFAULT_BASE_PACKAGE;
61-
}
62-
}
63-
} catch (IOException e) {
64-
throw new IllegalStateException(e);
65-
}
51+
public FreeMarkerLanguageDriver() {
52+
this(FreeMarkerLanguageDriverConfig.newInstance());
6653
}
6754

68-
protected freemarker.template.Configuration freemarkerCfg;
69-
70-
public FreeMarkerLanguageDriver() {
71-
freemarkerCfg = createFreeMarkerConfiguration();
55+
/**
56+
* Constructor.
57+
*
58+
* @param driverConfig
59+
* a language driver configuration
60+
* @since 1.2.0
61+
*/
62+
public FreeMarkerLanguageDriver(FreeMarkerLanguageDriverConfig driverConfig) {
63+
this.driverConfig = driverConfig;
64+
this.freemarkerCfg = createFreeMarkerConfiguration();
7265
}
7366

7467
/**
@@ -77,16 +70,17 @@ public FreeMarkerLanguageDriver() {
7770
*/
7871
protected freemarker.template.Configuration createFreeMarkerConfiguration() {
7972
freemarker.template.Configuration cfg = new freemarker.template.Configuration(
80-
freemarker.template.Configuration.VERSION_2_3_22);
73+
driverConfig.getIncompatibleImprovementsVersion());
8174

82-
TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass().getClassLoader(), basePackage);
75+
TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass().getClassLoader(),
76+
driverConfig.getBasePackage());
8377
cfg.setTemplateLoader(templateLoader);
8478

8579
// To avoid formatting numbers using spaces and commas in SQL
8680
cfg.setNumberFormat("computer");
8781

8882
// Because it defaults to default system encoding, we should set it always explicitly
89-
cfg.setDefaultEncoding(StandardCharsets.UTF_8.name());
83+
cfg.setDefaultEncoding(driverConfig.getDefaultEncoding().name());
9084

9185
return cfg;
9286
}
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/**
2+
* Copyright 2015-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.scripting.freemarker;
17+
18+
import java.io.BufferedReader;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.InputStreamReader;
22+
import java.nio.charset.Charset;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.Collections;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.Objects;
28+
import java.util.Optional;
29+
import java.util.Properties;
30+
import java.util.function.Consumer;
31+
import java.util.function.Function;
32+
33+
import freemarker.template.Configuration;
34+
import freemarker.template.Version;
35+
import org.apache.commons.text.WordUtils;
36+
import org.apache.ibatis.io.Resources;
37+
import org.apache.ibatis.reflection.DefaultReflectorFactory;
38+
import org.apache.ibatis.reflection.MetaObject;
39+
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
40+
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
41+
42+
/**
43+
* Configuration class for {@link FreeMarkerLanguageDriver}.
44+
*
45+
* @author Kazuki Shimizu
46+
* @since 1.2.0
47+
*/
48+
public class FreeMarkerLanguageDriverConfig {
49+
50+
private static final String PROPERTY_KEY_CONFIG_FILE = "mybatis-freemarker.config.file";
51+
private static final String PROPERTY_KEY_CONFIG_ENCODING = "mybatis-freemarker.config.encoding";
52+
private static final String DEFAULT_PROPERTIES_FILE = "mybatis-freemarker.properties";
53+
private static Map<Class<?>, Function<String, Object>> TYPE_CONVERTERS;
54+
55+
static {
56+
Map<Class<?>, Function<String, Object>> converters = new HashMap<>();
57+
converters.put(String.class, String::trim);
58+
converters.put(Version.class, v -> new Version(v.trim()));
59+
converters.put(Charset.class, v -> Charset.forName(v.trim()));
60+
TYPE_CONVERTERS = Collections.unmodifiableMap(converters);
61+
}
62+
63+
/**
64+
* The base directory for reading template resources.
65+
*/
66+
private String basePackage = "";
67+
68+
/**
69+
* The default encoding for reading template resources.
70+
*/
71+
private Charset defaultEncoding = StandardCharsets.UTF_8;
72+
73+
/**
74+
* The incompatible improvements version of freemarker.
75+
*/
76+
private Version incompatibleImprovementsVersion = Configuration.VERSION_2_3_22;
77+
78+
/**
79+
* Get a base directory for reading template resources.
80+
* <p>
81+
* Default is none (just under classpath).
82+
* </p>
83+
*
84+
* @return a base directory for reading template resources
85+
*/
86+
public String getBasePackage() {
87+
return basePackage;
88+
}
89+
90+
/**
91+
* Set a base directory for reading template resources.
92+
*
93+
* @param basePackage
94+
* a base directory for reading template resources
95+
*/
96+
public void setBasePackage(String basePackage) {
97+
this.basePackage = basePackage;
98+
}
99+
100+
/**
101+
* Get a default encoding for reading template resources.
102+
* <p>
103+
* Default is {@code UTF-8}.
104+
* </p>
105+
*
106+
* @return a default encoding for reading template resources
107+
*/
108+
public Charset getDefaultEncoding() {
109+
return defaultEncoding;
110+
}
111+
112+
/**
113+
* Set a default encoding for reading template resources.
114+
*
115+
* @param defaultEncoding
116+
* a default encoding for reading template resources
117+
*/
118+
public void setDefaultEncoding(Charset defaultEncoding) {
119+
this.defaultEncoding = defaultEncoding;
120+
}
121+
122+
/**
123+
* Get an incompatible improvements version of freemarker.
124+
* <p>
125+
* Default is 2.3.22.
126+
* </p>
127+
*
128+
* @return an incompatible improvements version of freemarker
129+
*/
130+
public Version getIncompatibleImprovementsVersion() {
131+
return incompatibleImprovementsVersion;
132+
}
133+
134+
/**
135+
* Set an incompatible improvements version of freemarker.
136+
*
137+
* @param incompatibleImprovementsVersion
138+
* an incompatible improvements version of freemarker
139+
*/
140+
public void setIncompatibleImprovementsVersion(Version incompatibleImprovementsVersion) {
141+
this.incompatibleImprovementsVersion = incompatibleImprovementsVersion;
142+
}
143+
144+
/**
145+
* Create an instance from default properties file. <br>
146+
* If you want to customize a default {@code TemplateEngine}, you can configure some property using
147+
* mybatis-freemarker.properties that encoded by UTF-8. Also, you can change the properties file that will read using
148+
* system property (-Dmybatis-freemarker.config.file=... -Dmybatis-freemarker.config.encoding=...). <br>
149+
* Supported properties are as follows:
150+
* <table border="1">
151+
* <caption>Supported properties</caption>
152+
* <tr>
153+
* <th>Property Key</th>
154+
* <th>Description</th>
155+
* <th>Default</th>
156+
* </tr>
157+
* <tr>
158+
* <th colspan="3">General configuration</th>
159+
* </tr>
160+
* <tr>
161+
* <td>base-package</td>
162+
* <td>The base directory for reading template resources</td>
163+
* <td>None(just under classpath)</td>
164+
* </tr>
165+
* <tr>
166+
* <td>default-encoding</td>
167+
* <td>The default encoding for reading template resources</td>
168+
* <td>UTF-8</td>
169+
* </tr>
170+
* <tr>
171+
* <td>freemarker-incompatible-improvements-version</td>
172+
* <td>The incompatible improvements version of freemarker</td>
173+
* <td>2.3.22</td>
174+
* </tr>
175+
* </table>
176+
*
177+
* @return a configuration instance
178+
*/
179+
public static FreeMarkerLanguageDriverConfig newInstance() {
180+
return newInstance(loadDefaultProperties());
181+
}
182+
183+
/**
184+
* Create an instance from specified properties.
185+
*
186+
* @param customProperties
187+
* custom configuration properties
188+
* @return a configuration instance
189+
* @see #newInstance()
190+
*/
191+
public static FreeMarkerLanguageDriverConfig newInstance(Properties customProperties) {
192+
FreeMarkerLanguageDriverConfig config = new FreeMarkerLanguageDriverConfig();
193+
Properties properties = loadDefaultProperties();
194+
Optional.ofNullable(customProperties).ifPresent(properties::putAll);
195+
override(config, properties);
196+
return config;
197+
}
198+
199+
/**
200+
* Create an instance using specified customizer and override using a default properties file.
201+
*
202+
* @param customizer
203+
* baseline customizer
204+
* @return a configuration instance
205+
* @see #newInstance()
206+
*/
207+
public static FreeMarkerLanguageDriverConfig newInstance(Consumer<FreeMarkerLanguageDriverConfig> customizer) {
208+
FreeMarkerLanguageDriverConfig config = new FreeMarkerLanguageDriverConfig();
209+
customizer.accept(config);
210+
override(config, loadDefaultProperties());
211+
return config;
212+
}
213+
214+
private static void override(FreeMarkerLanguageDriverConfig config, Properties properties) {
215+
MetaObject metaObject = MetaObject.forObject(config, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
216+
new DefaultReflectorFactory());
217+
properties.forEach((key, value) -> {
218+
String propertyPath = WordUtils
219+
.uncapitalize(WordUtils.capitalize(Objects.toString(key), '-').replaceAll("-", ""));
220+
Optional.ofNullable(value).ifPresent(v -> {
221+
Object convertedValue = TYPE_CONVERTERS.get(metaObject.getSetterType(propertyPath)).apply(value.toString());
222+
metaObject.setValue(propertyPath, convertedValue);
223+
});
224+
});
225+
}
226+
227+
private static Properties loadDefaultProperties() {
228+
return loadProperties(System.getProperty(PROPERTY_KEY_CONFIG_FILE, DEFAULT_PROPERTIES_FILE));
229+
}
230+
231+
private static Properties loadProperties(String resourcePath) {
232+
Properties properties = new Properties();
233+
InputStream in;
234+
try {
235+
in = Resources.getResourceAsStream(resourcePath);
236+
} catch (IOException e) {
237+
in = null;
238+
}
239+
if (in != null) {
240+
Charset encoding = Optional.ofNullable(System.getProperty(PROPERTY_KEY_CONFIG_ENCODING)).map(Charset::forName)
241+
.orElse(StandardCharsets.UTF_8);
242+
try (InputStreamReader inReader = new InputStreamReader(in, encoding);
243+
BufferedReader bufReader = new BufferedReader(inReader)) {
244+
properties.load(bufReader);
245+
} catch (IOException e) {
246+
throw new IllegalStateException(e);
247+
}
248+
}
249+
return properties;
250+
}
251+
252+
}

0 commit comments

Comments
 (0)