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.type;
17  
18  import static org.junit.jupiter.api.Assertions.assertEquals;
19  import static org.junit.jupiter.api.Assertions.assertFalse;
20  import static org.junit.jupiter.api.Assertions.assertNull;
21  import static org.junit.jupiter.api.Assertions.assertSame;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.net.URI;
25  import java.sql.CallableStatement;
26  import java.sql.PreparedStatement;
27  import java.sql.ResultSet;
28  import java.sql.SQLException;
29  import java.util.Date;
30  import java.util.List;
31  import java.util.concurrent.ExecutorService;
32  import java.util.concurrent.Executors;
33  import java.util.concurrent.Future;
34  import java.util.stream.Collectors;
35  import java.util.stream.IntStream;
36  
37  import org.apache.ibatis.domain.misc.RichType;
38  import org.junit.jupiter.api.BeforeEach;
39  import org.junit.jupiter.api.Test;
40  
41  class TypeHandlerRegistryTest {
42  
43    private TypeHandlerRegistry typeHandlerRegistry;
44  
45    @BeforeEach
46    void setup() {
47      typeHandlerRegistry = new TypeHandlerRegistry();
48    }
49  
50    @Test
51    void shouldRegisterAndRetrieveTypeHandler() {
52      TypeHandler<String> stringTypeHandler = typeHandlerRegistry.getTypeHandler(String.class);
53      typeHandlerRegistry.register(String.class, JdbcType.LONGVARCHAR, stringTypeHandler);
54      assertEquals(stringTypeHandler, typeHandlerRegistry.getTypeHandler(String.class, JdbcType.LONGVARCHAR));
55  
56      assertTrue(typeHandlerRegistry.hasTypeHandler(String.class));
57      assertFalse(typeHandlerRegistry.hasTypeHandler(RichType.class));
58      assertTrue(typeHandlerRegistry.hasTypeHandler(String.class, JdbcType.LONGVARCHAR));
59      assertTrue(typeHandlerRegistry.hasTypeHandler(String.class, JdbcType.INTEGER));
60      assertTrue(typeHandlerRegistry.getUnknownTypeHandler() instanceof UnknownTypeHandler);
61    }
62  
63    @Test
64    void shouldRegisterAndRetrieveComplexTypeHandler() {
65      TypeHandler<List<URI>> fakeHandler = new TypeHandler<>() {
66  
67        @Override
68        public void setParameter(PreparedStatement ps, int i, List<URI> parameter, JdbcType jdbcType) {
69          // do nothing, fake method
70        }
71  
72        @Override
73        public List<URI> getResult(CallableStatement cs, int columnIndex) {
74          // do nothing, fake method
75          return null;
76        }
77  
78        @Override
79        public List<URI> getResult(ResultSet rs, int columnIndex) {
80          // do nothing, fake method
81          return null;
82        }
83  
84        @Override
85        public List<URI> getResult(ResultSet rs, String columnName) {
86          // do nothing, fake method
87          return null;
88        }
89  
90      };
91  
92      TypeReference<List<URI>> type = new TypeReference<>() {
93      };
94  
95      typeHandlerRegistry.register(type, fakeHandler);
96      assertSame(fakeHandler, typeHandlerRegistry.getTypeHandler(type));
97    }
98  
99    @Test
100   void shouldAutoRegisterAndRetrieveComplexTypeHandler() {
101     TypeHandler<List<URI>> fakeHandler = new BaseTypeHandler<>() {
102 
103       @Override
104       public void setNonNullParameter(PreparedStatement ps, int i, List<URI> parameter, JdbcType jdbcType) {
105         // do nothing, fake method
106       }
107 
108       @Override
109       public List<URI> getNullableResult(ResultSet rs, String columnName) {
110         // do nothing, fake method
111         return null;
112       }
113 
114       @Override
115       public List<URI> getNullableResult(ResultSet rs, int columnIndex) {
116         // do nothing, fake method
117         return null;
118       }
119 
120       @Override
121       public List<URI> getNullableResult(CallableStatement cs, int columnIndex) {
122         // do nothing, fake method
123         return null;
124       }
125 
126     };
127 
128     typeHandlerRegistry.register(fakeHandler);
129 
130     assertSame(fakeHandler, typeHandlerRegistry.getTypeHandler(new TypeReference<List<URI>>() {
131     }));
132   }
133 
134   @Test
135   void shouldBindHandlersToWrappersAndPrimitivesIndividually() {
136     typeHandlerRegistry.register(Integer.class, DateTypeHandler.class);
137     assertSame(IntegerTypeHandler.class, typeHandlerRegistry.getTypeHandler(int.class).getClass());
138     typeHandlerRegistry.register(Integer.class, IntegerTypeHandler.class);
139     typeHandlerRegistry.register(int.class, DateTypeHandler.class);
140     assertSame(IntegerTypeHandler.class, typeHandlerRegistry.getTypeHandler(Integer.class).getClass());
141     typeHandlerRegistry.register(Integer.class, IntegerTypeHandler.class);
142   }
143 
144   @Test
145   void shouldReturnHandlerForSuperclassIfRegistered() {
146     class MyDate extends Date {
147       private static final long serialVersionUID = 1L;
148     }
149     assertEquals(DateTypeHandler.class, typeHandlerRegistry.getTypeHandler(MyDate.class).getClass());
150   }
151 
152   @Test
153   void shouldReturnHandlerForSuperSuperclassIfRegistered() {
154     class MyDate1 extends Date {
155       private static final long serialVersionUID = 1L;
156     }
157     class MyDate2 extends MyDate1 {
158       private static final long serialVersionUID = 1L;
159     }
160     assertEquals(DateTypeHandler.class, typeHandlerRegistry.getTypeHandler(MyDate2.class).getClass());
161   }
162 
163   interface SomeInterface {
164   }
165 
166   interface ExtendingSomeInterface extends SomeInterface {
167   }
168 
169   interface NoTypeHandlerInterface {
170   }
171 
172   enum SomeEnum implements SomeInterface {
173   }
174 
175   enum ExtendingSomeEnum implements ExtendingSomeInterface {
176   }
177 
178   enum ImplementingMultiInterfaceSomeEnum implements NoTypeHandlerInterface, ExtendingSomeInterface {
179   }
180 
181   enum NoTypeHandlerInterfaceEnum implements NoTypeHandlerInterface {
182   }
183 
184   class SomeClass implements SomeInterface {
185   }
186 
187   @MappedTypes(SomeInterface.class)
188   public static class SomeInterfaceTypeHandler<E extends Enum<E> & SomeInterface> extends BaseTypeHandler<E> {
189     @Override
190     public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
191     }
192 
193     @Override
194     public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
195       return null;
196     }
197 
198     @Override
199     public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
200       return null;
201     }
202 
203     @Override
204     public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
205       return null;
206     }
207   }
208 
209   @Test
210   void demoTypeHandlerForSuperInterface() {
211     typeHandlerRegistry.register(SomeInterfaceTypeHandler.class);
212     assertNull(typeHandlerRegistry.getTypeHandler(SomeClass.class), "Registering interface works only for enums.");
213     assertSame(EnumTypeHandler.class, typeHandlerRegistry.getTypeHandler(NoTypeHandlerInterfaceEnum.class).getClass(),
214         "When type handler for interface is not exist, apply default enum type handler.");
215     assertSame(SomeInterfaceTypeHandler.class, typeHandlerRegistry.getTypeHandler(SomeEnum.class).getClass());
216     assertSame(SomeInterfaceTypeHandler.class, typeHandlerRegistry.getTypeHandler(ExtendingSomeEnum.class).getClass());
217     assertSame(SomeInterfaceTypeHandler.class,
218         typeHandlerRegistry.getTypeHandler(ImplementingMultiInterfaceSomeEnum.class).getClass());
219   }
220 
221   @Test
222   void shouldRegisterReplaceNullMap() {
223     class Address {
224     }
225     assertFalse(typeHandlerRegistry.hasTypeHandler(Address.class));
226     typeHandlerRegistry.register(Address.class, StringTypeHandler.class);
227     assertTrue(typeHandlerRegistry.hasTypeHandler(Address.class));
228   }
229 
230   enum TestEnum {
231     ONE, TWO
232   }
233 
234   @Test
235   void shouldAutoRegisterEnumTypeInMultiThreadEnvironment() throws Exception {
236     // gh-1820
237     ExecutorService executorService = Executors.newCachedThreadPool();
238     try {
239       for (int iteration = 0; iteration < 2000; iteration++) {
240         TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
241         List<Future<Boolean>> taskResults = IntStream.range(0, 2)
242             .mapToObj(taskIndex -> executorService
243                 .submit(() -> typeHandlerRegistry.hasTypeHandler(TestEnum.class, JdbcType.VARCHAR)))
244             .collect(Collectors.toList());
245         for (Future<Boolean> future : taskResults) {
246           assertTrue(future.get(), "false is returned at round " + iteration);
247         }
248       }
249     } finally {
250       executorService.shutdownNow();
251     }
252   }
253 }