View Javadoc
1   /**
2    *    Copyright 2009-2015 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
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   * @author Clinton Begin
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           // ResultMap is still missing a resource...
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           // Cache ref is still missing a resource...
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           // Statement is still missing a resource...
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         // ignore, it will be retried
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       // skip this fragment if there is a previous one with a not null databaseId
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         //ignore, bound type is not required
401       }
402       if (boundType != null) {
403         if (!configuration.hasMapper(boundType)) {
404           // Spring may not know the real resource name so we set a flag
405           // to prevent loading again this resource from the mapper interface
406           // look at MapperAnnotationBuilder#loadXmlResource
407           configuration.addLoadedResource("namespace:" + namespace);
408           configuration.addMapper(boundType);
409         }
410       }
411     }
412   }
413 
414 }