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.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
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;
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
91 if (base.contains(".")) {
92 return base;
93 }
94 } else {
95
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
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
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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
345 SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
346 String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
347 boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
348 LanguageDriver lang) {
349 return addMappedStatement(
350 id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
351 parameterMap, parameterType, resultMap, resultType, resultSetType,
352 flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
353 keyColumn, databaseId, lang, null);
354 }
355
356 private <T> T valueOrDefault(T value, T defaultValue) {
357 return value == null ? defaultValue : value;
358 }
359
360 private ParameterMap getStatementParameterMap(
361 String parameterMapName,
362 Class<?> parameterTypeClass,
363 String statementId) {
364 parameterMapName = applyCurrentNamespace(parameterMapName, true);
365 ParameterMap parameterMap = null;
366 if (parameterMapName != null) {
367 try {
368 parameterMap = configuration.getParameterMap(parameterMapName);
369 } catch (IllegalArgumentException e) {
370 throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
371 }
372 } else if (parameterTypeClass != null) {
373 List<ParameterMapping> parameterMappings = new ArrayList<>();
374 parameterMap = new ParameterMap.Builder(
375 configuration,
376 statementId + "-Inline",
377 parameterTypeClass,
378 parameterMappings).build();
379 }
380 return parameterMap;
381 }
382
383 private List<ResultMap> getStatementResultMaps(
384 String resultMap,
385 Class<?> resultType,
386 String statementId) {
387 resultMap = applyCurrentNamespace(resultMap, true);
388
389 List<ResultMap> resultMaps = new ArrayList<>();
390 if (resultMap != null) {
391 String[] resultMapNames = resultMap.split(",");
392 for (String resultMapName : resultMapNames) {
393 try {
394 resultMaps.add(configuration.getResultMap(resultMapName.trim()));
395 } catch (IllegalArgumentException e) {
396 throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
397 }
398 }
399 } else if (resultType != null) {
400 ResultMap inlineResultMap = new ResultMap.Builder(
401 configuration,
402 statementId + "-Inline",
403 resultType,
404 new ArrayList<>(),
405 null).build();
406 resultMaps.add(inlineResultMap);
407 }
408 return resultMaps;
409 }
410
411 public ResultMapping buildResultMapping(
412 Class<?> resultType,
413 String property,
414 String column,
415 Class<?> javaType,
416 JdbcType jdbcType,
417 String nestedSelect,
418 String nestedResultMap,
419 String notNullColumn,
420 String columnPrefix,
421 Class<? extends TypeHandler<?>> typeHandler,
422 List<ResultFlag> flags,
423 String resultSet,
424 String foreignColumn,
425 boolean lazy) {
426 Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
427 TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
428 List<ResultMapping> composites;
429 if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
430 composites = Collections.emptyList();
431 } else {
432 composites = parseCompositeColumnName(column);
433 }
434 return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
435 .jdbcType(jdbcType)
436 .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
437 .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
438 .resultSet(resultSet)
439 .typeHandler(typeHandlerInstance)
440 .flags(flags == null ? new ArrayList<>() : flags)
441 .composites(composites)
442 .notNullColumns(parseMultipleColumnNames(notNullColumn))
443 .columnPrefix(columnPrefix)
444 .foreignColumn(foreignColumn)
445 .lazy(lazy)
446 .build();
447 }
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476 public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
477 JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
478 Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags) {
479 return buildResultMapping(
480 resultType, property, column, javaType, jdbcType, nestedSelect,
481 nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
482 }
483
484
485
486
487
488
489
490
491
492 @Deprecated
493 public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
494 return configuration.getLanguageDriver(langClass);
495 }
496
497 private Set<String> parseMultipleColumnNames(String columnName) {
498 Set<String> columns = new HashSet<>();
499 if (columnName != null) {
500 if (columnName.indexOf(',') > -1) {
501 StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
502 while (parser.hasMoreTokens()) {
503 String column = parser.nextToken();
504 columns.add(column);
505 }
506 } else {
507 columns.add(columnName);
508 }
509 }
510 return columns;
511 }
512
513 private List<ResultMapping> parseCompositeColumnName(String columnName) {
514 List<ResultMapping> composites = new ArrayList<>();
515 if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
516 StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
517 while (parser.hasMoreTokens()) {
518 String property = parser.nextToken();
519 String column = parser.nextToken();
520 ResultMapping complexResultMapping = new ResultMapping.Builder(
521 configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
522 composites.add(complexResultMapping);
523 }
524 }
525 return composites;
526 }
527
528 private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
529 if (javaType == null && property != null) {
530 try {
531 MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
532 javaType = metaResultType.getSetterType(property);
533 } catch (Exception e) {
534
535 }
536 }
537 if (javaType == null) {
538 javaType = Object.class;
539 }
540 return javaType;
541 }
542
543 private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {
544 if (javaType == null) {
545 if (JdbcType.CURSOR.equals(jdbcType)) {
546 javaType = java.sql.ResultSet.class;
547 } else if (Map.class.isAssignableFrom(resultType)) {
548 javaType = Object.class;
549 } else {
550 MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
551 javaType = metaResultType.getGetterType(property);
552 }
553 }
554 if (javaType == null) {
555 javaType = Object.class;
556 }
557 return javaType;
558 }
559
560 }