You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ni...@apache.org on 2009/08/23 02:50:23 UTC

svn commit: r806915 - in /commons/proper/beanutils/trunk/src: java/org/apache/commons/beanutils/MappedPropertyDescriptor.java test/org/apache/commons/beanutils/bugs/Jira347TestCase.java

Author: niallp
Date: Sun Aug 23 00:50:23 2009
New Revision: 806915

URL: http://svn.apache.org/viewvc?rev=806915&view=rev
Log:
BEANUTILS-347 MappedPropertyDescriptor throws an exception after method reference has been garbage collected - thanks to Eickvonder for the analysis and test case

Added:
    commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/bugs/Jira347TestCase.java   (with props)
Modified:
    commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java

Modified: commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java?rev=806915&r1=806914&r2=806915&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java (original)
+++ commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java Sun Aug 23 00:50:23 2009
@@ -431,7 +431,9 @@
         private String methodName;
         private Reference methodRef;
         private Reference classRef;
-        private Reference writeParamTypeRef;
+        private Reference writeParamTypeRef0;
+        private Reference writeParamTypeRef1;
+        private String[] writeParamClassNames;
         MappedMethodReference(Method m) {
             if (m != null) {
                 className = m.getDeclaringClass().getName();
@@ -440,7 +442,11 @@
                 classRef = new WeakReference(m.getDeclaringClass());
                 Class[] types = m.getParameterTypes();
                 if (types.length == 2) {
-                    writeParamTypeRef = new WeakReference(types[1]);
+                    writeParamTypeRef0 = new WeakReference(types[0]);
+                    writeParamTypeRef1 = new WeakReference(types[1]);
+                    writeParamClassNames = new String[2];
+                    writeParamClassNames[0] = types[0].getName();
+                    writeParamClassNames[1] = types[1].getName();
                 }
             }
         }
@@ -462,8 +468,22 @@
                             className + " could not be reconstructed - class reference has gone");
                 }
                 Class[] paramTypes = null;
