View Javadoc
1   /**
2    *    Copyright 2009-2019 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    *       http://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.Collections;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Properties;
25  import java.util.Set;
26  import java.util.StringTokenizer;
27  
28  import org.apache.ibatis.cache.Cache;
29  import org.apache.ibatis.cache.decorators.LruCache;
30  import org.apache.ibatis.cache.impl.PerpetualCache;
31  import org.apache.ibatis.executor.ErrorContext;
32  import org.apache.ibatis.executor.keygen.KeyGenerator;
33  import org.apache.ibatis.mapping.CacheBuilder;
34  import org.apache.ibatis.mapping.Discriminator;
35  import org.apache.ibatis.mapping.MappedStatement;
36  import org.apache.ibatis.mapping.ParameterMap;
37  import org.apache.ibatis.mapping.ParameterMapping;
38  import org.apache.ibatis.mapping.ParameterMode;
39  import org.apache.ibatis.mapping.ResultFlag;
40  import org.apache.ibatis.mapping.ResultMap;
41  import org.apache.ibatis.mapping.ResultMapping;
42  import org.apache.ibatis.mapping.ResultSetType;
43  import org.apache.ibatis.mapping.SqlCommandType;
44  import org.apache.ibatis.mapping.SqlSource;
45  import org.apache.ibatis.mapping.StatementType;
46  import org.apache.ibatis.reflection.MetaClass;
47  import org.apache.ibatis.scripting.LanguageDriver;
48  import org.apache.ibatis.session.Configuration;
49  import org.apache.ibatis.type.JdbcType;
50  import org.apache.ibatis.type.TypeHandler;
51  
52  /**
53   * @author Clinton Begin
54   */
55  public class MapperBuilderAssistant extends BaseBuilder {
56  
57    private String currentNamespace;
58    private final String resource;
59    private Cache currentCache;
60    private boolean unresolvedCacheRef; // issue #676
61  
62    public MapperBuilderAssistant(Configuration configuration, String resource) {
63      super(configuration);
64      ErrorContext.instance().resource(resource);
65      this.resource = resource;
66    }
67  
68    public String getCurrentNamespace() {
69      return currentNamespace;
70    }
71  
72    public void setCurrentNamespace(String currentNamespace) {
73      if (currentNamespace == null) {
74        throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
75      }
76  
77      if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
78        throw new BuilderException("Wrong namespace. Expected '"
79            + this.currentNamespace + "' but found '" + currentNamespace + "'.");
80      }
81  
82      this.currentNamespace = currentNamespace;
83    }
84  
85    public String applyCurrentNamespace(String base, boolean isReference) {
86      if (base == null) {
87        return null;
88      }
89      if (isReference) {
90        // is it qualified with any namespace yet?
91        if (base.contains(".")) {
92          return base;
93        }
94      } else {
95        // is it qualified with this namespace yet?
96        if (base.startsWith(currentNamespace + ".")) {
97          return base;
98        }
99        if (base.contains(".")) {
100         throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
101       }
102     }
103     return currentNamespace + "." + base;
104   }
105 
106   public Cache useCacheRef(String namespace) {
107     if (namespace == null) {
108       throw new BuilderException("cache-ref element requires a namespace attribute.");
109     }
110     try {
111       unresolvedCacheRef = true;
112       Cache cache = configuration.getCache(namespace);
113       if (cache == null) {
114         throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
115       }
116       currentCache = cache;
117       unresolvedCacheRef = false;
118       return cache;
119     } catch (IllegalArgumentException e) {
120       throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
121     }
122   }
123 
124   public Cache useNewCache(Class<? extends Cache> typeClass,
125       Class<? extends Cache> evictionClass,
126       Long flushInterval,
127       Integer size,
128       boolean readWrite,
129       boolean blocking,
130       Properties props) {
131     Cache cache = new CacheBuilder(currentNamespace)
132         .implementation(valueOrDefault(typeClass, PerpetualCache.class))
133         .addDecorator(valueOrDefault(evictionClass, LruCache.class))
134         .clearInterval(flushInterval)
135         .size(size)
136         .readWrite(readWrite)
137         .blocking(blocking)
138         .properties(props)
139         .build();
140     configuration.addCache(cache);
141     currentCache = cache;
142     return cache;
143   }
144 
145   public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
146     id = applyCurrentNamespace(id, false);
147     ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
148     configuration.addParameterMap(parameterMap);
149     return parameterMap;
150   }
151 
152   public ParameterMapping buildParameterMapping(
153       Class<?> parameterType,
154       String property,
155       Class<?> javaType,
156       JdbcType jdbcType,
157       String resultMap,
158       ParameterMode parameterMode,
159       Class<? extends TypeHandler<?>> typeHandler,
160       Integer numericScale) {
161     resultMap = applyCurrentNamespace(resultMap, true);
162 
163     // Class parameterType = parameterMapBuilder.type();
164     Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
165     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
166 
167     return new ParameterMapping.Builder(configuration, property, javaTypeClass)
168         .jdbcType(jdbcType)
169         .resultMapId(resultMap)
170         .mode(parameterMode)
171         .numericScale(numericScale)
172         .typeHandler(typeHandlerInstance)
173         .build();
174   }
175 
176   public ResultMap addResultMap(
177       String id,
178       Class<?> type,
179       String extend,
180       Discriminator discriminator,
181       List<ResultMapping> resultMappings,
182       Boolean autoMapping) {
183     id = applyCurrentNamespace(id, false);
184     extend = applyCurrentNamespace(extend, true);
185 
186     if (extend != null) {
187       if (!configuration.hasResultMap(extend)) {
188         throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
189       }
190       ResultMap resultMap = configuration.getResultMap(extend);
191       List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
192       extendedResultMappings.removeAll(resultMappings);
193       // Remove parent constructor if this resultMap declares a constructor.
194       boolean declaresConstructor = false;
195       for (ResultMapping resultMapping : resultMappings) {
196         if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
197           declaresConstructor = true;
198           break;
199         }
200       }
201       if (declaresConstructor) {
202         extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
203       }
204       resultMappings.addAll(extendedResultMappings);
205     }
206     ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
207         .discriminator(discriminator)
208         .build();
209     configuration.addResultMap(resultMap);
210     return resultMap;
211   }
212 
213   public Discriminator buildDiscriminator(
214       Class<?> resultType,
215       String column,
216       Class<?> javaType,
217       JdbcType jdbcType,
218       Class<? extends TypeHandler<?>> typeHandler,
219       Map<String, String> discriminatorMap) {
220     ResultMapping resultMapping = buildResultMapping(
221         resultType,
222         null,
223         column,
224         javaType,
225         jdbcType,
226         null,
227         null,
228         null,
229         null,
230         typeHandler,
231         new ArrayList<>(),
232         null,
233         null,
234         false);
235     Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
236     for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
237       String resultMap = e.getValue();
238       resultMap = applyCurrentNamespace(resultMap, true);
239       namespaceDiscriminatorMap.put(e.getKey(), resultMap);
240     }
241     return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
242   }
243 
244   public MappedStatement addMappedStatement(
245       String id,
246       SqlSource sqlSource,
247       StatementType statementType,
248       SqlCommandType sqlCommandType,
249       Integer fetchSize,
250       Integer timeout,
251       String parameterMap,
252       Class<?> parameterType,
253       String resultMap,
254       Class<?> resultType,
255       ResultSetType resultSetType,
256       boolean flushCache,
257       boolean useCache,
258       boolean resultOrdered,
259       KeyGenerator keyGenerator,
260       String keyProperty,
261       String keyColumn,
262       String databaseId,
263       LanguageDriver lang,
264       String resultSets) {
265 
266     if (unresolvedCacheRef) {
267       throw new IncompleteElementException("Cache-ref not yet resolved");
268     }
269 
270     id = applyCurrentNamespace(id, false);
271     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
272 
273     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
274         .resource(resource)
275         .fetchSize(fetchSize)
276         .timeout(timeout)
277         .statementType(statementType)
278         .keyGenerator(keyGenerator)
279         .keyProperty(keyProperty)
280         .keyColumn(keyColumn)
281         .databaseId(databaseId)
282         .lang(lang)
283         .resultOrdered(resultOrdered)
284         .resultSets(resultSets)
285         .resultMaps(getStatementResultMaps(resultMap, resultType, id))
286         .resultSetType(resultSetType)
287         .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
288         .useCache(valueOrDefault(useCache, isSelect))
289         .cache(currentCache);
290 
291     ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
292     if (statementParameterMap != null) {
293       statementBuilder.parameterMap(statementParameterMap);
294     }
295 
296     MappedStatement statement = statementBuilder.build();
297     configuration.addMappedStatement(statement);
298     return statement;
299   }
300 
301   private <T> T valueOrDefault(T value, T defaultValue) {
302     return value == null ? defaultValue : value;
303   }
304 
305   private ParameterMap getStatementParameterMap(
306       String parameterMapName,
307       Class<?> parameterTypeClass,
308       String statementId) {
309     parameterMapName = applyCurrentNamespace(parameterMapName, true);
310     ParameterMap parameterMap = null;
311     if (parameterMapName != null) {
312       try {
313         parameterMap = configuration.getParameterMap(parameterMapName);
314       } catch (IllegalArgumentException e) {
315         throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
316       }
317     } else if (parameterTypeClass != null) {
318       List<ParameterMapping> parameterMappings = new ArrayList<>();
319       parameterMap = new ParameterMap.Builder(
320           configuration,
321           statementId + "-Inline",
322           parameterTypeClass,
323           parameterMappings).build();
324     }
325     return parameterMap;
326   }
327 
328   private List<ResultMap> getStatementResultMaps(
329       String resultMap,
330       Class<?> resultType,
331       String statementId) {
332     resultMap = applyCurrentNamespace(resultMap, true);
333 
334     List<ResultMap> resultMaps = new ArrayList<>();
335     if (resultMap != null) {
336       String[] resultMapNames = resultMap.split(",");
337       for (String resultMapName : resultMapNames) {
338         try {
339           resultMaps.add(configuration.getResultMap(resultMapName.trim()));
340         } catch (IllegalArgumentException e) {
341           throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
342         }
343       }
344     } else if (resultType != null) {
345       ResultMap inlineResultMap = new ResultMap.Builder(
346           configuration,
347           statementId + "-Inline",
348           resultType,
349           new ArrayList<>(),
350           null).build();
351       resultMaps.add(inlineResultMap);
352     }
353     return resultMaps;
354   }
355 
356   public ResultMapping buildResultMapping(
357       Class<?> resultType,
358       String property,
359       String column,
360       Class<?> javaType,
361       JdbcType jdbcType,
362       String nestedSelect,
363       String nestedResultMap,
364       String notNullColumn,
365       String columnPrefix,
366       Class<? extends TypeHandler<?>> typeHandler,
367       List<ResultFlag> flags,
368       String resultSet,
369       String foreignColumn,
370       boolean lazy) {
371     Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
372     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
373     List<ResultMapping> composites;
374     if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
375       composites = Collections.emptyList();
376     } else {
377       composites = parseCompositeColumnName(column);
378     }
379     return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
380         .jdbcType(jdbcType)
381         .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
382         .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
383         .resultSet(resultSet)
384         .typeHandler(typeHandlerInstance)
385         .flags(flags == null ? new ArrayList<>() : flags)
386         .composites(composites)
387         .notNullColumns(parseMultipleColumnNames(notNullColumn))
388         .columnPrefix(columnPrefix)
389         .foreignColumn(foreignColumn)
390         .lazy(lazy)
391         .build();
392   }
393 
394   private Set<String> parseMultipleColumnNames(String columnName) {
395     Set<String> columns = new HashSet<>();
396     if (columnName != null) {
397       if (columnName.indexOf(',') > -1) {
398         StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
399         while (parser.hasMoreTokens()) {
400           String column = parser.nextToken();
401           columns.add(column);
402         }
403       } else {
404         columns.add(columnName);
405       }
406     }
407     return columns;
408   }
409 
410   private List<ResultMapping> parseCompositeColumnName(String columnName) {
411     List<ResultMapping> composites = new ArrayList<>();
412     if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
413       StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
414       while (parser.hasMoreTokens()) {
415         String property = parser.nextToken();
416         String column = parser.nextToken();
417         ResultMapping complexResultMapping = new ResultMapping.Builder(
418             configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
419         composites.add(complexResultMapping);
420       }
421     }
422     return composites;
423   }
424 
425   private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
426     if (javaType == null && property != null) {
427       try {
428         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
429         javaType = metaResultType.getSetterType(property);
430       } catch (Exception e) {
431         //ignore, following null check statement will deal with the situation
432       }
433     }
434     if (javaType == null) {
435       javaType = Object.class;
436     }
437     return javaType;
438   }
439 
440   private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {
441     if (javaType == null) {
442       if (JdbcType.CURSOR.equals(jdbcType)) {
443         javaType = java.sql.ResultSet.class;
444       } else if (Map.class.isAssignableFrom(resultType)) {
445         javaType = Object.class;
446       } else {
447         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
448         javaType = metaResultType.getGetterType(property);
449       }
450     }
451     if (javaType == null) {
452       javaType = Object.class;
453     }
454     return javaType;
455   }
456 
457   /** Backward compatibility signature. */
458   public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
459       JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
460       Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags) {
461     return buildResultMapping(
462       resultType, property, column, javaType, jdbcType, nestedSelect,
463       nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
464   }
465 
466   /**
467    * @deprecated Use {@link Configuration#getLanguageDriver(Class)}
468    */
469   @Deprecated
470   public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
471     return configuration.getLanguageDriver(langClass);
472   }
473 
474   /** Backward compatibility signature. */
475   public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
476       SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
477       String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
478       boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
479       LanguageDriver lang) {
480     return addMappedStatement(
481       id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
482       parameterMap, parameterType, resultMap, resultType, resultSetType,
483       flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
484       keyColumn, databaseId, lang, null);
485   }
486 
487 }