View Javadoc
1   /*
2    *    Copyright 2009-2024 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    *       https://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;
17  
18  import static com.googlecode.catchexception.apis.BDDCatchException.caughtException;
19  import static com.googlecode.catchexception.apis.BDDCatchException.when;
20  import static org.assertj.core.api.Assertions.assertThat;
21  import static org.assertj.core.api.BDDAssertions.then;
22  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
23  import static org.junit.jupiter.api.Assertions.assertEquals;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.StringReader;
31  import java.math.RoundingMode;
32  import java.sql.CallableStatement;
33  import java.sql.PreparedStatement;
34  import java.sql.ResultSet;
35  import java.sql.SQLException;
36  import java.util.Arrays;
37  import java.util.HashSet;
38  import java.util.Properties;
39  
40  import org.apache.ibatis.builder.mapper.CustomMapper;
41  import org.apache.ibatis.builder.typehandler.CustomIntegerTypeHandler;
42  import org.apache.ibatis.builder.xml.XMLConfigBuilder;
43  import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
44  import org.apache.ibatis.domain.blog.Author;
45  import org.apache.ibatis.domain.blog.Blog;
46  import org.apache.ibatis.domain.blog.mappers.BlogMapper;
47  import org.apache.ibatis.domain.blog.mappers.NestedBlogMapper;
48  import org.apache.ibatis.domain.jpetstore.Cart;
49  import org.apache.ibatis.executor.loader.cglib.CglibProxyFactory;
50  import org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory;
51  import org.apache.ibatis.io.JBoss6VFS;
52  import org.apache.ibatis.io.Resources;
53  import org.apache.ibatis.logging.slf4j.Slf4jImpl;
54  import org.apache.ibatis.mapping.Environment;
55  import org.apache.ibatis.mapping.ResultSetType;
56  import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
57  import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
58  import org.apache.ibatis.session.AutoMappingBehavior;
59  import org.apache.ibatis.session.AutoMappingUnknownColumnBehavior;
60  import org.apache.ibatis.session.Configuration;
61  import org.apache.ibatis.session.ExecutorType;
62  import org.apache.ibatis.session.LocalCacheScope;
63  import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
64  import org.apache.ibatis.type.BaseTypeHandler;
65  import org.apache.ibatis.type.EnumOrdinalTypeHandler;
66  import org.apache.ibatis.type.EnumTypeHandler;
67  import org.apache.ibatis.type.JdbcType;
68  import org.apache.ibatis.type.TypeHandler;
69  import org.apache.ibatis.type.TypeHandlerRegistry;
70  import org.junit.jupiter.api.Assertions;
71  import org.junit.jupiter.api.Tag;
72  import org.junit.jupiter.api.Test;
73  
74  class XmlConfigBuilderTest {
75  
76    @Test
77    void shouldSuccessfullyLoadMinimalXMLConfigFile() throws Exception {
78      String resource = "org/apache/ibatis/builder/MinimalMapperConfig.xml";
79      try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
80        XMLConfigBuilder builder = new XMLConfigBuilder(inputStream);
81        Configuration config = builder.parse();
82        assertNotNull(config);
83        assertThat(config.getAutoMappingBehavior()).isEqualTo(AutoMappingBehavior.PARTIAL);
84        assertThat(config.getAutoMappingUnknownColumnBehavior()).isEqualTo(AutoMappingUnknownColumnBehavior.NONE);
85        assertThat(config.isCacheEnabled()).isTrue();
86        assertThat(config.getProxyFactory()).isInstanceOf(JavassistProxyFactory.class);
87        assertThat(config.isLazyLoadingEnabled()).isFalse();
88        assertThat(config.isAggressiveLazyLoading()).isFalse();
89        assertThat(config.isUseColumnLabel()).isTrue();
90        assertThat(config.isUseGeneratedKeys()).isFalse();
91        assertThat(config.getDefaultExecutorType()).isEqualTo(ExecutorType.SIMPLE);
92        assertNull(config.getDefaultStatementTimeout());
93        assertNull(config.getDefaultFetchSize());
94        assertNull(config.getDefaultResultSetType());
95        assertThat(config.isMapUnderscoreToCamelCase()).isFalse();
96        assertThat(config.isSafeRowBoundsEnabled()).isFalse();
97        assertThat(config.getLocalCacheScope()).isEqualTo(LocalCacheScope.SESSION);
98        assertThat(config.getJdbcTypeForNull()).isEqualTo(JdbcType.OTHER);
99        assertThat(config.getLazyLoadTriggerMethods())
100           .isEqualTo(new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString")));
101       assertThat(config.isSafeResultHandlerEnabled()).isTrue();
102       assertThat(config.getDefaultScriptingLanuageInstance()).isInstanceOf(XMLLanguageDriver.class);
103       assertThat(config.isCallSettersOnNulls()).isFalse();
104       assertNull(config.getLogPrefix());
105       assertNull(config.getLogImpl());
106       assertNull(config.getConfigurationFactory());
107       assertThat(config.getTypeHandlerRegistry().getTypeHandler(RoundingMode.class))
108           .isInstanceOf(EnumTypeHandler.class);
109       assertThat(config.isShrinkWhitespacesInSql()).isFalse();
110       assertThat(config.isArgNameBasedConstructorAutoMapping()).isFalse();
111       assertThat(config.getDefaultSqlProviderType()).isNull();
112       assertThat(config.isNullableOnForEach()).isFalse();
113     }
114   }
115 
116   enum MyEnum {
117 
118     ONE,
119 
120     TWO
121 
122   }
123 
124   public static class EnumOrderTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
125 
126     private final E[] constants;
127 
128     public EnumOrderTypeHandler(Class<E> javaType) {
129       constants = javaType.getEnumConstants();
130     }
131 
132     @Override
133     public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
134       ps.setInt(i, parameter.ordinal() + 1); // 0 means NULL so add +1
135     }
136 
137     @Override
138     public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
139       int index = rs.getInt(columnName) - 1;
140       return index < 0 ? null : constants[index];
141     }
142 
143     @Override
144     public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
145       int index = rs.getInt(rs.getInt(columnIndex)) - 1;
146       return index < 0 ? null : constants[index];
147     }
148 
149     @Override
150     public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
151       int index = cs.getInt(columnIndex) - 1;
152       return index < 0 ? null : constants[index];
153     }
154   }
155 
156   @Test
157   void registerJavaTypeInitializingTypeHandler() {
158     // @formatter:off
159     final String MAPPER_CONFIG = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
160         + "<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"https://mybatis.org/dtd/mybatis-3-config.dtd\">\n"
161         + "<configuration>\n"
162         + "  <typeHandlers>\n"
163         + "    <typeHandler javaType=\"org.apache.ibatis.builder.XmlConfigBuilderTest$MyEnum\"\n"
164         + "      handler=\"org.apache.ibatis.builder.XmlConfigBuilderTest$EnumOrderTypeHandler\"/>\n"
165         + "  </typeHandlers>\n"
166         + "</configuration>\n";
167     // @formatter:on
168 
169     XMLConfigBuilder builder = new XMLConfigBuilder(new StringReader(MAPPER_CONFIG));
170     builder.parse();
171 
172     TypeHandlerRegistry typeHandlerRegistry = builder.getConfiguration().getTypeHandlerRegistry();
173     TypeHandler<MyEnum> typeHandler = typeHandlerRegistry.getTypeHandler(MyEnum.class);
174 
175     assertTrue(typeHandler instanceof EnumOrderTypeHandler);
176     assertArrayEquals(MyEnum.values(), ((EnumOrderTypeHandler<MyEnum>) typeHandler).constants);
177   }
178 
179   @Tag("RequireIllegalAccess")
180   @Test
181   void shouldSuccessfullyLoadXMLConfigFile() throws Exception {
182     String resource = "org/apache/ibatis/builder/CustomizedSettingsMapperConfig.xml";
183     try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
184       Properties props = new Properties();
185       props.put("prop2", "cccc");
186       XMLConfigBuilder builder = new XMLConfigBuilder(inputStream, null, props);
187       Configuration config = builder.parse();
188 
189       assertThat(config.getAutoMappingBehavior()).isEqualTo(AutoMappingBehavior.NONE);
190       assertThat(config.getAutoMappingUnknownColumnBehavior()).isEqualTo(AutoMappingUnknownColumnBehavior.WARNING);
191       assertThat(config.isCacheEnabled()).isFalse();
192       assertThat(config.getProxyFactory()).isInstanceOf(CglibProxyFactory.class);
193       assertThat(config.isLazyLoadingEnabled()).isTrue();
194       assertThat(config.isAggressiveLazyLoading()).isTrue();
195       assertThat(config.isUseColumnLabel()).isFalse();
196       assertThat(config.isUseGeneratedKeys()).isTrue();
197       assertThat(config.getDefaultExecutorType()).isEqualTo(ExecutorType.BATCH);
198       assertThat(config.getDefaultStatementTimeout()).isEqualTo(10);
199       assertThat(config.getDefaultFetchSize()).isEqualTo(100);
200       assertThat(config.getDefaultResultSetType()).isEqualTo(ResultSetType.SCROLL_INSENSITIVE);
201       assertThat(config.isMapUnderscoreToCamelCase()).isTrue();
202       assertThat(config.isSafeRowBoundsEnabled()).isTrue();
203       assertThat(config.getLocalCacheScope()).isEqualTo(LocalCacheScope.STATEMENT);
204       assertThat(config.getJdbcTypeForNull()).isEqualTo(JdbcType.NULL);
205       assertThat(config.getLazyLoadTriggerMethods())
206           .isEqualTo(new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString", "xxx")));
207       assertThat(config.isSafeResultHandlerEnabled()).isFalse();
208       assertThat(config.getDefaultScriptingLanuageInstance()).isInstanceOf(RawLanguageDriver.class);
209       assertThat(config.isCallSettersOnNulls()).isTrue();
210       assertThat(config.getLogPrefix()).isEqualTo("mybatis_");
211       assertThat(config.getLogImpl().getName()).isEqualTo(Slf4jImpl.class.getName());
212       assertThat(config.getVfsImpl().getName()).isEqualTo(JBoss6VFS.class.getName());
213       assertThat(config.getConfigurationFactory().getName()).isEqualTo(String.class.getName());
214       assertThat(config.isShrinkWhitespacesInSql()).isTrue();
215       assertThat(config.isArgNameBasedConstructorAutoMapping()).isTrue();
216       assertThat(config.getDefaultSqlProviderType().getName()).isEqualTo(MySqlProvider.class.getName());
217       assertThat(config.isNullableOnForEach()).isTrue();
218 
219       assertThat(config.getTypeAliasRegistry().getTypeAliases().get("blogauthor")).isEqualTo(Author.class);
220       assertThat(config.getTypeAliasRegistry().getTypeAliases().get("blog")).isEqualTo(Blog.class);
221       assertThat(config.getTypeAliasRegistry().getTypeAliases().get("cart")).isEqualTo(Cart.class);
222 
223       assertThat(config.getTypeHandlerRegistry().getTypeHandler(Integer.class))
224           .isInstanceOf(CustomIntegerTypeHandler.class);
225       assertThat(config.getTypeHandlerRegistry().getTypeHandler(Long.class)).isInstanceOf(CustomLongTypeHandler.class);
226       assertThat(config.getTypeHandlerRegistry().getTypeHandler(String.class))
227           .isInstanceOf(CustomStringTypeHandler.class);
228       assertThat(config.getTypeHandlerRegistry().getTypeHandler(String.class, JdbcType.VARCHAR))
229           .isInstanceOf(CustomStringTypeHandler.class);
230       assertThat(config.getTypeHandlerRegistry().getTypeHandler(RoundingMode.class))
231           .isInstanceOf(EnumOrdinalTypeHandler.class);
232 
233       ExampleObjectFactory objectFactory = (ExampleObjectFactory) config.getObjectFactory();
234       assertThat(objectFactory.getProperties().size()).isEqualTo(1);
235       assertThat(objectFactory.getProperties().getProperty("objectFactoryProperty")).isEqualTo("100");
236 
237       assertThat(config.getObjectWrapperFactory()).isInstanceOf(CustomObjectWrapperFactory.class);
238 
239       assertThat(config.getReflectorFactory()).isInstanceOf(CustomReflectorFactory.class);
240 
241       ExamplePlugin plugin = (ExamplePlugin) config.getInterceptors().get(0);
242       assertThat(plugin.getProperties().size()).isEqualTo(1);
243       assertThat(plugin.getProperties().getProperty("pluginProperty")).isEqualTo("100");
244 
245       Environment environment = config.getEnvironment();
246       assertThat(environment.getId()).isEqualTo("development");
247       assertThat(environment.getDataSource()).isInstanceOf(UnpooledDataSource.class);
248       assertThat(environment.getTransactionFactory()).isInstanceOf(JdbcTransactionFactory.class);
249 
250       assertThat(config.getDatabaseId()).isEqualTo("derby");
251 
252       assertThat(config.getMapperRegistry().getMappers().size()).isEqualTo(4);
253       assertThat(config.getMapperRegistry().hasMapper(CachedAuthorMapper.class)).isTrue();
254       assertThat(config.getMapperRegistry().hasMapper(CustomMapper.class)).isTrue();
255       assertThat(config.getMapperRegistry().hasMapper(BlogMapper.class)).isTrue();
256       assertThat(config.getMapperRegistry().hasMapper(NestedBlogMapper.class)).isTrue();
257     }
258   }
259 
260   @Test
261   void shouldSuccessfullyLoadXMLConfigFileWithPropertiesUrl() throws Exception {
262     String resource = "org/apache/ibatis/builder/PropertiesUrlMapperConfig.xml";
263     try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
264       XMLConfigBuilder builder = new XMLConfigBuilder(inputStream);
265       Configuration config = builder.parse();
266       assertThat(config.getVariables().get("driver").toString()).isEqualTo("org.apache.derby.jdbc.EmbeddedDriver");
267       assertThat(config.getVariables().get("prop1").toString()).isEqualTo("bbbb");
268     }
269   }
270 
271   @Test
272   void parseIsTwice() throws Exception {
273     String resource = "org/apache/ibatis/builder/MinimalMapperConfig.xml";
274     try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
275       XMLConfigBuilder builder = new XMLConfigBuilder(inputStream);
276       builder.parse();
277 
278       when(builder::parse);
279       then(caughtException()).isInstanceOf(BuilderException.class)
280           .hasMessage("Each XMLConfigBuilder can only be used once.");
281     }
282   }
283 
284   @Test
285   void unknownSettings() {
286     // @formatter:off
287     final String MAPPER_CONFIG = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
288             + "<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"https://mybatis.org/dtd/mybatis-3-config.dtd\">\n"
289             + "<configuration>\n"
290             + "  <settings>\n"
291             + "    <setting name=\"foo\" value=\"bar\"/>\n"
292             + "  </settings>\n"
293             + "</configuration>\n";
294     // @formatter:on
295 
296     XMLConfigBuilder builder = new XMLConfigBuilder(new StringReader(MAPPER_CONFIG));
297     when(builder::parse);
298     then(caughtException()).isInstanceOf(BuilderException.class)
299         .hasMessageContaining("The setting foo is not known.  Make sure you spelled it correctly (case sensitive).");
300   }
301 
302   @Test
303   void unknownJavaTypeOnTypeHandler() {
304     // @formatter:off
305     final String MAPPER_CONFIG = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
306             + "<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"https://mybatis.org/dtd/mybatis-3-config.dtd\">\n"
307             + "<configuration>\n"
308             + "  <typeAliases>\n"
309             + "    <typeAlias type=\"a.b.c.Foo\"/>\n"
310             + "  </typeAliases>\n"
311             + "</configuration>\n";
312     // @formatter:on
313 
314     XMLConfigBuilder builder = new XMLConfigBuilder(new StringReader(MAPPER_CONFIG));
315     when(builder::parse);
316     then(caughtException()).isInstanceOf(BuilderException.class)
317         .hasMessageContaining("Error registering typeAlias for 'null'. Cause: ");
318   }
319 
320   @Test
321   void propertiesSpecifyResourceAndUrlAtSameTime() {
322     // @formatter:off
323     final String MAPPER_CONFIG = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
324         + "<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"https://mybatis.org/dtd/mybatis-3-config.dtd\">\n"
325         + "<configuration>\n"
326         + "  <properties resource=\"a/b/c/foo.properties\" url=\"file:./a/b/c/jdbc.properties\"/>\n"
327         + "</configuration>\n";
328     // @formatter:on
329 
330     XMLConfigBuilder builder = new XMLConfigBuilder(new StringReader(MAPPER_CONFIG));
331     when(builder::parse);
332     then(caughtException()).isInstanceOf(BuilderException.class).hasMessageContaining(
333         "The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
334   }
335 
336   static class MySqlProvider {
337     @SuppressWarnings("unused")
338     public static String provideSql() {
339       return "SELECT 1";
340     }
341 
342     private MySqlProvider() {
343     }
344   }
345 
346   @Test
347   void shouldAllowSubclassedConfiguration() throws IOException {
348     String resource = "org/apache/ibatis/builder/MinimalMapperConfig.xml";
349     try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
350       XMLConfigBuilder builder = new XMLConfigBuilder(MyConfiguration.class, inputStream, null, null);
351       Configuration config = builder.parse();
352 
353       assertThat(config).isInstanceOf(MyConfiguration.class);
354     }
355   }
356 
357   @Test
358   void noDefaultConstructorForSubclassedConfiguration() throws IOException {
359     String resource = "org/apache/ibatis/builder/MinimalMapperConfig.xml";
360     try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
361       Exception exception = Assertions.assertThrows(Exception.class,
362           () -> new XMLConfigBuilder(BadConfiguration.class, inputStream, null, null));
363       assertEquals("Failed to create a new Configuration instance.", exception.getMessage());
364     }
365   }
366 
367   public static class MyConfiguration extends Configuration {
368     // only using to check configuration was used
369   }
370 
371   public static class BadConfiguration extends Configuration {
372 
373     public BadConfiguration(String parameter) {
374       // should have a default constructor
375     }
376   }
377 
378 }