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