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