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 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
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
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
468
469 @Deprecated
470 public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
471 return configuration.getLanguageDriver(langClass);
472 }
473
474
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 }