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