View Javadoc
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  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.StringTokenizer;
24  
25  import org.apache.ibatis.session.Configuration;
26  
27  /**
28   * @author Clinton Begin
29   */
30  public class TrimSqlNode implements SqlNode {
31  
32    private final SqlNode contents;
33    private final String prefix;
34    private final String suffix;
35    private final List<String> prefixesToOverride;
36    private final List<String> suffixesToOverride;
37    private final Configuration configuration;
38  
39    public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride,
40        String suffix, String suffixesToOverride) {
41      this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix,
42          parseOverrides(suffixesToOverride));
43    }
44  
45    protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride,
46        String suffix, List<String> suffixesToOverride) {
47      this.contents = contents;
48      this.prefix = prefix;
49      this.prefixesToOverride = prefixesToOverride;
50      this.suffix = suffix;
51      this.suffixesToOverride = suffixesToOverride;
52      this.configuration = configuration;
53    }
54  
55    @Override
56    public boolean apply(DynamicContext context) {
57      FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
58      boolean result = contents.apply(filteredDynamicContext);
59      filteredDynamicContext.applyAll();
60      return result;
61    }
62  
63    private static List<String> parseOverrides(String overrides) {
64      if (overrides != null) {
65        final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
66        final List<String> list = new ArrayList<>(parser.countTokens());
67        while (parser.hasMoreTokens()) {
68          list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
69        }
70        return list;
71      }
72      return Collections.emptyList();
73    }
74  
75    private class FilteredDynamicContext extends DynamicContext {
76      private final DynamicContext delegate;
77      private boolean prefixApplied;
78      private boolean suffixApplied;
79      private StringBuilder sqlBuffer;
80  
81      public FilteredDynamicContext(DynamicContext delegate) {
82        super(configuration, null);
83        this.delegate = delegate;
84        this.prefixApplied = false;
85        this.suffixApplied = false;
86        this.sqlBuffer = new StringBuilder();
87      }
88  
89      public void applyAll() {
90        sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
91        String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
92        if (trimmedUppercaseSql.length() > 0) {
93          applyPrefix(sqlBuffer, trimmedUppercaseSql);
94          applySuffix(sqlBuffer, trimmedUppercaseSql);
95        }
96        delegate.appendSql(sqlBuffer.toString());
97      }
98  
99      @Override
100     public Map<String, Object> getBindings() {
101       return delegate.getBindings();
102     }
103 
104     @Override
105     public void bind(String name, Object value) {
106       delegate.bind(name, value);
107     }
108 
109     @Override
110     public int getUniqueNumber() {
111       return delegate.getUniqueNumber();
112     }
113 
114     @Override
115     public void appendSql(String sql) {
116       sqlBuffer.append(sql);
117     }
118 
119     @Override
120     public String getSql() {
121       return delegate.getSql();
122     }
123 
124     private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
125       if (prefixApplied) {
126         return;
127       }
128       prefixApplied = true;
129       if (prefixesToOverride != null) {
130         prefixesToOverride.stream().filter(trimmedUppercaseSql::startsWith).findFirst()
131             .ifPresent(toRemove -> sql.delete(0, toRemove.trim().length()));
132       }
133       if (prefix != null) {
134         sql.insert(0, " ").insert(0, prefix);
135       }
136     }
137 
138     private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
139       if (suffixApplied) {
140         return;
141       }
142       suffixApplied = true;
143       if (suffixesToOverride != null) {
144         suffixesToOverride.stream()
145             .filter(toRemove -> trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim()))
146             .findFirst().ifPresent(toRemove -> {
147               int start = sql.length() - toRemove.trim().length();
148               int end = sql.length();
149               sql.delete(start, end);
150             });
151       }
152       if (suffix != null) {
153         sql.append(" ").append(suffix);
154       }
155     }
156 
157   }
158 
159 }