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.lang.reflect.InvocationTargetException;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Properties;
26 import java.util.Set;
27 import java.util.function.BiFunction;
28
29 import org.apache.ibatis.builder.SqlSourceBuilder;
30 import org.apache.ibatis.mapping.BoundSql;
31 import org.apache.ibatis.mapping.SqlSource;
32 import org.apache.ibatis.reflection.MetaClass;
33 import org.apache.ibatis.scripting.xmltags.DynamicContext;
34 import org.apache.ibatis.session.Configuration;
35 import org.thymeleaf.context.IContext;
36
37
38
39
40
41
42
43
44
45
46 class ThymeleafSqlSource implements SqlSource {
47
48 private static class TemporaryTakeoverKeys {
49 private static final String CONFIGURATION = "__configuration__";
50 private static final String DYNAMIC_CONTEXT = "__dynamicContext__";
51 private static final String PROCESSING_PARAMETER_TYPE = "__processingParameterType__";
52 }
53
54 private final Configuration configuration;
55 private final SqlGenerator sqlGenerator;
56 private final SqlSourceBuilder sqlSourceBuilder;
57 private final String sqlTemplate;
58 private final Class<?> parameterType;
59
60
61
62
63
64
65
66
67
68
69
70
71
72 ThymeleafSqlSource(Configuration configuration, SqlGenerator sqlGenerator, String sqlTemplate,
73 Class<?> parameterType) {
74 this.configuration = configuration;
75 this.sqlGenerator = sqlGenerator;
76 this.sqlTemplate = sqlTemplate;
77 this.parameterType = parameterType;
78 this.sqlSourceBuilder = new SqlSourceBuilder(configuration);
79 }
80
81
82
83
84 @Override
85 public BoundSql getBoundSql(Object parameterObject) {
86 Class<?> processingParameterType;
87 if (parameterType == null) {
88 processingParameterType = parameterObject == null ? Object.class : parameterObject.getClass();
89 } else {
90 processingParameterType = parameterType;
91 }
92
93 DynamicContext dynamicContext = new DynamicContext(configuration, parameterObject);
94 Map<String, Object> customVariables = dynamicContext.getBindings();
95 customVariables.put(TemporaryTakeoverKeys.CONFIGURATION, configuration);
96 customVariables.put(TemporaryTakeoverKeys.DYNAMIC_CONTEXT, dynamicContext);
97 customVariables.put(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE, processingParameterType);
98 String sql = sqlGenerator.generate(sqlTemplate, parameterObject, dynamicContext::bind, customVariables);
99
100 SqlSource sqlSource = sqlSourceBuilder.parse(sql, processingParameterType, dynamicContext.getBindings());
101 BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
102 dynamicContext.getBindings().forEach(boundSql::setAdditionalParameter);
103
104 return boundSql;
105 }
106
107
108
109
110
111
112 static class ContextFactory implements BiFunction<Object, Map<String, Object>, IContext> {
113
114
115
116 @Override
117 public IContext apply(Object parameter, Map<String, Object> customVariable) {
118 Configuration configuration = (Configuration) customVariable.remove(TemporaryTakeoverKeys.CONFIGURATION);
119 DynamicContext dynamicContext = (DynamicContext) customVariable.remove(TemporaryTakeoverKeys.DYNAMIC_CONTEXT);
120 Class<?> processingParameterType = (Class<?>) customVariable
121 .remove(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE);
122 MyBatisBindingContext bindingContext = new MyBatisBindingContext(
123 parameter != null && configuration.getTypeHandlerRegistry().hasTypeHandler(processingParameterType));
124 dynamicContext.bind(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext);
125 IContext context;
126 if (parameter instanceof Map) {
127 @SuppressWarnings(value = "unchecked")
128 Map<String, Object> map = (Map<String, Object>) parameter;
129 context = new MapBasedContext(map, dynamicContext, configuration.getVariables());
130 } else {
131 MetaClass metaClass = MetaClass.forClass(processingParameterType, configuration.getReflectorFactory());
132 context = new MetaClassBasedContext(parameter, metaClass, processingParameterType, dynamicContext,
133 configuration.getVariables());
134 }
135 return context;
136 }
137 }
138
139 private abstract static class AbstractContext implements IContext {
140
141 private final DynamicContext dynamicContext;
142 private final Properties configurationProperties;
143 private final Set<String> variableNames;
144
145 private AbstractContext(DynamicContext dynamicContext, Properties configurationProperties) {
146 this.dynamicContext = dynamicContext;
147 this.configurationProperties = configurationProperties;
148 this.variableNames = new HashSet<>();
149 addVariableNames(dynamicContext.getBindings().keySet());
150 Optional.ofNullable(configurationProperties).ifPresent(v -> addVariableNames(v.stringPropertyNames()));
151 }
152
153 void addVariableNames(Collection<String> names) {
154 variableNames.addAll(names);
155 }
156
157
158
159
160 @Override
161 public Locale getLocale() {
162 return Locale.getDefault();
163 }
164
165
166
167
168 @Override
169 public boolean containsVariable(String name) {
170 return variableNames.contains(name);
171 }
172
173
174
175
176 @Override
177 public Set<String> getVariableNames() {
178 return variableNames;
179 }
180
181
182
183
184 @Override
185 public Object getVariable(String name) {
186 if (dynamicContext.getBindings().containsKey(name)) {
187 return dynamicContext.getBindings().get(name);
188 }
189 if (configurationProperties != null && configurationProperties.containsKey(name)) {
190 return configurationProperties.getProperty(name);
191 }
192 return getParameterValue(name);
193 }
194
195 abstract Object getParameterValue(String name);
196
197 }
198
199 private static class MapBasedContext extends AbstractContext {
200
201 private final Map<String, Object> variables;
202
203 private MapBasedContext(Map<String, Object> parameterMap, DynamicContext dynamicContext,
204 Properties configurationProperties) {
205 super(dynamicContext, configurationProperties);
206 this.variables = parameterMap;
207 addVariableNames(parameterMap.keySet());
208 }
209
210
211
212
213 @Override
214 public Object getParameterValue(String name) {
215 return variables.get(name);
216 }
217
218 }
219
220 private static class MetaClassBasedContext extends AbstractContext {
221
222 private final Object parameterObject;
223 private final MetaClass parameterMetaClass;
224 private final Class<?> parameterType;
225
226 private MetaClassBasedContext(Object parameterObject, MetaClass parameterMetaClass, Class<?> parameterType,
227 DynamicContext dynamicContext, Properties configurationProperties) {
228 super(dynamicContext, configurationProperties);
229 this.parameterObject = parameterObject;
230 this.parameterMetaClass = parameterMetaClass;
231 this.parameterType = parameterType;
232 addVariableNames(Arrays.asList(parameterMetaClass.getGetterNames()));
233 }
234
235
236
237
238 @Override
239 public Object getParameterValue(String name) {
240 try {
241 return parameterMetaClass.getGetInvoker(name).invoke(parameterObject, null);
242 } catch (IllegalAccessException | InvocationTargetException e) {
243 throw new IllegalStateException(
244 String.format("Cannot get a value for property named '%s' in '%s'", name, parameterType), e);
245 }
246 }
247
248 }
249
250 }