View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils;
19  
20  import java.lang.ref.ReferenceQueue;
21  import java.lang.ref.WeakReference;
22  import java.util.Map;
23  import java.util.WeakHashMap;
24  
25  import junit.framework.Test;
26  import junit.framework.TestCase;
27  import junit.framework.TestSuite;
28  
29  import org.apache.commons.logging.LogFactory;
30  
31  /**
32   * <p>
33   * Test Case for changes made during Beanutils Beanification
34   * </p>
35   *
36   * @version $Id$
37   */
38  
39  public class BeanificationTestCase extends TestCase {
40  
41      // ---------------------------------------------------- Constants
42  
43      /** Maximum number of iterations before our test fails */
44      public static final int MAX_GC_ITERATIONS = 50;
45  
46      // ---------------------------------------------------- Instance Variables
47  
48  
49      // ---------------------------------------------------------- Constructors
50  
51  
52      /**
53       * Construct a new instance of this test case.
54       *
55       * @param name Name of the test case
56       */
57      public BeanificationTestCase(final String name) {
58          super(name);
59      }
60  
61  
62      // -------------------------------------------------- Overall Test Methods
63  
64  
65      /**
66       * Set up instance variables required by this test case.
67       */
68      @Override
69      public void setUp() {
70  
71          ConvertUtils.deregister();
72  
73      }
74  
75  
76      /**
77       * Return the tests included in this test suite.
78       */
79      public static Test suite() {
80          return (new TestSuite(BeanificationTestCase.class));
81      }
82  
83  
84      /**
85       * Tear down instance variables required by this test case.
86       */
87      @Override
88      public void tearDown() {
89          // No action required
90      }
91  
92  
93      // ------------------------------------------------ Individual Test Methods
94  
95      /** Test of the methodology we'll use for some of the later tests */
96      public void testMemoryTestMethodology() throws Exception {
97          // test methodology
98          // many thanks to Juozas Baliuka for suggesting this method
99          ClassLoader loader = new ClassLoader(this.getClass().getClassLoader()) {};
100         final WeakReference<ClassLoader> reference = new  WeakReference<ClassLoader>(loader);
101         @SuppressWarnings("unused")
102         Class<?> myClass = loader.loadClass("org.apache.commons.beanutils.BetaBean");
103 
104         assertNotNull("Weak reference released early", reference.get());
105 
106         // dereference class loader and class:
107         loader = null;
108         myClass = null;
109 
110         int iterations = 0;
111         int bytz = 2;
112         while(true) {
113             System.gc();
114             if(iterations++ > MAX_GC_ITERATIONS){
115                 fail("Max iterations reached before resource released.");
116             }
117             if( reference.get() == null ) {
118                 break;
119 
120             } else {
121                 // create garbage:
122                 @SuppressWarnings("unused")
123                 final
124                 byte[] b =  new byte[bytz];
125                 bytz = bytz * 2;
126             }
127         }
128     }
129 
130     /** Tests whether classloaders and beans are released from memory by the map used by beanutils */
131     public void testMemoryLeak2() throws Exception {
132         // tests when the map used by beanutils has the right behaviour
133 
134         if (BeanUtilsTestCase.isPre14JVM()) {
135             System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
136             return;
137         }
138 
139         // many thanks to Juozas Baliuka for suggesting this methodology
140         TestClassLoader loader = new TestClassLoader();
141         final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
142         final WeakReference<ClassLoader> loaderReference = new WeakReference<ClassLoader>(loader, queue);
143         Integer test = new Integer(1);
144 
145         final WeakReference<Integer> testReference = new WeakReference<Integer>(test, queue);
146         //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true);
147         final Map<Object, Object> map = new WeakHashMap<Object, Object>();
148         map.put(loader, test);
149 
150         assertEquals("In map", test, map.get(loader));
151         assertNotNull("Weak reference released early (1)", loaderReference.get());
152         assertNotNull("Weak reference released early (2)", testReference.get());
153 
154         // dereference strong references
155         loader = null;
156         test = null;
157 
158         int iterations = 0;
159         int bytz = 2;
160         while(true) {
161             System.gc();
162             if(iterations++ > MAX_GC_ITERATIONS){
163                 fail("Max iterations reached before resource released.");
164             }
165             map.isEmpty();
166 
167             if(
168                 loaderReference.get() == null &&
169                 testReference.get() == null) {
170                 break;
171 
172             } else {
173                 // create garbage:
174                 @SuppressWarnings("unused")
175                 final
176                 byte[] b =  new byte[bytz];
177                 bytz = bytz * 2;
178             }
179         }
180     }
181 
182     /** Tests whether classloaders and beans are released from memory */
183     public void testMemoryLeak() throws Exception {
184         if (BeanUtilsTestCase.isPre14JVM()) {
185             System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
186             return;
187         }
188 
189         // many thanks to Juozas Baliuka for suggesting this methodology
190         TestClassLoader loader = new TestClassLoader();
191         final WeakReference<ClassLoader> loaderReference = new  WeakReference<ClassLoader>(loader);
192         BeanUtilsBean.getInstance();
193 
194         class GetBeanUtilsBeanThread extends Thread {
195 
196             BeanUtilsBean beanUtils;
197             ConvertUtilsBean convertUtils;
198             PropertyUtilsBean propertyUtils;
199 
200             GetBeanUtilsBeanThread() {}
201 
202             @Override
203             public void run() {
204                 beanUtils = BeanUtilsBean.getInstance();
205                 convertUtils = ConvertUtilsBean.getInstance();
206                 propertyUtils = PropertyUtilsBean.getInstance();
207                 // XXX Log keeps a reference around!
208                 LogFactory.releaseAll();
209             }
210 
211             @Override
212             public String toString() {
213                 return "GetBeanUtilsBeanThread";
214             }
215         }
216 
217 
218         GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
219         @SuppressWarnings("unused")
220         final
221         WeakReference<Thread> threadWeakReference = new WeakReference<Thread>(thread);
222         thread.setContextClassLoader(loader);
223 
224         thread.start();
225         thread.join();
226 
227         final WeakReference<BeanUtilsBean> beanUtilsReference = new WeakReference<BeanUtilsBean>(thread.beanUtils);
228         final WeakReference<PropertyUtilsBean> propertyUtilsReference =  new WeakReference<PropertyUtilsBean>(thread.propertyUtils);
229         final WeakReference<ConvertUtilsBean> convertUtilsReference = new WeakReference<ConvertUtilsBean>(thread.convertUtils);
230 
231         assertNotNull("Weak reference released early (1)", loaderReference.get());
232         assertNotNull("Weak reference released early (2)", beanUtilsReference.get());
233         assertNotNull("Weak reference released early (3)", propertyUtilsReference.get());
234         assertNotNull("Weak reference released early (4)", convertUtilsReference.get());
235 
236         // dereference strong references
237         loader = null;
238         thread.setContextClassLoader(null);
239         thread = null;
240 
241         int iterations = 0;
242         int bytz = 2;
243         while(true) {
244             BeanUtilsBean.getInstance();
245             System.gc();
246             if(iterations++ > MAX_GC_ITERATIONS){
247                 fail("Max iterations reached before resource released.");
248             }
249 
250             if(
251                 loaderReference.get() == null &&
252                 beanUtilsReference.get() == null &&
253                 propertyUtilsReference.get() == null &&
254                 convertUtilsReference.get() == null) {
255                 break;
256 
257             } else {
258                 // create garbage:
259                 @SuppressWarnings("unused")
260                 final
261                 byte[] b =  new byte[bytz];
262                 bytz = bytz * 2;
263             }
264         }
265     }
266 
267     /**
268      * Tests whether difference instances are loaded by different
269      * context classloaders.
270      */
271     public void testGetByContextClassLoader() throws Exception {
272 
273         class GetBeanUtilsBeanThread extends Thread {
274 
275             private final Signal signal;
276 
277             GetBeanUtilsBeanThread(final Signal signal) {
278                 this.signal = signal;
279             }
280 
281             @Override
282             public void run() {
283                 signal.setSignal(2);
284                 signal.setBean(BeanUtilsBean.getInstance());
285                 signal.setConvertUtils(ConvertUtilsBean.getInstance());
286                 signal.setPropertyUtils(PropertyUtilsBean.getInstance());
287             }
288 
289             @Override
290             public String toString() {
291                 return "GetBeanUtilsBeanThread";
292             }
293         }
294 
295         final Signal signal = new Signal();
296         signal.setSignal(1);
297 
298         final GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal);
299         thread.setContextClassLoader(new TestClassLoader());
300 
301         thread.start();
302         thread.join();
303 
304         assertEquals("Signal not set by test thread", 2, signal.getSignal());
305         assertTrue(
306                     "Different BeanUtilsBean instances per context classloader",
307                     BeanUtilsBean.getInstance() != signal.getBean());
308         assertTrue(
309                     "Different ConvertUtilsBean instances per context classloader",
310                     ConvertUtilsBean.getInstance() != signal.getConvertUtils());
311         assertTrue(
312                     "Different PropertyUtilsBean instances per context classloader",
313                     PropertyUtilsBean.getInstance() != signal.getPropertyUtils());
314     }
315 
316 
317     /**
318      * Tests whether difference instances are loaded by different
319      * context classloaders.
320      */
321     public void testContextClassLoaderLocal() throws Exception {
322 
323         class CCLLTesterThread extends Thread {
324 
325             private final Signal signal;
326             private final ContextClassLoaderLocal<Integer> ccll;
327 
328             CCLLTesterThread(final Signal signal, final ContextClassLoaderLocal<Integer> ccll) {
329                 this.signal = signal;
330                 this.ccll = ccll;
331             }
332 
333             @Override
334             public void run() {
335                 ccll.set(new Integer(1789));
336                 signal.setSignal(2);
337                 signal.setMarkerObject(ccll.get());
338             }
339 
340             @Override
341             public String toString() {
342                 return "CCLLTesterThread";
343             }
344         }
345 
346         final ContextClassLoaderLocal<Integer> ccll = new ContextClassLoaderLocal<Integer>();
347         ccll.set(new Integer(1776));
348         assertEquals("Start thread sets value", new Integer(1776), ccll.get());
349 
350         final Signal signal = new Signal();
351         signal.setSignal(1);
352 
353         final CCLLTesterThread thread = new CCLLTesterThread(signal, ccll);
354         thread.setContextClassLoader(new TestClassLoader());
355 
356         thread.start();
357         thread.join();
358 
359         assertEquals("Signal not set by test thread", 2, signal.getSignal());
360         assertEquals("Second thread preserves value", new Integer(1776), ccll.get());
361         assertEquals("Second thread gets value it set", new Integer(1789), signal.getMarkerObject());
362     }
363 
364     /** Tests whether calls are independent for different classloaders */
365     public void testContextClassloaderIndependence() throws Exception {
366 
367         class TestIndependenceThread extends Thread {
368             private final Signal signal;
369             private final PrimitiveBean bean;
370 
371             TestIndependenceThread(final Signal signal, final PrimitiveBean bean) {
372                 this.signal = signal;
373                 this.bean = bean;
374             }
375 
376             @Override
377             public void run() {
378                 try {
379                     signal.setSignal(3);
380                     ConvertUtils.register(new Converter() {
381                                             public <T> T convert(final Class<T> type, final Object value) {
382                                                 return ConvertUtils.primitiveToWrapper(type).cast(new Integer(9));
383                                             }
384                                                 }, Integer.TYPE);
385                     BeanUtils.setProperty(bean, "int", new Integer(1));
386                 } catch (final Exception e) {
387                     e.printStackTrace();
388                     signal.setException(e);
389                 }
390             }
391 
392             @Override
393             public String toString() {
394                 return "TestIndependenceThread";
395             }
396         }
397 
398         final PrimitiveBean bean = new PrimitiveBean();
399         BeanUtils.setProperty(bean, "int", new Integer(1));
400         assertEquals("Wrong property value (1)", 1, bean.getInt());
401 
402         ConvertUtils.register(new Converter() {
403                                 public <T> T convert(final Class<T> type, final Object value) {
404                                     return ConvertUtils.primitiveToWrapper(type).cast(new Integer(5));
405                                 }
406                                     }, Integer.TYPE);
407         BeanUtils.setProperty(bean, "int", new Integer(1));
408         assertEquals("Wrong property value(2)", 5, bean.getInt());
409 
410         final Signal signal = new Signal();
411         signal.setSignal(1);
412         final TestIndependenceThread thread = new TestIndependenceThread(signal, bean);
413         thread.setContextClassLoader(new TestClassLoader());
414 
415         thread.start();
416         thread.join();
417 
418         assertNull("Exception thrown by test thread:" + signal.getException(), signal.getException());
419         assertEquals("Signal not set by test thread", 3, signal.getSignal());
420         assertEquals("Wrong property value(3)", 9, bean.getInt());
421 
422     }
423 
424     /** Tests whether different threads can set beanutils instances correctly */
425     public void testBeanUtilsBeanSetInstance() throws Exception {
426 
427         class SetInstanceTesterThread extends Thread {
428 
429             private final Signal signal;
430             private final BeanUtilsBean bean;
431 
432             SetInstanceTesterThread(final Signal signal, final BeanUtilsBean bean) {
433                 this.signal = signal;
434                 this.bean = bean;
435             }
436 
437             @Override
438             public void run() {
439                 BeanUtilsBean.setInstance(bean);
440                 signal.setSignal(21);
441                 signal.setBean(BeanUtilsBean.getInstance());
442             }
443 
444             @Override
445             public String toString() {
446                 return "SetInstanceTesterThread";
447             }
448         }
449 
450         final Signal signal = new Signal();
451         signal.setSignal(1);
452 
453         final BeanUtilsBean beanOne = new BeanUtilsBean();
454         final BeanUtilsBean beanTwo = new BeanUtilsBean();
455 
456         final SetInstanceTesterThread thread = new SetInstanceTesterThread(signal, beanTwo);
457         thread.setContextClassLoader(new TestClassLoader());
458 
459         BeanUtilsBean.setInstance(beanOne);
460         assertEquals("Start thread gets right instance", beanOne, BeanUtilsBean.getInstance());
461 
462         thread.start();
463         thread.join();
464 
465         assertEquals("Signal not set by test thread", 21, signal.getSignal());
466         assertEquals("Second thread preserves value", beanOne, BeanUtilsBean.getInstance());
467         assertEquals("Second thread gets value it set", beanTwo, signal.getBean());
468     }
469 
470     /** Tests whether the unset method works*/
471     public void testContextClassLoaderUnset() throws Exception {
472         final BeanUtilsBean beanOne = new BeanUtilsBean();
473         final ContextClassLoaderLocal<BeanUtilsBean> ccll = new ContextClassLoaderLocal<BeanUtilsBean>();
474         ccll.set(beanOne);
475         assertEquals("Start thread gets right instance", beanOne, ccll.get());
476         ccll.unset();
477         assertTrue("Unset works", !beanOne.equals(ccll.get()));
478     }
479 
480     // ---- Auxillary classes
481 
482     class TestClassLoader extends ClassLoader {
483         @Override
484         public String toString() {
485             return "TestClassLoader";
486         }
487     }
488 
489     class Signal {
490         private Exception e;
491         private int signal = 0;
492         private BeanUtilsBean bean;
493         private PropertyUtilsBean propertyUtils;
494         private ConvertUtilsBean convertUtils;
495         private Object marker;
496 
497         public Exception getException() {
498             return e;
499         }
500 
501         public void setException(final Exception e) {
502             this.e = e;
503         }
504 
505         public int getSignal() {
506             return signal;
507         }
508 
509         public void setSignal(final int signal) {
510             this.signal = signal;
511         }
512 
513         public Object getMarkerObject() {
514             return marker;
515         }
516 
517         public void setMarkerObject(final Object marker) {
518             this.marker = marker;
519         }
520 
521         public BeanUtilsBean getBean() {
522             return bean;
523         }
524 
525         public void setBean(final BeanUtilsBean bean) {
526             this.bean = bean;
527         }
528 
529         public PropertyUtilsBean getPropertyUtils() {
530             return propertyUtils;
531         }
532 
533         public void setPropertyUtils(final PropertyUtilsBean propertyUtils) {
534             this.propertyUtils = propertyUtils;
535         }
536 
537         public ConvertUtilsBean getConvertUtils() {
538             return convertUtils;
539         }
540 
541         public void setConvertUtils(final ConvertUtilsBean convertUtils) {
542             this.convertUtils = convertUtils;
543         }
544     }
545 }
546