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