001/** 002 * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.mybatisflex.core.mybatis; 017 018import org.apache.ibatis.builder.BaseBuilder; 019import org.apache.ibatis.builder.BuilderException; 020import org.apache.ibatis.builder.xml.XMLMapperBuilder; 021import org.apache.ibatis.builder.xml.XMLMapperEntityResolver; 022import org.apache.ibatis.datasource.DataSourceFactory; 023import org.apache.ibatis.executor.ErrorContext; 024import org.apache.ibatis.executor.loader.ProxyFactory; 025import org.apache.ibatis.io.Resources; 026import org.apache.ibatis.io.VFS; 027import org.apache.ibatis.logging.Log; 028import org.apache.ibatis.mapping.DatabaseIdProvider; 029import org.apache.ibatis.mapping.Environment; 030import org.apache.ibatis.parsing.XNode; 031import org.apache.ibatis.parsing.XPathParser; 032import org.apache.ibatis.plugin.Interceptor; 033import org.apache.ibatis.reflection.DefaultReflectorFactory; 034import org.apache.ibatis.reflection.MetaClass; 035import org.apache.ibatis.reflection.ReflectorFactory; 036import org.apache.ibatis.reflection.factory.ObjectFactory; 037import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; 038import org.apache.ibatis.session.*; 039import org.apache.ibatis.transaction.TransactionFactory; 040import org.apache.ibatis.type.JdbcType; 041 042import javax.sql.DataSource; 043import java.io.InputStream; 044import java.io.Reader; 045import java.util.Properties; 046 047/** 048 * @author Clinton Begin 049 * @author Kazuki Shimizu 050 */ 051public class FlexXMLConfigBuilder extends BaseBuilder { 052 053 private boolean parsed; 054 private final XPathParser parser; 055 private String environment; 056 private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); 057 058 public FlexXMLConfigBuilder(Reader reader) { 059 this(reader, null, null); 060 } 061 062 public FlexXMLConfigBuilder(Reader reader, String environment) { 063 this(reader, environment, null); 064 } 065 066 public FlexXMLConfigBuilder(Reader reader, String environment, Properties props) { 067 this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); 068 } 069 070 public FlexXMLConfigBuilder(InputStream inputStream) { 071 this(inputStream, null, null); 072 } 073 074 public FlexXMLConfigBuilder(InputStream inputStream, String environment) { 075 this(inputStream, environment, null); 076 } 077 078 public FlexXMLConfigBuilder(InputStream inputStream, String environment, Properties props) { 079 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); 080 } 081 082 private FlexXMLConfigBuilder(XPathParser parser, String environment, Properties props) { 083 // 目前暂时只能通过这里替换 configuration 084 // 新的版本已经支持自定义 configuration,但需新版本 release: 085 // 详情 https://github.com/mybatis/mybatis-3/commit/d7826d71a7005a8b4d4e0e7a020db0f6c7e253a4 086 super(new FlexConfiguration()); 087 ErrorContext.instance().resource("SQL Mapper Configuration"); 088 this.configuration.setVariables(props); 089 this.parsed = false; 090 this.environment = environment; 091 this.parser = parser; 092 } 093 094 public Configuration parse() { 095 if (parsed) { 096 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 097 } 098 parsed = true; 099 parseConfiguration(parser.evalNode("/configuration")); 100 return configuration; 101 } 102 103 private void parseConfiguration(XNode root) { 104 try { 105 // issue #117 read properties first 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 // read it after objectFactory and objectWrapperFactory issue #631 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 // Check that all settings are known to the configuration class 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 // awful patch to keep backward compatibility 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}