View Javadoc
1   /**
2    *    Copyright 2009-2019 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.Properties;
21  import javax.sql.DataSource;
22  
23  import org.apache.ibatis.builder.BaseBuilder;
24  import org.apache.ibatis.builder.BuilderException;
25  import org.apache.ibatis.datasource.DataSourceFactory;
26  import org.apache.ibatis.executor.ErrorContext;
27  import org.apache.ibatis.executor.loader.ProxyFactory;
28  import org.apache.ibatis.io.Resources;
29  import org.apache.ibatis.io.VFS;
30  import org.apache.ibatis.logging.Log;
31  import org.apache.ibatis.mapping.DatabaseIdProvider;
32  import org.apache.ibatis.mapping.Environment;
33  import org.apache.ibatis.parsing.XNode;
34  import org.apache.ibatis.parsing.XPathParser;
35  import org.apache.ibatis.plugin.Interceptor;
36  import org.apache.ibatis.reflection.DefaultReflectorFactory;
37  import org.apache.ibatis.reflection.MetaClass;
38  import org.apache.ibatis.reflection.ReflectorFactory;
39  import org.apache.ibatis.reflection.factory.ObjectFactory;
40  import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
41  import org.apache.ibatis.session.AutoMappingBehavior;
42  import org.apache.ibatis.session.AutoMappingUnknownColumnBehavior;
43  import org.apache.ibatis.session.Configuration;
44  import org.apache.ibatis.session.ExecutorType;
45  import org.apache.ibatis.session.LocalCacheScope;
46  import org.apache.ibatis.transaction.TransactionFactory;
47  import org.apache.ibatis.type.JdbcType;
48  
49  /**
50   * @author Clinton Begin
51   * @author Kazuki Shimizu
52   */
53  public class XMLConfigBuilder extends BaseBuilder {
54  
55    private boolean parsed;
56    private final XPathParser parser;
57    private String environment;
58    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
59  
60    public XMLConfigBuilder(Reader reader) {
61      this(reader, null, null);
62    }
63  
64    public XMLConfigBuilder(Reader reader, String environment) {
65      this(reader, environment, null);
66    }
67  
68    public XMLConfigBuilder(Reader reader, String environment, Properties props) {
69      this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
70    }
71  
72    public XMLConfigBuilder(InputStream inputStream) {
73      this(inputStream, null, null);
74    }
75  
76    public XMLConfigBuilder(InputStream inputStream, String environment) {
77      this(inputStream, environment, null);
78    }
79  
80    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
81      this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
82    }
83  
84    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
85      super(new Configuration());
86      ErrorContext.instance().resource("SQL Mapper Configuration");
87      this.configuration.setVariables(props);
88      this.parsed = false;
89      this.environment = environment;
90      this.parser = parser;
91    }
92  
93    public Configuration parse() {
94      if (parsed) {
95        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
96      }
97      parsed = true;
98      parseConfiguration(parser.evalNode("/configuration"));
99      return configuration;
100   }
101 
102   private void parseConfiguration(XNode root) {
103     try {
104       //issue #117 read properties first
105       propertiesElement(root.evalNode("properties"));
106       Properties settings = settingsAsProperties(root.evalNode("settings"));
107       loadCustomVfs(settings);
108       loadCustomLogImpl(settings);
109       typeAliasesElement(root.evalNode("typeAliases"));
110       pluginElement(root.evalNode("plugins"));
111       objectFactoryElement(root.evalNode("objectFactory"));
112       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
113       reflectorFactoryElement(root.evalNode("reflectorFactory"));
114       settingsElement(settings);
115       // read it after objectFactory and objectWrapperFactory issue #631
116       environmentsElement(root.evalNode("environments"));
117       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
118       typeHandlerElement(root.evalNode("typeHandlers"));
119       mapperElement(root.evalNode("mappers"));
120     } catch (Exception e) {
121       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
122     }
123   }
124 
125   private Properties settingsAsProperties(XNode context) {
126     if (context == null) {
127       return new Properties();
128     }
129     Properties props = context.getChildrenAsProperties();
130     // Check that all settings are known to the configuration class
131     MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
132     for (Object key : props.keySet()) {
133       if (!metaConfig.hasSetter(String.valueOf(key))) {
134         throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
135       }
136     }
137     return props;
138   }
139 
140   private void loadCustomVfs(Properties props) throws ClassNotFoundException {
141     String value = props.getProperty("vfsImpl");
142     if (value != null) {
143       String[] clazzes = value.split(",");
144       for (String clazz : clazzes) {
145         if (!clazz.isEmpty()) {
146           @SuppressWarnings("unchecked")
147           Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
148           configuration.setVfsImpl(vfsImpl);
149         }
150       }
151     }
152   }
153 
154   private void loadCustomLogImpl(Properties props) {
155     Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
156     configuration.setLogImpl(logImpl);
157   }
158 
159   private void typeAliasesElement(XNode parent) {
160     if (parent != null) {
161       for (XNode child : parent.getChildren()) {
162         if ("package".equals(child.getName())) {
163           String typeAliasPackage = child.getStringAttribute("name");
164           configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
165         } else {
166           String alias = child.getStringAttribute("alias");
167           String type = child.getStringAttribute("type");
168           try {
169             Class<?> clazz = Resources.classForName(type);
170             if (alias == null) {
171               typeAliasRegistry.registerAlias(clazz);
172             } else {
173               typeAliasRegistry.registerAlias(alias, clazz);
174             }
175           } catch (ClassNotFoundException e) {
176             throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
177           }
178         }
179       }
180     }
181   }
182 
183   private void pluginElement(XNode parent) throws Exception {
184     if (parent != null) {
185       for (XNode child : parent.getChildren()) {
186         String interceptor = child.getStringAttribute("interceptor");
187         Properties properties = child.getChildrenAsProperties();
188         Interceptor/apache/ibatis/plugin/Interceptor.html#Interceptor">Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
189         interceptorInstance.setProperties(properties);
190         configuration.addInterceptor(interceptorInstance);
191       }
192     }
193   }
194 
195   private void objectFactoryElement(XNode context) throws Exception {
196     if (context != null) {
197       String type = context.getStringAttribute("type");
198       Properties properties = context.getChildrenAsProperties();
199       ObjectFactory/../../org/apache/ibatis/reflection/factory/ObjectFactory.html#ObjectFactory">ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
200       factory.setProperties(properties);
201       configuration.setObjectFactory(factory);
202     }
203   }
204 
205   private void objectWrapperFactoryElement(XNode context) throws Exception {
206     if (context != null) {
207       String type = context.getStringAttribute("type");
208       ObjectWrapperFactoryorg/apache/ibatis/reflection/wrapper/ObjectWrapperFactory.html#ObjectWrapperFactory">ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
209       configuration.setObjectWrapperFactory(factory);
210     }
211   }
212 
213   private void reflectorFactoryElement(XNode context) throws Exception {
214     if (context != null) {
215       String type = context.getStringAttribute("type");
216       ReflectorFactory/../org/apache/ibatis/reflection/ReflectorFactory.html#ReflectorFactory">ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
217       configuration.setReflectorFactory(factory);
218     }
219   }
220 
221   private void propertiesElement(XNode context) throws Exception {
222     if (context != null) {
223       Properties defaults = context.getChildrenAsProperties();
224       String resource = context.getStringAttribute("resource");
225       String url = context.getStringAttribute("url");
226       if (resource != null && url != null) {
227         throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
228       }
229       if (resource != null) {
230         defaults.putAll(Resources.getResourceAsProperties(resource));
231       } else if (url != null) {
232         defaults.putAll(Resources.getUrlAsProperties(url));
233       }
234       Properties vars = configuration.getVariables();
235       if (vars != null) {
236         defaults.putAll(vars);
237       }
238       parser.setVariables(defaults);
239       configuration.setVariables(defaults);
240     }
241   }
242 
243   private void settingsElement(Properties props) {
244     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
245     configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
246     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
247     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
248     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
249     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
250     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
251     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
252     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
253     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
254     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
255     configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
256     configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
257     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
258     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
259     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
260     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
261     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
262     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
263     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
264     configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
265     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
266     configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
267     configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
268     configuration.setLogPrefix(props.getProperty("logPrefix"));
269     configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
270   }
271 
272   private void environmentsElement(XNode context) throws Exception {
273     if (context != null) {
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)
284               .transactionFactory(txFactory)
285               .dataSource(dataSource);
286           configuration.setEnvironment(environmentBuilder.build());
287         }
288       }
289     }
290   }
291 
292   private void databaseIdProviderElement(XNode context) throws Exception {
293     DatabaseIdProvider databaseIdProvider = null;
294     if (context != null) {
295       String type = context.getStringAttribute("type");
296       // awful patch to keep backward compatibility
297       if ("VENDOR".equals(type)) {
298         type = "DB_VENDOR";
299       }
300       Properties properties = context.getChildrenAsProperties();
301       databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
302       databaseIdProvider.setProperties(properties);
303     }
304     Environment environment = configuration.getEnvironment();
305     if (environment != null && databaseIdProvider != null) {
306       String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
307       configuration.setDatabaseId(databaseId);
308     }
309   }
310 
311   private TransactionFactory transactionManagerElement(XNode context) throws Exception {
312     if (context != null) {
313       String type = context.getStringAttribute("type");
314       Properties props = context.getChildrenAsProperties();
315       TransactionFactory./org/apache/ibatis/transaction/TransactionFactory.html#TransactionFactory">TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
316       factory.setProperties(props);
317       return factory;
318     }
319     throw new BuilderException("Environment declaration requires a TransactionFactory.");
320   }
321 
322   private DataSourceFactory dataSourceElement(XNode context) throws Exception {
323     if (context != null) {
324       String type = context.getStringAttribute("type");
325       Properties props = context.getChildrenAsProperties();
326       DataSourceFactory../org/apache/ibatis/datasource/DataSourceFactory.html#DataSourceFactory">DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
327       factory.setProperties(props);
328       return factory;
329     }
330     throw new BuilderException("Environment declaration requires a DataSourceFactory.");
331   }
332 
333   private void typeHandlerElement(XNode parent) {
334     if (parent != null) {
335       for (XNode child : parent.getChildren()) {
336         if ("package".equals(child.getName())) {
337           String typeHandlerPackage = child.getStringAttribute("name");
338           typeHandlerRegistry.register(typeHandlerPackage);
339         } else {
340           String javaTypeName = child.getStringAttribute("javaType");
341           String jdbcTypeName = child.getStringAttribute("jdbcType");
342           String handlerTypeName = child.getStringAttribute("handler");
343           Class<?> javaTypeClass = resolveClass(javaTypeName);
344           JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
345           Class<?> typeHandlerClass = resolveClass(handlerTypeName);
346           if (javaTypeClass != null) {
347             if (jdbcType == null) {
348               typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
349             } else {
350               typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
351             }
352           } else {
353             typeHandlerRegistry.register(typeHandlerClass);
354           }
355         }
356       }
357     }
358   }
359 
360   private void mapperElement(XNode parent) throws Exception {
361     if (parent != null) {
362       for (XNode child : parent.getChildren()) {
363         if ("package".equals(child.getName())) {
364           String mapperPackage = child.getStringAttribute("name");
365           configuration.addMappers(mapperPackage);
366         } else {
367           String resource = child.getStringAttribute("resource");
368           String url = child.getStringAttribute("url");
369           String mapperClass = child.getStringAttribute("class");
370           if (resource != null && url == null && mapperClass == null) {
371             ErrorContext.instance().resource(resource);
372             InputStream inputStream = Resources.getResourceAsStream(resource);
373             XMLMapperBuilderilder.html#XMLMapperBuilder">XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
374             mapperParser.parse();
375           } else if (resource == null && url != null && mapperClass == null) {
376             ErrorContext.instance().resource(url);
377             InputStream inputStream = Resources.getUrlAsStream(url);
378             XMLMapperBuilderilder.html#XMLMapperBuilder">XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
379             mapperParser.parse();
380           } else if (resource == null && url == null && mapperClass != null) {
381             Class<?> mapperInterface = Resources.classForName(mapperClass);
382             configuration.addMapper(mapperInterface);
383           } else {
384             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
385           }
386         }
387       }
388     }
389   }
390 
391   private boolean isSpecifiedEnvironment(String id) {
392     if (environment == null) {
393       throw new BuilderException("No environment specified.");
394     } else if (id == null) {
395       throw new BuilderException("Environment requires an id attribute.");
396     } else if (environment.equals(id)) {
397       return true;
398     }
399     return false;
400   }
401 
402 }