View Javadoc
1   /*
2    *    Copyright 2015-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.spring.boot.autoconfigure;
17  
18  import static org.assertj.core.api.Assertions.assertThat;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  import java.nio.charset.Charset;
23  import java.nio.charset.StandardCharsets;
24  import java.util.Map;
25  
26  import org.apache.ibatis.mapping.BoundSql;
27  import org.apache.ibatis.mapping.SqlSource;
28  import org.apache.ibatis.scripting.LanguageDriver;
29  import org.apache.ibatis.session.Configuration;
30  import org.apache.velocity.context.InternalContextAdapter;
31  import org.apache.velocity.exception.MethodInvocationException;
32  import org.apache.velocity.exception.ParseErrorException;
33  import org.apache.velocity.exception.ResourceNotFoundException;
34  import org.apache.velocity.runtime.RuntimeConstants;
35  import org.apache.velocity.runtime.directive.Directive;
36  import org.apache.velocity.runtime.parser.node.Node;
37  import org.junit.jupiter.api.AfterEach;
38  import org.junit.jupiter.api.BeforeEach;
39  import org.junit.jupiter.api.Test;
40  import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver;
41  import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig;
42  import org.mybatis.scripting.thymeleaf.TemplateEngineCustomizer;
43  import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver;
44  import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriverConfig;
45  import org.mybatis.scripting.velocity.VelocityFacade;
46  import org.mybatis.scripting.velocity.VelocityLanguageDriver;
47  import org.mybatis.scripting.velocity.VelocityLanguageDriverConfig;
48  import org.springframework.boot.autoconfigure.AutoConfigurations;
49  import org.springframework.boot.context.properties.EnableConfigurationProperties;
50  import org.springframework.boot.test.context.runner.ApplicationContextRunner;
51  import org.springframework.context.annotation.Bean;
52  import org.thymeleaf.TemplateEngine;
53  
54  /**
55   * Tests for {@link MybatisLanguageDriverAutoConfiguration}.
56   *
57   * @author Kazuki Shimizu
58   * @author EddĂș MelĂ©ndez
59   */
60  class MybatisLanguageDriverAutoConfigurationTest {
61  
62    private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
63        .withConfiguration(AutoConfigurations.of(MybatisLanguageDriverAutoConfiguration.class));
64  
65    @BeforeEach
66    @AfterEach
67    void initializeVelocity() {
68      VelocityFacade.destroy();
69    }
70  
71    @Test
72    void testDefaultConfiguration() {
73      this.contextRunner.run(context -> {
74        Map<String, LanguageDriver> languageDriverBeans = context.getBeansOfType(LanguageDriver.class);
75        assertThat(languageDriverBeans).hasSize(3).containsKeys("freeMarkerLanguageDriver", "velocityLanguageDriver",
76            "thymeleafLanguageDriver");
77        assertThat(languageDriverBeans.get("freeMarkerLanguageDriver")).isInstanceOf(FreeMarkerLanguageDriver.class);
78        assertThat(languageDriverBeans.get("velocityLanguageDriver")).isInstanceOf(VelocityLanguageDriver.class);
79        assertThat(languageDriverBeans.get("thymeleafLanguageDriver")).isInstanceOf(ThymeleafLanguageDriver.class);
80  
81        ThymeleafLanguageDriverConfig thymeleafLanguageDriverConfig = context
82            .getBean(ThymeleafLanguageDriverConfig.class);
83        assertThat(thymeleafLanguageDriverConfig.isUse2way()).isTrue();
84        assertThat(thymeleafLanguageDriverConfig.getDialect().getPrefix()).isEqualTo("mb");
85        assertThat(thymeleafLanguageDriverConfig.getDialect().getLikeAdditionalEscapeTargetChars()).isNull();
86        assertThat(thymeleafLanguageDriverConfig.getDialect().getLikeEscapeChar()).isEqualTo('\\');
87        assertThat(thymeleafLanguageDriverConfig.getDialect().getLikeEscapeClauseFormat()).isEqualTo("ESCAPE '%s'");
88        assertThat(thymeleafLanguageDriverConfig.getTemplateFile().getBaseDir()).isEmpty();
89        assertThat(thymeleafLanguageDriverConfig.getTemplateFile().getCacheTtl()).isNull();
90        assertThat(thymeleafLanguageDriverConfig.getTemplateFile().getEncoding()).isEqualTo(StandardCharsets.UTF_8);
91        assertThat(thymeleafLanguageDriverConfig.getTemplateFile().getPatterns()).hasSize(1).contains("*.sql");
92        assertThat(thymeleafLanguageDriverConfig.getCustomizer()).isNull();
93  
94        FreeMarkerLanguageDriverConfig freeMarkerLanguageDriverConfig = context
95            .getBean(FreeMarkerLanguageDriverConfig.class);
96        assertThat(freeMarkerLanguageDriverConfig.getBasePackage()).isEmpty();
97        assertThat(freeMarkerLanguageDriverConfig.getFreemarkerSettings()).isEmpty();
98  
99        VelocityLanguageDriverConfig velocityLanguageDriverConfig = context.getBean(VelocityLanguageDriverConfig.class);
100       @SuppressWarnings("deprecation")
101       String[] userDirective = velocityLanguageDriverConfig.getUserdirective();
102       assertThat(userDirective).isEmpty();
103       assertThat(velocityLanguageDriverConfig.getAdditionalContextAttributes()).isEmpty();
104       assertThat(velocityLanguageDriverConfig.getVelocitySettings()).hasSize(2);
105       assertThat(velocityLanguageDriverConfig.getVelocitySettings()).containsEntry(RuntimeConstants.RESOURCE_LOADERS,
106           "class");
107       assertThat(velocityLanguageDriverConfig.getVelocitySettings()).containsEntry(
108           RuntimeConstants.RESOURCE_LOADER + ".class.class",
109           "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
110       assertThat(velocityLanguageDriverConfig.generateCustomDirectivesString()).isEqualTo(
111           "org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective");
112     });
113   }
114 
115   @Test
116   void testCustomConfiguration() {
117     this.contextRunner.withUserConfiguration(MyLanguageDriverConfig.class).run(context -> {
118       Map<String, LanguageDriver> languageDriverBeans = context.getBeansOfType(LanguageDriver.class);
119       assertThat(languageDriverBeans).hasSize(3).containsKeys("myFreeMarkerLanguageDriver", "myVelocityLanguageDriver",
120           "myThymeleafLanguageDriver");
121     });
122   }
123 
124   @Test
125   @SuppressWarnings("deprecation")
126   void testLegacyConfiguration() {
127     new ApplicationContextRunner()
128         .withUserConfiguration(TestingLegacyFreeMarkerConfiguration.class, TestingLegacyVelocityConfiguration.class)
129         .run(context -> {
130           Map<String, LanguageDriver> languageDriverBeans = context.getBeansOfType(LanguageDriver.class);
131           assertThat(languageDriverBeans).hasSize(2).containsKeys("freeMarkerLanguageDriver", "velocityLanguageDriver");
132           assertThat(context.getBean(org.mybatis.scripting.velocity.Driver.class)).isNotNull();
133           assertThat(context.getBean(FreeMarkerLanguageDriver.class)).isNotNull();
134           assertThat(context.getBeanNamesForType(VelocityLanguageDriverConfig.class)).isEmpty();
135           assertThat(context.getBeanNamesForType(FreeMarkerLanguageDriverConfig.class)).isEmpty();
136         });
137   }
138 
139   @Test
140   void testCustomThymeleafConfig() {
141     this.contextRunner.withUserConfiguration(ThymeleafCustomLanguageDriverConfig.class).run(context -> {
142       ThymeleafLanguageDriver driver = context.getBean(ThymeleafLanguageDriver.class);
143       SqlSource sqlSource = driver.createSqlSource(new Configuration(),
144           "SELECT * FROM users WHERE id = /*[# m:p='id']*/ 1 /*[/]*/", Integer.class);
145       BoundSql boundSql = sqlSource.getBoundSql(10);
146       assertThat(boundSql.getSql()).isEqualTo("SELECT * FROM users WHERE id = ?");
147       assertThat(boundSql.getParameterObject()).isEqualTo(10);
148       assertThat(boundSql.getParameterMappings().get(0).getProperty()).isEqualTo("id");
149       assertThat(boundSql.getParameterMappings().get(0).getJavaType()).isEqualTo(Integer.class);
150       ThymeleafLanguageDriverConfig config = context.getBean(ThymeleafLanguageDriverConfig.class);
151       assertThat(config.isUse2way()).isTrue();
152       assertThat(config.getDialect().getPrefix()).isEqualTo("m");
153       assertThat(config.getDialect().getLikeAdditionalEscapeTargetChars()).isNull();
154       assertThat(config.getDialect().getLikeEscapeChar()).isEqualTo('\\');
155       assertThat(config.getDialect().getLikeEscapeClauseFormat()).isEqualTo("ESCAPE '%s'");
156       assertThat(config.getTemplateFile().getBaseDir()).isEmpty();
157       assertThat(config.getTemplateFile().getCacheTtl()).isNull();
158       assertThat(config.getTemplateFile().getEncoding()).isEqualTo(StandardCharsets.UTF_8);
159       assertThat(config.getTemplateFile().getPatterns()).hasSize(1).contains("*.sql");
160       assertThat(config.getCustomizer()).isNull();
161     });
162   }
163 
164   @Test
165   void testCustomFreeMarkerConfig() {
166     this.contextRunner.withUserConfiguration(FreeMarkerCustomLanguageDriverConfig.class).run(context -> {
167       FreeMarkerLanguageDriver driver = context.getBean(FreeMarkerLanguageDriver.class);
168       @SuppressWarnings("unused")
169       class Param {
170         private Integer id;
171         private Integer version;
172       }
173       Param params = new Param();
174       params.id = 10;
175       params.version = 20;
176       SqlSource sqlSource = driver.createSqlSource(new Configuration(),
177           "SELECT * FROM users WHERE id = #{id} and version = <@p name='version'/>", Param.class);
178       BoundSql boundSql = sqlSource.getBoundSql(params);
179       assertThat(boundSql.getSql()).isEqualTo("SELECT * FROM users WHERE id = ? and version = ?");
180       assertThat(boundSql.getParameterMappings().get(0).getProperty()).isEqualTo("id");
181       assertThat(boundSql.getParameterMappings().get(0).getJavaType()).isEqualTo(Integer.class);
182       assertThat(boundSql.getParameterMappings().get(1).getProperty()).isEqualTo("version");
183       assertThat(boundSql.getParameterMappings().get(1).getJavaType()).isEqualTo(Integer.class);
184       FreeMarkerLanguageDriverConfig config = context.getBean(FreeMarkerLanguageDriverConfig.class);
185       assertThat(config.getBasePackage()).isEmpty();
186       assertThat(config.getFreemarkerSettings()).hasSize(1);
187       assertThat(config.getFreemarkerSettings()).containsEntry("interpolation_syntax", "dollar");
188     });
189   }
190 
191   @Test
192   void testCustomVelocityConfig() {
193     this.contextRunner.withUserConfiguration(VelocityCustomLanguageDriverConfig.class).run(context -> {
194       VelocityLanguageDriver driver = context.getBean(VelocityLanguageDriver.class);
195       @SuppressWarnings("unused")
196       class Param {
197         private Integer id;
198         private Integer version;
199       }
200       Param params = new Param();
201       params.id = 10;
202       params.version = 20;
203       SqlSource sqlSource = driver.createSqlSource(new Configuration(), "#now()", Param.class);
204       BoundSql boundSql = sqlSource.getBoundSql(params);
205       assertThat(boundSql.getSql()).isEqualTo("SELECT CURRENT_TIMESTAMP");
206       VelocityLanguageDriverConfig config = context.getBean(VelocityLanguageDriverConfig.class);
207       @SuppressWarnings("deprecation")
208       String[] userDirective = config.getUserdirective();
209       assertThat(userDirective).isEmpty();
210       assertThat(config.getAdditionalContextAttributes()).isEmpty();
211       assertThat(config.getVelocitySettings()).hasSize(3);
212       assertThat(config.getVelocitySettings()).containsEntry(RuntimeConstants.RESOURCE_LOADERS, "class");
213       assertThat(config.getVelocitySettings()).containsEntry(RuntimeConstants.RESOURCE_LOADER + ".class.class",
214           "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
215       assertThat(config.generateCustomDirectivesString()).isEqualTo(NowDirective.class.getName()
216           + ",org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective");
217     });
218   }
219 
220   @Test
221   void testCustomThymeleafConfigUsingConfigurationProperty() {
222     this.contextRunner.withUserConfiguration(MyAutoConfiguration.class).withPropertyValues(
223         "mybatis.scripting-language-driver.thymeleaf.use2way=false",
224         "mybatis.scripting-language-driver.thymeleaf.dialect.like-additional-escape-target-chars=*,?",
225         "mybatis.scripting-language-driver.thymeleaf.dialect.like-escape-char=~",
226         "mybatis.scripting-language-driver.thymeleaf.dialect.like-escape-clause-format=escape '%s'",
227         "mybatis.scripting-language-driver.thymeleaf.dialect.prefix=mybatis",
228         "mybatis.scripting-language-driver.thymeleaf.template-file.base-dir=sqls",
229         "mybatis.scripting-language-driver.thymeleaf.template-file.cache-enabled=false",
230         "mybatis.scripting-language-driver.thymeleaf.template-file.cache-ttl=1234",
231         "mybatis.scripting-language-driver.thymeleaf.template-file.encoding=Windows-31J",
232         "mybatis.scripting-language-driver.thymeleaf.template-file.patterns=*.sql,*.sqlf",
233         "mybatis.scripting-language-driver.thymeleaf.template-file.path-provider.prefix=sql/",
234         "mybatis.scripting-language-driver.thymeleaf.template-file.path-provider.includes-package-path=false",
235         "mybatis.scripting-language-driver.thymeleaf.template-file.path-provider.separate-directory-per-mapper=false",
236         "mybatis.scripting-language-driver.thymeleaf.template-file.path-provider.includes-mapper-name-when-separate-directory=false",
237         "mybatis.scripting-language-driver.thymeleaf.template-file.path-provider.cache-enabled=false",
238         "mybatis.scripting-language-driver.thymeleaf.customizer=org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfigurationTest$MyTemplateEngineCustomizer")
239         .run(context -> {
240           ThymeleafLanguageDriver driver = context.getBean(ThymeleafLanguageDriver.class);
241           SqlSource sqlSource = driver.createSqlSource(new Configuration(),
242               "SELECT * FROM users WHERE id = [# mybatis:p='id' /]", Integer.class);
243           BoundSql boundSql = sqlSource.getBoundSql(10);
244           assertThat(boundSql.getSql()).isEqualTo("SELECT * FROM users WHERE id = ?");
245           assertThat(boundSql.getParameterObject()).isEqualTo(10);
246           assertThat(boundSql.getParameterMappings().get(0).getProperty()).isEqualTo("id");
247           assertThat(boundSql.getParameterMappings().get(0).getJavaType()).isEqualTo(Integer.class);
248           ThymeleafLanguageDriverConfig config = context.getBean(ThymeleafLanguageDriverConfig.class);
249           assertThat(config.isUse2way()).isFalse();
250           assertThat(config.getDialect().getPrefix()).isEqualTo("mybatis");
251           assertThat(config.getDialect().getLikeAdditionalEscapeTargetChars()).hasSize(2).contains('*', '?');
252           assertThat(config.getDialect().getLikeEscapeChar()).isEqualTo('~');
253           assertThat(config.getDialect().getLikeEscapeClauseFormat()).isEqualTo("escape '%s'");
254           assertThat(config.getTemplateFile().getBaseDir()).isEqualTo("sqls");
255           assertThat(config.getTemplateFile().getCacheTtl()).isEqualTo(1234);
256           assertThat(config.getTemplateFile().getEncoding()).isEqualTo(Charset.forName("Windows-31J"));
257           assertThat(config.getTemplateFile().getPatterns()).hasSize(2).contains("*.sql", "*.sqlf");
258           assertThat(config.getTemplateFile().getPathProvider().getPrefix()).isEqualTo("sql/");
259           assertThat(config.getTemplateFile().getPathProvider().isIncludesPackagePath()).isFalse();
260           assertThat(config.getTemplateFile().getPathProvider().isSeparateDirectoryPerMapper()).isFalse();
261           assertThat(config.getTemplateFile().getPathProvider().isIncludesMapperNameWhenSeparateDirectory()).isFalse();
262           assertThat(config.getTemplateFile().getPathProvider().isCacheEnabled()).isFalse();
263           assertThat(config.getCustomizer()).isEqualTo(MyTemplateEngineCustomizer.class);
264         });
265   }
266 
267   @Test
268   void testCustomFreeMarkerConfigUsingConfigurationProperty() {
269     this.contextRunner.withUserConfiguration(MyAutoConfiguration.class)
270         .withPropertyValues("mybatis.scripting-language-driver.freemarker.base-package=sqls",
271             "mybatis.scripting-language-driver.freemarker.freemarker-settings.interpolation_syntax=dollar",
272             "mybatis.scripting-language-driver.freemarker.freemarker-settings.whitespace_stripping=yes")
273         .run(context -> {
274           FreeMarkerLanguageDriver driver = context.getBean(FreeMarkerLanguageDriver.class);
275           @SuppressWarnings("unused")
276           class Param {
277             private Integer id;
278             private Integer version;
279           }
280           Param params = new Param();
281           params.id = 10;
282           params.version = 20;
283           SqlSource sqlSource = driver.createSqlSource(new Configuration(),
284               "SELECT * FROM users WHERE id = #{id} and version = <@p name='version'/>", Param.class);
285           BoundSql boundSql = sqlSource.getBoundSql(params);
286           assertThat(boundSql.getSql()).isEqualTo("SELECT * FROM users WHERE id = ? and version = ?");
287           assertThat(boundSql.getParameterMappings().get(0).getProperty()).isEqualTo("id");
288           assertThat(boundSql.getParameterMappings().get(0).getJavaType()).isEqualTo(Integer.class);
289           assertThat(boundSql.getParameterMappings().get(1).getProperty()).isEqualTo("version");
290           assertThat(boundSql.getParameterMappings().get(1).getJavaType()).isEqualTo(Integer.class);
291           FreeMarkerLanguageDriverConfig config = context.getBean(FreeMarkerLanguageDriverConfig.class);
292           assertThat(config.getBasePackage()).isEqualTo("sqls");
293           assertThat(config.getFreemarkerSettings()).hasSize(2);
294           assertThat(config.getFreemarkerSettings()).containsEntry("interpolation_syntax", "dollar");
295           assertThat(config.getFreemarkerSettings()).containsEntry("whitespace_stripping", "yes");
296         });
297   }
298 
299   @Test
300   void testCustomVelocityConfigUsingConfigurationProperty() {
301     this.contextRunner.withUserConfiguration(MyAutoConfiguration.class)
302         .withPropertyValues("mybatis.scripting-language-driver.velocity.userdirective=" + NowDirective.class.getName(),
303             "mybatis.scripting-language-driver.velocity.velocity-settings." + RuntimeConstants.INPUT_ENCODING + "="
304                 + RuntimeConstants.ENCODING_DEFAULT,
305             "mybatis.scripting-language-driver.velocity.additional-context-attributes.attribute1=java.lang.String",
306             "mybatis.scripting-language-driver.velocity.additional-context-attributes.attribute2=java.util.HashMap")
307         .run(context -> {
308           VelocityLanguageDriver driver = context.getBean(VelocityLanguageDriver.class);
309           @SuppressWarnings("unused")
310           class Param {
311             private Integer id;
312             private Integer version;
313           }
314           Param params = new Param();
315           params.id = 10;
316           params.version = 20;
317           SqlSource sqlSource = driver.createSqlSource(new Configuration(), "#now()", Param.class);
318           BoundSql boundSql = sqlSource.getBoundSql(params);
319           assertThat(boundSql.getSql()).isEqualTo("SELECT CURRENT_TIMESTAMP");
320           VelocityLanguageDriverConfig config = context.getBean(VelocityLanguageDriverConfig.class);
321           @SuppressWarnings("deprecation")
322           String[] userDirective = config.getUserdirective();
323           assertThat(userDirective).hasSize(1).contains(NowDirective.class.getName());
324           assertThat(config.getAdditionalContextAttributes()).hasSize(2);
325           assertThat(config.getAdditionalContextAttributes()).containsEntry("attribute1", "java.lang.String");
326           assertThat(config.getAdditionalContextAttributes()).containsEntry("attribute2", "java.util.HashMap");
327           assertThat(config.getVelocitySettings()).hasSize(3);
328           assertThat(config.getVelocitySettings()).containsEntry(RuntimeConstants.RESOURCE_LOADERS, "class");
329           assertThat(config.getVelocitySettings()).containsEntry(RuntimeConstants.RESOURCE_LOADER + ".class.class",
330               "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
331           assertThat(config.generateCustomDirectivesString()).isEqualTo(NowDirective.class.getName()
332               + ",org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective");
333           assertThat(config.getVelocitySettings()).containsEntry(RuntimeConstants.INPUT_ENCODING,
334               RuntimeConstants.ENCODING_DEFAULT);
335         });
336   }
337 
338   @Test
339   void testExcludeMybatisLanguageDriverAutoConfiguration() {
340     new ApplicationContextRunner().withUserConfiguration(MyAutoConfiguration.class)
341         .run(context -> assertThat(context.getBeanNamesForType(LanguageDriver.class)).isEmpty());
342   }
343 
344   @EnableConfigurationProperties
345   static class MyAutoConfiguration {
346   }
347 
348   @org.springframework.context.annotation.Configuration
349   static class TestingLegacyFreeMarkerConfiguration
350       extends MybatisLanguageDriverAutoConfiguration.LegacyFreeMarkerConfiguration {
351   }
352 
353   @org.springframework.context.annotation.Configuration
354   static class TestingLegacyVelocityConfiguration
355       extends MybatisLanguageDriverAutoConfiguration.LegacyVelocityConfiguration {
356   }
357 
358   @org.springframework.context.annotation.Configuration
359   static class MyLanguageDriverConfig {
360     @Bean
361     FreeMarkerLanguageDriver myFreeMarkerLanguageDriver() {
362       return new FreeMarkerLanguageDriver();
363     }
364 
365     @Bean
366     VelocityLanguageDriver myVelocityLanguageDriver() {
367       return new VelocityLanguageDriver();
368     }
369 
370     @Bean
371     ThymeleafLanguageDriver myThymeleafLanguageDriver() {
372       return new ThymeleafLanguageDriver();
373     }
374   }
375 
376   @org.springframework.context.annotation.Configuration
377   static class ThymeleafCustomLanguageDriverConfig {
378     @Bean
379     ThymeleafLanguageDriverConfig thymeleafLanguageDriverConfig() {
380       return ThymeleafLanguageDriverConfig.newInstance(c -> c.getDialect().setPrefix("m"));
381     }
382   }
383 
384   @org.springframework.context.annotation.Configuration
385   static class FreeMarkerCustomLanguageDriverConfig {
386     @Bean
387     FreeMarkerLanguageDriverConfig freeMarkerLanguageDriverConfig() {
388       return FreeMarkerLanguageDriverConfig
389           .newInstance(c -> c.getFreemarkerSettings().put("interpolation_syntax", "dollar"));
390     }
391   }
392 
393   @org.springframework.context.annotation.Configuration
394   static class VelocityCustomLanguageDriverConfig {
395     @Bean
396     VelocityLanguageDriverConfig velocityLanguageDriverConfig() {
397       return VelocityLanguageDriverConfig.newInstance(
398           c -> c.getVelocitySettings().put(RuntimeConstants.CUSTOM_DIRECTIVES, NowDirective.class.getName()));
399     }
400   }
401 
402   public static class MyTemplateEngineCustomizer implements TemplateEngineCustomizer {
403     @Override
404     public void customize(TemplateEngine defaultTemplateEngine) {
405     }
406   }
407 
408   public static class NowDirective extends Directive {
409 
410     @Override
411     public String getName() {
412       return "now";
413     }
414 
415     @Override
416     public int getType() {
417       return LINE;
418     }
419 
420     @Override
421     public boolean render(InternalContextAdapter context, Writer writer, Node node)
422         throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
423       writer.append("SELECT CURRENT_TIMESTAMP");
424       return true;
425     }
426 
427   }
428 }