All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mybatis.scripting.thymeleaf.SqlGenerator Maven / Gradle / Ivy

/*
 *    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.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import org.mybatis.scripting.thymeleaf.expression.Likes;
import org.thymeleaf.ITemplateEngine;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.IContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.StringTemplateResolver;

/**
 * The sql template engine for integrating with Thymeleaf.
 *
 * @author Kazuki Shimizu
 *
 * @version 1.0.2
 */
public class SqlGenerator {

  static class ContextKeys {
    static final String PARAMETER_OBJECT = "_parameter";
  }

  private final ITemplateEngine templateEngine;
  private Map defaultCustomVariables = Collections.emptyMap();
  private PropertyAccessor propertyAccessor = PropertyAccessor.BuiltIn.STANDARD;
  private BiFunction, IContext> contextFactory = DefaultContext::new;

  /**
   * Constructor for creating instance with default {@code TemplateEngine}.
   */
  public SqlGenerator() {
    this.templateEngine = createDefaultTemplateEngine(SqlGeneratorConfig.newInstance());
  }

  /**
   * Constructor for creating instance with user specified {@link SqlGenerator}.
   *
   * @param config
   *          A user defined {@link SqlGeneratorConfig} instance
   */
  public SqlGenerator(SqlGeneratorConfig config) {
    this.templateEngine = createDefaultTemplateEngine(config);
  }

  /**
   * Constructor for creating instance with user defined {@code ITemplateEngine}.
   *
   * @param templateEngine
   *          A user defined {@code ITemplateEngine} instance
   */
  public SqlGenerator(ITemplateEngine templateEngine) {
    this.templateEngine = templateEngine;
  }

  /**
   * Set default custom variables.
   *
   * @param defaultCustomVariables
   *          a default custom variables for passing to template engine
   */
  public void setDefaultCustomVariables(Map defaultCustomVariables) {
    this.defaultCustomVariables = Optional.ofNullable(defaultCustomVariables).map(Collections::unmodifiableMap)
        .orElseGet(Collections::emptyMap);
  }

  /**
   * Get specified default custom variables.
   *
   * @return specified default custom variables
   */
  public Map getDefaultCustomVariables() {
    return defaultCustomVariables;
  }

