SqlSourceBuilder.java

  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. import java.sql.ResultSet;
  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.StringTokenizer;

  22. import org.apache.ibatis.mapping.ParameterMapping;
  23. import org.apache.ibatis.mapping.SqlSource;
  24. import org.apache.ibatis.parsing.GenericTokenParser;
  25. import org.apache.ibatis.parsing.TokenHandler;
  26. import org.apache.ibatis.reflection.MetaClass;
  27. import org.apache.ibatis.reflection.MetaObject;
  28. import org.apache.ibatis.session.Configuration;
  29. import org.apache.ibatis.type.JdbcType;

  30. /**
  31.  * @author Clinton Begin
  32.  */
  33. public class SqlSourceBuilder extends BaseBuilder {

  34.   private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

  35.   public SqlSourceBuilder(Configuration configuration) {
  36.     super(configuration);
  37.   }

  38.   public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  39.     ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
  40.         additionalParameters);
  41.     GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  42.     String sql;
  43.     if (configuration.isShrinkWhitespacesInSql()) {
  44.       sql = parser.parse(removeExtraWhitespaces(originalSql));
  45.     } else {
  46.       sql = parser.parse(originalSql);
  47.     }
  48.     return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  49.   }

  50.   public static String removeExtraWhitespaces(String original) {
  51.     StringTokenizer tokenizer = new StringTokenizer(original);
  52.     StringBuilder builder = new StringBuilder();
  53.     boolean hasMoreTokens = tokenizer.hasMoreTokens();
  54.     while (hasMoreTokens) {
  55.       builder.append(tokenizer.nextToken());
  56.       hasMoreTokens = tokenizer.hasMoreTokens();
  57.       if (hasMoreTokens) {
  58.         builder.append(' ');
  59.       }
  60.     }
  61.     return builder.toString();
  62.   }

  63.   private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

  64.     private final List<ParameterMapping> parameterMappings = new ArrayList<>();
  65.     private final Class<?> parameterType;
  66.     private final MetaObject metaParameters;

  67.     public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,
  68.         Map<String, Object> additionalParameters) {
  69.       super(configuration);
  70.       this.parameterType = parameterType;
  71.       this.metaParameters = configuration.newMetaObject(additionalParameters);
  72.     }

  73.     public List<ParameterMapping> getParameterMappings() {
  74.       return parameterMappings;
  75.     }

  76.     @Override
  77.     public String handleToken(String content) {
  78.       parameterMappings.add(buildParameterMapping(content));
  79.       return "?";
  80.     }

  81.     private ParameterMapping buildParameterMapping(String content) {
  82.       Map<String, String> propertiesMap = parseParameterMapping(content);
  83.       String property = propertiesMap.get("property");
  84.       Class<?> propertyType;
  85.       if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
  86.         propertyType = metaParameters.getGetterType(property);
  87.       } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
  88.         propertyType = parameterType;
  89.       } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
  90.         propertyType = ResultSet.class;
  91.       } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
  92.         propertyType = Object.class;
  93.       } else {
  94.         MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
  95.         if (metaClass.hasGetter(property)) {
  96.           propertyType = metaClass.getGetterType(property);
  97.         } else {
  98.           propertyType = Object.class;
  99.         }
  100.       }
  101.       ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
  102.       Class<?> javaType = propertyType;
  103.       String typeHandlerAlias = null;
  104.       for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
  105.         String name = entry.getKey();
  106.         String value = entry.getValue();
  107.         if ("javaType".equals(name)) {
  108.           javaType = resolveClass(value);
  109.           builder.javaType(javaType);
  110.         } else if ("jdbcType".equals(name)) {
  111.           builder.jdbcType(resolveJdbcType(value));
  112.         } else if ("mode".equals(name)) {
  113.           builder.mode(resolveParameterMode(value));
  114.         } else if ("numericScale".equals(name)) {
  115.           builder.numericScale(Integer.valueOf(value));
  116.         } else if ("resultMap".equals(name)) {
  117.           builder.resultMapId(value);
  118.         } else if ("typeHandler".equals(name)) {
  119.           typeHandlerAlias = value;
  120.         } else if ("jdbcTypeName".equals(name)) {
  121.           builder.jdbcTypeName(value);
  122.         } else if ("property".equals(name)) {
  123.           // Do Nothing
  124.         } else if ("expression".equals(name)) {
  125.           throw new BuilderException("Expression based parameters are not supported yet");
  126.         } else {
  127.           throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content
  128.               + "}.  Valid properties are " + PARAMETER_PROPERTIES);
  129.         }
  130.       }
  131.       if (typeHandlerAlias != null) {
  132.         builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
  133.       }
  134.       return builder.build();
  135.     }

  136.     private Map<String, String> parseParameterMapping(String content) {
  137.       try {
  138.         return new ParameterExpression(content);
  139.       } catch (BuilderException ex) {
  140.         throw ex;
  141.       } catch (Exception ex) {
  142.         throw new BuilderException("Parsing error was found in mapping #{" + content
  143.             + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
  144.       }
  145.     }
  146.   }

  147. }