View Javadoc
1   /*
2    * Copyright 2004-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 com.ibatis.sqlmap.engine.mapping.parameter;
17  
18  import com.ibatis.common.beans.Probe;
19  import com.ibatis.common.beans.ProbeFactory;
20  import com.ibatis.common.resources.Resources;
21  import com.ibatis.sqlmap.client.SqlMapException;
22  import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;
23  import com.ibatis.sqlmap.engine.mapping.sql.SqlText;
24  import com.ibatis.sqlmap.engine.type.CustomTypeHandler;
25  import com.ibatis.sqlmap.engine.type.DomTypeMarker;
26  import com.ibatis.sqlmap.engine.type.TypeHandler;
27  import com.ibatis.sqlmap.engine.type.TypeHandlerFactory;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.StringTokenizer;
32  
33  /**
34   * The Class InlineParameterMapParser.
35   */
36  public class InlineParameterMapParser {
37  
38    /** The Constant PROBE. */
39    private static final Probe PROBE = ProbeFactory.getProbe();
40  
41    /** The Constant PARAMETER_TOKEN. */
42    private static final String PARAMETER_TOKEN = "#";
43  
44    /** The Constant PARAM_DELIM. */
45    private static final String PARAM_DELIM = ":";
46  
47    /**
48     * Parses the inline parameter map.
49     *
50     * @param typeHandlerFactory
51     *          the type handler factory
52     * @param sqlStatement
53     *          the sql statement
54     *
55     * @return the sql text
56     */
57    public SqlText parseInlineParameterMap(TypeHandlerFactory typeHandlerFactory, String sqlStatement) {
58      return parseInlineParameterMap(typeHandlerFactory, sqlStatement, null);
59    }
60  
61    /**
62     * Parses the inline parameter map.
63     *
64     * @param typeHandlerFactory
65     *          the type handler factory
66     * @param sqlStatement
67     *          the sql statement
68     * @param parameterClass
69     *          the parameter class
70     *
71     * @return the sql text
72     */
73    public SqlText parseInlineParameterMap(TypeHandlerFactory typeHandlerFactory, String sqlStatement,
74        Class parameterClass) {
75  
76      String newSql = sqlStatement;
77  
78      List mappingList = new ArrayList();
79  
80      StringTokenizer parser = new StringTokenizer(sqlStatement, PARAMETER_TOKEN, true);
81      StringBuilder newSqlBuffer = new StringBuilder();
82  
83      String token = null;
84      String lastToken = null;
85      while (parser.hasMoreTokens()) {
86        token = parser.nextToken();
87        if (PARAMETER_TOKEN.equals(lastToken)) {
88          if (PARAMETER_TOKEN.equals(token)) {
89            newSqlBuffer.append(PARAMETER_TOKEN);
90            token = null;
91          } else {
92            ParameterMapping mapping = null;
93            if (token.indexOf(PARAM_DELIM) > -1) {
94              mapping = oldParseMapping(token, parameterClass, typeHandlerFactory);
95            } else {
96              mapping = newParseMapping(token, parameterClass, typeHandlerFactory);
97            }
98  
99            mappingList.add(mapping);
100           newSqlBuffer.append("?");
101           boolean hasMoreTokens = parser.hasMoreTokens();
102           if (hasMoreTokens)
103             token = parser.nextToken();
104           if (!hasMoreTokens || !PARAMETER_TOKEN.equals(token)) {
105             throw new SqlMapException(
106                 "Unterminated inline parameter in mapped statement near '" + newSqlBuffer.toString() + "'");
107           }
108           token = null;
109         }
110       } else {
111         if (!PARAMETER_TOKEN.equals(token)) {
112           newSqlBuffer.append(token);
113         }
114       }
115 
116       lastToken = token;
117     }
118 
119     newSql = newSqlBuffer.toString();
120 
121     ParameterMapping[] mappingArray = (ParameterMapping[]) mappingList
122         .toArray(new ParameterMapping[mappingList.size()]);
123 
124     SqlText sqlText = new SqlText();
125     sqlText.setText(newSql);
126     sqlText.setParameterMappings(mappingArray);
127     return sqlText;
128   }
129 
130   /**
131    * New parse mapping.
132    *
133    * @param token
134    *          the token
135    * @param parameterClass
136    *          the parameter class
137    * @param typeHandlerFactory
138    *          the type handler factory
139    *
140    * @return the parameter mapping
141    */
142   private ParameterMapping newParseMapping(String token, Class parameterClass, TypeHandlerFactory typeHandlerFactory) {
143     ParameterMapping mapping = new ParameterMapping();
144 
145     // #propertyName,javaType=string,jdbcType=VARCHAR,mode=IN,nullValue=N/A,handler=string,numericScale=2#
146 
147     StringTokenizer paramParser = new StringTokenizer(token, "=,", false);
148     mapping.setPropertyName(paramParser.nextToken());
149 
150     while (paramParser.hasMoreTokens()) {
151       String field = paramParser.nextToken();
152       if (paramParser.hasMoreTokens()) {
153         String value = paramParser.nextToken();
154         if ("javaType".equals(field)) {
155           value = typeHandlerFactory.resolveAlias(value);
156           mapping.setJavaTypeName(value);
157         } else if ("jdbcType".equals(field)) {
158           mapping.setJdbcTypeName(value);
159         } else if ("mode".equals(field)) {
160           mapping.setMode(value);
161         } else if ("nullValue".equals(field)) {
162           mapping.setNullValue(value);
163         } else if ("handler".equals(field)) {
164           try {
165             value = typeHandlerFactory.resolveAlias(value);
166             Object impl = Resources.instantiate(value);
167             if (impl instanceof TypeHandlerCallback) {
168               mapping.setTypeHandler(new CustomTypeHandler((TypeHandlerCallback) impl));
169             } else if (impl instanceof TypeHandler) {
170               mapping.setTypeHandler((TypeHandler) impl);
171             } else {
172               throw new SqlMapException(
173                   "The class " + value + " is not a valid implementation of TypeHandler or TypeHandlerCallback");
174             }
175           } catch (Exception e) {
176             throw new SqlMapException("Error loading class specified by handler field in " + token + ".  Cause: " + e,
177                 e);
178           }
179         } else if ("numericScale".equals(field)) {
180           try {
181             Integer numericScale = Integer.valueOf(value);
182             if (numericScale.intValue() < 0) {
183               throw new SqlMapException("Value specified for numericScale must be greater than or equal to zero");
184             }
185             mapping.setNumericScale(numericScale);
186           } catch (NumberFormatException e) {
187             throw new SqlMapException("Value specified for numericScale is not a valid Integer");
188           }
189         } else {
190           throw new SqlMapException("Unrecognized parameter mapping field: '" + field + "' in " + token);
191         }
192       } else {
193         throw new SqlMapException("Incorrect inline parameter map format (missmatched name=value pairs): " + token);
194       }
195     }
196 
197     if (mapping.getTypeHandler() == null) {
198       TypeHandler handler;
199       if (parameterClass == null) {
200         handler = typeHandlerFactory.getUnkownTypeHandler();
201       } else {
202         handler = resolveTypeHandler(typeHandlerFactory, parameterClass, mapping.getPropertyName(),
203             mapping.getJavaTypeName(), mapping.getJdbcTypeName());
204       }
205       mapping.setTypeHandler(handler);
206     }
207 
208     return mapping;
209   }
210 
211   /**
212    * Old parse mapping.
213    *
214    * @param token
215    *          the token
216    * @param parameterClass
217    *          the parameter class
218    * @param typeHandlerFactory
219    *          the type handler factory
220    *
221    * @return the parameter mapping
222    */
223   private ParameterMapping oldParseMapping(String token, Class parameterClass, TypeHandlerFactory typeHandlerFactory) {
224     ParameterMapping mapping = new ParameterMapping();
225     if (token.indexOf(PARAM_DELIM) > -1) {
226       StringTokenizer paramParser = new StringTokenizer(token, PARAM_DELIM, true);
227       int n1 = paramParser.countTokens();
228       if (n1 == 3) {
229         String name = paramParser.nextToken();
230         paramParser.nextToken(); // ignore ":"
231         String type = paramParser.nextToken();
232         mapping.setPropertyName(name);
233         mapping.setJdbcTypeName(type);
234         TypeHandler handler;
235         if (parameterClass == null) {
236           handler = typeHandlerFactory.getUnkownTypeHandler();
237         } else {
238           handler = resolveTypeHandler(typeHandlerFactory, parameterClass, name, null, type);
239         }
240         mapping.setTypeHandler(handler);
241         return mapping;
242       } else if (n1 >= 5) {
243         String name = paramParser.nextToken();
244         paramParser.nextToken(); // ignore ":"
245         String type = paramParser.nextToken();
246         paramParser.nextToken(); // ignore ":"
247         String nullValue = paramParser.nextToken();
248         while (paramParser.hasMoreTokens()) {
249           nullValue = nullValue + paramParser.nextToken();
250         }
251         mapping.setPropertyName(name);
252         mapping.setJdbcTypeName(type);
253         mapping.setNullValue(nullValue);
254         TypeHandler handler;
255         if (parameterClass == null) {
256           handler = typeHandlerFactory.getUnkownTypeHandler();
257         } else {
258           handler = resolveTypeHandler(typeHandlerFactory, parameterClass, name, null, type);
259         }
260         mapping.setTypeHandler(handler);
261         return mapping;
262       } else {
263         throw new SqlMapException("Incorrect inline parameter map format: " + token);
264       }
265     } else {
266       mapping.setPropertyName(token);
267       TypeHandler handler;
268       if (parameterClass == null) {
269         handler = typeHandlerFactory.getUnkownTypeHandler();
270       } else {
271         handler = resolveTypeHandler(typeHandlerFactory, parameterClass, token, null, null);
272       }
273       mapping.setTypeHandler(handler);
274       return mapping;
275     }
276   }
277 
278   /**
279    * Resolve type handler.
280    *
281    * @param typeHandlerFactory
282    *          the type handler factory
283    * @param clazz
284    *          the clazz
285    * @param propertyName
286    *          the property name
287    * @param javaType
288    *          the java type
289    * @param jdbcType
290    *          the jdbc type
291    *
292    * @return the type handler
293    */
294   private TypeHandler resolveTypeHandler(TypeHandlerFactory typeHandlerFactory, Class clazz, String propertyName,
295       String javaType, String jdbcType) {
296     TypeHandler handler = null;
297     if (clazz == null) {
298       // Unknown
299       handler = typeHandlerFactory.getUnkownTypeHandler();
300     } else if (DomTypeMarker.class.isAssignableFrom(clazz)) {
301       // DOM
302       handler = typeHandlerFactory.getTypeHandler(String.class, jdbcType);
303     } else if (java.util.Map.class.isAssignableFrom(clazz)) {
304       // Map
305       if (javaType == null) {
306         handler = typeHandlerFactory.getUnkownTypeHandler(); // BUG 1012591 -
307                                                              // typeHandlerFactory.getTypeHandler(java.lang.Object.class,
308                                                              // jdbcType);
309       } else {
310         try {
311           javaType = typeHandlerFactory.resolveAlias(javaType);
312           Class javaClass = Resources.classForName(javaType);
313           handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
314         } catch (Exception e) {
315           throw new SqlMapException("Error.  Could not set TypeHandler.  Cause: " + e, e);
316         }
317       }
318     } else if (typeHandlerFactory.getTypeHandler(clazz, jdbcType) != null) {
319       // Primitive
320       handler = typeHandlerFactory.getTypeHandler(clazz, jdbcType);
321     } else {
322       // JavaBean
323       if (javaType == null) {
324 
325         Class type = PROBE.getPropertyTypeForGetter(clazz, propertyName);
326         handler = typeHandlerFactory.getTypeHandler(type, jdbcType);
327 
328       } else {
329         try {
330           javaType = typeHandlerFactory.resolveAlias(javaType);
331           Class javaClass = Resources.classForName(javaType);
332           handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
333         } catch (Exception e) {
334           throw new SqlMapException("Error.  Could not set TypeHandler.  Cause: " + e, e);
335         }
336       }
337     }
338     return handler;
339   }
340 
341 }