1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.beanutils2.locale;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
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.lang.ref.ReferenceQueue;
27 import java.lang.ref.WeakReference;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.WeakHashMap;
31
32 import org.apache.commons.beanutils2.BeanUtilsBean;
33 import org.apache.commons.beanutils2.ContextClassLoaderLocal;
34 import org.apache.commons.beanutils2.ConvertUtils;
35 import org.apache.commons.beanutils2.PrimitiveBean;
36 import org.apache.commons.beanutils2.locale.converters.LongLocaleConverter;
37 import org.apache.commons.logging.LogFactory;
38 import org.junit.jupiter.api.BeforeEach;
39 import org.junit.jupiter.api.Test;
40
41
42
43
44
45
46 public class LocaleBeanificationTest {
47
48 final class Signal {
49 private Exception e;
50 private int signal;
51 private LocaleBeanUtilsBean bean;
52 private LocaleConvertUtilsBean convertUtils;
53 private Object marker;
54
55 public LocaleBeanUtilsBean getBean() {
56 return bean;
57 }
58
59 public LocaleConvertUtilsBean getConvertUtils() {
60 return convertUtils;
61 }
62
63 public Exception getException() {
64 return e;
65 }
66
67 public Object getMarkerObject() {
68 return marker;
69 }
70
71 public int getSignal() {
72 return signal;
73 }
74
75 public void setBean(final LocaleBeanUtilsBean bean) {
76 this.bean = bean;
77 }
78
79 public void setConvertUtils(final LocaleConvertUtilsBean convertUtils) {
80 this.convertUtils = convertUtils;
81 }
82
83 public void setException(final Exception e) {
84 this.e = e;
85 }
86
87 public void setMarkerObject(final Object marker) {
88 this.marker = marker;
89 }
90
91 public void setSignal(final int signal) {
92 this.signal = signal;
93 }
94 }
95
96 final class TestClassLoader extends ClassLoader {
97 @Override
98 public String toString() {
99 return "TestClassLoader";
100 }
101 }
102
103
104 public static final int MAX_GC_ITERATIONS = 50;
105
106
107
108
109 @BeforeEach
110 public void setUp() {
111 LocaleConvertUtils.deregister();
112 }
113
114
115 @Test
116 public void testBeanUtilsBeanSetInstance() throws Exception {
117
118 final class SetInstanceTesterThread extends Thread {
119
120 private final Signal signal;
121 private final LocaleBeanUtilsBean bean;
122
123 SetInstanceTesterThread(final Signal signal, final LocaleBeanUtilsBean bean) {
124 this.signal = signal;
125 this.bean = bean;
126 }
127
128 @Override
129 public void run() {
130 LocaleBeanUtilsBean.setInstance(bean);
131 signal.setSignal(21);
132 signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
133 }
134
135 @Override
136 public String toString() {
137 return "SetInstanceTesterThread";
138 }
139 }
140
141 final Signal signal = new Signal();
142 signal.setSignal(1);
143
144 final LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean();
145 final LocaleBeanUtilsBean beanTwo = new LocaleBeanUtilsBean();
146
147 final SetInstanceTesterThread thread = new SetInstanceTesterThread(signal, beanTwo);
148 thread.setContextClassLoader(new TestClassLoader());
149
150 LocaleBeanUtilsBean.setInstance(beanOne);
151 assertEquals(beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance(), "Start thread gets right instance");
152
153 thread.start();
154 thread.join();
155
156 assertEquals(21, signal.getSignal(), "Signal not set by test thread");
157 assertEquals(beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance(), "Second thread preserves value");
158 assertEquals(beanTwo, signal.getBean(), "Second thread gets value it set");
159 }
160
161
162 @Test
163 public void testContextClassloaderIndependence() throws Exception {
164
165 final class TestIndependenceThread extends Thread {
166 private final Signal signal;
167 private final PrimitiveBean bean;
168
169 TestIndependenceThread(final Signal signal, final PrimitiveBean bean) {
170 this.signal = signal;
171 this.bean = bean;
172 }
173
174 @Override
175 public void run() {
176 try {
177 signal.setSignal(3);
178 LocaleConvertUtils.register(new LocaleConverter<Integer>() {
179 @Override
180 public <R> R convert(final Class<R> type, final Object value) {
181 return ConvertUtils.primitiveToWrapper(type).cast(9);
182 }
183
184 @Override
185 public <R> R convert(final Class<R> type, final Object value, final String pattern) {
186 return ConvertUtils.primitiveToWrapper(type).cast(9);
187 }
188 }, Integer.TYPE, Locale.getDefault());
189 LocaleBeanUtils.setProperty(bean, "int", "1");
190 } catch (final Exception e) {
191 e.printStackTrace();
192 signal.setException(e);
193 }
194 }
195
196 @Override
197 public String toString() {
198 return "TestIndependenceThread";
199 }
200 }
201
202 final PrimitiveBean bean = new PrimitiveBean();
203 LocaleBeanUtils.setProperty(bean, "int", new Integer(1));
204 assertEquals(1, bean.getInt(), "Wrong property value (1)");
205
206 LocaleConvertUtils.register(new LocaleConverter<Integer>() {
207 @Override
208 public <R> R convert(final Class<R> type, final Object value) {
209 return ConvertUtils.primitiveToWrapper(type).cast(5);
210 }
211
212 @Override
213 public <R> R convert(final Class<R> type, final Object value, final String pattern) {
214 return ConvertUtils.primitiveToWrapper(type).cast(5);
215 }
216 }, Integer.TYPE, Locale.getDefault());
217 LocaleBeanUtils.setProperty(bean, "int", "1");
218 assertEquals(5, bean.getInt(), "Wrong property value(2)");
219
220 final Signal signal = new Signal();
221 signal.setSignal(1);
222 final TestIndependenceThread thread = new TestIndependenceThread(signal, bean);
223 thread.setContextClassLoader(new TestClassLoader());
224
225 thread.start();
226 thread.join();
227
228 assertNull(signal.getException(), "Exception thrown by test thread:" + signal.getException());
229 assertEquals(3, signal.getSignal(), "Signal not set by test thread");
230 assertEquals(9, bean.getInt(), "Wrong property value(3)");
231
232 }
233
234
235
236
237 @Test
238 public void testContextClassLoaderLocal() throws Exception {
239
240 final class CCLLTesterThread extends Thread {
241
242 private final Signal signal;
243 private final ContextClassLoaderLocal<Integer> ccll;
244
245 CCLLTesterThread(final Signal signal, final ContextClassLoaderLocal<Integer> ccll) {
246 this.signal = signal;
247 this.ccll = ccll;
248 }
249
250 @Override
251 public void run() {
252 ccll.set(new Integer(1789));
253 signal.setSignal(2);
254 signal.setMarkerObject(ccll.get());
255 }
256
257 @Override
258 public String toString() {
259 return "CCLLTesterThread";
260 }
261 }
262
263 final ContextClassLoaderLocal<Integer> ccll = new ContextClassLoaderLocal<>();
264 ccll.set(1776);
265 assertEquals(new Integer(1776), ccll.get(), "Start thread sets value");
266
267 final Signal signal = new Signal();
268 signal.setSignal(1);
269
270 final CCLLTesterThread thread = new CCLLTesterThread(signal, ccll);
271 thread.setContextClassLoader(new TestClassLoader());
272
273 thread.start();
274 thread.join();
275
276 assertEquals(2, signal.getSignal(), "Signal not set by test thread");
277 assertEquals(new Integer(1776), ccll.get(), "Second thread preserves value");
278 assertEquals(new Integer(1789), signal.getMarkerObject(), "Second thread gets value it set");
279 }
280
281
282 @Test
283 public void testContextClassLoaderUnset() {
284 final LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean();
285 final ContextClassLoaderLocal<LocaleBeanUtilsBean> ccll = new ContextClassLoaderLocal<>();
286 ccll.set(beanOne);
287 assertEquals(beanOne, ccll.get(), "Start thread gets right instance");
288 ccll.unset();
289 assertTrue(!beanOne.equals(ccll.get()), "Unset works");
290 }
291
292
293
294
295 @Test
296 public void testGetByContextClassLoader() throws Exception {
297
298 final class GetBeanUtilsBeanThread extends Thread {
299
300 private final Signal signal;
301
302 GetBeanUtilsBeanThread(final Signal signal) {
303 this.signal = signal;
304 }
305
306 @Override
307 public void run() {
308 signal.setSignal(2);
309 signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
310 signal.setConvertUtils(LocaleConvertUtilsBean.getInstance());
311 }
312
313 @Override
314 public String toString() {
315 return "GetBeanUtilsBeanThread";
316 }
317 }
318
319 final Signal signal = new Signal();
320 signal.setSignal(1);
321
322 final GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal);
323 thread.setContextClassLoader(new TestClassLoader());
324
325 thread.start();
326 thread.join();
327
328 assertEquals(2, signal.getSignal(), "Signal not set by test thread");
329 assertTrue(BeanUtilsBean.getInstance() != signal.getBean(), "Different LocaleBeanUtilsBean instances per context classloader");
330 assertTrue(LocaleConvertUtilsBean.getInstance() != signal.getConvertUtils(), "Different LocaleConvertUtilsBean instances per context classloader");
331 }
332
333
334
335
336 @Test
337 public void testLocaleAwareConverterInConvertUtils() {
338 try {
339
340 Long data = (Long) ConvertUtils.convert("777", Long.class);
341 assertEquals(777, data.longValue(), "Standard format long converted ok");
342
343
344
345
346
347 data = (Long) ConvertUtils.convert("1.000.000", Long.class);
348 assertEquals(0, data.longValue(), "Standard format behaved as expected");
349
350
351
352
353
354 final Locale germanLocale = Locale.GERMAN;
355 final LongLocaleConverter longLocaleConverter = LongLocaleConverter.builder().setLocale(germanLocale).get();
356 ConvertUtils.register(longLocaleConverter, Long.class);
357
358 data = (Long) ConvertUtils.convert("1.000.000", Long.class);
359 assertEquals(1000000, data.longValue(), "German-format long converted ok");
360 } finally {
361 ConvertUtils.deregister();
362 }
363 }
364
365
366 @Test
367 public void testMemoryLeak() throws Exception {
368
369 TestClassLoader loader = new TestClassLoader();
370 final WeakReference<TestClassLoader> loaderReference = new WeakReference<>(loader);
371 LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
372
373 final class GetBeanUtilsBeanThread extends Thread {
374
375 LocaleBeanUtilsBean beanUtils;
376 LocaleConvertUtilsBean convertUtils;
377
378 GetBeanUtilsBeanThread() {
379 }
380
381 @Override
382 public void run() {
383 beanUtils = LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
384 convertUtils = LocaleConvertUtilsBean.getInstance();
385
386 LogFactory.releaseAll();
387 }
388
389 @Override
390 public String toString() {
391 return "GetBeanUtilsBeanThread";
392 }
393 }
394
395 GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
396 final WeakReference<GetBeanUtilsBeanThread> threadWeakReference = new WeakReference<>(thread);
397 thread.setContextClassLoader(loader);
398
399 thread.start();
400 thread.join();
401
402 final WeakReference<LocaleBeanUtilsBean> beanUtilsReference = new WeakReference<>(thread.beanUtils);
403 final WeakReference<LocaleConvertUtilsBean> convertUtilsReference = new WeakReference<>(thread.convertUtils);
404
405 assertNotNull(loaderReference.get(), "Weak reference released early (1)");
406 assertNotNull(beanUtilsReference.get(), "Weak reference released early (2)");
407 assertNotNull(convertUtilsReference.get(), "Weak reference released early (4)");
408
409
410 loader = null;
411 thread.setContextClassLoader(null);
412 thread = null;
413
414 int iterations = 0;
415 int bytz = 2;
416 while (true) {
417 LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
418 System.gc();
419
420 assertFalse(iterations++ > MAX_GC_ITERATIONS, "Max iterations reached before resource released.");
421
422 if (loaderReference.get() == null && beanUtilsReference.get() == null && convertUtilsReference.get() == null) {
423 break;
424
425 }
426
427 final byte[] b = new byte[bytz];
428 bytz *= 2;
429 }
430 }
431
432
433 @Test
434 public void testMemoryLeak2() {
435
436 TestClassLoader loader = new TestClassLoader();
437 final ReferenceQueue<Object> queue = new ReferenceQueue<>();
438 final WeakReference<ClassLoader> loaderReference = new WeakReference<>(loader, queue);
439 Integer test = new Integer(1);
440
441 final WeakReference<Integer> testReference = new WeakReference<>(test, queue);
442
443 final Map<TestClassLoader, Integer> map = new WeakHashMap<>();
444 map.put(loader, test);
445
446 assertEquals(test, map.get(loader), "In map");
447 assertNotNull(loaderReference.get(), "Weak reference released early (1)");
448 assertNotNull(testReference.get(), "Weak reference released early (2)");
449
450
451 loader = null;
452 test = null;
453
454 int iterations = 0;
455 int bytz = 2;
456 while (true) {
457 System.gc();
458 assertFalse(iterations++ > MAX_GC_ITERATIONS, "Max iterations reached before resource released.");
459 map.isEmpty();
460
461 if (loaderReference.get() == null && testReference.get() == null) {
462 break;
463
464 }
465
466 final byte[] b = new byte[bytz];
467 bytz *= 2;
468 }
469 }
470
471
472 @Test
473 public void testMemoryTestMethodology() throws Exception {
474
475
476 ClassLoader loader = new ClassLoader(this.getClass().getClassLoader()) {
477 };
478 final WeakReference<ClassLoader> reference = new WeakReference<>(loader);
479 Class<?> myClass = loader.loadClass("org.apache.commons.beanutils2.BetaBean");
480
481 assertNotNull(reference.get(), "Weak reference released early");
482
483
484 loader = null;
485 myClass = null;
486
487 int iterations = 0;
488 int bytz = 2;
489 while (true) {
490 System.gc();
491 assertFalse(iterations++ > MAX_GC_ITERATIONS, "Max iterations reached before resource released.");
492 if (reference.get() == null) {
493 break;
494
495 }
496
497 final byte[] b = new byte[bytz];
498 bytz *= 2;
499 }
500 }
501 }