ThymeleafSqlSource.java
- /*
- * Copyright 2018-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.mybatis.scripting.thymeleaf;
- import java.lang.reflect.InvocationTargetException;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.HashSet;
- import java.util.Locale;
- import java.util.Map;
- import java.util.Optional;
- import java.util.Properties;
- import java.util.Set;
- import java.util.function.BiFunction;
- import org.apache.ibatis.builder.SqlSourceBuilder;
- import org.apache.ibatis.mapping.BoundSql;
- import org.apache.ibatis.mapping.SqlSource;
- import org.apache.ibatis.reflection.MetaClass;
- import org.apache.ibatis.scripting.xmltags.DynamicContext;
- import org.apache.ibatis.session.Configuration;
- import org.thymeleaf.context.IContext;
- /**
- * The {@code SqlSource} for integrating with Thymeleaf.
- *
- * @author Kazuki Shimizu
- *
- * @version 1.0.0
- *
- * @see ThymeleafLanguageDriver
- */
- class ThymeleafSqlSource implements SqlSource {
- private static class TemporaryTakeoverKeys {
- private static final String CONFIGURATION = "__configuration__";
- private static final String DYNAMIC_CONTEXT = "__dynamicContext__";
- private static final String PROCESSING_PARAMETER_TYPE = "__processingParameterType__";
- }
- private final Configuration configuration;
- private final SqlGenerator sqlGenerator;
- private final SqlSourceBuilder sqlSourceBuilder;
- private final String sqlTemplate;
- private final Class<?> parameterType;
- /**
- * Constructor for for integrating with template engine provide by Thymeleaf.
- *
- * @param configuration
- * A configuration instance of MyBatis
- * @param sqlGenerator
- * A sql generator using the Thymeleaf feature
- * @param sqlTemplate
- * A template string of SQL (inline SQL or template file path)
- * @param parameterType
- * A parameter type that specified at mapper method argument or xml element
- */
- ThymeleafSqlSource(Configuration configuration, SqlGenerator sqlGenerator, String sqlTemplate,
- Class<?> parameterType) {
- this.configuration = configuration;
- this.sqlGenerator = sqlGenerator;
- this.sqlTemplate = sqlTemplate;
- this.parameterType = parameterType;
- this.sqlSourceBuilder = new SqlSourceBuilder(configuration);
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public BoundSql getBoundSql(Object parameterObject) {
- Class<?> processingParameterType;
- if (parameterType == null) {
- processingParameterType = parameterObject == null ? Object.class : parameterObject.getClass();
- } else {
- processingParameterType = parameterType;
- }
- DynamicContext dynamicContext = new DynamicContext(configuration, parameterObject);
- Map<String, Object> customVariables = dynamicContext.getBindings();
- customVariables.put(TemporaryTakeoverKeys.CONFIGURATION, configuration);
- customVariables.put(TemporaryTakeoverKeys.DYNAMIC_CONTEXT, dynamicContext);
- customVariables.put(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE, processingParameterType);
- String sql = sqlGenerator.generate(sqlTemplate, parameterObject, dynamicContext::bind, customVariables);
- SqlSource sqlSource = sqlSourceBuilder.parse(sql, processingParameterType, dynamicContext.getBindings());
- BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
- dynamicContext.getBindings().forEach(boundSql::setAdditionalParameter);
- return boundSql;
- }
- /**
- * The factory class for Thymeleaf's context.
- *
- * @since 1.0.2
- */
- static class ContextFactory implements BiFunction<Object, Map<String, Object>, IContext> {
- /**
- * {@inheritDoc}
- */
- @Override
- public IContext apply(Object parameter, Map<String, Object> customVariable) {
- Configuration configuration = (Configuration) customVariable.remove(TemporaryTakeoverKeys.CONFIGURATION);
- DynamicContext dynamicContext = (DynamicContext) customVariable.remove(TemporaryTakeoverKeys.DYNAMIC_CONTEXT);
- Class<?> processingParameterType = (Class<?>) customVariable
- .remove(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE);
- MyBatisBindingContext bindingContext = new MyBatisBindingContext(
- parameter != null && configuration.getTypeHandlerRegistry().hasTypeHandler(processingParameterType));
- dynamicContext.bind(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext);
- IContext context;
- if (parameter instanceof Map) {
- @SuppressWarnings(value = "unchecked")
- Map<String, Object> map = (Map<String, Object>) parameter;
- context = new MapBasedContext(map, dynamicContext, configuration.getVariables());
- } else {
- MetaClass metaClass = MetaClass.forClass(processingParameterType, configuration.getReflectorFactory());
- context = new MetaClassBasedContext(parameter, metaClass, processingParameterType, dynamicContext,
- configuration.getVariables());
- }
- return context;
- }
- }
- private abstract static class AbstractContext implements IContext {
- private final DynamicContext dynamicContext;
- private final Properties configurationProperties;
- private final Set<String> variableNames;
- private AbstractContext(DynamicContext dynamicContext, Properties configurationProperties) {
- this.dynamicContext = dynamicContext;
- this.configurationProperties = configurationProperties;
- this.variableNames = new HashSet<>();
- addVariableNames(dynamicContext.getBindings().keySet());
- Optional.ofNullable(configurationProperties).ifPresent(v -> addVariableNames(v.stringPropertyNames()));
- }
- void addVariableNames(Collection<String> names) {
- variableNames.addAll(names);
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public Locale getLocale() {
- return Locale.getDefault();
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean containsVariable(String name) {
- return variableNames.contains(name);
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public Set<String> getVariableNames() {
- return variableNames;
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public Object getVariable(String name) {
- if (dynamicContext.getBindings().containsKey(name)) {
- return dynamicContext.getBindings().get(name);
- }
- if (configurationProperties != null && configurationProperties.containsKey(name)) {
- return configurationProperties.getProperty(name);
- }
- return getParameterValue(name);
- }
- abstract Object getParameterValue(String name);
- }
- private static class MapBasedContext extends AbstractContext {
- private final Map<String, Object> variables;
- private MapBasedContext(Map<String, Object> parameterMap, DynamicContext dynamicContext,
- Properties configurationProperties) {
- super(dynamicContext, configurationProperties);
- this.variables = parameterMap;
- addVariableNames(parameterMap.keySet());
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public Object getParameterValue(String name) {
- return variables.get(name);
- }
- }
- private static class MetaClassBasedContext extends AbstractContext {
- private final Object parameterObject;
- private final MetaClass parameterMetaClass;
- private final Class<?> parameterType;
- private MetaClassBasedContext(Object parameterObject, MetaClass parameterMetaClass, Class<?> parameterType,
- DynamicContext dynamicContext, Properties configurationProperties) {
- super(dynamicContext, configurationProperties);
- this.parameterObject = parameterObject;
- this.parameterMetaClass = parameterMetaClass;
- this.parameterType = parameterType;
- addVariableNames(Arrays.asList(parameterMetaClass.getGetterNames()));
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public Object getParameterValue(String name) {
- try {
- return parameterMetaClass.getGetInvoker(name).invoke(parameterObject, null);
- } catch (IllegalAccessException | InvocationTargetException e) {
- throw new IllegalStateException(
- String.format("Cannot get a value for property named '%s' in '%s'", name, parameterType), e);
- }
- }
- }
- }