XMLConfigBuilder.java

  1. /*
  2.  *    Copyright 2009-2024 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.  *       https://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. import java.io.InputStream;
  18. import java.io.Reader;
  19. import java.util.Properties;

  20. import javax.sql.DataSource;

  21. import org.apache.ibatis.builder.BaseBuilder;
  22. import org.apache.ibatis.builder.BuilderException;
  23. import org.apache.ibatis.datasource.DataSourceFactory;
  24. import org.apache.ibatis.executor.ErrorContext;
  25. import org.apache.ibatis.executor.loader.ProxyFactory;
  26. import org.apache.ibatis.io.Resources;
  27. import org.apache.ibatis.io.VFS;
  28. import org.apache.ibatis.logging.Log;
  29. import org.apache.ibatis.mapping.DatabaseIdProvider;
  30. import org.apache.ibatis.mapping.Environment;
  31. import org.apache.ibatis.parsing.XNode;
  32. import org.apache.ibatis.parsing.XPathParser;
  33. import org.apache.ibatis.plugin.Interceptor;
  34. import org.apache.ibatis.reflection.DefaultReflectorFactory;
  35. import org.apache.ibatis.reflection.MetaClass;
  36. import org.apache.ibatis.reflection.ReflectorFactory;
  37. import org.apache.ibatis.reflection.factory.ObjectFactory;
  38. import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
  39. import org.apache.ibatis.session.AutoMappingBehavior;
  40. import org.apache.ibatis.session.AutoMappingUnknownColumnBehavior;
  41. import org.apache.ibatis.session.Configuration;
  42. import org.apache.ibatis.session.ExecutorType;
  43. import org.apache.ibatis.session.LocalCacheScope;
  44. import org.apache.ibatis.transaction.TransactionFactory;
  45. import org.apache.ibatis.type.JdbcType;

  46. /**
  47.  * @author Clinton Begin
  48.  * @author Kazuki Shimizu
  49.  */
  50. public class XMLConfigBuilder extends BaseBuilder {

  51.   private boolean parsed;
  52.   private final XPathParser parser;
  53.   private String environment;
  54.   private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

  55.   public XMLConfigBuilder(Reader reader) {
  56.     this(reader, null, null);
  57.   }

  58.   public XMLConfigBuilder(Reader reader, String environment) {
  59.     this(reader, environment, null);
  60.   }

  61.   public XMLConfigBuilder(Reader reader, String environment, Properties props) {
  62.     this(Configuration.class, reader, environment, props);
  63.   }

  64.   public XMLConfigBuilder(Class<? extends Configuration> configClass, Reader reader, String environment,
  65.       Properties props) {
  66.     this(configClass, new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  67.   }

  68.   public XMLConfigBuilder(InputStream inputStream) {
  69.     this(inputStream, null, null);
  70.   }

  71.   public XMLConfigBuilder(InputStream inputStream, String environment) {
  72.     this(inputStream, environment, null);
  73.   }

  74.   public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  75.     this(Configuration.class, inputStream, environment, props);
  76.   }

  77.   public XMLConfigBuilder(Class<? extends Configuration> configClass, InputStream inputStream, String environment,
  78.       Properties props) {
  79.     this(configClass, new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  80.   }

  81.   private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,
  82.       Properties props) {
  83.     super(newConfig(configClass));
  84.     ErrorContext.instance().resource("SQL Mapper Configuration");
  85.     this.configuration.setVariables(props);
  86.     this.parsed = false;
  87.     this.environment = environment;
  88.     this.parser = parser;
  89.   }

  90.   public Configuration parse() {
  91.     if (parsed) {
  92.       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  93.     }
  94.     parsed = true;
  95.     parseConfiguration(parser.evalNode("/configuration"));
  96.     return configuration;
  97.   }

  98.   private void parseConfiguration(XNode root) {
  99.     try {
  100.       // issue #117 read properties first
  101.       propertiesElement(root.evalNode("properties"));
  102.       Properties settings = settingsAsProperties(root.evalNode("settings"));
  103.       loadCustomVfsImpl(settings);
  104.       loadCustomLogImpl(settings);
  105.       typeAliasesElement(root.evalNode("typeAliases"));
  106.       pluginsElement(root.evalNode("plugins"));
  107.       objectFactoryElement(root.evalNode("objectFactory"));
  108.       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  109.       reflectorFactoryElement(root.evalNode("reflectorFactory"));
  110.       settingsElement(settings);
  111.       // read it after objectFactory and objectWrapperFactory issue #631
  112.       environmentsElement(root.evalNode("environments"));
  113.       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  114.       typeHandlersElement(root.evalNode("typeHandlers"));
  115.       mappersElement(root.evalNode("mappers"));
  116.     } catch (Exception e) {
  117.       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  118.     }
  119.   }

  120.   private Properties settingsAsProperties(XNode context) {
  121.     if (context == null) {
  122.       return new Properties();
  123.     }
  124.     Properties props = context.getChildrenAsProperties();
  125.     // Check that all settings are known to the configuration class
  126.     MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  127.     for (Object key : props.keySet()) {
  128.       if (!metaConfig.hasSetter(String.valueOf(key))) {
  129.         throw new BuilderException(
  130.             "The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
  131.       }
  132.     }
  133.     return props;
  134.   }

  135.   private void loadCustomVfsImpl(Properties props) throws ClassNotFoundException {
  136.     String value = props.getProperty("vfsImpl");
  137.     if (value == null) {
  138.       return;
  139.     }
  140.     String[] clazzes = value.split(",");
  141.     for (String clazz : clazzes) {
  142.       if (!clazz.isEmpty()) {
  143.         @SuppressWarnings("unchecked")
  144.         Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
  145.         configuration.setVfsImpl(vfsImpl);
  146.       }
  147.     }
  148.   }

  149.   private void loadCustomLogImpl(Properties props) {
  150.     Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
  151.     configuration.setLogImpl(logImpl);
  152.   }

  153.   private void typeAliasesElement(XNode context) {
  154.     if (context == null) {
  155.       return;
  156.     }
  157.     for (XNode child : context.getChildren()) {
  158.       if ("package".equals(child.getName())) {
  159.         String typeAliasPackage = child.getStringAttribute("name");
  160.         configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
  161.       } else {
  162.         String alias = child.getStringAttribute("alias");
  163.         String type = child.getStringAttribute("type");
  164.         try {
  165.           Class<?> clazz = Resources.classForName(type);
  166.           if (alias == null) {
  167.             typeAliasRegistry.registerAlias(clazz);
  168.           } else {
  169.             typeAliasRegistry.registerAlias(alias, clazz);
  170.           }
  171.         } catch (ClassNotFoundException e) {
  172.           throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
  173.         }
  174.       }
  175.     }
  176.   }

  177.   private void pluginsElement(XNode context) throws Exception {
  178.     if (context != null) {
  179.       for (XNode child : context.getChildren()) {
  180.         String interceptor = child.getStringAttribute("interceptor");
  181.         Properties properties = child.getChildrenAsProperties();
  182.         Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor()
  183.             .newInstance();
  184.         interceptorInstance.setProperties(properties);
  185.         configuration.addInterceptor(interceptorInstance);
  186.       }
  187.     }
  188.   }

  189.   private void objectFactoryElement(XNode context) throws Exception {
  190.     if (context != null) {
  191.       String type = context.getStringAttribute("type");
  192.       Properties properties = context.getChildrenAsProperties();
  193.       ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
  194.       factory.setProperties(properties);
  195.       configuration.setObjectFactory(factory);
  196.     }
  197.   }

  198.   private void objectWrapperFactoryElement(XNode context) throws Exception {
  199.     if (context != null) {
  200.       String type = context.getStringAttribute("type");
  201.       ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
  202.       configuration.setObjectWrapperFactory(factory);
  203.     }
  204.   }

  205.   private void reflectorFactoryElement(XNode context) throws Exception {
  206.     if (context != null) {
  207.       String type = context.getStringAttribute("type");
  208.       ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
  209.       configuration.setReflectorFactory(factory);
  210.     }
  211.   }

  212.   private void propertiesElement(XNode context) throws Exception {
  213.     if (context == null) {
  214.       return;
  215.     }
  216.     Properties defaults = context.getChildrenAsProperties();
  217.     String resource = context.getStringAttribute("resource");
  218.     String url = context.getStringAttribute("url");
  219.     if (resource != null && url != null) {
  220.       throw new BuilderException(
  221.           "The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
  222.     }
  223.     if (resource != null) {
  224.       defaults.putAll(Resources.getResourceAsProperties(resource));
  225.     } else if (url != null) {
  226.       defaults.putAll(Resources.getUrlAsProperties(url));
  227.     }
  228.     Properties vars = configuration.getVariables();
  229.     if (vars != null) {
  230.       defaults.putAll(vars);
  231.     }
  232.     parser.setVariables(defaults);
  233.     configuration.setVariables(defaults);
  234.   }

  235.   private void settingsElement(Properties props) {
  236.     configuration
  237.         .setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
  238.     configuration.setAutoMappingUnknownColumnBehavior(
  239.         AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
  240.     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
  241.     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
  242.     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
  243.     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
  244.     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
  245.     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
  246.     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
  247.     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
  248.     configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
  249.     configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
  250.     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
  251.     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
  252.     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
  253.     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
  254.     configuration.setLazyLoadTriggerMethods(
  255.         stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
  256.     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
  257.     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
  258.     configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
  259.     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
  260.     configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
  261.     configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
  262.     configuration.setLogPrefix(props.getProperty("logPrefix"));
  263.     configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  264.     configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
  265.     configuration.setArgNameBasedConstructorAutoMapping(
  266.         booleanValueOf(props.getProperty("argNameBasedConstructorAutoMapping"), false));
  267.     configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
  268.     configuration.setNullableOnForEach(booleanValueOf(props.getProperty("nullableOnForEach"), false));
  269.   }

  270.   private void environmentsElement(XNode context) throws Exception {
  271.     if (context == null) {
  272.       return;
  273.     }
  274.     if (environment == null) {
  275.       environment = context.getStringAttribute("default");
  276.     }
  277.     for (XNode child : context.getChildren()) {
  278.       String id = child.getStringAttribute("id");
  279.       if (isSpecifiedEnvironment(id)) {
  280.         TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
  281.         DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
  282.         DataSource dataSource = dsFactory.getDataSource();
  283.         Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory)
  284.             .dataSource(dataSource);
  285.         configuration.setEnvironment(environmentBuilder.build());
  286.         break;
  287.       }
  288.     }
  289.   }

  290.   private void databaseIdProviderElement(XNode context) throws Exception {
  291.     if (context == null) {
  292.       return;
  293.     }
  294.     String type = context.getStringAttribute("type");
  295.     // awful patch to keep backward compatibility
  296.     if ("VENDOR".equals(type)) {
  297.       type = "DB_VENDOR";
  298.     }
  299.     Properties properties = context.getChildrenAsProperties();
  300.     DatabaseIdProvider databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor()
  301.         .newInstance();
  302.     databaseIdProvider.setProperties(properties);
  303.     Environment environment = configuration.getEnvironment();
  304.     if (environment != null) {
  305.       String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
  306.       configuration.setDatabaseId(databaseId);
  307.     }
  308.   }

  309.   private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  310.     if (context != null) {
  311.       String type = context.getStringAttribute("type");
  312.       Properties props = context.getChildrenAsProperties();
  313.       TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
  314.       factory.setProperties(props);
  315.       return factory;
  316.     }
  317.     throw new BuilderException("Environment declaration requires a TransactionFactory.");
  318.   }

  319.   private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  320.     if (context != null) {
  321.       String type = context.getStringAttribute("type");
  322.       Properties props = context.getChildrenAsProperties();
  323.       DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
  324.       factory.setProperties(props);
  325.       return factory;
  326.     }
  327.     throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  328.   }

  329.   private void typeHandlersElement(XNode context) {
  330.     if (context == null) {
  331.       return;
  332.     }
  333.     for (XNode child : context.getChildren()) {
  334.       if ("package".equals(child.getName())) {
  335.         String typeHandlerPackage = child.getStringAttribute("name");
  336.         typeHandlerRegistry.register(typeHandlerPackage);
  337.       } else {
  338.         String javaTypeName = child.getStringAttribute("javaType");
  339.         String jdbcTypeName = child.getStringAttribute("jdbcType");
  340.         String handlerTypeName = child.getStringAttribute("handler");
  341.         Class<?> javaTypeClass = resolveClass(javaTypeName);
  342.         JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
  343.         Class<?> typeHandlerClass = resolveClass(handlerTypeName);
  344.         if (javaTypeClass != null) {
  345.           if (jdbcType == null) {
  346.             typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
  347.           } else {
  348.             typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
  349.           }
  350.         } else {
  351.           typeHandlerRegistry.register(typeHandlerClass);
  352.         }
  353.       }
  354.     }
  355.   }

  356.   private void mappersElement(XNode context) throws Exception {
  357.     if (context == null) {
  358.       return;
  359.     }
  360.     for (XNode child : context.getChildren()) {
  361.       if ("package".equals(child.getName())) {
  362.         String mapperPackage = child.getStringAttribute("name");
  363.         configuration.addMappers(mapperPackage);
  364.       } else {
  365.         String resource = child.getStringAttribute("resource");
  366.         String url = child.getStringAttribute("url");
  367.         String mapperClass = child.getStringAttribute("class");
  368.         if (resource != null && url == null && mapperClass == null) {
  369.           ErrorContext.instance().resource(resource);
  370.           try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
  371.             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
  372.                 configuration.getSqlFragments());
  373.             mapperParser.parse();
  374.           }
  375.         } else if (resource == null && url != null && mapperClass == null) {
  376.           ErrorContext.instance().resource(url);
  377.           try (InputStream inputStream = Resources.getUrlAsStream(url)) {
  378.             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
  379.                 configuration.getSqlFragments());
  380.             mapperParser.parse();
  381.           }
  382.         } else if (resource == null && url == null && mapperClass != null) {
  383.           Class<?> mapperInterface = Resources.classForName(mapperClass);
  384.           configuration.addMapper(mapperInterface);
  385.         } else {
  386.           throw new BuilderException(
  387.               "A mapper element may only specify a url, resource or class, but not more than one.");
  388.         }
  389.       }
  390.     }
  391.   }

  392.   private boolean isSpecifiedEnvironment(String id) {
  393.     if (environment == null) {
  394.       throw new BuilderException("No environment specified.");
  395.     }
  396.     if (id == null) {
  397.       throw new BuilderException("Environment requires an id attribute.");
  398.     }
  399.     return environment.equals(id);
  400.   }

  401.   private static Configuration newConfig(Class<? extends Configuration> configClass) {
  402.     try {
  403.       return configClass.getDeclaredConstructor().newInstance();
  404.     } catch (Exception ex) {
  405.       throw new BuilderException("Failed to create a new Configuration instance.", ex);
  406.     }
  407.   }

  408. }