• 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

96.55
/src/main/java/org/mybatis/scripting/thymeleaf/SqlGenerator.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.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
 * The sql template engine for integrating with Thymeleaf.
40
 *
41
 * @author Kazuki Shimizu
42
 *
43
 * @version 1.0.2
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();
1✔
53
  private PropertyAccessor propertyAccessor = PropertyAccessor.BuiltIn.STANDARD;
1✔
54
  private BiFunction<Object, Map<String, Object>, IContext> contextFactory = DefaultContext::new;
1✔
55

56
  /**
57
   * Constructor for creating instance with default {@code TemplateEngine}.
58
   */
59
  public SqlGenerator() {
1✔
60
    this.templateEngine = createDefaultTemplateEngine(SqlGeneratorConfig.newInstance());
1✔
61
  }
1✔
62

63
  /**
64
   * Constructor for creating instance with user specified {@link SqlGenerator}.
65
   *
66
   * @param config
67
   *          A user defined {@link SqlGeneratorConfig} instance
68
   */
69
  public SqlGenerator(SqlGeneratorConfig config) {
1✔
70
    this.templateEngine = createDefaultTemplateEngine(config);
1✔
71
  }
1✔
72

73
  /**
74
   * Constructor for creating instance with user defined {@code ITemplateEngine}.
75
   *
76
   * @param templateEngine
77
   *          A user defined {@code ITemplateEngine} instance
78
   */
79
  public SqlGenerator(ITemplateEngine templateEngine) {
1✔
80
    this.templateEngine = templateEngine;
1✔
81
  }
1✔
82

83
  /**
84
   * Set default custom variables.
85
   *
86
   * @param defaultCustomVariables
87
   *          a default custom variables for passing to template engine
88
   */
89
  public void setDefaultCustomVariables(Map<String, Object> defaultCustomVariables) {
90
    this.defaultCustomVariables = Optional.ofNullable(defaultCustomVariables).map(Collections::unmodifiableMap)
1✔
91
        .orElseGet(Collections::emptyMap);
1✔
92
  }
1✔
93

94
  /**
95
   * Get specified default custom variables.
96
   *
97
   * @return specified default custom variables
98
   */
99
  public Map<String, Object> getDefaultCustomVariables() {
100
    return defaultCustomVariables;
×
101
  }
102

103
  /**
104
   * Set a property accessor.
105
   * <p>
106
   * Default is {@link PropertyAccessor.BuiltIn#STANDARD}.
107
   * </p>
108
   *
109
   * @param propertyAccessor
110
   *          a property accessor
111
   */
112
  public void setPropertyAccessor(PropertyAccessor propertyAccessor) {
113
    this.propertyAccessor = Optional.ofNullable(propertyAccessor).orElse(PropertyAccessor.BuiltIn.STANDARD);
1✔
114
  }
1✔
115

116
  /**
117
   * Set a factory function for creating instance of custom context.
118
   *
119
   * @param contextFactory
120
   *          a factory function
121
   */
122
  void setContextFactory(BiFunction<Object, Map<String, Object>, IContext> contextFactory) {
123
    this.contextFactory = contextFactory;
1✔
124
  }
1✔
125

126
  private ITemplateEngine createDefaultTemplateEngine(SqlGeneratorConfig config) {
127
    MyBatisDialect dialect = new MyBatisDialect(config.getDialect().getPrefix());
1✔
128
    Optional.ofNullable(config.getDialect().getBindVariableRenderInstance()).ifPresent(dialect::setBindVariableRender);
1✔
129
    Likes likes = Likes.newBuilder().escapeChar(config.getDialect().getLikeEscapeChar())
1✔
130
        .escapeClauseFormat(config.getDialect().getLikeEscapeClauseFormat())
1✔
131
        .additionalEscapeTargetChars(config.getDialect().getLikeAdditionalEscapeTargetChars()).build();
1✔
132
    dialect.setLikes(likes);
1✔
133

134
    // Create an ClassLoaderTemplateResolver instance
135
    ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
1✔
136
    TemplateMode mode = config.isUse2way() ? TemplateMode.CSS : TemplateMode.TEXT;
1✔
137
    classLoaderTemplateResolver.setOrder(1);
1✔
138
    classLoaderTemplateResolver.setTemplateMode(mode);
1✔
139
    classLoaderTemplateResolver
1✔
140
        .setResolvablePatterns(Arrays.stream(config.getTemplateFile().getPatterns()).collect(Collectors.toSet()));
1✔
141
    classLoaderTemplateResolver.setCharacterEncoding(config.getTemplateFile().getEncoding().name());
1✔
142
    classLoaderTemplateResolver.setCacheable(config.getTemplateFile().isCacheEnabled());
1✔
143
    classLoaderTemplateResolver.setCacheTTLMs(config.getTemplateFile().getCacheTtl());
1✔
144
    classLoaderTemplateResolver.setPrefix(config.getTemplateFile().getBaseDir());
1✔
145

146
    // Create an StringTemplateResolver instance
147
    StringTemplateResolver stringTemplateResolver = new StringTemplateResolver();
1✔
148
    stringTemplateResolver.setOrder(2);
1✔
149
    stringTemplateResolver.setTemplateMode(mode);
1✔
150

151
    // Create an TemplateEngine instance
152
    TemplateEngine targetTemplateEngine = new TemplateEngine();
1✔
153
    targetTemplateEngine.addTemplateResolver(classLoaderTemplateResolver);
1✔
154
    targetTemplateEngine.addTemplateResolver(stringTemplateResolver);
1✔
155
    targetTemplateEngine.addDialect(dialect);
1✔
156
    targetTemplateEngine.setEngineContextFactory(
1✔
157
        new MyBatisIntegratingEngineContextFactory(targetTemplateEngine.getEngineContextFactory()));
1✔
158

159
    // Create an TemplateEngineCustomizer instance and apply
160
    Optional.ofNullable(config.getCustomizerInstance()).ifPresent(x -> x.accept(targetTemplateEngine));
1✔
161

162
    return targetTemplateEngine;
1✔
163
  }
