1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.scripting.thymeleaf;
17
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.function.BiConsumer;
27 import java.util.function.BiFunction;
28 import java.util.stream.Collectors;
29
30 import org.mybatis.scripting.thymeleaf.expression.Likes;
31 import org.thymeleaf.ITemplateEngine;
32 import org.thymeleaf.TemplateEngine;
33 import org.thymeleaf.context.IContext;
34 import org.thymeleaf.templatemode.TemplateMode;
35 import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
36 import org.thymeleaf.templateresolver.StringTemplateResolver;
37
38
39
40
41
42
43
44
45 public class SqlGenerator {
46
47 static class ContextKeys {
48 static final String PARAMETER_OBJECT = "_parameter";
49 }
50
51 private final ITemplateEngine templateEngine;
52 private Map<String, Object> defaultCustomVariables = Collections.emptyMap();
53 private PropertyAccessor propertyAccessor = PropertyAccessor.BuiltIn.STANDARD;
54 private BiFunction<Object, Map<String, Object>, IContext> contextFactory = DefaultContext::new;
55
56
57
58
59 public SqlGenerator() {
60 this.templateEngine = createDefaultTemplateEngine(SqlGeneratorConfig.newInstance());
61 }
62
63
64
65
66
67
68
69 public SqlGenerator(SqlGeneratorConfig config) {
70 this.templateEngine = createDefaultTemplateEngine(config);
71 }
72
73
74
75
76
77
78
79 public SqlGenerator(ITemplateEngine templateEngine) {
80 this.templateEngine = templateEngine;
81 }
82
83
84
85
86
87
88
89 public void setDefaultCustomVariables(Map<String, Object> defaultCustomVariables) {
90 this.defaultCustomVariables = Optional.ofNullable(defaultCustomVariables).map(Collections::unmodifiableMap)
91 .orElseGet(Collections::emptyMap);
92 }
93
94
95
96
97
98
99 public Map<String, Object> getDefaultCustomVariables() {
100 return defaultCustomVariables;
101 }
102
103
104
105
106
107
108
109
110
111
112 public void setPropertyAccessor(PropertyAccessor propertyAccessor) {
113 this.propertyAccessor = Optional.ofNullable(propertyAccessor).orElse(PropertyAccessor.BuiltIn.STANDARD);
114 }
115
116
117
118
119
120
121
122 void setContextFactory(BiFunction<Object, Map<String, Object>, IContext> contextFactory) {
123 this.contextFactory = contextFactory;
124 }
125
126 private ITemplateEngine createDefaultTemplateEngine(SqlGeneratorConfig config) {
127 MyBatisDialect dialect = new MyBatisDialect(config.getDialect().getPrefix());
128 Optional.ofNullable(config.getDialect().getBindVariableRenderInstance()).ifPresent(dialect::setBindVariableRender);
129 Likes likes = Likes.newBuilder().escapeChar(config.getDialect().getLikeEscapeChar())
130 .escapeClauseFormat(config.getDialect().getLikeEscapeClauseFormat())
131 .additionalEscapeTargetChars(config.getDialect().getLikeAdditionalEscapeTargetChars()).build();
132 dialect.setLikes(likes);
133
134
135 ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
136 TemplateMode mode = config.isUse2way() ? TemplateMode.CSS : TemplateMode.TEXT;
137 classLoaderTemplateResolver.setOrder(1);
138 classLoaderTemplateResolver.setTemplateMode(mode);
139 classLoaderTemplateResolver
140 .setResolvablePatterns(Arrays.stream(config.getTemplateFile().getPatterns()).collect(Collectors.toSet()));
141 classLoaderTemplateResolver.setCharacterEncoding(config.getTemplateFile().getEncoding().name());
142 classLoaderTemplateResolver.setCacheable(config.getTemplateFile().isCacheEnabled());
143 classLoaderTemplateResolver.setCacheTTLMs(config.getTemplateFile().getCacheTtl());
144 classLoaderTemplateResolver.setPrefix(config.getTemplateFile().getBaseDir());
145
146
147 StringTemplateResolver stringTemplateResolver = new StringTemplateResolver();
148 stringTemplateResolver.setOrder(2);
149 stringTemplateResolver.setTemplateMode(mode);
150
151
152 TemplateEngine targetTemplateEngine = new TemplateEngine();
153 targetTemplateEngine.addTemplateResolver(classLoaderTemplateResolver);
154 targetTemplateEngine.addTemplateResolver(stringTemplateResolver);
155 targetTemplateEngine.addDialect(dialect);
156 targetTemplateEngine.setEngineContextFactory(
157 new MyBatisIntegratingEngineContextFactory(targetTemplateEngine.getEngineContextFactory()));
158
159
160 Optional.ofNullable(config.getCustomizerInstance()).ifPresent(x -> x.accept(targetTemplateEngine));
161
162 return targetTemplateEngine;
163 }
164
165
166
167
168
169
170
171
172
173
174
175 public String generate(CharSequence sqlTemplate, Object parameter) {
176 return generate(sqlTemplate, parameter, null, null);
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190
191 public String generate(CharSequence sqlTemplate, Object parameter,
192 BiConsumer<String, Object> customBindVariableBinder) {
193 return generate(sqlTemplate, parameter, customBindVariableBinder, null);
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public String generate(CharSequence sqlTemplate, Object parameter, Map<String, Object> customVariables) {
209 return generate(sqlTemplate, parameter, null, customVariables);
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 public String generate(CharSequence sqlTemplate, Object parameter,
227 BiConsumer<String, Object> customBindVariableBinder, Map<String, Object> customVariables) {
228
229 Map<String, Object> processingCustomVariables = new HashMap<>(defaultCustomVariables);
230 Optional.ofNullable(customVariables).ifPresent(processingCustomVariables::putAll);
231
232 IContext context = contextFactory.apply(parameter, processingCustomVariables);
233 String sql = templateEngine.process(sqlTemplate.toString(), context);
234
235 MyBatisBindingContext bindingContext = MyBatisBindingContext.load(context);
236 if (bindingContext != null && customBindVariableBinder != null) {
237 bindingContext.getCustomBindVariables().forEach(customBindVariableBinder);
238 }
239
240 return sql;
241 }
242
243 private class DefaultContext implements IContext {
244
245 private final Object parameter;
246 private final Map<String, Object> mapParameter;
247 private final Set<String> propertyNames = new HashSet<>();
248 private final Map<String, Object> customVariables;
249
250 private DefaultContext(Object parameter, Map<String, Object> customVariables) {
251 this.parameter = parameter;
252 boolean fallback;
253 if (parameter instanceof Map) {
254 @SuppressWarnings("unchecked")
255 Map<String, Object> map = (Map<String, Object>) parameter;
256 propertyNames.addAll(map.keySet());
257 this.mapParameter = map;
258 fallback = false;
259 } else {
260 this.mapParameter = null;
261 if (parameter != null) {
262 propertyNames.addAll(propertyAccessor.getPropertyNames(parameter.getClass()));
263 }
264 fallback = propertyNames.isEmpty();
265 }
266 MyBatisBindingContext bindingContext = new MyBatisBindingContext(fallback);
267 this.customVariables = customVariables;
268 customVariables.put(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext);
269 customVariables.put(ContextKeys.PARAMETER_OBJECT, parameter);
270 }
271
272 @Override
273 public Locale getLocale() {
274 return Locale.getDefault();
275 }
276
277 @Override
278 public boolean containsVariable(String name) {
279 return customVariables.containsKey(name) || propertyNames.contains(name);
280 }
281
282 @Override
283 public Set<String> getVariableNames() {
284 Set<String> variableNames = new HashSet<>(customVariables.keySet());
285 variableNames.addAll(propertyNames);
286 return variableNames;
287 }
288
289 @Override
290 public Object getVariable(String name) {
291 if (customVariables.containsKey(name)) {
292 return customVariables.get(name);
293 }
294 if (mapParameter == null) {
295 return propertyAccessor.getPropertyValue(parameter, name);
296 } else {
297 return mapParameter.get(name);
298 }
299 }
300
301 }
302
303 }