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