View Javadoc
1   /**
2    *    Copyright 2009-2015 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.HashMap;
20  import java.util.HashSet;
21  import java.util.Iterator;
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 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     typeClass = valueOrDefault(typeClass, PerpetualCache.class);
132     evictionClass = valueOrDefault(evictionClass, LruCache.class);
133     Cache cache = new CacheBuilder(currentNamespace)
134         .implementation(typeClass)
135         .addDecorator(evictionClass)
136         .clearInterval(flushInterval)
137         .size(size)
138         .readWrite(readWrite)
139         .blocking(blocking)
140         .properties(props)
141         .build();
142     configuration.addCache(cache);
143     currentCache = cache;
144     return cache;
145   }
146 
147   public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
148     id = applyCurrentNamespace(id, false);
149     ParameterMap.Builder parameterMapBuilder = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings);
150     ParameterMap parameterMap = parameterMapBuilder.build();
151     configuration.addParameterMap(parameterMap);
152     return parameterMap;
153   }
154 
155   public ParameterMapping buildParameterMapping(
156       Class<?> parameterType,
157       String property,
158       Class<?> javaType,
159       JdbcType jdbcType,
160       String resultMap,
161       ParameterMode parameterMode,
162       Class<? extends TypeHandler<?>> typeHandler,
163       Integer numericScale) {
164     resultMap = applyCurrentNamespace(resultMap, true);
165 
166     // Class parameterType = parameterMapBuilder.type();
167     Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
168     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
169 
170     ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, javaTypeClass);
171     builder.jdbcType(jdbcType);
172     builder.resultMapId(resultMap);
173     builder.mode(parameterMode);
174     builder.numericScale(numericScale);
175     builder.typeHandler(typeHandlerInstance);
176     return builder.build();
177   }
178 
179   public ResultMap addResultMap(
180       String id,
181       Class<?> type,
182       String extend,
183       Discriminator discriminator,
184       List<ResultMapping> resultMappings,
185       Boolean autoMapping) {
186     id = applyCurrentNamespace(id, false);
187     extend = applyCurrentNamespace(extend, true);
188 
189     ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
190     if (extend != null) {
191       if (!configuration.hasResultMap(extend)) {
192         throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
193       }
194       ResultMap resultMap = configuration.getResultMap(extend);
195       List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
196       extendedResultMappings.removeAll(resultMappings);
197       // Remove parent constructor if this resultMap declares a constructor.
198       boolean declaresConstructor = false;
199       for (ResultMapping resultMapping : resultMappings) {
200         if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
201           declaresConstructor = true;
202           break;
203         }
204       }
205       if (declaresConstructor) {
206         Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
207         while (extendedResultMappingsIter.hasNext()) {
208           if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
209             extendedResultMappingsIter.remove();
210           }
211         }
212       }
213       resultMappings.addAll(extendedResultMappings);
214     }
215     resultMapBuilder.discriminator(discriminator);
216     ResultMap resultMap = resultMapBuilder.build();
217     configuration.addResultMap(resultMap);
218     return resultMap;
219   }
220 
221   public Discriminator buildDiscriminator(
222       Class<?> resultType,
223       String column,
224       Class<?> javaType,
225       JdbcType jdbcType,
226       Class<? extends TypeHandler<?>> typeHandler,
227       Map<String, String> discriminatorMap) {
228     ResultMapping resultMapping = buildResultMapping(
229         resultType,
230         null,
231         column,
232         javaType,
233         jdbcType,
234         null,
235         null,
236         null,
237         null,
238         typeHandler,
239         new ArrayList<ResultFlag>(),
240         null,
241         null,
242         false);
243     Map<String, String> namespaceDiscriminatorMap = new HashMap<String, String>();
244     for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
245       String resultMap = e.getValue();
246       resultMap = applyCurrentNamespace(resultMap, true);
247       namespaceDiscriminatorMap.put(e.getKey(), resultMap);
248     }
249     Discriminator.Builder discriminatorBuilder = new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap);
250     return discriminatorBuilder.build();
251   }
252 
253   public MappedStatement addMappedStatement(
254       String id,
255       SqlSource sqlSource,
256       StatementType statementType,
257       SqlCommandType sqlCommandType,
258       Integer fetchSize,
259       Integer timeout,
260       String parameterMap,
261       Class<?> parameterType,
262       String resultMap,
263       Class<?> resultType,
264       ResultSetType resultSetType,
265       boolean flushCache,
266       boolean useCache,
267       boolean resultOrdered,
268       KeyGenerator keyGenerator,
269       String keyProperty,
270       String keyColumn,
271       String databaseId,
272       LanguageDriver lang,
273       String resultSets) {
274 
275     if (unresolvedCacheRef) {
276       throw new IncompleteElementException("Cache-ref not yet resolved");
277     }
278 
279     id = applyCurrentNamespace(id, false);
280     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
281 
282     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
283     statementBuilder.resource(resource);
284     statementBuilder.fetchSize(fetchSize);
285     statementBuilder.statementType(statementType);
286     statementBuilder.keyGenerator(keyGenerator);
287     statementBuilder.keyProperty(keyProperty);
288     statementBuilder.keyColumn(keyColumn);
289     statementBuilder.databaseId(databaseId);
290     statementBuilder.lang(lang);
291     statementBuilder.resultOrdered(resultOrdered);
292     statementBuilder.resulSets(resultSets);
293     setStatementTimeout(timeout, statementBuilder);
294 
295     setStatementParameterMap(parameterMap, parameterType, statementBuilder);
296     setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
297     setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
298 
299     MappedStatement statement = statementBuilder.build();
300     configuration.addMappedStatement(statement);
301     return statement;
302   }
303 
304   private <T> T valueOrDefault(T value, T defaultValue) {
305     return value == null ? defaultValue : value;
306   }
307 
308   private void setStatementCache(
309       boolean isSelect,
310       boolean flushCache,
311       boolean useCache,
312       Cache cache,
313       MappedStatement.Builder statementBuilder) {
314     flushCache = valueOrDefault(flushCache, !isSelect);
315     useCache = valueOrDefault(useCache, isSelect);
316     statementBuilder.flushCacheRequired(flushCache);
317     statementBuilder.useCache(useCache);
318     statementBuilder.cache(cache);
319   }
320 
321   private void setStatementParameterMap(
322       String parameterMap,
323       Class<?> parameterTypeClass,
324       MappedStatement.Builder statementBuilder) {
325     parameterMap = applyCurrentNamespace(parameterMap, true);
326 
327     if (parameterMap != null) {
328       try {
329         statementBuilder.parameterMap(configuration.getParameterMap(parameterMap));
330       } catch (IllegalArgumentException e) {
331         throw new IncompleteElementException("Could not find parameter map " + parameterMap, e);
332       }
333     } else if (parameterTypeClass != null) {
334       List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
335       ParameterMap.Builder inlineParameterMapBuilder = new ParameterMap.Builder(
336           configuration,
337           statementBuilder.id() + "-Inline",
338           parameterTypeClass,
339           parameterMappings);
340       statementBuilder.parameterMap(inlineParameterMapBuilder.build());
341     }
342   }
343 
344   private void setStatementResultMap(
345       String resultMap,
346       Class<?> resultType,
347       ResultSetType resultSetType,
348       MappedStatement.Builder statementBuilder) {
349     resultMap = applyCurrentNamespace(resultMap, true);
350 
351     List<ResultMap> resultMaps = new ArrayList<ResultMap>();
352     if (resultMap != null) {
353       String[] resultMapNames = resultMap.split(",");
354       for (String resultMapName : resultMapNames) {
355         try {
356           resultMaps.add(configuration.getResultMap(resultMapName.trim()));
357         } catch (IllegalArgumentException e) {
358           throw new IncompleteElementException("Could not find result map " + resultMapName, e);
359         }
360       }
361     } else if (resultType != null) {
362       ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
363           configuration,
364           statementBuilder.id() + "-Inline",
365           resultType,
366           new ArrayList<ResultMapping>(),
367           null);
368       resultMaps.add(inlineResultMapBuilder.build());
369     }
370     statementBuilder.resultMaps(resultMaps);
371 
372     statementBuilder.resultSetType(resultSetType);
373   }
374 
375   private void setStatementTimeout(Integer timeout, MappedStatement.Builder statementBuilder) {
376     if (timeout == null) {
377       timeout = configuration.getDefaultStatementTimeout();
378     }
379     statementBuilder.timeout(timeout);
380   }
381 
382   public ResultMapping buildResultMapping(
383       Class<?> resultType,
384       String property,
385       String column,
386       Class<?> javaType,
387       JdbcType jdbcType,
388       String nestedSelect,
389       String nestedResultMap,
390       String notNullColumn,
391       String columnPrefix,
392       Class<? extends TypeHandler<?>> typeHandler,
393       List<ResultFlag> flags,
394       String resultSet,
395       String foreignColumn,
396       boolean lazy) {
397     Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
398     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
399     List<ResultMapping> composites = parseCompositeColumnName(column);
400     if (composites.size() > 0) {
401       column = null;
402     }
403     ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
404     builder.jdbcType(jdbcType);
405     builder.nestedQueryId(applyCurrentNamespace(nestedSelect, true));
406     builder.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true));
407     builder.resultSet(resultSet);
408     builder.typeHandler(typeHandlerInstance);
409     builder.flags(flags == null ? new ArrayList<ResultFlag>() : flags);
410     builder.composites(composites);
411     builder.notNullColumns(parseMultipleColumnNames(notNullColumn));
412     builder.columnPrefix(columnPrefix);
413     builder.foreignColumn(foreignColumn);
414     builder.lazy(lazy);
415     return builder.build();
416   }
417 
418   private Set<String> parseMultipleColumnNames(String columnName) {
419     Set<String> columns = new HashSet<String>();
420     if (columnName != null) {
421       if (columnName.indexOf(',') > -1) {
422         StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
423         while (parser.hasMoreTokens()) {
424           String column = parser.nextToken();
425           columns.add(column);
426         }
427       } else {
428         columns.add(columnName);
429       }
430     }
431     return columns;
432   }
433 
434   private List<ResultMapping> parseCompositeColumnName(String columnName) {
435     List<ResultMapping> composites = new ArrayList<ResultMapping>();
436     if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
437       StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
438       while (parser.hasMoreTokens()) {
439         String property = parser.nextToken();
440         String column = parser.nextToken();
441         ResultMapping.Builder complexBuilder = new ResultMapping.Builder(configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler());
442         composites.add(complexBuilder.build());
443       }
444     }
445     return composites;
446   }
447 
448   private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
449     if (javaType == null && property != null) {
450       try {
451         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
452         javaType = metaResultType.getSetterType(property);
453       } catch (Exception e) {
454         //ignore, following null check statement will deal with the situation
455       }
456     }
457     if (javaType == null) {
458       javaType = Object.class;
459     }
460     return javaType;
461   }
462 
463   private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {
464     if (javaType == null) {
465       if (JdbcType.CURSOR.equals(jdbcType)) {
466         javaType = java.sql.ResultSet.class;
467       } else if (Map.class.isAssignableFrom(resultType)) {
468         javaType = Object.class;
469       } else {
470         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
471         javaType = metaResultType.getGetterType(property);
472       }
473     }
474     if (javaType == null) {
475       javaType = Object.class;
476     }
477     return javaType;
478   }
479 
480   /** Backward compatibility signature */
481   public ResultMapping buildResultMapping(
482       Class<?> resultType,
483       String property,
484       String column,
485       Class<?> javaType,
486       JdbcType jdbcType,
487       String nestedSelect,
488       String nestedResultMap,
489       String notNullColumn,
490       String columnPrefix,
491       Class<? extends TypeHandler<?>> typeHandler,
492       List<ResultFlag> flags) {
493       return buildResultMapping(
494         resultType, property, column, javaType, jdbcType, nestedSelect,
495         nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
496   }
497 
498   public LanguageDriver getLanguageDriver(Class<?> langClass) {
499     if (langClass != null) {
500       configuration.getLanguageRegistry().register(langClass);
501     } else {
502       langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
503     }
504     return configuration.getLanguageRegistry().getDriver(langClass);
505   }
506 
507   /** Backward compatibility signature */
508   public MappedStatement addMappedStatement(
509     String id,
510     SqlSource sqlSource,
511     StatementType statementType,
512     SqlCommandType sqlCommandType,
513     Integer fetchSize,
514     Integer timeout,
515     String parameterMap,
516     Class<?> parameterType,
517     String resultMap,
518     Class<?> resultType,
519     ResultSetType resultSetType,
520     boolean flushCache,
521     boolean useCache,
522     boolean resultOrdered,
523     KeyGenerator keyGenerator,
524     String keyProperty,
525     String keyColumn,
526     String databaseId,
527     LanguageDriver lang) {
528     return addMappedStatement(
529       id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
530       parameterMap, parameterType, resultMap, resultType, resultSetType,
531       flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
532       keyColumn, databaseId, lang, null);
533   }
534 
535 }