Skip to content

Commit 488741f

Browse files
committed
Initial commit.
0 parents  commit 488741f

File tree

19 files changed

+762
-0
lines changed

19 files changed

+762
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea/
2+
mybatis-freemarker.iml
3+
target/

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
language: java
2+
jdk:
3+
- oraclejdk8
4+
- oraclejdk7
5+
- openjdk7

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
MyBatis FreeMarker Support
2+
========================
3+
4+
[![Build Status](https://travis-ci.org/elw00d/mybatis-freemarker.svg?branch=master)](https://travis-ci.org/elw00d/mybatis-freemarker)
5+
6+
![mybatis-velocity](http://mybatis.github.io/images/mybatis-logo.png)
7+
8+
MyBatis FreeMarker Scripting Support.
9+
10+
See tests to view examples.

pom.xml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>org.mybatis.scripting</groupId>
8+
<artifactId>mybatis-freemarker</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
<packaging>jar</packaging>
11+
12+
<name>MyBatis FreeMarker</name>
13+
<description>FreeMarker support for MyBatis</description>
14+
<url>https://github.com/elw00d/mybatis-freemarker</url>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>org.mybatis</groupId>
19+
<artifactId>mybatis</artifactId>
20+
<version>3.2.8</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>org.freemarker</groupId>
24+
<artifactId>freemarker</artifactId>
25+
<version>2.3.22</version>
26+
</dependency>
27+
28+
<!-- TEST -->
29+
<dependency>
30+
<groupId>junit</groupId>
31+
<artifactId>junit</artifactId>
32+
<version>4.12</version>
33+
<scope>test</scope>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.hsqldb</groupId>
37+
<artifactId>hsqldb</artifactId>
38+
<version>2.3.2</version>
39+
<scope>test</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>log4j</groupId>
43+
<artifactId>log4j</artifactId>
44+
<version>1.2.17</version>
45+
<scope>test</scope>
46+
</dependency>
47+
</dependencies>
48+
49+
<build>
50+
<plugins>
51+
<plugin>
52+
<groupId>org.apache.maven.plugins</groupId>
53+
<artifactId>maven-compiler-plugin</artifactId>
54+
<version>3.2</version>
55+
<configuration>
56+
<source>1.7</source>
57+
<target>1.7</target>
58+
</configuration>
59+
</plugin>
60+
</plugins>
61+
</build>
62+
</project>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.mybatis.scripting.freemarker;
2+
3+
import freemarker.cache.ClassTemplateLoader;
4+
import freemarker.cache.TemplateLoader;
5+
import freemarker.template.Template;
6+
import org.apache.ibatis.executor.parameter.ParameterHandler;
7+
import org.apache.ibatis.mapping.BoundSql;
8+
import org.apache.ibatis.mapping.MappedStatement;
9+
import org.apache.ibatis.mapping.SqlSource;
10+
import org.apache.ibatis.parsing.XNode;
11+
import org.apache.ibatis.scripting.LanguageDriver;
12+
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
13+
import org.apache.ibatis.session.Configuration;
14+
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.io.StringReader;
18+
import java.util.Properties;
19+
20+
/**
21+
* Adds FreeMarker templates support to scripting in MyBatis.
22+
*
23+
* @author elwood
24+
*/
25+
public class FreeMarkerLanguageDriver implements LanguageDriver {
26+
/**
27+
* Base package for all FreeMarker templates.
28+
*/
29+
public static final String basePackage;
30+
31+
public static final String DEFAULT_BASE_PACKAGE = "";
32+
33+
static {
34+
Properties properties = new Properties();
35+
try {
36+
try (InputStream stream = FreeMarkerLanguageDriver.class.getClassLoader()
37+
.getResourceAsStream("mybatis-freemarker.properties")) {
38+
if (stream != null) {
39+
properties.load(stream);
40+
basePackage = properties.getProperty("basePackage", DEFAULT_BASE_PACKAGE);
41+
} else {
42+
basePackage = DEFAULT_BASE_PACKAGE;
43+
}
44+
}
45+
} catch (IOException e) {
46+
throw new IllegalStateException(e);
47+
}
48+
}
49+
50+
/**
51+
* Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement.
52+
*
53+
* @see DefaultParameterHandler
54+
* @param mappedStatement The mapped statement that is being executed
55+
* @param parameterObject The input parameter object (can be null)
56+
* @param boundSql The resulting SQL once the dynamic language has been executed.
57+
*/
58+
@Override
59+
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
60+
// As default XMLLanguageDriver
61+
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
62+
}
63+
64+
/**
65+
* Creates an {@link SqlSource} that will hold the statement read from a mapper xml file.
66+
* It is called during startup, when the mapped statement is read from a class or an xml file.
67+
*
68+
* @param configuration The MyBatis configuration
69+
* @param script XNode parsed from a XML file
70+
* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
71+
*/
72+
@Override
73+
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
74+
return createSqlSource(configuration, script.getNode().getTextContent());
75+
}
76+
77+
/**
78+
* Creates an {@link SqlSource} that will hold the statement read from an annotation.
79+
* It is called during startup, when the mapped statement is read from a class or an xml file.
80+
*
81+
* @param configuration The MyBatis configuration
82+
* @param script The content of the annotation
83+
* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
84+
*/
85+
@Override
86+
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
87+
return createSqlSource(configuration, script);
88+
}
89+
90+
private SqlSource createSqlSource(Configuration configuration, String scriptText) {
91+
Template template;
92+
freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_22);
93+
if (scriptText.trim().contains(" ")) {
94+
// Consider that script is inline script
95+
try {
96+
template = new Template(null, new StringReader(scriptText), cfg);
97+
} catch (IOException e) {
98+
throw new RuntimeException(e);
99+
}
100+
} else {
101+
// Consider that script is template name, trying to find the template in classpath
102+
TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass().getClassLoader(), basePackage);
103+
cfg.setTemplateLoader(templateLoader);
104+
try {
105+
template = cfg.getTemplate(scriptText.trim());
106+
} catch (IOException e) {
107+
throw new RuntimeException(e);
108+
}
109+
}
110+
111+
return new FreeMarkerSqlSource(template, configuration);
112+
}
113+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.mybatis.scripting.freemarker;
2+
3+
import freemarker.template.Template;
4+
import freemarker.template.TemplateException;
5+
import org.apache.ibatis.builder.SqlSourceBuilder;
6+
import org.apache.ibatis.mapping.BoundSql;
7+
import org.apache.ibatis.mapping.SqlSource;
8+
import org.apache.ibatis.session.Configuration;
9+
10+
import java.io.CharArrayWriter;
11+
import java.io.IOException;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
15+
/**
16+
* Applies provided parameter(s) to FreeMarker template.
17+
* Then passes the result into default MyBatis engine (and it finally replaces #{}-params to '?'-params).
18+
* So, FreeMarker is used as preprocessor for MyBatis engine.
19+
*
20+
* @author elwood
21+
*/
22+
public class FreeMarkerSqlSource implements SqlSource {
23+
private final Template template;
24+
private final Configuration configuration;
25+
26+
public FreeMarkerSqlSource(Template template, Configuration configuration) {
27+
this.template = template;
28+
this.configuration = configuration;
29+
}
30+
31+
@Override
32+
public BoundSql getBoundSql(Object parameterObject) {
33+
// Add to passed parameterObject our predefined directive - MyBatisParamDirective
34+
// It will be available as "p" inside templates
35+
Object dataContext;
36+
if (parameterObject != null) {
37+
if (parameterObject instanceof Map) {
38+
HashMap<String, Object> map = new HashMap<>((Map<String, Object>) parameterObject);
39+
map.put(MyBatisParamDirective.DEFAULT_KEY, new MyBatisParamDirective());
40+
dataContext = map;
41+
} else {
42+
dataContext = new ParamObjectAdapter(parameterObject);
43+
}
44+
} else {
45+
HashMap<Object, Object> map = new HashMap<>();
46+
map.put(MyBatisParamDirective.DEFAULT_KEY, new MyBatisParamDirective());
47+
dataContext = map;
48+
}
49+
50+
CharArrayWriter writer = new CharArrayWriter();
51+
try {
52+
template.process(dataContext, writer);
53+
} catch (TemplateException | IOException e) {
54+
throw new RuntimeException(e);
55+
}
56+
57+
// We got SQL ready for MyBatis here. This SQL contains params declarations like "#{param}",
58+
// they will be replaced to '?' by MyBatis engine further
59+
String sql = writer.toString();
60+
61+
// Pass retrieved SQL into MyBatis engine, it will substitute prepared-statements parameters
62+
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
63+
Class<?> parameterType1 = parameterObject == null ? Object.class : parameterObject.getClass();
64+
SqlSource sqlSource = sqlSourceParser.parse(sql, parameterType1, new HashMap<String, Object>());
65+
return sqlSource.getBoundSql(parameterObject);
66+
}
67+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.mybatis.scripting.freemarker;
2+
3+
import freemarker.core.Environment;
4+
import freemarker.template.TemplateDirectiveBody;
5+
import freemarker.template.TemplateDirectiveModel;
6+
import freemarker.template.TemplateException;
7+
import freemarker.template.TemplateModel;
8+
9+
import java.io.IOException;
10+
import java.util.Map;
11+
12+
/**
13+
* Custom FreeMarker directive for generating "#{paramName}" declarations in convenient way.
14+
* Problem is FreeMarker supports this syntax natively and there are no chance to disable this
15+
* (although it is deprecated). And to get "#{paramName}" we should write ${r"#{paramName}"}.
16+
* With this directive you can write more simple:
17+
*
18+
* <blockquote><pre>
19+
* &lt;@p name="paramName"/&gt;
20+
* </pre></blockquote>
21+
*
22+
* @author elwood
23+
*/
24+
public class MyBatisParamDirective implements TemplateDirectiveModel {
25+
public static String DEFAULT_KEY = "p";
26+
27+
@Override
28+
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
29+
env.getOut().write(String.format("#{%s}", params.get("name")));
30+
}
31+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.mybatis.scripting.freemarker;
2+
3+
import freemarker.ext.beans.BeanModel;
4+
import freemarker.ext.beans.BeansWrapperBuilder;
5+
import freemarker.template.Configuration;
6+
import freemarker.template.TemplateHashModel;
7+
import freemarker.template.TemplateModel;
8+
import freemarker.template.TemplateModelException;
9+
10+
/**
11+
* Important: if you are using some object that already has property "p", then
12+
* MyBatisParamDirective will be unavailable from script.
13+
*
14+
* @author elwood
15+
*/
16+
public class ParamObjectAdapter implements TemplateHashModel {
17+
private final BeanModel beanModel;
18+
19+
public ParamObjectAdapter(Object paramObject) {
20+
beanModel = new BeanModel(paramObject, new BeansWrapperBuilder(Configuration.VERSION_2_3_22).build());
21+
}
22+
23+
@Override
24+
public TemplateModel get(String key) throws TemplateModelException {
25+
TemplateModel value = beanModel.get(key);
26+
if (value == null && MyBatisParamDirective.DEFAULT_KEY.equals(key)) {
27+
return new MyBatisParamDirective();
28+
}
29+
return value;
30+
}
31+
32+
@Override
33+
public boolean isEmpty() throws TemplateModelException {
34+
return beanModel.isEmpty();
35+
}
36+
}

0 commit comments

Comments
 (0)