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