TrimSqlNode.java

  1. /*
  2.  *    Copyright 2009-2023 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.apache.ibatis.scripting.xmltags;

  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.List;
  20. import java.util.Locale;
  21. import java.util.Map;
  22. import java.util.StringTokenizer;

  23. import org.apache.ibatis.session.Configuration;

  24. /**
  25.  * @author Clinton Begin
  26.  */
  27. public class TrimSqlNode implements SqlNode {

  28.   private final SqlNode contents;
  29.   private final String prefix;
  30.   private final String suffix;
  31.   private final List<String> prefixesToOverride;
  32.   private final List<String> suffixesToOverride;
  33.   private final Configuration configuration;

  34.   public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride,
  35.       String suffix, String suffixesToOverride) {
  36.     this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix,
  37.         parseOverrides(suffixesToOverride));
  38.   }

  39.   protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride,
  40.       String suffix, List<String> suffixesToOverride) {
  41.     this.contents = contents;
  42.     this.prefix = prefix;
  43.     this.prefixesToOverride = prefixesToOverride;
  44.     this.suffix = suffix;
  45.     this.suffixesToOverride = suffixesToOverride;
  46.     this.configuration = configuration;
  47.   }

  48.   @Override
  49.   public boolean apply(DynamicContext context) {
  50.     FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
  51.     boolean result = contents.apply(filteredDynamicContext);
  52.     filteredDynamicContext.applyAll();
  53.     return result;
  54.   }

  55.   private static List<String> parseOverrides(String overrides) {
  56.     if (overrides != null) {
  57.       final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
  58.       final List<String> list = new ArrayList<>(parser.countTokens());
  59.       while (parser.hasMoreTokens()) {
  60.         list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
  61.       }
  62.       return list;
  63.     }
  64.     return Collections.emptyList();
  65.   }

  66.   private class FilteredDynamicContext extends DynamicContext {
  67.     private final DynamicContext delegate;
  68.     private boolean prefixApplied;
  69.     private boolean suffixApplied;
  70.     private StringBuilder sqlBuffer;

  71.     public FilteredDynamicContext(DynamicContext delegate) {
  72.       super(configuration, null);
  73.       this.delegate = delegate;
  74.       this.prefixApplied = false;
  75.       this.suffixApplied = false;
  76.       this.sqlBuffer = new StringBuilder();
  77.     }

  78.     public void applyAll() {
  79.       sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
  80.       String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
  81.       if (trimmedUppercaseSql.length() > 0) {
  82.         applyPrefix(sqlBuffer, trimmedUppercaseSql);
  83.         applySuffix(sqlBuffer, trimmedUppercaseSql);
  84.       }
  85.       delegate.appendSql(sqlBuffer.toString());
  86.     }

  87.     @Override
  88.     public Map<String, Object> getBindings() {
  89.       return delegate.getBindings();
  90.     }

  91.     @Override
  92.     public void bind(String name, Object value) {
  93.       delegate.bind(name, value);
  94.     }

  95.     @Override
  96.     public int getUniqueNumber() {
  97.       return delegate.getUniqueNumber();
  98.     }

  99.     @Override
  100.     public void appendSql(String sql) {
  101.       sqlBuffer.append(sql);
  102.     }

  103.     @Override
  104.     public String getSql() {
  105.       return delegate.getSql();
  106.     }

  107.     private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
  108.       if (prefixApplied) {
  109.         return;
  110.       }
  111.       prefixApplied = true;
  112.       if (prefixesToOverride != null) {
  113.         prefixesToOverride.stream().filter(trimmedUppercaseSql::startsWith).findFirst()
  114.             .ifPresent(toRemove -> sql.delete(0, toRemove.trim().length()));
  115.       }
  116.       if (prefix != null) {
  117.         sql.insert(0, " ").insert(0, prefix);
  118.       }
  119.     }

  120.     private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
  121.       if (suffixApplied) {
  122.         return;
  123.       }
  124.       suffixApplied = true;
  125.       if (suffixesToOverride != null) {
  126.         suffixesToOverride.stream()
  127.             .filter(toRemove -> trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim()))
  128.             .findFirst().ifPresent(toRemove -> {
  129.               int start = sql.length() - toRemove.trim().length();
  130.               int end = sql.length();
  131.               sql.delete(start, end);
  132.             });
  133.       }
  134.       if (suffix != null) {
  135.         sql.append(" ").append(suffix);
  136.       }
  137.     }

  138.   }

  139. }