commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rdon...@apache.org
Subject cvs commit: jakarta-commons/beanutils/src/test/org/apache/commons/beanutils BeanificationTestCase.java
Date Fri, 16 May 2003 14:50:22 GMT
rdonkin     2003/05/16 07:50:22

  Modified:    beanutils build.xml
               beanutils/src/java/org/apache/commons/beanutils
                        BeanUtilsBean.java ConvertUtilsBean.java
                        PropertyUtilsBean.java
  Added:       beanutils/src/test/org/apache/commons/beanutils
                        BeanificationTestCase.java
  Log:
  Changed from singletons backing Utility classes to pseudo-singletons (per thread context
classloader). This should isolated the use of BeanUtils between web apps. Also added a test
case. Note that for some 1.3 JVMs, WeakHashMap has a bug which results in a memory leak. Since
commons-logging has the same vunerability and this results in the beanutils classes remaining
in memory after they should have been released, I decided to go with the standard class rather
than use the collections replacement which does not have this bug.
  
  Revision  Changes    Path
  1.55      +12 -2     jakarta-commons/beanutils/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/beanutils/build.xml,v
  retrieving revision 1.54
  retrieving revision 1.55
  diff -u -r1.54 -r1.55
  --- build.xml	6 May 2003 11:32:23 -0000	1.54
  +++ build.xml	16 May 2003 14:50:21 -0000	1.55
  @@ -263,7 +263,8 @@
                                   test.dyna.row,
                                   test.bean.comparator,
                                   test.locale.convert,
  -                                test.converters
  +                                test.converters,
  +                                test.beanification
                                  "
      description="Run all unit test cases">
     </target>
  @@ -461,6 +462,15 @@
         <sysproperty key="org.apache.commons.logging.simplelog.defaultlog"
                    value="${test.level}"/>
         <arg value="org.apache.commons.beanutils.converters.ConverterTestSuite"/>
  +      <classpath refid="test.classpath"/>
  +    </java>
  +  </target>
  +  
  +  <target name="test.beanification" depends="compile.tests">
  +    <echo message="Running Beanification tests ..."/>
  +    <java classname="${test.runner}" fork="yes"
  +        failonerror="${test.failonerror}">
  +      <arg value="org.apache.commons.beanutils.BeanificationTestCase"/>
         <classpath refid="test.classpath"/>
       </java>
     </target>
  
  
  
  1.7       +44 -8     jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/BeanUtilsBean.java
  
  Index: BeanUtilsBean.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/BeanUtilsBean.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- BeanUtilsBean.java	6 May 2003 11:32:23 -0000	1.6
  +++ BeanUtilsBean.java	16 May 2003 14:50:22 -0000	1.7
  @@ -73,6 +73,7 @@
   import java.util.HashMap;
   import java.util.Iterator;
   import java.util.Map;
  +import java.util.WeakHashMap;
   
   import org.apache.commons.collections.FastHashMap;
   import org.apache.commons.logging.Log;
  @@ -104,8 +105,43 @@
   
       /** Singleton instance */
       private static final BeanUtilsBean singleton = new BeanUtilsBean();
  -    /** Basic factory method*/
  -    protected static BeanUtilsBean getInstance() {
  +    /** 
  +     * Map contains <code>BeanUtilsBean</code> instances indexed by context
classloader.
  +     * <strong>Note:</strong> A WeakHashMap bug in several 1.3 JVMs results
in a memory leak
  +     * for those JVMs.
  +     */
  +    private static final Map beansByClassLoader
  +            = new WeakHashMap();
  +    
  +    /** 
  +     * Gets the instance which provides the functionality for {@link BeanUtils}.
  +     * This is a pseudo-singleton - an single instance is provided per (thread) context
classloader.
  +     * This mechanism provides isolation for web apps deployed in the same container. 
  +     */
  +    public synchronized static BeanUtilsBean getInstance() {
  +        // synchronizing the whole method is a bit slower 
  +        // but guarentees no subtle threading problems
  +        
  +        // make sure that the map is given a change to purge itself
  +        beansByClassLoader.isEmpty();
  +        try {
  +            
  +            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
  +            if (contextClassLoader != null) {
  +                
  +                BeanUtilsBean instance = (BeanUtilsBean) beansByClassLoader.get(contextClassLoader);
  +                    
  +                if (instance == null) {
  +                    instance = new BeanUtilsBean();
  +                    beansByClassLoader.put(contextClassLoader, instance);
  +                }
  +                return instance;
  +                
  +            }
  +            
  +        } catch (SecurityException e) { /* SWALLOW - should we log this? */ }
  +        
  +        // if in doubt, return the basic
           return singleton;
       }
   
  @@ -125,11 +161,11 @@
       // --------------------------------------------------------- Constuctors
   
       /** 
  -     * <p>Constructs an instance using standard (singleton) property 
  +     * <p>Constructs an instance using new property 
        * and conversion instances.</p>
        */
       public BeanUtilsBean() {
  -        this(ConvertUtilsBean.getInstance(),PropertyUtilsBean.getInstance());
  +        this(new ConvertUtilsBean(), new PropertyUtilsBean());
       }
   
       /** 
  
  
  
  1.7       +5 -9      jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/ConvertUtilsBean.java
  
  Index: ConvertUtilsBean.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/ConvertUtilsBean.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- ConvertUtilsBean.java	6 May 2003 11:32:23 -0000	1.6
  +++ ConvertUtilsBean.java	16 May 2003 14:50:22 -0000	1.7
  @@ -154,14 +154,10 @@
   
   public class ConvertUtilsBean {
       
  -    // ------------------------------------------------------- Class Variables
  -    /** Default singleton */
  -    private static final ConvertUtilsBean singleton = new ConvertUtilsBean();
  -    
       // ------------------------------------------------------- Class Methods
       /** Get singleton instance */
       protected static ConvertUtilsBean getInstance() {
  -        return singleton;
  +        return BeanUtilsBean.getInstance().getConvertUtils();
       }
   
       // ------------------------------------------------------- Variables
  
  
  
  1.8       +5 -6      jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/PropertyUtilsBean.java
  
  Index: PropertyUtilsBean.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/PropertyUtilsBean.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- PropertyUtilsBean.java	12 May 2003 21:42:56 -0000	1.7
  +++ PropertyUtilsBean.java	16 May 2003 14:50:22 -0000	1.8
  @@ -143,9 +143,8 @@
   
       // --------------------------------------------------------- Class Methods
       
  -    private static final PropertyUtilsBean singleton = new PropertyUtilsBean();
       protected static PropertyUtilsBean getInstance() {
  -        return singleton;
  +        return BeanUtilsBean.getInstance().getPropertyUtils();
       }	
   
       // --------------------------------------------------------- Variables
  
  
  
  1.1                  jakarta-commons/beanutils/src/test/org/apache/commons/beanutils/BeanificationTestCase.java
  
  Index: BeanificationTestCase.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/beanutils/src/test/org/apache/commons/beanutils/BeanificationTestCase.java,v
1.1 2003/05/16 14:50:22 rdonkin Exp $
   * $Revision: 1.1 $
   * $Date: 2003/05/16 14:50:22 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
  
  package org.apache.commons.beanutils;
  
  import java.util.*;
  
  import java.lang.ref.WeakReference;
  import java.lang.ref.ReferenceQueue;
  
  import junit.framework.TestCase;
  import junit.framework.Test;
  import junit.framework.TestSuite;
  
  import org.apache.commons.collections.ReferenceMap;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * <p>
   * Test Case for changes made during Beanutils Beanification
   * </p>
   *
   * @author Robert Burrell Donkin
   * @author Juozas Baliuka
   * @version $Revision: 1.1 $ $Date: 2003/05/16 14:50:22 $
   */
  
  public class BeanificationTestCase extends TestCase {
      
      // ---------------------------------------------------- Constants
      
      /** Maximum number of iterations before our test fails */
      public static final int MAX_GC_ITERATIONS = 50;
      
      // ---------------------------------------------------- Instance Variables
  
  
      // ---------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new instance of this test case.
       *
       * @param name Name of the test case
       */
      public BeanificationTestCase(String name) {
          super(name);
      }
  
  
      // -------------------------------------------------- Overall Test Methods
  
  
      /**
       * Set up instance variables required by this test case.
       */
      public void setUp() {
  
          ConvertUtils.deregister();
  
      }
  
  
      /**
       * Return the tests included in this test suite.
       */
      public static Test suite() {
          return (new TestSuite(BeanificationTestCase.class));
      }
  
  
      /**
       * Tear down instance variables required by this test case.
       */
      public void tearDown() {
          ;    // No action required
      }
  
  
      // ------------------------------------------------ Individual Test Methods
      
      /** Test of the methodology we'll use for some of the later tests */
      public void testMemoryTestMethodology() throws Exception {
          // test methodology
          // many thanks to Juozas Baliuka for suggesting this method
          ClassLoader loader = new ClassLoader() {};
          WeakReference reference = new  WeakReference(loader);
          Class myClass = loader.loadClass("org.apache.commons.beanutils.BetaBean");
          
          assertNotNull("Weak reference released early", reference.get());
          
          // dereference class loader and class:
          loader = null;
          myClass = null;
          
          int iterations = 0;
          int bytz = 2;
          while(true) {
              System.gc();
              if(iterations++ > MAX_GC_ITERATIONS){
                  fail("Max iterations reached before resource released.");
              }
              if( reference.get() == null ) {
                  break;
                  
              } else {
                  // create garbage:
                  byte[] b =  new byte[bytz];
                  bytz = bytz * 2;
              }
          }
      }
      
      /** Tests whether classloaders and beans are released from memory by the map used by
beanutils */
      public void testMemoryLeak2() throws Exception {
          // tests when the map used by beanutils has the right behaviour
          
          if (isPre14JVM()) {
              System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
              return;
          }
          
          // many thanks to Juozas Baliuka for suggesting this methodology
          TestClassLoader loader = new TestClassLoader();
          ReferenceQueue queue = new ReferenceQueue();
          WeakReference loaderReference = new WeakReference(loader, queue);
          Integer test = new Integer(1);
          
          WeakReference testReference = new WeakReference(test, queue);
          //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true);
          Map map = new WeakHashMap();
          map.put(loader, test);
          
          assertEquals("In map", test, map.get(loader));
          assertNotNull("Weak reference released early (1)", loaderReference.get());
          assertNotNull("Weak reference released early (2)", testReference.get());
          
          // dereference strong references
          loader = null;
          test = null;
          
          int iterations = 0;
          int bytz = 2;
          while(true) {
              System.gc();
              if(iterations++ > MAX_GC_ITERATIONS){
                  fail("Max iterations reached before resource released.");
              }
              map.isEmpty();
              
              if( 
                  loaderReference.get() == null &&
                  testReference.get() == null) {
                  break;
                  
              } else {
                  // create garbage:
                  byte[] b =  new byte[bytz];
                  bytz = bytz * 2;
              }
          }
      }
      
      /** Tests whether classloaders and beans are released from memory */
      public void testMemoryLeak() throws Exception {
          if (isPre14JVM()) {
              System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
              return;
          }
          
          // many thanks to Juozas Baliuka for suggesting this methodology
          TestClassLoader loader = new TestClassLoader();
          WeakReference loaderReference = new  WeakReference(loader);
          BeanUtilsBean.getInstance();
  
          class GetBeanUtilsBeanThread extends Thread {
              
              BeanUtilsBean beanUtils;
              ConvertUtilsBean convertUtils;
              PropertyUtilsBean propertyUtils;
          
              GetBeanUtilsBeanThread() {}
              
              public void run() {
                  beanUtils = BeanUtilsBean.getInstance();
                  convertUtils = ConvertUtilsBean.getInstance();
                  propertyUtils = PropertyUtilsBean.getInstance();
                  // XXX Log keeps a reference around!
                  LogFactory.releaseAll();
              }
              
              public String toString() {
                  return "GetBeanUtilsBeanThread";
              }
          }
          
      
          GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
          WeakReference threadWeakReference = new WeakReference(thread);
          thread.setContextClassLoader(loader);
  
          thread.start();
          thread.join();
          
          WeakReference beanUtilsReference = new WeakReference(thread.beanUtils);
          WeakReference propertyUtilsReference =  new WeakReference(thread.propertyUtils);
          WeakReference convertUtilsReference = new WeakReference(thread.convertUtils);
          
          assertNotNull("Weak reference released early (1)", loaderReference.get());
          assertNotNull("Weak reference released early (2)", beanUtilsReference.get());
          assertNotNull("Weak reference released early (3)", propertyUtilsReference.get());
          assertNotNull("Weak reference released early (4)", convertUtilsReference.get());
          
          // dereference strong references
          loader = null;
          thread.setContextClassLoader(null);
          thread = null;
          
          int iterations = 0;
          int bytz = 2;
          while(true) {
              BeanUtilsBean.getInstance();
              System.gc();
              if(iterations++ > MAX_GC_ITERATIONS){
                  fail("Max iterations reached before resource released.");
              }
  
              if( 
                  loaderReference.get() == null &&
                  beanUtilsReference.get() == null && 
                  propertyUtilsReference.get() == null && 
                  convertUtilsReference.get() == null) {
                  break;
                  
              } else {
                  // create garbage:
                  byte[] b =  new byte[bytz];
                  bytz = bytz * 2;
              }
          }
      }
      
      /** 
       * Tests whether difference instances are loaded by different 
       * context classloaders.
       */
      public void testGetByContextClassLoader() throws Exception {
              
          class GetBeanUtilsBeanThread extends Thread {
              
              private Signal signal;
          
              GetBeanUtilsBeanThread(Signal signal) {
                  this.signal = signal;
              }
              
              public void run() {
                  signal.setSignal(2);
                  signal.setBean(BeanUtilsBean.getInstance());
                  signal.setConvertUtils(ConvertUtilsBean.getInstance());
                  signal.setPropertyUtils(PropertyUtilsBean.getInstance());
              }
              
              public String toString() {
                  return "GetBeanUtilsBeanThread";
              }
          }
              
          Signal signal = new Signal();
          signal.setSignal(1);
          
          GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal);
          thread.setContextClassLoader(new TestClassLoader());
          
          thread.start();
          thread.join();
          
          assertEquals("Signal not set by test thread", 2, signal.getSignal());
          assertTrue(
                      "Different BeanUtilsBean instances per context classloader", 
                      BeanUtilsBean.getInstance() != signal.getBean());
          assertTrue(
                      "Different ConvertUtilsBean instances per context classloader", 
                      ConvertUtilsBean.getInstance() != signal.getConvertUtils());       
            
          assertTrue(
                      "Different PropertyUtilsBean instances per context classloader", 
                      PropertyUtilsBean.getInstance() != signal.getPropertyUtils());     
  
      }
      
      /** Tests whether calls are independent for different classloaders */
      public void testContextClassloaderIndependence() throws Exception {
      
          class TestIndependenceThread extends Thread {
              private Signal signal;
              private PrimitiveBean bean;
          
              TestIndependenceThread(Signal signal, PrimitiveBean bean) {
                  this.signal = signal;
                  this.bean = bean;
              }
              
              public void run() {
                  try {
                      signal.setSignal(3);
                      ConvertUtils.register(new Converter() {
                                              public Object convert(Class type, Object value)
{
                                                  return new Integer(9);
                                              }
                                                  }, Integer.TYPE);
                      BeanUtils.setProperty(bean, "int", new Integer(1));
                  } catch (Exception e) {
                      e.printStackTrace();
                      signal.setException(e);
                  }
              }
              
              public String toString() {
                  return "TestIndependenceThread";
              }
          }
          
          PrimitiveBean bean = new PrimitiveBean();
          BeanUtils.setProperty(bean, "int", new Integer(1));
          assertEquals("Wrong property value (1)", 1, bean.getInt());
  
          ConvertUtils.register(new Converter() {
                                  public Object convert(Class type, Object value) {
                                      return new Integer(5);
                                  }
                                      }, Integer.TYPE);
          BeanUtils.setProperty(bean, "int", new Integer(1));
          assertEquals("Wrong property value(2)", 5, bean.getInt());
      
          Signal signal = new Signal();
          signal.setSignal(1);
          TestIndependenceThread thread = new TestIndependenceThread(signal, bean);
          thread.setContextClassLoader(new TestClassLoader());
          
          thread.start();
          thread.join();
          
          assertNull("Exception thrown by test thread:" + signal.getException(), signal.getException());
          assertEquals("Signal not set by test thread", 3, signal.getSignal());
          assertEquals("Wrong property value(3)", 9, bean.getInt());
          
      }
      
      private boolean isPre14JVM() {
          // some pre 1.4 JVM have buggy WeakHashMap implementations 
          // this is used to test for those JVM
          String version = System.getProperty("java.specification.version");
          StringTokenizer tokenizer = new StringTokenizer(version,".");
          if (tokenizer.nextToken().equals("1")) {
              String minorVersion = tokenizer.nextToken();
              if (minorVersion.equals("0")) return true;
              if (minorVersion.equals("1")) return true;
              if (minorVersion.equals("2")) return true;
              if (minorVersion.equals("3")) return true;
          }
          return false;
      }
      
      // ---- Auxillary classes
      
      class TestClassLoader extends ClassLoader {
          public String toString() {
              return "TestClassLoader";
          }
      }
      
      class Signal {
          private Exception e;
          private int signal = 0;
          private BeanUtilsBean bean;
          private PropertyUtilsBean propertyUtils;
          private ConvertUtilsBean convertUtils;
          
          public Exception getException() {
              return e;
          }
          
          public void setException(Exception e) {
              this.e = e;
          }
          
          public int getSignal() {
              return signal;
          }
          
          public void setSignal(int signal) {
              this.signal = signal;
          }
          
          public BeanUtilsBean getBean() {
              return bean;
          }
          
          public void setBean(BeanUtilsBean bean) {
              this.bean = bean;
          }
          
          public PropertyUtilsBean getPropertyUtils() {
              return propertyUtils;
          }
          
          public void setPropertyUtils(PropertyUtilsBean propertyUtils) {
              this.propertyUtils = propertyUtils;
          }
          
          public ConvertUtilsBean getConvertUtils() {
              return convertUtils;
          }
          
          public void setConvertUtils(ConvertUtilsBean convertUtils) {
              this.convertUtils = convertUtils;
          }
      }
  }
  
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message