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