164

165
  /**
166
   * Generate a sql using Thymeleaf template engine.
167
   *
168
   * @param sqlTemplate
169
   *          a template SQL
170
   * @param parameter
171
   *          a parameter object
172
   *
173
   * @return a processed SQL by template engine
174
   */
175
  public String generate(CharSequence sqlTemplate, Object parameter) {
176
    return generate(sqlTemplate, parameter, null, null);
1✔
177
  }
178

179
  /**
180
   * Generate a sql using Thymeleaf template engine.
181
   *
182
   * @param sqlTemplate
183
   *          a template SQL
184
   * @param parameter
185
   *          a parameter object
186
   * @param customBindVariableBinder
187
   *          a binder for a custom bind variable that generated with {@code mb:bind} or {@code mb:param}
188
   *
189
   * @return a processed SQL by template engine
190
   */
191
  public String generate(CharSequence sqlTemplate, Object parameter,
192
      BiConsumer<String, Object> customBindVariableBinder) {
193
    return generate(sqlTemplate, parameter, customBindVariableBinder, null);
1✔
194
  }
195

196
  /**
197
   * Generate a sql using Thymeleaf template engine.
198
   *
199
   * @param sqlTemplate
200
   *          a template SQL
201
   * @param parameter
202
   *          a parameter object
203
   * @param customVariables
204
   *          a custom variables for passing to template engine
205
   *
206
   * @return a processed SQL by template engine
207
   */
208
  public String generate(CharSequence sqlTemplate, Object parameter, Map<String, Object> customVariables) {
209
    return generate(sqlTemplate, parameter, null, customVariables);
1✔
210
  }
211

212
  /**
213
   * Generate a sql using Thymeleaf template engine.
214
   *
215
   * @param sqlTemplate
216
   *          a template SQL
217
   * @param parameter
218
   *          a parameter object
219
   * @param customBindVariableBinder
220
   *          a binder for a custom bind variable that generated with {@code mb:bind} or {@code mb:param}
221
   * @param customVariables
222
   *          a custom variables for passing to template engine
223
   *
224
   * @return a processed SQL by template engine
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);
1✔
230
    Optional.ofNullable(customVariables).ifPresent(processingCustomVariables::putAll);
1✔
231

232
    IContext context = contextFactory.apply(parameter, processingCustomVariables);
1✔
233
    String sql = templateEngine.process(sqlTemplate.toString(), context);
1✔
234

235
    MyBatisBindingContext bindingContext = MyBatisBindingContext.load(context);
1✔
236
    if (bindingContext != null && customBindVariableBinder != null) {
1✔
237
      bindingContext.getCustomBindVariables().forEach(customBindVariableBinder);
1✔
238
    }
239

240
    return sql;
1✔
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<>();
1✔
248
    private final Map<String, Object> customVariables;
249

250
    private DefaultContext(Object parameter, Map<String, Object> customVariables) {
1✔
251
      this.parameter = parameter;
1✔
252
      boolean fallback;
253
      if (parameter instanceof Map) {
1✔
254
        @SuppressWarnings("unchecked")
255
        Map<String, Object> map = (Map<String, Object>) parameter;
1✔
256
        propertyNames.addAll(map.keySet());
1✔
257
        this.mapParameter = map;
1✔
258
        fallback = false;
1✔
259
      } else {
1✔
260
        this.mapParameter = null;
1✔
261
        if (parameter != null) {
1✔
262
          propertyNames.addAll(propertyAccessor.getPropertyNames(parameter.getClass()));
1✔
263
        }
264
        fallback = propertyNames.isEmpty();
1✔
265
      }
266
      MyBatisBindingContext bindingContext = new MyBatisBindingContext(fallback);
1✔
267
      this.customVariables = customVariables;
1✔
268
      customVariables.put(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext);
1✔
269
      customVariables.put(ContextKeys.PARAMETER_OBJECT, parameter);
1✔
270
    }
1✔
271

272
    @Override
273
    public Locale getLocale() {
274
      return Locale.getDefault();
1✔
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());
1✔
285
      variableNames.addAll(propertyNames);
1✔
286
      return variableNames;
1✔
287
    }
288

289
    @Override
290
    public Object getVariable(String name) {
291
      if (customVariables.containsKey(name)) {
1✔
292
        return customVariables.get(name);
1✔
293
      }
294
      if (mapParameter == null) {
1✔
295
        return propertyAccessor.getPropertyValue(parameter, name);
1✔
296
      } else {
297
        return mapParameter.get(name);
1✔
298
      }
299
    }
300

301
  }
302

303
}
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