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