  /**
   * Set a property accessor.
   * 

* Default is {@link PropertyAccessor.BuiltIn#STANDARD}. *

* * @param propertyAccessor * a property accessor */ public void setPropertyAccessor(PropertyAccessor propertyAccessor) { this.propertyAccessor = Optional.ofNullable(propertyAccessor).orElse(PropertyAccessor.BuiltIn.STANDARD); } /** * Set a factory function for creating instance of custom context. * * @param contextFactory * a factory function */ void setContextFactory(BiFunction, IContext> contextFactory) { this.contextFactory = contextFactory; } private ITemplateEngine createDefaultTemplateEngine(SqlGeneratorConfig config) { MyBatisDialect dialect = new MyBatisDialect(config.getDialect().getPrefix()); Optional.ofNullable(config.getDialect().getBindVariableRenderInstance()).ifPresent(dialect::setBindVariableRender); Likes likes = Likes.newBuilder().escapeChar(config.getDialect().getLikeEscapeChar()) .escapeClauseFormat(config.getDialect().getLikeEscapeClauseFormat()) .additionalEscapeTargetChars(config.getDialect().getLikeAdditionalEscapeTargetChars()).build(); dialect.setLikes(likes); // Create an ClassLoaderTemplateResolver instance ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver(); TemplateMode mode = config.isUse2way() ? TemplateMode.CSS : TemplateMode.TEXT; classLoaderTemplateResolver.setOrder(1); classLoaderTemplateResolver.setTemplateMode(mode); classLoaderTemplateResolver .setResolvablePatterns(Arrays.stream(config.getTemplateFile().getPatterns()).collect(Collectors.toSet())); classLoaderTemplateResolver.setCharacterEncoding(config.getTemplateFile().getEncoding().name()); classLoaderTemplateResolver.setCacheable(config.getTemplateFile().isCacheEnabled()); classLoaderTemplateResolver.setCacheTTLMs(config.getTemplateFile().getCacheTtl()); classLoaderTemplateResolver.setPrefix(config.getTemplateFile().getBaseDir()); // Create an StringTemplateResolver instance StringTemplateResolver stringTemplateResolver = new StringTemplateResolver(); stringTemplateResolver.setOrder(2); stringTemplateResolver.setTemplateMode(mode); // Create an TemplateEngine instance TemplateEngine targetTemplateEngine = new TemplateEngine(); targetTemplateEngine.addTemplateResolver(classLoaderTemplateResolver); targetTemplateEngine.addTemplateResolver(stringTemplateResolver); targetTemplateEngine.addDialect(dialect); targetTemplateEngine.setEngineContextFactory( new MyBatisIntegratingEngineContextFactory(targetTemplateEngine.getEngineContextFactory())); // Create an TemplateEngineCustomizer instance and apply Optional.ofNullable(config.getCustomizerInstance()).ifPresent(x -> x.accept(targetTemplateEngine)); return targetTemplateEngine; } /** * Generate a sql using Thymeleaf template engine. * * @param sqlTemplate * a template SQL * @param parameter * a parameter object * * @return a processed SQL by template engine */ public String generate(CharSequence sqlTemplate, Object parameter) { return generate(sqlTemplate, parameter, null, null); } /** * Generate a sql using Thymeleaf template engine. * * @param sqlTemplate * a template SQL * @param parameter * a parameter object * @param customBindVariableBinder * a binder for a custom bind variable that generated with {@code mb:bind} or {@code mb:param} * * @return a processed SQL by template engine */ public String generate(CharSequence sqlTemplate, Object parameter, BiConsumer customBindVariableBinder) { return generate(sqlTemplate, parameter, customBindVariableBinder, null); } /** * Generate a sql using Thymeleaf template engine. * * @param sqlTemplate * a template SQL * @param parameter * a parameter object * @param customVariables * a custom variables for passing to template engine * * @return a processed SQL by template engine */ public String generate(CharSequence sqlTemplate, Object parameter, Map customVariables) { return generate(sqlTemplate, parameter, null, customVariables); } /** * Generate a sql using Thymeleaf template engine. * * @param sqlTemplate * a template SQL * @param parameter * a parameter object * @param customBindVariableBinder * a binder for a custom bind variable that generated with {@code mb:bind} or {@code mb:param} * @param customVariables * a custom variables for passing to template engine * * @return a processed SQL by template engine */ public String generate(CharSequence sqlTemplate, Object parameter, BiConsumer customBindVariableBinder, Map customVariables) { Map processingCustomVariables = new HashMap<>(defaultCustomVariables); Optional.ofNullable(customVariables).ifPresent(processingCustomVariables::putAll); IContext context = contextFactory.apply(parameter, processingCustomVariables); String sql = templateEngine.process(sqlTemplate.toString(), context); MyBatisBindingContext bindingContext = MyBatisBindingContext.load(context); if (bindingContext != null && customBindVariableBinder != null) { bindingContext.getCustomBindVariables().forEach(customBindVariableBinder); } return sql; } private class DefaultContext implements IContext { private final Object parameter; private final Map mapParameter; private final Set propertyNames = new HashSet<>(); private final Map customVariables; private DefaultContext(Object parameter, Map customVariables) { this.parameter = parameter; boolean fallback; if (parameter instanceof Map) { @SuppressWarnings("unchecked") Map map = (Map) parameter; propertyNames.addAll(map.keySet()); this.mapParameter = map; fallback = false; } else { this.mapParameter = null; if (parameter != null) { propertyNames.addAll(propertyAccessor.getPropertyNames(parameter.getClass())); } fallback = propertyNames.isEmpty(); } MyBatisBindingContext bindingContext = new MyBatisBindingContext(fallback); this.customVariables = customVariables; customVariables.put(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext); customVariables.put(ContextKeys.PARAMETER_OBJECT, parameter); } @Override public Locale getLocale() { return Locale.getDefault(); } @Override public boolean containsVariable(String name) { return customVariables.containsKey(name) || propertyNames.contains(name); } @Override public Set getVariableNames() { Set variableNames = new HashSet<>(customVariables.keySet()); variableNames.addAll(propertyNames); return variableNames; } @Override public Object getVariable(String name) { if (customVariables.containsKey(name)) { return customVariables.get(name); } if (mapParameter == null) { return propertyAccessor.getPropertyValue(parameter, name); } else { return mapParameter.get(name); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy