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.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
54
55 public class MapperBuilderAssistant extends BaseBuilder {
56
57 private String currentNamespace;
58 private 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 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
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
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
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
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
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 }