    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.util.*;
    21
    22  import java.lang.ref.WeakReference;
    23  import java.lang.ref.ReferenceQueue;
    24
    25  import junit.framework.TestCase;
    26  import junit.framework.Test;
    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   * @author Robert Burrell Donkin
    37   * @author Juozas Baliuka
    38   * @version $Revision: 469737 $ $Date: 2006-11-01 01:16:55 +0000 (Wed, 01 Nov 2006) $
    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