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