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