1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.builder.xml;
17
18 import java.io.InputStream;
19 import java.io.Reader;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Properties;
29
30 import org.apache.ibatis.builder.BaseBuilder;
31 import org.apache.ibatis.builder.BuilderException;
32 import org.apache.ibatis.builder.CacheRefResolver;
33 import org.apache.ibatis.builder.IncompleteElementException;
34 import org.apache.ibatis.builder.MapperBuilderAssistant;
35 import org.apache.ibatis.builder.ResultMapResolver;
36 import org.apache.ibatis.cache.Cache;
37 import org.apache.ibatis.executor.ErrorContext;
38 import org.apache.ibatis.io.Resources;
39 import org.apache.ibatis.mapping.Discriminator;
40 import org.apache.ibatis.mapping.ParameterMapping;
41 import org.apache.ibatis.mapping.ParameterMode;
42 import org.apache.ibatis.mapping.ResultFlag;
43 import org.apache.ibatis.mapping.ResultMap;
44 import org.apache.ibatis.mapping.ResultMapping;
45 import org.apache.ibatis.parsing.XNode;
46 import org.apache.ibatis.parsing.XPathParser;
47 import org.apache.ibatis.reflection.MetaClass;
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
56 public class XMLMapperBuilder extends BaseBuilder {
57
58 private final XPathParser parser;
59 private final MapperBuilderAssistant builderAssistant;
60 private final Map<String, XNode> sqlFragments;
61 private final String resource;
62
63 @Deprecated
64 public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments,
65 String namespace) {
66 this(reader, configuration, resource, sqlFragments);
67 this.builderAssistant.setCurrentNamespace(namespace);
68 }
69
70 @Deprecated
71 public XMLMapperBuilder(Reader reader, Configuration configuration, String resource,
72 Map<String, XNode> sqlFragments) {
73 this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
74 resource, sqlFragments);
75 }
76
77 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
78 Map<String, XNode> sqlFragments, String namespace) {
79 this(inputStream, configuration, resource, sqlFragments);
80 this.builderAssistant.setCurrentNamespace(namespace);
81 }
82
83 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
84 Map<String, XNode> sqlFragments) {
85 this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
86 resource, sqlFragments);
87 }
88
89 private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource,
90 Map<String, XNode> sqlFragments) {
91 super(configuration);
92 this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
93 this.parser = parser;
94 this.sqlFragments = sqlFragments;
95 this.resource = resource;
96 }
97
98 public void parse() {
99 if (!configuration.isResourceLoaded(resource)) {
100 configurationElement(parser.evalNode("/mapper"));
101 configuration.addLoadedResource(resource);
102 bindMapperForNamespace();
103 }
104 parsePendingResultMaps();
105 parsePendingCacheRefs();
106 parsePendingStatements();
107 }
108
109 public XNode getSqlFragment(String refid) {
110 return sqlFragments.get(refid);
111 }
112
113 private void configurationElement(XNode context) {
114 try {
115 String namespace = context.getStringAttribute("namespace");
116 if (namespace == null || namespace.isEmpty()) {
117 throw new BuilderException("Mapper's namespace cannot be empty");
118 }
119 builderAssistant.setCurrentNamespace(namespace);
120 cacheRefElement(context.evalNode("cache-ref"));
121 cacheElement(context.evalNode("cache"));
122 parameterMapElement(context.evalNodes("/mapper/parameterMap"));
123 resultMapElements(context.evalNodes("/mapper/resultMap"));
124 sqlElement(context.evalNodes("/mapper/sql"));
125 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
126 } catch (Exception e) {
127 throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
128 }
129 }
130
131 private void buildStatementFromContext(List<XNode> list) {
132 if (configuration.getDatabaseId() != null) {
133 buildStatementFromContext(list, configuration.getDatabaseId());
134 }
135 buildStatementFromContext(list, null);
136 }
137
138 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
139 for (XNode context : list) {
140 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
141 requiredDatabaseId);
142 try {
143 statementParser.parseStatementNode();
144 } catch (IncompleteElementException e) {
145 configuration.addIncompleteStatement(statementParser);
146 }
147 }
148 }
149
150 private void parsePendingResultMaps() {
151 Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
152 synchronized (incompleteResultMaps) {
153 Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
154 while (iter.hasNext()) {
155 try {
156 iter.next().resolve();
157 iter.remove();
158 } catch (IncompleteElementException e) {
159
160 }
161 }
162 }
163 }
164
165 private void parsePendingCacheRefs() {
166 Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
167 synchronized (incompleteCacheRefs) {
168 Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
169 while (iter.hasNext()) {
170 try {
171 iter.next().resolveCacheRef();
172 iter.remove();
173 } catch (IncompleteElementException e) {
174
175 }
176 }
177 }
178 }
179
180 private void parsePendingStatements() {
181 Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
182 synchronized (incompleteStatements) {
183 Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
184 while (iter.hasNext()) {
185 try {
186 iter.next().parseStatementNode();
187 iter.remove();
188 } catch (IncompleteElementException e) {
189
190 }
191 }
192 }
193 }
194
195 private void cacheRefElement(XNode context) {
196 if (context != null) {
197 configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
198 CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant,
199 context.getStringAttribute("namespace"));
200 try {
201 cacheRefResolver.resolveCacheRef();
202 } catch (IncompleteElementException e) {
203 configuration.addIncompleteCacheRef(cacheRefResolver);
204 }
205 }
206 }
207
208 private void cacheElement(XNode context) {
209 if (context != null) {
210 String type = context.getStringAttribute("type", "PERPETUAL");
211 Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
212 String eviction = context.getStringAttribute("eviction", "LRU");
213 Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
214 Long flushInterval = context.getLongAttribute("flushInterval");
215 Integer size = context.getIntAttribute("size");
216 boolean readWrite = !context.getBooleanAttribute("readOnly", false);
217 boolean blocking = context.getBooleanAttribute("blocking", false);
218 Properties props = context.getChildrenAsProperties();
219 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
220 }
221 }
222
223 private void parameterMapElement(List<XNode> list) {
224 for (XNode parameterMapNode : list) {
225 String id = parameterMapNode.getStringAttribute("id");
226 String type = parameterMapNode.getStringAttribute("type");
227 Class<?> parameterClass = resolveClass(type);
228 List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
229 List<ParameterMapping> parameterMappings = new ArrayList<>();
230 for (XNode parameterNode : parameterNodes) {
231 String property = parameterNode.getStringAttribute("property");
232 String javaType = parameterNode.getStringAttribute("javaType");
233 String jdbcType = parameterNode.getStringAttribute("jdbcType");
234 String resultMap = parameterNode.getStringAttribute("resultMap");
235 String mode = parameterNode.getStringAttribute("mode");
236 String typeHandler = parameterNode.getStringAttribute("typeHandler");
237 Integer numericScale = parameterNode.getIntAttribute("numericScale");
238 ParameterMode modeEnum = resolveParameterMode(mode);
239 Class<?> javaTypeClass = resolveClass(javaType);
240 JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
241 Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
242 ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property,
243 javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
244 parameterMappings.add(parameterMapping);
245 }
246 builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
247 }
248 }
249
250 private void resultMapElements(List<XNode> list) {
251 for (XNode resultMapNode : list) {
252 try {
253 resultMapElement(resultMapNode);
254 } catch (IncompleteElementException e) {
255
256 }
257 }
258 }
259
260 private ResultMap resultMapElement(XNode resultMapNode) {
261 return resultMapElement(resultMapNode, Collections.emptyList(), null);
262 }
263
264 private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
265 Class<?> enclosingType) {
266 ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
267 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
268 resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
269 Class<?> typeClass = resolveClass(type);
270 if (typeClass == null) {
271 typeClass = inheritEnclosingType(resultMapNode, enclosingType);
272 }
273 Discriminator discriminator = null;
274 List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
275 List<XNode> resultChildren = resultMapNode.getChildren();
276 for (XNode resultChild : resultChildren) {
277 if ("constructor".equals(resultChild.getName())) {
278 processConstructorElement(resultChild, typeClass, resultMappings);
279 } else if ("discriminator".equals(resultChild.getName())) {
280 discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
281 } else {
282 List<ResultFlag> flags = new ArrayList<>();
283 if ("id".equals(resultChild.getName())) {
284 flags.add(ResultFlag.ID);
285 }
286 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
287 }
288 }
289 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
290 String extend = resultMapNode.getStringAttribute("extends");
291 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
292 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
293 resultMappings, autoMapping);
294 try {
295 return resultMapResolver.resolve();
296 } catch (IncompleteElementException e) {
297 configuration.addIncompleteResultMap(resultMapResolver);
298 throw e;
299 }
300 }
301
302 protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
303 if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
304 String property = resultMapNode.getStringAttribute("property");
305 if (property != null && enclosingType != null) {
306 MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
307 return metaResultType.getSetterType(property);
308 }
309 } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
310 return enclosingType;
311 }
312 return null;
313 }
314
315 private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
316 List<XNode> argChildren = resultChild.getChildren();
317 for (XNode argChild : argChildren) {
318 List<ResultFlag> flags = new ArrayList<>();
319 flags.add(ResultFlag.CONSTRUCTOR);
320 if ("idArg".equals(argChild.getName())) {
321 flags.add(ResultFlag.ID);
322 }
323 resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
324 }
325 }
326
327 private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType,
328 List<ResultMapping> resultMappings) {
329 String column = context.getStringAttribute("column");
330 String javaType = context.getStringAttribute("javaType");
331 String jdbcType = context.getStringAttribute("jdbcType");
332 String typeHandler = context.getStringAttribute("typeHandler");
333 Class<?> javaTypeClass = resolveClass(javaType);
334 Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
335 JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
336 Map<String, String> discriminatorMap = new HashMap<>();
337 for (XNode caseChild : context.getChildren()) {
338 String value = caseChild.getStringAttribute("value");
339 String resultMap = caseChild.getStringAttribute("resultMap",
340 processNestedResultMappings(caseChild, resultMappings, resultType));
341 discriminatorMap.put(value, resultMap);
342 }
343 return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass,
344 discriminatorMap);
345 }
346
347 private void sqlElement(List<XNode> list) {
348 if (configuration.getDatabaseId() != null) {
349 sqlElement(list, configuration.getDatabaseId());
350 }
351 sqlElement(list, null);
352 }
353
354 private void sqlElement(List<XNode> list, String requiredDatabaseId) {
355 for (XNode context : list) {
356 String databaseId = context.getStringAttribute("databaseId");
357 String id = context.getStringAttribute("id");
358 id = builderAssistant.applyCurrentNamespace(id, false);
359 if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
360 sqlFragments.put(id, context);
361 }
362 }
363 }
364
365 private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
366 if (requiredDatabaseId != null) {
367 return requiredDatabaseId.equals(databaseId);
368 }
369 if (databaseId != null) {
370 return false;
371 }
372 if (!this.sqlFragments.containsKey(id)) {
373 return true;
374 }
375
376 XNode context = this.sqlFragments.get(id);
377 return context.getStringAttribute("databaseId") == null;
378 }
379
380 private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
381 String property;
382 if (flags.contains(ResultFlag.CONSTRUCTOR)) {
383 property = context.getStringAttribute("name");
384 } else {
385 property = context.getStringAttribute("property");
386 }
387 String column = context.getStringAttribute("column");
388 String javaType = context.getStringAttribute("javaType");
389 String jdbcType = context.getStringAttribute("jdbcType");
390 String nestedSelect = context.getStringAttribute("select");
391 String nestedResultMap = context.getStringAttribute("resultMap",
392 () -> processNestedResultMappings(context, Collections.emptyList(), resultType));
393 String notNullColumn = context.getStringAttribute("notNullColumn");
394 String columnPrefix = context.getStringAttribute("columnPrefix");
395 String typeHandler = context.getStringAttribute("typeHandler");
396 String resultSet = context.getStringAttribute("resultSet");
397 String foreignColumn = context.getStringAttribute("foreignColumn");
398 boolean lazy = "lazy"
399 .equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
400 Class<?> javaTypeClass = resolveClass(javaType);
401 Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
402 JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
403 return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,
404 nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
405 }
406
407 private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings,
408 Class<?> enclosingType) {
409 if (Arrays.asList("association", "collection", "case").contains(context.getName())
410 && context.getStringAttribute("select") == null) {
411 validateCollection(context, enclosingType);
412 ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
413 return resultMap.getId();
414 }
415 return null;
416 }
417
418 protected void validateCollection(XNode context, Class<?> enclosingType) {
419 if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
420 && context.getStringAttribute("javaType") == null) {
421 MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
422 String property = context.getStringAttribute("property");
423 if (!metaResultType.hasSetter(property)) {
424 throw new BuilderException(
425 "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
426 }
427 }
428 }
429
430 private void bindMapperForNamespace() {
431 String namespace = builderAssistant.getCurrentNamespace();
432 if (namespace != null) {
433 Class<?> boundType = null;
434 try {
435 boundType = Resources.classForName(namespace);
436 } catch (ClassNotFoundException e) {
437
438 }
439 if (boundType != null && !configuration.hasMapper(boundType)) {
440
441
442
443 configuration.addLoadedResource("namespace:" + namespace);
444 configuration.addMapper(boundType);
445 }
446 }
447 }
448
449 }