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     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<ResultMapping>(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         Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
203         while (extendedResultMappingsIter.hasNext()) {
204           if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
205             extendedResultMappingsIter.remove();
206           }
207         }
208       }
209       resultMappings.addAll(extendedResultMappings);
210     }
211     ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
212         .discriminator(discriminator)
213         .build();
214     configuration.addResultMap(resultMap);
215     return resultMap;
216   }
217 
218   public Discriminator buildDiscriminator(
219       Class<?> resultType,
220       String column,
221       Class<?> javaType,
222       JdbcType jdbcType,
223       Class<? extends TypeHandler<?>> typeHandler,
224       Map<String, String> discriminatorMap) {
225     ResultMapping resultMapping = buildResultMapping(
226         resultType,
227         null,
228         column,
229         javaType,
230         jdbcType,
231         null,
232         null,
233         null,
234         null,
235         typeHandler,
236         new ArrayList<ResultFlag>(),
237         null,
238         null,
239         false);
240     Map<String, String> namespaceDiscriminatorMap = new HashMap<String, String>();
241     for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
242       String resultMap = e.getValue();
243       resultMap = applyCurrentNamespace(resultMap, true);
244       namespaceDiscriminatorMap.put(e.getKey(), resultMap);
245     }
246     return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
247   }
248 
249   public MappedStatement addMappedStatement(
250       String id,
251       SqlSource sqlSource,
252       StatementType statementType,
253       SqlCommandType sqlCommandType,
254       Integer fetchSize,
255       Integer timeout,
256       String parameterMap,
257       Class<?> parameterType,
258       String resultMap,
259       Class<?> resultType,
260       ResultSetType resultSetType,
261       boolean flushCache,
262       boolean useCache,
263       boolean resultOrdered,
264       KeyGenerator keyGenerator,
265       String keyProperty,
266       String keyColumn,
267       String databaseId,
268       LanguageDriver lang,
269       String resultSets) {
270 
271     if (unresolvedCacheRef) {
272       throw new IncompleteElementException("Cache-ref not yet resolved");
273     }
274 
275     id = applyCurrentNamespace(id, false);
276     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
277 
278     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
279         .resource(resource)
280         .fetchSize(fetchSize)
281         .timeout(timeout)
282         .statementType(statementType)
283         .keyGenerator(keyGenerator)
284         .keyProperty(keyProperty)
285         .keyColumn(keyColumn)
286         .databaseId(databaseId)
287         .lang(lang)
288         .resultOrdered(resultOrdered)
289         .resulSets(resultSets)
290         .resultMaps(getStatementResultMaps(resultMap, resultType, id))
291         .resultSetType(resultSetType)
292         .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
293         .useCache(valueOrDefault(useCache, isSelect))
294         .cache(currentCache);
295 
296     ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
297     if (statementParameterMap != null) {
298       statementBuilder.parameterMap(statementParameterMap);
299     }
300 
301     MappedStatement statement = statementBuilder.build();
302     configuration.addMappedStatement(statement);
303     return statement;
304   }
305 
306   private <T> T valueOrDefault(T value, T defaultValue) {
307     return value == null ? defaultValue : value;
308   }
309 
310   private ParameterMap getStatementParameterMap(
311       String parameterMapName,
312       Class<?> parameterTypeClass,
313       String statementId) {
314     parameterMapName = applyCurrentNamespace(parameterMapName, true);
315     ParameterMap parameterMap = null;
316     if (parameterMapName != null) {
317       try {
318         parameterMap = configuration.getParameterMap(parameterMapName);
319       } catch (IllegalArgumentException e) {
320         throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
321       }
322     } else if (parameterTypeClass != null) {
323       List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
324       parameterMap = new ParameterMap.Builder(
325           configuration,
326           statementId + "-Inline",
327           parameterTypeClass,
328           parameterMappings).build();
329     }
330     return parameterMap;
331   }
332 
333   private List<ResultMap> getStatementResultMaps(
334       String resultMap,
335       Class<?> resultType,
336       String statementId) {
337     resultMap = applyCurrentNamespace(resultMap, true);
338 
339     List<ResultMap> resultMaps = new ArrayList<ResultMap>();
340     if (resultMap != null) {
341       String[] resultMapNames = resultMap.split(",");
342       for (String resultMapName : resultMapNames) {
343         try {
344           resultMaps.add(configuration.getResultMap(resultMapName.trim()));
345         } catch (IllegalArgumentException e) {
346           throw new IncompleteElementException("Could not find result map " + resultMapName, e);
347         }
348       }
349     } else if (resultType != null) {
350       ResultMap inlineResultMap = new ResultMap.Builder(
351           configuration,
352           statementId + "-Inline",
353           resultType,
354           new ArrayList<ResultMapping>(),
355           null).build();
356       resultMaps.add(inlineResultMap);
357     }
358     return resultMaps;
359   }
360 
361   public ResultMapping buildResultMapping(
362       Class<?> resultType,
363       String property,
364       String column,
365       Class<?> javaType,
366       JdbcType jdbcType,
367       String nestedSelect,
368       String nestedResultMap,
369       String notNullColumn,
370       String columnPrefix,
371       Class<? extends TypeHandler<?>> typeHandler,
372       List<ResultFlag> flags,
373       String resultSet,
374       String foreignColumn,
375       boolean lazy) {
376     Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
377     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
378     List<ResultMapping> composites = parseCompositeColumnName(column);
379     if (composites.size() > 0) {
380       column = null;
381     }
382     return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
383         .jdbcType(jdbcType)
384         .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
385         .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
386         .resultSet(resultSet)
387         .typeHandler(typeHandlerInstance)
388         .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
389         .composites(composites)
390         .notNullColumns(parseMultipleColumnNames(notNullColumn))
391         .columnPrefix(columnPrefix)
392         .foreignColumn(foreignColumn)
393         .lazy(lazy)
394         .build();
395   }
396 
397   private Set<String> parseMultipleColumnNames(String columnName) {
398     Set<String> columns = new HashSet<String>();
399     if (columnName != null) {
400       if (columnName.indexOf(',') > -1) {
401         StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
402         while (parser.hasMoreTokens()) {
403           String column = parser.nextToken();
404           columns.add(column);
405         }
406       } else {
407         columns.add(columnName);
408       }
409     }
410     return columns;
411   }
412 
413   private List<ResultMapping> parseCompositeColumnName(String columnName) {
414     List<ResultMapping> composites = new ArrayList<ResultMapping>();
415     if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
416       StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
417       while (parser.hasMoreTokens()) {
418         String property = parser.nextToken();
419         String column = parser.nextToken();
420         ResultMapping complexResultMapping = new ResultMapping.Builder(
421             configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
422         composites.add(complexResultMapping);
423       }
424     }
425     return composites;
426   }
427 
428   private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
429     if (javaType == null && property != null) {
430       try {
431         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
432         javaType = metaResultType.getSetterType(property);
433       } catch (Exception e) {
434         //ignore, following null check statement will deal with the situation
435       }
436     }
437     if (javaType == null) {
438       javaType = Object.class;
439     }
440     return javaType;
441   }
442 
443   private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {
444     if (javaType == null) {
445       if (JdbcType.CURSOR.equals(jdbcType)) {
446         javaType = java.sql.ResultSet.class;
447       } else if (Map.class.isAssignableFrom(resultType)) {
448         javaType = Object.class;
449       } else {
450         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
451         javaType = metaResultType.getGetterType(property);
452       }
453     }
454     if (javaType == null) {
455       javaType = Object.class;
456     }
457     return javaType;
458   }
459 
460   /** Backward compatibility signature */
461   public ResultMapping buildResultMapping(
462       Class<?> resultType,
463       String property,
464       String column,
465       Class<?> javaType,
466       JdbcType jdbcType,
467       String nestedSelect,
468       String nestedResultMap,
469       String notNullColumn,
470       String columnPrefix,
471       Class<? extends TypeHandler<?>> typeHandler,
472       List<ResultFlag> flags) {
473       return buildResultMapping(
474         resultType, property, column, javaType, jdbcType, nestedSelect,
475         nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
476   }
477 
478   public LanguageDriver getLanguageDriver(Class<?> langClass) {
479     if (langClass != null) {
480       configuration.getLanguageRegistry().register(langClass);
481     } else {
482       langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
483     }
484     return configuration.getLanguageRegistry().getDriver(langClass);
485   }
486 
487   /** Backward compatibility signature */
488   public MappedStatement addMappedStatement(
489     String id,
490     SqlSource sqlSource,
491     StatementType statementType,
492     SqlCommandType sqlCommandType,
493     Integer fetchSize,
494     Integer timeout,
495     String parameterMap,
496     Class<?> parameterType,
497     String resultMap,
498     Class<?> resultType,
499     ResultSetType resultSetType,
500     boolean flushCache,
501     boolean useCache,
502     boolean resultOrdered,
503     KeyGenerator keyGenerator,
504     String keyProperty,
505     String keyColumn,
506     String databaseId,
507     LanguageDriver lang) {
508     return addMappedStatement(
509       id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
510       parameterMap, parameterType, resultMap, resultType, resultSetType,
511       flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
512       keyColumn, databaseId, lang, null);
513   }
514 
515 }