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