1
0

LogFactory's Hashtable implementation (used to store LogFactoryImpl by classloader) can now be subclassed. This will default to WeakHashtable when this is present on the classpath, Hashtable otherwise. The implementation class can be specified by a system property. Based on a contribution by Brian Stansberry.

git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/logging/trunk@139056 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Robert Burrell Donkin
2004-11-10 23:00:47 +00:00
parent 23e71e6d58
commit 7a03f06584
9 changed files with 746 additions and 5 deletions

View File

@@ -21,7 +21,7 @@
<!-- <!--
"Logging" component of the Jakarta Commons Subproject "Logging" component of the Jakarta Commons Subproject
$Id: build.xml,v 1.47 2004/11/04 22:59:02 rdonkin Exp $ $Id: build.xml,v 1.48 2004/11/10 22:59:04 rdonkin Exp $
--> -->
@@ -462,7 +462,7 @@ limitations under the License.--&gt;'>
<target name="test" <target name="test"
depends="log4j12-warning, compile.tests,test.jdk14,test.log4j,test.simple,test.avalon,test.log4j12" depends="test.alt-hashtable, log4j12-warning, compile.tests,test.jdk14,test.log4j,test.simple,test.avalon,test.log4j12"
if="test.entry" if="test.entry"
description="Run all unit test cases"> description="Run all unit test cases">
<java classname="${test.runner}" fork="yes" <java classname="${test.runner}" fork="yes"
@@ -922,4 +922,26 @@ limitations under the License.--&gt;'>
</target> </target>
<target name="test.alt-hashtable" depends="compile.tests"
description="Tests for hashtable substitution">
<echo message="Hashtable substitution Tests"/>
<java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}">
<arg value="org.apache.commons.logging.AltHashtableTest"/>
<classpath refid="test.classpath"/>
<sysproperty key="org.apache.commons.logging.LogFactory.HashtableImpl"
value="org.apache.commons.logging.AltHashtable"/>
</java>
<echo message="Bad property test"/>
<java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}">
<arg value="org.apache.commons.logging.BadHashtablePropertyTest"/>
<classpath refid="test.classpath"/>
<sysproperty key="org.apache.commons.logging.LogFactory.HashtableImpl"
value="org.apache.commons.logging.bad.BogusHashTable"/>
</java>
</target>
</project> </project>

View File

