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