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