@@ -0,0 +1,283 @@
/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.logging.impl;
import java.lang.ref.WeakReference;
import java.util.*;
/**
* <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s
* to hold it's keys thus allowing them to be reclaimed by the garbage collector.
* </p>
* <p>
* <strong>Usage:</strong> typical use case is as a drop-in replacement
* for the <code>Hashtable</code> use in <code>LogFactory</code> for J2EE enviroments
* running 1.3+ JVMs. Use of this class will allow classloaders to be collected by
* the garbage collector without the need to call release.
* </p>
*/
public final class WeakHashtable extends Hashtable {
public WeakHashtable() {}
/**
*@see Hashtable
*/
public boolean contains(Object value) {
if (value instanceof Referenced) {
return super.contains(value);
}
Referenced referenced = new Referenced(value);
return super.contains(referenced);
}
/**
*@see Hashtable
*/
public boolean containsKey(Object key) {
Referenced referenced = new Referenced(key);
return super.containsKey(referenced);
}
/**
*@see Hashtable
*/
public boolean containsValue(Object value) {
if (value instanceof Referenced) {
return super.contains(value);
}
Referenced referenced = new Referenced(value);
return super.containsValue(referenced);
}
/**
*@see Hashtable
*/
public Enumeration elements() {
final Enumeration enum = super.elements();
return new Enumeration() {
public boolean hasMoreElements() {
return enum.hasMoreElements();
}
public Object nextElement() {
Referenced nextReference = (Referenced) enum.nextElement();
return nextReference.getValue();
}
};
}
/**
*@see Hashtable
*/
public Set entrySet() {
Set referencedEntries = super.entrySet();
Set unreferencedEntries = new HashSet();
for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Referenced referencedKey = (Referenced) entry.getKey();
Object key = referencedKey.getValue();
Referenced referencedValue = (Referenced) entry.getValue();
Object value = referencedValue.getValue();
if (key != null) {
Entry dereferencedEntry = new Entry(key, value);
unreferencedEntries.add(dereferencedEntry);
}
}
return unreferencedEntries;
}
/**
*@see Hashtable
*/
public Object get(Object key) {
Object result = null;
Referenced referenceKey = new Referenced(key);
Referenced referencedValue = (Referenced) super.get(referenceKey);
if (referencedValue != null) {
result = referencedValue.getValue();
}
return result;
}
/**
*@see Hashtable
*/
public Enumeration keys() {
final Enumeration enum = super.keys();
return new Enumeration() {
public boolean hasMoreElements() {
return enum.hasMoreElements();
}
public Object nextElement() {
Referenced nextReference = (Referenced) enum.nextElement();
return nextReference.getValue();
}
};
}
/**
*@see Hashtable
*/
public Set keySet() {
Set referencedKeys = super.keySet();
Set unreferencedKeys = new HashSet();
for (Iterator it=referencedKeys.iterator(); it.hasNext();) {
Referenced referenceKey = (Referenced) it.next();
Object keyValue = referenceKey.getValue();
if (keyValue != null) {
unreferencedKeys.add(keyValue);
}
}
return unreferencedKeys;
}
/**
*@see Hashtable
*/
public Object put(Object key, Object value) {
Object result = null;
Referenced lastValue = (Referenced) super.put(new Referenced(key), new Referenced(value));
if (lastValue != null) {
result = lastValue.getValue();
}
return result;
}
/**
*@see Hashtable
*/
public void putAll(Map t) {
if (t != null) {
Set entrySet = t.entrySet();
for (Iterator it=entrySet.iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
put(entry.getKey(), entry.getValue());
}
}
}
/**
*@see Hashtable
*/
public Collection values() {
Collection referencedValues = super.values();
ArrayList unreferencedValues = new ArrayList();
for (Iterator it = referencedValues.iterator(); it.hasNext();) {
Referenced reference = (Referenced) it.next();
Object value = reference.getValue();
if (value != null) {
unreferencedValues.add(value);
}
}
return unreferencedValues;
}
/**
*@see Hashtable
*/
public Object remove(Object key) {
return super.remove(new Referenced(key));
}
private final static class Entry implements Map.Entry {
private Object key;
private Object value;
private Entry(Object key, Object value) {
this.key = key;
this.value = value;
}
public boolean equals(Object o) {
boolean result = false;
if (o != null && o instanceof Map.Entry) {
Map.Entry entry = (Map.Entry) o;
result = (getKey()==null ?
entry.getKey() == null :
getKey().equals(entry.getKey()))
&&
(getValue()==null ?
entry.getValue() == null :
getValue().equals(entry.getValue()));
}
return result;
}
public int hashCode() {
return (getKey()==null ? 0 : getKey().hashCode()) ^
(getValue()==null ? 0 : getValue().hashCode());
}
public Object setValue(Object value) {
throw new UnsupportedOperationException("Entry.setValue is not supported.");
}
public Object getValue() {
return value;
}
public Object getKey() {
return key;
}
}
/** Wrapper giving correct symantics for equals and hashcode */
private final static class Referenced {
private final WeakReference reference;
private Referenced(Object referant) {
reference = new WeakReference(referant);
}
public int hashCode() {
int result = 0;
Object keyValue = getValue();
if (keyValue != null) {
result = keyValue.hashCode();
}
return result;
}
private Object getValue() {
return reference.get();
}
public boolean equals(Object o) {
boolean result = false;
if (o instanceof Referenced) {
Referenced otherKey = (Referenced) o;
Object thisKeyValue = getValue();
Object otherKeyValue = otherKey.getValue();
if (thisKeyValue == null) {
result = (otherKeyValue == null);
}
else
{
result = thisKeyValue.equals(otherKeyValue);
}
}
return result;
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.logging;
import junit.framework.*;
import java.util.*;
import org.apache.commons.logging.impl.WeakHashtable;
public class LogFactoryTest extends TestCase {
/** Maximum number of iterations before our test fails */
private static final int MAX_GC_ITERATIONS = 50;
public LogFactoryTest(String testName) {
super(testName);
}
public void testLogFactoryType() {
assertTrue(LogFactory.factories instanceof WeakHashtable);
}
}

View File

@@ -19,13 +19,14 @@ package org.apache.commons.logging;
import junit.framework.*; import junit.framework.*;
import org.apache.commons.logging.impl.MemoryLogTest; import org.apache.commons.logging.impl.MemoryLogTest;
import org.apache.commons.logging.impl.WeakHashtableTest;
/** /**
* <p> The build script calls just one <code>TestSuite</code> - this one! * <p> The build script calls just one <code>TestSuite</code> - this one!
* All tests should be written into separate <code>TestSuite</code>'s * All tests should be written into separate <code>TestSuite</code>'s
* and added to this. Don't clutter this class with implementations. </p> * and added to this. Don't clutter this class with implementations. </p>
* *
* @version $Revision: 1.1 $ * @version $Revision: 1.2 $
*/ */
public class TestAll extends TestCase { public class TestAll extends TestCase {
@@ -38,6 +39,8 @@ public class TestAll extends TestCase {
TestSuite suite = new TestSuite(); TestSuite suite = new TestSuite();
suite.addTest(MemoryLogTest.suite()); suite.addTest(MemoryLogTest.suite());
suite.addTestSuite(WeakHashtableTest.class);
suite.addTestSuite(LogFactoryTest.class);
return suite; return suite;
} }

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.logging.impl;
import junit.framework.*;
import java.util.*;
public class WeakHashtableTest extends TestCase {
/** Maximum number of iterations before our test fails */
private static final int MAX_GC_ITERATIONS = 50;
private WeakHashtable weakHashtable;
private Long keyOne;
private Long keyTwo;
private Long keyThree;
private Long valueOne;
private Long valueTwo;
private Long valueThree;
public WeakHashtableTest(String testName) {
super(testName);
}
protected void setUp() throws Exception {
super.setUp();
weakHashtable = new WeakHashtable();
keyOne = new Long(1);
keyTwo = new Long(2);
keyThree = new Long(3);
valueOne = new Long(100);
valueTwo = new Long(200);
valueThree = new Long(300);
weakHashtable.put(keyOne, valueOne);
weakHashtable.put(keyTwo, valueTwo);
weakHashtable.put(keyThree, valueThree);
}
/** Tests public boolean contains(ObjectÊvalue) */
public void testContains() throws Exception {
assertFalse(weakHashtable.contains(new Long(1)));
assertFalse(weakHashtable.contains(new Long(2)));
assertFalse(weakHashtable.contains(new Long(3)));
assertTrue(weakHashtable.contains(new Long(100)));
assertTrue(weakHashtable.contains(new Long(200)));
assertTrue(weakHashtable.contains(new Long(300)));
assertFalse(weakHashtable.contains(new Long(400)));
}
/** Tests public boolean containsKey(ObjectÊkey) */
public void testContainsKey() throws Exception {
assertTrue(weakHashtable.containsKey(new Long(1)));
assertTrue(weakHashtable.containsKey(new Long(2)));
assertTrue(weakHashtable.containsKey(new Long(3)));
assertFalse(weakHashtable.containsKey(new Long(100)));
assertFalse(weakHashtable.containsKey(new Long(200)));
assertFalse(weakHashtable.containsKey(new Long(300)));
assertFalse(weakHashtable.containsKey(new Long(400)));
}
/** Tests public boolean containsValue(ObjectÊvalue) */
public void testContainsValue() throws Exception {
assertFalse(weakHashtable.containsValue(new Long(1)));
assertFalse(weakHashtable.containsValue(new Long(2)));
assertFalse(weakHashtable.containsValue(new Long(3)));
assertTrue(weakHashtable.containsValue(new Long(100)));
assertTrue(weakHashtable.containsValue(new Long(200)));
assertTrue(weakHashtable.containsValue(new Long(300)));
assertFalse(weakHashtable.containsValue(new Long(400)));
}
/** Tests public Enumeration elements() */
public void testElements() throws Exception {
ArrayList elements = new ArrayList();
for (Enumeration e = weakHashtable.elements(); e.hasMoreElements();) {
elements.add(e.nextElement());
}
assertEquals(3, elements.size());
assertTrue(elements.contains(valueOne));
assertTrue(elements.contains(valueTwo));
assertTrue(elements.contains(valueThree));
}
/** Tests public Set entrySet() */
public void testEntrySet() throws Exception {
Set entrySet = weakHashtable.entrySet();
for (Iterator it = entrySet.iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Object key = entry.getKey();
if (keyOne.equals(key)) {
assertEquals(valueOne, entry.getValue());
} else if (keyTwo.equals(key)) {
assertEquals(valueTwo, entry.getValue());
} else if (keyThree.equals(key)) {
assertEquals(valueThree, entry.getValue());
} else {
fail("Unexpected key");
}
}
}
/** Tests public Object get(ObjectÊkey) */
public void testGet() throws Exception {
assertEquals(valueOne, weakHashtable.get(keyOne));
assertEquals(valueTwo, weakHashtable.get(keyTwo));
assertEquals(valueThree, weakHashtable.get(keyThree));
assertNull(weakHashtable.get(new Long(50)));
}
/** Tests public Enumeration keys() */
public void testKeys() throws Exception {
ArrayList keys = new ArrayList();
for (Enumeration e = weakHashtable.keys(); e.hasMoreElements();) {
keys.add(e.nextElement());
}
assertEquals(3, keys.size());
assertTrue(keys.contains(keyOne));
assertTrue(keys.contains(keyTwo));
assertTrue(keys.contains(keyThree));
}
/** Tests public Set keySet() */
public void testKeySet() throws Exception {
Set keySet = weakHashtable.keySet();
assertEquals(3, keySet.size());
assertTrue(keySet.contains(keyOne));
assertTrue(keySet.contains(keyTwo));
assertTrue(keySet.contains(keyThree));
}
/** Tests public Object put(ObjectÊkey, ObjectÊvalue) */
public void testPut() throws Exception {
Long anotherKey = new Long(2004);
weakHashtable.put(anotherKey, new Long(1066));
assertEquals(new Long(1066), weakHashtable.get(anotherKey));
}
/** Tests public void putAll(MapÊt) */
public void testPutAll() throws Exception {
Map newValues = new HashMap();
Long newKey = new Long(1066);
Long newValue = new Long(1415);
newValues.put(newKey, newValue);
Long anotherNewKey = new Long(1645);
Long anotherNewValue = new Long(1815);
newValues.put(anotherNewKey, anotherNewValue);
weakHashtable.putAll(newValues);
assertEquals(5, weakHashtable.size());
assertEquals(newValue, weakHashtable.get(newKey));
assertEquals(anotherNewValue, weakHashtable.get(anotherNewKey));
}
/** Tests public Object remove(ObjectÊkey) */
public void testRemove() throws Exception {
weakHashtable.remove(keyOne);
assertEquals(2, weakHashtable.size());
assertNull(weakHashtable.get(keyOne));
}
/** Tests public Collection values() */
public void testValues() throws Exception {
Collection values = weakHashtable.values();
assertEquals(3, values.size());
assertTrue(values.contains(valueOne));
assertTrue(values.contains(valueTwo));
assertTrue(values.contains(valueThree));
}
public void testRelease() throws Exception {
// lose our references
keyOne = null;
keyTwo = null;
keyThree = null;
valueOne = null;
valueTwo = null;
valueThree = null;
int iterations = 0;
int bytz = 2;
while(true) {
System.gc();
if(iterations++ > MAX_GC_ITERATIONS){
fail("Max iterations reached before resource released.");
}
if(weakHashtable.get(new Long(1)) == null) {
break;
} else {
// create garbage:
byte[] b = new byte[bytz];
bytz = bytz * 2;
}
}
}
}

View File

@@ -42,7 +42,7 @@ import java.util.Properties;
* @author Craig R. McClanahan * @author Craig R. McClanahan
* @author Costin Manolache * @author Costin Manolache
* @author Richard A. Sitze * @author Richard A. Sitze
* @version $Revision: 1.27 $ $Date: 2004/06/06 21:15:12 $ * @version $Revision: 1.28 $ $Date: 2004/11/10 22:59:04 $
*/ */
public abstract class LogFactory { public abstract class LogFactory {
@@ -79,6 +79,42 @@ public abstract class LogFactory {
protected static final String SERVICE_ID = protected static final String SERVICE_ID =
"META-INF/services/org.apache.commons.logging.LogFactory"; "META-INF/services/org.apache.commons.logging.LogFactory";
/**
* <p>Setting this system property value allows the <code>Hashtable</code> used to store
* classloaders to be substituted by an alternative implementation.
* </p>
* <p>
* <strong>Note:</strong> <code>LogFactory</code> will print:
* <code><pre>
* [ERROR] LogFactory: Load of custom hashtable failed</em>
* </code></pre>
* to system error and then continue using a standard Hashtable.
* </p>
* <p>
* <strong>Usage:</strong> Set this property when Java is invoked
* and <code>LogFactory</code> will attempt to load a new instance
* of the given implementation class.
* For example, running the following ant scriplet:
* <code><pre>
* &lt;java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}"&gt;
* ...
* &lt;sysproperty
* key="org.apache.commons.logging.LogFactory.HashtableImpl"
* value="org.apache.commons.logging.AltHashtable"/&gt;
* &lt;/java&gt;
* </pre></code>
* will mean that <code>LogFactory</code> will load an instance of
* <code>org.apache.commons.logging.AltHashtable</code>.
* </p>
* <p>
* A typical use case is to allow a custom
* Hashtable implementation using weak references to be substituted.
* This will allow classloaders to be garbage collected without
* the need to release them (on 1.3+ JVMs only, of course ;)
* </p>
*/
public static final String HASHTABLE_IMPLEMENTATION_PROPERTY =
"org.apache.commons.logging.LogFactory.HashtableImpl";
// ----------------------------------------------------------- Constructors // ----------------------------------------------------------- Constructors
@@ -181,7 +217,28 @@ public abstract class LogFactory {
* The previously constructed <code>LogFactory</code> instances, keyed by * The previously constructed <code>LogFactory</code> instances, keyed by
* the <code>ClassLoader</code> with which it was created. * the <code>ClassLoader</code> with which it was created.
*/ */
protected static Hashtable factories = new Hashtable(); protected static Hashtable factories = createFactoryStore();
private static final Hashtable createFactoryStore() {
Hashtable result = null;
String storeImplementationClass
= System.getProperty(HASHTABLE_IMPLEMENTATION_PROPERTY);
if (storeImplementationClass == null) {
storeImplementationClass = "org.apache.commons.logging.impl.WeakHashtable";
}
try {
Class implementationClass = Class.forName(storeImplementationClass);
result = (Hashtable) implementationClass.newInstance();
} catch (Exception e) {
// ignore
System.err.println("[ERROR] LogFactory: Load of custom hashtable failed");
}
if (result == null) {
result = new Hashtable();
}
return result;
}
// --------------------------------------------------------- Static Methods // --------------------------------------------------------- Static Methods

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.logging;
import java.util.Hashtable;
public class AltHashtable extends Hashtable {
public static Object lastKey;
public static Object lastValue;
public Object put(Object key, Object value) {
lastKey = key;
lastValue = value;
return super.put(key, value);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.logging;
import junit.framework.*;
public class AltHashtableTest extends TestCase {
public AltHashtableTest(String testName) {
super(testName);
}
public void testType() {
assertTrue(LogFactory.factories instanceof AltHashtable);
}
public void testPutCalled() throws Exception {
AltHashtable.lastKey = null;
AltHashtable.lastValue = null;
ClassLoader classLoader = new ClassLoader() {};
Thread thread = new Thread(
new Runnable() {
public void run() {
LogFactory.getLog(AltHashtableTest.class);
}
}
);
thread.setContextClassLoader(classLoader);
thread.start();
thread.join();
assertEquals(classLoader, AltHashtable.lastKey);
assertNotNull(AltHashtable.lastValue);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.logging;
import junit.framework.*;
import java.util.Hashtable;
/**
* Tests behaviour when the property is misconfigured.
*/
public class BadHashtablePropertyTest extends TestCase {
public BadHashtablePropertyTest(String testName) {
super(testName);
}
public void testType() {
assertTrue(LogFactory.factories instanceof Hashtable);
}
public void testPutCalled() throws Exception {
Log log = LogFactory.getLog(BadHashtablePropertyTest.class);
}
}