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