• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / thymeleaf-scripting / #1164

pending completion
#1164

Pull #189

github

web-flow
Merge e930cb29c into 34bbfca66
Pull Request #189: Update dependency org.mybatis:mybatis-parent to v38

611 of 621 relevant lines covered (98.39%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.53
/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafSqlSource.java
1
/*
2
 *    Copyright 2018-2022 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
 *       https://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.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
 * The {@code SqlSource} for integrating with Thymeleaf.
39
 *
40
 * @author Kazuki Shimizu
41
 *
42
 * @version 1.0.0
43
 *
44
 * @see ThymeleafLanguageDriver
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
   * Constructor for for integrating with template engine provide by Thymeleaf.
62
   *
63
   * @param configuration
64
   *          A configuration instance of MyBatis
65
   * @param sqlGenerator
66
   *          A sql generator using the Thymeleaf feature
67
   * @param sqlTemplate
68
   *          A template string of SQL (inline SQL or template file path)
69
   * @param parameterType
70
   *          A parameter type that specified at mapper method argument or xml element
71
   */
72
  ThymeleafSqlSource(Configuration configuration, SqlGenerator sqlGenerator, String sqlTemplate,
73
      Class<?> parameterType) {
1✔
74
    this.configuration = configuration;
1✔
75
    this.sqlGenerator = sqlGenerator;
1✔
76
    this.sqlTemplate = sqlTemplate;
1✔
77
    this.parameterType = parameterType;
1✔
78
    this.sqlSourceBuilder = new SqlSourceBuilder(configuration);
1✔
79
  }
1✔
80

81
  /**
82
   * {@inheritDoc}
83
   */
84
  @Override
85
  public BoundSql getBoundSql(Object parameterObject) {
86
    Class<?> processingParameterType;
87
    if (parameterType == null) {
1✔
88
      processingParameterType = parameterObject == null ? Object.class : parameterObject.getClass();
1✔
89
    } else {
90
      processingParameterType = parameterType;
1✔
91
    }
92

93
    DynamicContext dynamicContext = new DynamicContext(configuration, parameterObject);
1✔
94
    Map<String, Object> customVariables = dynamicContext.getBindings();
1✔
95
    customVariables.put(TemporaryTakeoverKeys.CONFIGURATION, configuration);
1✔
96
    customVariables.put(TemporaryTakeoverKeys.DYNAMIC_CONTEXT, dynamicContext);
1✔
97
    customVariables.put(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE, processingParameterType);
1✔
98
    String sql = sqlGenerator.generate(sqlTemplate, parameterObject, dynamicContext::bind, customVariables);
1✔
99

100
    SqlSource sqlSource = sqlSourceBuilder.parse(sql, processingParameterType, dynamicContext.getBindings());
1✔
101
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
1✔
102
    dynamicContext.getBindings().forEach(boundSql::setAdditionalParameter);
1✔
103

104
    return boundSql;
1✔
105
  }
106

107
  /**
108
   * The factory class for Thymeleaf's context.
109
   *
110
   * @since 1.0.2
111
   */
112
  static class ContextFactory implements BiFunction<Object, Map<String, Object>, IContext> {
1✔
113
    /**
114
     * {@inheritDoc}
115
     */
116
    @Override
117
    public IContext apply(Object parameter, Map<String, Object> customVariable) {
118
      Configuration configuration = (Configuration) customVariable.remove(TemporaryTakeoverKeys.CONFIGURATION);
1✔
119
      DynamicContext dynamicContext = (DynamicContext) customVariable.remove(TemporaryTakeoverKeys.DYNAMIC_CONTEXT);
1✔
120
      Class<?> processingParameterType = (Class<?>) customVariable
1✔
121
          .remove(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE);
1✔
122
      MyBatisBindingContext bindingContext = new MyBatisBindingContext(
1✔
123
          parameter != null && configuration.getTypeHandlerRegistry().hasTypeHandler(processingParameterType));
1✔
124
      dynamicContext.bind(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext);
1✔
125
      IContext context;
126
      if (parameter instanceof Map) {
1✔
127
        @SuppressWarnings(value = "unchecked")
128
        Map<String, Object> map = (Map<String, Object>) parameter;
1✔
129
        context = new MapBasedContext(map, dynamicContext, configuration.getVariables());
1✔
130
      } else {
1✔
131
        MetaClass metaClass = MetaClass.forClass(processingParameterType, configuration.getReflectorFactory());
1✔
132
        context = new MetaClassBasedContext(parameter, metaClass, processingParameterType, dynamicContext,
1✔
133
            configuration.getVariables());
1✔
134
      }
135
      return context;
1✔
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) {
1✔
146
      this.dynamicContext = dynamicContext;
1✔
147
      this.configurationProperties = configurationProperties;
1✔
148
      this.variableNames = new HashSet<>();
1✔
149
      addVariableNames(dynamicContext.getBindings().keySet());
1✔
150
      Optional.ofNullable(configurationProperties).ifPresent(v -> addVariableNames(v.stringPropertyNames()));
1✔
151
    }
1✔
152

153
    void addVariableNames(Collection<String> names) {
154
      variableNames.addAll(names);
1✔
155
    }
1✔
156

157
    /**
158
     * {@inheritDoc}
159
     */
160
    @Override
161
    public Locale getLocale() {
162
      return Locale.getDefault();
1✔
163
    }
164

165
    /**
166
     * {@inheritDoc}
167
     */
168
    @Override
169
    public boolean containsVariable(String name) {
170
      return variableNames.contains(name);
×
171
    }
172

173
    /**
174
     * {@inheritDoc}
175
     */
176
    @Override
177
    public Set<String> getVariableNames() {
178
      return variableNames;
1✔
179
    }
180

181
    /**
182
     * {@inheritDoc}
183
     */
184
    @Override
185
    public Object getVariable(String name) {
186
      if (dynamicContext.getBindings().containsKey(name)) {
1✔
187
        return dynamicContext.getBindings().get(name);
1✔
188
      }
189
      if (configurationProperties != null && configurationProperties.containsKey(name)) {
1✔
190
        return configurationProperties.getProperty(name);
1✔
191
      }
192
      return getParameterValue(name);
1✔
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);
1✔
206
      this.variables = parameterMap;
1✔
207
      addVariableNames(parameterMap.keySet());
1✔
208
    }
1✔
209

210
    /**
211
     * {@inheritDoc}
212
     */
213
    @Override
214
    public Object getParameterValue(String name) {
215
      return variables.get(name);
1✔
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);
1✔
229
      this.parameterObject = parameterObject;
1✔
230
      this.parameterMetaClass = parameterMetaClass;
1✔
231
      this.parameterType = parameterType;
1✔
232
      addVariableNames(Arrays.asList(parameterMetaClass.getGetterNames()));
1✔
233
    }
1✔
234

235
    /**
236
     * {@inheritDoc}
237
     */
238
    @Override
239
    public Object getParameterValue(String name) {
240
      try {
241
        return parameterMetaClass.getGetInvoker(name).invoke(parameterObject, null);
1✔
242
      } catch (IllegalAccessException | InvocationTargetException e) {
1✔
243
        throw new IllegalStateException(
1✔
244
            String.format("Cannot get a value for property named '%s' in '%s'", name, parameterType), e);
1✔
245
      }
246
    }
247

248
  }
249

250
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc