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.builder;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.Map;
21  import java.util.StringTokenizer;
22  
23  import org.apache.ibatis.mapping.ParameterMapping;
24  import org.apache.ibatis.mapping.SqlSource;
25  import org.apache.ibatis.parsing.GenericTokenParser;
26  import org.apache.ibatis.parsing.TokenHandler;
27  import org.apache.ibatis.reflection.MetaClass;
28  import org.apache.ibatis.reflection.MetaObject;
29  import org.apache.ibatis.session.Configuration;
30  import org.apache.ibatis.type.JdbcType;
31  
32  /**
33   * @author Clinton Begin
34   */
35  public class SqlSourceBuilder extends BaseBuilder {
36  
37    private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
38  
39    public SqlSourceBuilder(Configuration configuration) {
40      super(configuration);
41    }
42  
43    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
44      ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
45          additionalParameters);
46      GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
47      String sql;
48      if (configuration.isShrinkWhitespacesInSql()) {
49        sql = parser.parse(removeExtraWhitespaces(originalSql));
50      } else {
51        sql = parser.parse(originalSql);
52      }
53      return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
54    }
55  
56    public static String removeExtraWhitespaces(String original) {
57      StringTokenizer tokenizer = new StringTokenizer(original);
58      StringBuilder builder = new StringBuilder();
59      boolean hasMoreTokens = tokenizer.hasMoreTokens();
60      while (hasMoreTokens) {
61        builder.append(tokenizer.nextToken());
62        hasMoreTokens = tokenizer.hasMoreTokens();
63        if (hasMoreTokens) {
64          builder.append(' ');
65        }
66      }
67      return builder.toString();
68    }
69  
70    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
71  
72      private final List<ParameterMapping> parameterMappings = new ArrayList<>();
73      private final Class<?> parameterType;
74      private final MetaObject metaParameters;
75  
76      public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,
77          Map<String, Object> additionalParameters) {
78        super(configuration);
79        this.parameterType = parameterType;
80        this.metaParameters = configuration.newMetaObject(additionalParameters);
81      }
82  
83      public List<ParameterMapping> getParameterMappings() {
84        return parameterMappings;
85      }
86  
87      @Override
88      public String handleToken(String content) {
89        parameterMappings.add(buildParameterMapping(content));
90        return "?";
91      }
92  
93      private ParameterMapping buildParameterMapping(String content) {
94        Map<String, String> propertiesMap = parseParameterMapping(content);
95        String property = propertiesMap.get("property");
96        Class<?> propertyType;
97        if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
98          propertyType = metaParameters.getGetterType(property);
99        } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
100         propertyType = parameterType;
101       } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
102         propertyType = java.sql.ResultSet.class;
103       } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
104         propertyType = Object.class;
105       } else {
106         MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
107         if (metaClass.hasGetter(property)) {
108           propertyType = metaClass.getGetterType(property);
109         } else {
110           propertyType = Object.class;
111         }
112       }
113       ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
114       Class<?> javaType = propertyType;
115       String typeHandlerAlias = null;
116       for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
117         String name = entry.getKey();
118         String value = entry.getValue();
119         if ("javaType".equals(name)) {
120           javaType = resolveClass(value);
121           builder.javaType(javaType);
122         } else if ("jdbcType".equals(name)) {
123           builder.jdbcType(resolveJdbcType(value));
124         } else if ("mode".equals(name)) {
125           builder.mode(resolveParameterMode(value));
126         } else if ("numericScale".equals(name)) {
127           builder.numericScale(Integer.valueOf(value));
128         } else if ("resultMap".equals(name)) {
129           builder.resultMapId(value);
130         } else if ("typeHandler".equals(name)) {
131           typeHandlerAlias = value;
132         } else if ("jdbcTypeName".equals(name)) {
133           builder.jdbcTypeName(value);
134         } else if ("property".equals(name)) {
135           // Do Nothing
136         } else if ("expression".equals(name)) {
137           throw new BuilderException("Expression based parameters are not supported yet");
138         } else {
139           throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content
140               + "}.  Valid properties are " + PARAMETER_PROPERTIES);
141         }
142       }
143       if (typeHandlerAlias != null) {
144         builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
145       }
146       return builder.build();
147     }
148 
149     private Map<String, String> parseParameterMapping(String content) {
150       try {
151         return new ParameterExpression(content);
152       } catch (BuilderException ex) {
153         throw ex;
154       } catch (Exception ex) {
155         throw new BuilderException("Parsing error was found in mapping #{" + content
156             + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
157       }
158     }
159   }
160 
161 }