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