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