-                if (writeParamTypeRef != null) {
-                    paramTypes = new Class[] {String.class, (Class)writeParamTypeRef.get()};
+                if (writeParamClassNames != null) {
+                    paramTypes = new Class[2];
+                    paramTypes[0] = (Class)writeParamTypeRef0.get();
+                    if (paramTypes[0] == null) {
+                        paramTypes[0] = reLoadClass(writeParamClassNames[0]);
+                        if (paramTypes[0] != null) {
+                            writeParamTypeRef0 = new WeakReference(paramTypes[0]);
+                        }
+                    }
+                    paramTypes[1] = (Class)writeParamTypeRef1.get();
+                    if (paramTypes[1] == null) {
+                        paramTypes[1] = reLoadClass(writeParamClassNames[1]);
+                        if (paramTypes[1] != null) {
+                            writeParamTypeRef1 = new WeakReference(paramTypes[1]);
+                        }
+                    }
                 } else {
                     paramTypes = STRING_CLASS_PARAMETER;
                 }
@@ -484,13 +504,20 @@
          * Try to re-load the class
          */
         private Class reLoadClass() {
+            return reLoadClass(className);
+        }
+
+        /**
+         * Try to re-load the class
+         */
+        private Class reLoadClass(String name) {
 
             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 
             // Try the context class loader
             if (classLoader != null) {
                 try {
-                    return classLoader.loadClass(className);
+                    return classLoader.loadClass(name);
                 } catch (ClassNotFoundException e) {
                     // ignore
                 }
@@ -499,7 +526,7 @@
             // Try this class's class loader
             classLoader = MappedPropertyDescriptor.class.getClassLoader();
             try {
-                return classLoader.loadClass(className);
+                return classLoader.loadClass(name);
             } catch (ClassNotFoundException e) {
                 return null;
             }

Added: commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/bugs/Jira347TestCase.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/bugs/Jira347TestCase.java?rev=806915&view=auto
==============================================================================
--- commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/bugs/Jira347TestCase.java (added)
+++ commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/bugs/Jira347TestCase.java Sun Aug 23 00:50:23 2009
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils.bugs;
+
+import java.beans.IntrospectionException;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.beanutils.MappedPropertyDescriptor;
+import org.apache.commons.beanutils.memoryleaktests.MemoryLeakTestCase;
+
+/**
+ * Test case for Jira issue# BEANUTILS-347.
+ * <br/>
+ * See https://issues.apache.org/jira/browse/BEANUTILS-347
+ *
+ * @version $Revision$ $Date$
+ */
+public class Jira347TestCase extends TestCase {
+    
+    /**
+     * Tests that MappedPropertyDescriptor does not throw an exception while re-creating a Method reference after it
+     * has been garbage collected under the following circumstances.
+     * - a class has a property 'mappedProperty'
+     * - there is no getter for this property
+     * - there is method setMappedProperty(MappedPropertyTestBean,MappedPropertyTestBean)
+     * 
+     * In this case getMappedWriteMethod should not throw an exception after the method reference has been garbage collected.
+     * It is essential that in both cases the same method is returned or in both cases null. 
+     * If the constructor of the MappedPropertyDescriptor would recognize the situation (of the first param not being of type String)
+     * this would be fine as well.          
+     */
+    public void testMappedPropertyDescriptor_AnyArgsProperty() throws Exception {
+        String className = "org.apache.commons.beanutils.MappedPropertyTestBean";
+        ClassLoader loader = newClassLoader();
+        Class beanClass    = loader.loadClass(className);
+        Object bean        = beanClass.newInstance();
+        // -----------------------------------------------------------------------------
+
+        // Sanity checks only
+        assertNotNull("ClassLoader is null", loader);
+        assertNotNull("BeanClass is null", beanClass);
+        assertNotSame("ClassLoaders should be different..", getClass().getClassLoader(), beanClass.getClassLoader());
+        assertSame("BeanClass ClassLoader incorrect", beanClass.getClassLoader(), loader);
+
+        // now start the test
+        MappedPropertyDescriptor descriptor = null;
+        try {
+          descriptor = new MappedPropertyDescriptor("anyMapped", beanClass);
+        }
+        catch (IntrospectionException e) {
+          // this would be fine as well
+        }
+        
+        if (descriptor != null) {
+            String m1 = getMappedWriteMethod(descriptor);
+             forceGarbageCollection();
+             try {
+                 String m2 = getMappedWriteMethod(descriptor);
+                 assertEquals("Method returned post garbage collection differs from Method returned prior to gc", m1, m2);
+             }
+             catch (RuntimeException e) {
+                 fail("getMappedWriteMethod threw an exception after garbage collection " + e);
+             }
+        }
+    }
+    
+    /**
+     * Retrieves the string representation of the mapped write method
+     * for the given descriptor.
+     * This conversion is needed as there must not be strong reference to the 
+     * Method object outside of this method as otherwise the garbage collector will not
+     * clean up the soft reference within the MappedPropertyDescriptor. 
+     * 
+     * @return the string representation or null if mapped write method does not exist
+     */
+    private String getMappedWriteMethod(MappedPropertyDescriptor descriptor) {
+        Method m = descriptor.getMappedWriteMethod();
+        return m == null ? null : m.toString();
+    }
+    
+    /**
+     * Try to force the garbage collector to run by filling up memory and calling System.gc().
+     */
+    private void forceGarbageCollection() throws Exception {
+        // Fill up memory
+        SoftReference ref = new SoftReference(new Object());
+        int count = 0;
+        while(ref.get() != null && count++ < 5) {
+            java.util.ArrayList list = new java.util.ArrayList();
+            try {
+                long i = 0;
+                while (true && ref.get() != null) {
+                    list.add("A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String " + (i++));
+                }
+            } catch (Throwable ignored) {
+            }
+            list.clear();
+            list = null;
+            // System.out.println("Count " + count + " : " + getMemoryStats());
+            System.gc(); 
+            Thread.sleep(1000);
+        }
+        // System.out.println("After GC: " + getMemoryStats());
+        
+        if (ref.get() != null) {
+            throw new IllegalStateException("Your JVM is not releasing SoftReference, try running the testcase with less memory (-Xmx)");
+        }
+    }
+    
+    /**
+     * Create a new class loader instance.
+     */
+    private static URLClassLoader newClassLoader() throws MalformedURLException {
+
+        String dataFilePath = MemoryLeakTestCase.class.getResource("pojotests").getFile();
+        //System.out.println("dataFilePath: " + dataFilePath);
+        String location = "file://" + dataFilePath.substring(0,dataFilePath.length()-"org.apache.commons.beanutils.memoryleaktests.pojotests".length());
+        //System.out.println("location: " + location);
+
+        StringBuffer newString = new StringBuffer();
+        for (int i=0;i<location.length();i++) {
+            if (location.charAt(i)=='\\') {
+                newString.append("/");
+            } else {
+                newString.append(location.charAt(i));
+            }
+        }
+        String classLocation = newString.toString();
+        //System.out.println("classlocation: " + classLocation);
+
+        URLClassLoader theLoader = URLClassLoader.newInstance(new URL[]{new URL(classLocation)},null);
+        return theLoader;
+    }
+}

Propchange: commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/bugs/Jira347TestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/bugs/Jira347TestCase.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL