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