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 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<ResultMapping>(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 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
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
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
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 }