You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by pe...@apache.org on 2010/08/18 11:00:01 UTC

svn commit: r986600 - in /incubator/river/jtsk/trunk/src: net/jini/security/Security.java org/apache/river/api/security/ExecutionContextManager.java org/apache/river/imp/security/policy/se/ECM.java org/apache/river/imp/util/ConcurrentSoftIdentityMap.java

Author: peter_firmstone
Date: Wed Aug 18 09:00:01 2010
New Revision: 986600

URL: http://svn.apache.org/viewvc?rev=986600&view=rev
Log:
ExecutionContextManager implementation for the RevokableDynamicPolicy, please review.

Added:
    incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java   (with props)
Modified:
    incubator/river/jtsk/trunk/src/net/jini/security/Security.java
    incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java
    incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java

Modified: incubator/river/jtsk/trunk/src/net/jini/security/Security.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/net/jini/security/Security.java?rev=986600&r1=986599&r2=986600&view=diff
==============================================================================
--- incubator/river/jtsk/trunk/src/net/jini/security/Security.java (original)
+++ incubator/river/jtsk/trunk/src/net/jini/security/Security.java Wed Aug 18 09:00:01 2010
@@ -26,6 +26,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.rmi.RemoteException;
 import java.security.AccessControlContext;
+import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.DomainCombiner;
 import java.security.Permission;
@@ -50,6 +51,8 @@ import javax.security.auth.Subject;
 import javax.security.auth.SubjectDomainCombiner;
 import net.jini.security.policy.DynamicPolicy;
 import net.jini.security.policy.SecurityContextSource;
+import org.apache.river.api.security.ExecutionContextManager;
+import org.apache.river.api.security.RevokeableDynamicPolicy;
 
 /**
  * Provides methods for executing actions with privileges enabled, for
@@ -166,7 +169,11 @@ public final class Security {
 	AccessController.doPrivileged(new PrivilegedAction() {
 	    public Object run() { return new ClassContextAccess(); }
 	});
-
+    /**
+     * ExecutionContextManager 
+     * null unless found, never returned.
+     */
+     private static volatile ExecutionContextManager ecm = null;
     /**
      * Non-instantiable.
      */
@@ -550,6 +557,67 @@ public final class Security {
     }
     
     /**
+     * <p>
+     * An optimised permission check, using the following strategies to
+     * reduce the cost of checking Permission:
+     * </p>
+     * <OL>
+     * <LI>Multiple permission checks may be performed together, the current
+     * execution AccessControlContext is only obtained once per invocation
+     * of this method, since identified as an expensive native code call. 
+     * The returned AccessControlContext is used for all Permission checks.
+     * <LI>The result from an AccessControlContext.checkPermission(Permission)
+     * call is cached for that AccessControlContext, so checkPermission will 
+     * only be called once, if it succeeds. If a RevokeableDynamicPolicy 
+     * is installed, checkPermission may be called again, on a following
+     * invocation if a Permission revocation has cleared it's result from the cache.
+     * <LI>Permission's are only stored in the cache based on equality, implies
+     * is not called in the cache.  This doesn't widen the scope of the cache
+     * since the tradeoff is that some implies calls are expensive.
+     * <LI>The cache used is backed by a SoftReference based ConcurrentHashMap
+     * </OL>
+     * <p>
+     * This is designed to be used in combination with Permission checks
+     * for Permission's that are Revokeable, but may also be used for optimising other
+     * non revokeable Permission checks.  A non revokeable Permission is a 
+     * Permission that guards a reference or prevents object creation, but
+     * does not prevent use of a privelege or object, if it reference escapes.
+     * see the RevokeableDynamicPolicy for details.
+     * </p><p>
+     * By lessening the cost of Permission calls it is intended that the performance
+     * impact of security checking be reduced sufficiently to provide a 
+     * wider scope for revokeable Permissions.
+     * </p><p>
+     * If a RevokeableDynamicPolicy is not installed, this method will call
+     * AccessController.checkPermission, without any optimisations.
+     * </p>
+     * @param permissions
+     * @throws java.lang.NullPointerException
+     * @throws java.security.AccessControlException
+     * @see RevokeableDynamicPolicy
+     * @see ExecutionContextManager
+     * @see AccessController
+     * @see AccessControlContext
+     */
+    public static void checkPermission(Collection<Permission> permissions)
+	    throws NullPointerException, AccessControlException
+    {
+	if (ecm == null){
+	    Policy policy = getPolicy();
+	    if (!(policy instanceof RevokeableDynamicPolicy)) {
+		Iterator<Permission> perms = permissions.iterator();
+		while (perms.hasNext()){
+		    Permission perm = perms.next();
+		    AccessController.checkPermission(perm);
+		}
+		return;
+	    }
+	    ecm = ((RevokeableDynamicPolicy) policy).getExecutionContextManager();
+	}
+	ecm.checkPermission(permissions);
+    }
+    
+    /**
      * Creates privileged context that contains the protection domain of the
      * given caller class (if non-null) and uses the domain combiner of the
      * specified context.  This method assumes it is called from within a

Modified: incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java?rev=986600&r1=986599&r2=986600&view=diff
==============================================================================
--- incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java (original)
+++ incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java Wed Aug 18 09:00:01 2010
@@ -20,6 +20,7 @@ package org.apache.river.api.security;
 import java.security.AccessControlException;
 import java.security.AccessControlContext;
 import java.security.Permission;
+import java.util.Collection;
 
 /**
  * <p>
@@ -64,7 +65,7 @@ import java.security.Permission;
  */
 public interface ExecutionContextManager {
     
-    /**
+    /*
      * <p>
      * Marks the beginning of Management of the Execution context, of the
      * AccessControlContext and submits a reaper to intercept and clean up
@@ -91,7 +92,8 @@ public interface ExecutionContextManager
      * the current thread is not interrupted, rather the reaper is expected
      * to know what resources need to be closed.
      */
-    void begin(Reaper r);
+    // To Be removed
+    //void begin(Reaper r);
 
     /**
      * <p>
@@ -116,9 +118,8 @@ public interface ExecutionContextManager
      * </p><p>
      * ExecutionContextManager provides a more thorough form of protection.
      * </p><p>
-     * ExecutionContextManager should be used sparingly and only for repeated
-     * calls, if permission checking only happens occasionaly, use the
-     * AccessController or SecurityManager.
+     * ExecutionContextManager should be used for repeated
+     * calls, it caches the results from the AccessControlContext.
      * </p><p>
      * Clients using the ExecutionContextManager, should be careful
      * to release references to their Permission objects,
@@ -136,14 +137,14 @@ public interface ExecutionContextManager
      * has been called.
      * </p>
      * 
-     * @param p Permission to be checked, if result not already in cache.
+     * @param perms Permissions to be checked, if result not already in cache.
      * @throws java.security.AccessControlException
      * @throws java.lang.NullPointerException 
      */
-    public void checkPermission(Permission p) throws AccessControlException,
+    public void checkPermission(Collection<Permission> perms) throws AccessControlException,
 	    NullPointerException;
     
-    /**
+    /*
      * <p>
      * This method is to advise the ExecutionContextManager that the
      * current method or protected region has returned, it must
@@ -178,5 +179,6 @@ public interface ExecutionContextManager
      * This should not be confused with AccessController.doPrivileged blocks
      * </p>
      */
-    void end();
+    // To be removed
+    //void end();
 }
\ No newline at end of file

Modified: incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java?rev=986600&r1=986599&r2=986600&view=diff
==============================================================================
--- incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java (original)
+++ incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java Wed Aug 18 09:00:01 2010
@@ -23,6 +23,7 @@ import java.security.AccessControlExcept
 import java.security.AccessController;
 import java.security.Permission;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -47,16 +48,24 @@ import org.apache.river.imp.util.Concurr
  * Only a Single instance of ECM is required per policy, it is threadsafe.
  * Threads will wait until revoke is complete.
  * 
+ * Implementers Note:  It hasn't been determined if the cache should be
+ * Map<AccessControlContext,Set<Permission>> or the opposite as implemented,
+ * in any case the emphasis needs to be placed on the permission check
+ * since these are called the most.  There will be less exposure to hashCode
+ * and equals() implementation issues if the current cache structure is reversed.
+ * However the performance of AccessControlContext.hashCode needs to be 
+ * determined first.
+ * 
  * @author Peter Firmstone
  */
 class ECM implements ExecutionContextManager{
     
     private final ConcurrentMap<Permission,Set<AccessControlContext>> checkedCache;
-    private final ConcurrentMap<AccessControlContext, Set<Thread>> executionCache;
-    private final ConcurrentMap<Thread, Set<AccessControlContext>> threadAssociation;
-    private final ConcurrentMap<Thread, Reaper> association;
-    private final ExecutorService executor;
-    private final ReadWriteLock blockLock;
+    //private final ConcurrentMap<AccessControlContext, Set<Thread>> executionCache;
+    //private final ConcurrentMap<Thread, Set<AccessControlContext>> threadAssociation;
+    //private final ConcurrentMap<Thread, Reaper> association;
+    //private final ExecutorService executor;
+    private final ReadWriteLock revokeLock;
     private final Lock rl; // This lock is held briefly by callers of begin and end.
     private final Lock wl; // This lock is held by revocation.
     
@@ -69,21 +78,21 @@ class ECM implements ExecutionContextMan
 	 * strong reference from the thread, which is removed when end()
 	 * is executed.
 	 */ 
-	executionCache = new ConcurrentWeakIdentityMap<AccessControlContext, Set<Thread>>();
+	//executionCache = new ConcurrentWeakIdentityMap<AccessControlContext, Set<Thread>>();
 	/* Thread association is utilised to track a thread as it enters and
 	 * leaves the ExecutionContextManager try finally block.
 	 */ 
-	threadAssociation = new ConcurrentHashMap<Thread, Set<AccessControlContext>>();
+	//threadAssociation = new ConcurrentHashMap<Thread, Set<AccessControlContext>>();
 	/* The association is only made while threads are within the clients
 	 * try finally block.
 	 */ 
-	association = new ConcurrentHashMap<Thread, Reaper>();
+	//association = new ConcurrentHashMap<Thread, Reaper>();
 	// TODO: Analyse needs, enable client configuration of thread pool.
-	executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()+1);
+	//executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()+1);
 	/* This lock guards revocation */ 
-	blockLock = new ReentrantReadWriteLock();
-	rl = blockLock.readLock();
-	wl = blockLock.writeLock();
+	revokeLock = new ReentrantReadWriteLock();
+	rl = revokeLock.readLock();
+	wl = revokeLock.writeLock();
     }
     
     void revoke(Set<Permission> perms) throws InterruptedException, ExecutionException{
@@ -122,137 +131,144 @@ class ECM implements ExecutionContextMan
 		permClasses.add(itp.next().getClass());
 	    }
 	    // Remove Permission's and AccessControlContexts from the checked cache.
-	    Map<Permission, Set<AccessControlContext>> removed = 
-		    new HashMap<Permission, Set<AccessControlContext>>();
+//	    Map<Permission, Set<AccessControlContext>> removed = 
+//		    new HashMap<Permission, Set<AccessControlContext>>();
 	    Iterator<Permission> keysIt = checkedCache.keySet().iterator();
 	    while (keysIt.hasNext()){
 		Permission p = keysIt.next();
 		if (permClasses.contains(p.getClass())){
 		    Set<AccessControlContext> a = checkedCache.get(p);
 		    keysIt.remove();
-		    removed.put(p, a);
-		}		
-	    }
-	    // Match the AccessControlContexts with the execution cache;
-	    Set<AccessControlContext> exCache = executionCache.keySet();
-	    // Get the AccessControlContext's in the execution cache that fail.
-	    Set<AccessControlContext> accFails = new HashSet<AccessControlContext>();
-	    Iterator<Permission> retests = removed.keySet().iterator();
-	    while (retests.hasNext()){
-		Permission p = retests.next();
-		Set<AccessControlContext> rechecks = removed.get(p);
-		Iterator<AccessControlContext> recheck = rechecks.iterator();
-		while (recheck.hasNext()){
-		    AccessControlContext a = recheck.next();
-		    if (accFails.contains(a)) continue;
-		    // This really narrows down the checks.
-		    if (exCache.contains(a)){
-			try { 
-			    a.checkPermission(p);
-			} catch (AccessControlException e){
-			    accFails.add(a);
-			}
-		    }
-		}
-	    }
-	    // Identify the threads and prepare reapers.
-	    Set<Runnable> reapers = new HashSet<Runnable>();
-	    Iterator<AccessControlContext> failedAcc = accFails.iterator();
-	    while (failedAcc.hasNext()){
-		AccessControlContext fail = failedAcc.next();
-		Set<Thread> threads = executionCache.get(fail);
-		Iterator<Thread> i = threads.iterator();
-		while (i.hasNext()) {
-		    Thread t = i.next();
-		    Reaper r = association.get(t);
-		    if ( r == null ) continue;
-		    r.put(t);
-		    reapers.add(r);
+//		    removed.put(p, a);
 		}		
 	    }
-	    /* Process the reapers, this requires a thread pool, but we don't
-	     * want to return until all reapers have completed.
-	     */
-	    Iterator<Runnable> reaper = reapers.iterator();
-	    List<Future> results = new ArrayList<Future>(reapers.size());
-	    while ( reaper.hasNext()) {
-		results.add(executor.submit(reaper.next()));
-	    }
-	    Iterator<Future> result = results.iterator();
-	    while (result.hasNext()){
-		// Waits for result.
-		result.next().get();
-	    }
+//	    // Match the AccessControlContexts with the execution cache;
+//	    Set<AccessControlContext> exCache = executionCache.keySet();
+//	    // Get the AccessControlContext's in the execution cache that fail.
+//	    Set<AccessControlContext> accFails = new HashSet<AccessControlContext>();
+//	    Iterator<Permission> retests = removed.keySet().iterator();
+//	    while (retests.hasNext()){
+//		Permission p = retests.next();
+//		Set<AccessControlContext> rechecks = removed.get(p);
+//		Iterator<AccessControlContext> recheck = rechecks.iterator();
+//		while (recheck.hasNext()){
+//		    AccessControlContext a = recheck.next();
+//		    if (accFails.contains(a)) continue;
+//		    // This really narrows down the checks.
+//		    if (exCache.contains(a)){
+//			try { 
+//			    a.checkPermission(p);
+//			} catch (AccessControlException e){
+//			    accFails.add(a);
+//			}
+//		    }
+//		}
+//	    }
+//	    // Identify the threads and prepare reapers.
+//	    Set<Runnable> reapers = new HashSet<Runnable>();
+//	    Iterator<AccessControlContext> failedAcc = accFails.iterator();
+//	    while (failedAcc.hasNext()){
+//		AccessControlContext fail = failedAcc.next();
+//		Set<Thread> threads = executionCache.get(fail);
+//		Iterator<Thread> i = threads.iterator();
+//		while (i.hasNext()) {
+//		    Thread t = i.next();
+//		    Reaper r = association.get(t);
+//		    if ( r == null ) continue;
+//		    r.put(t);
+//		    reapers.add(r);
+//		}		
+//	    }
+//	    /* Process the reapers, this requires a thread pool, but we don't
+//	     * want to return until all reapers have completed.
+//	     */
+//	    Iterator<Runnable> reaper = reapers.iterator();
+//	    List<Future> results = new ArrayList<Future>(reapers.size());
+//	    while ( reaper.hasNext()) {
+//		results.add(executor.submit(reaper.next()));
+//	    }
+//	    Iterator<Future> result = results.iterator();
+//	    while (result.hasNext()){
+//		// Waits for result.
+//		result.next().get();
+//	    }
 	    /* We're done, go home & rest */
 	} finally {
 	    wl.unlock();
 	}
     }
 
-    public void begin(Reaper r) {
-	Thread currentThread = Thread.currentThread();
-	if ( r == null ) return;
-	association.put(currentThread, r);	
-    }
+//    public void begin(Reaper r) {
+//	Thread currentThread = Thread.currentThread();
+//	if ( r == null ) return;
+//	association.put(currentThread, r);	
+//    }
 
-    public void checkPermission(Permission p) throws AccessControlException {
-	if (p == null ) throw new NullPointerException("Permission null");
-	Thread currentThread = Thread.currentThread();
+    public void checkPermission(Collection<Permission> perms) throws AccessControlException {
+	if (perms == null ) throw new NullPointerException("Permission Collection null");
+	if (perms.isEmpty()) return; // Should we do this or is it a bug?
+	//Thread currentThread = Thread.currentThread();
 	AccessControlContext executionContext = AccessController.getContext();
+	HashSet<Permission> permissions = new HashSet<Permission>(perms.size());
+	permissions.addAll(perms);
 	rl.lock();
 	try {
-	    // execution cache, fast for repeated calls.
-	    Set<Thread> exCacheThreadSet = executionCache.get(executionContext);
-	    if ( exCacheThreadSet == null ){
-		exCacheThreadSet = Collections.synchronizedSet(new HashSet<Thread>());
-		Set<Thread> existed = executionCache.putIfAbsent(executionContext, exCacheThreadSet);
-		if (existed != null){
-		    exCacheThreadSet = existed;
-		}
-	    }
-	    exCacheThreadSet.add(currentThread);// end execution cache.
-	    // thread association, fast for repeated calls.
-	    Set<AccessControlContext> thAssocSet = threadAssociation.get(currentThread);
-	    if ( thAssocSet == null ){
-		thAssocSet = Collections.synchronizedSet(new HashSet<AccessControlContext>());
-		Set<AccessControlContext> existed = threadAssociation.putIfAbsent(currentThread, thAssocSet);
-		if (existed != null){
-		    thAssocSet = existed;
-		}
-	    }
-	    thAssocSet.add(executionContext); // end thread association.
+//	    // execution cache, fast for repeated calls.
+//	    Set<Thread> exCacheThreadSet = executionCache.get(executionContext);
+//	    if ( exCacheThreadSet == null ){
+//		exCacheThreadSet = Collections.synchronizedSet(new HashSet<Thread>());
+//		Set<Thread> existed = executionCache.putIfAbsent(executionContext, exCacheThreadSet);
+//		if (existed != null){
+//		    exCacheThreadSet = existed;
+//		}
+//	    }
+//	    exCacheThreadSet.add(currentThread);// end execution cache.
+//	    // thread association, fast for repeated calls.
+//	    Set<AccessControlContext> thAssocSet = threadAssociation.get(currentThread);
+//	    if ( thAssocSet == null ){
+//		thAssocSet = Collections.synchronizedSet(new HashSet<AccessControlContext>());
+//		Set<AccessControlContext> existed = threadAssociation.putIfAbsent(currentThread, thAssocSet);
+//		if (existed != null){
+//		    thAssocSet = existed;
+//		}
+//	    }
+//	    thAssocSet.add(executionContext); // end thread association.
 	    // checkedCache - the permission check, fast for repeated calls.
-	    Set<AccessControlContext> checked = checkedCache.get(p);
-	    if (checked == null ){
-		checked = Collections.synchronizedSet(new HashSet<AccessControlContext>());
-		Set<AccessControlContext> existed = checkedCache.putIfAbsent(p, checked);
-		if (existed != null){
-		    checked = existed;
+	    Iterator<Permission> permiter = permissions.iterator();
+	    while (permiter.hasNext()){
+		Permission p = permiter.next();
+		Set<AccessControlContext> checked = checkedCache.get(p);
+		if (checked == null ){
+		    checked = Collections.synchronizedSet(new HashSet<AccessControlContext>());
+		    Set<AccessControlContext> existed = checkedCache.putIfAbsent(p, checked);
+		    if (existed != null){
+			checked = existed;
+		    }
 		}
+		if ( checked.contains(executionContext)) continue; // it's passed before.
+		executionContext.checkPermission(p); // Throws AccessControlException
+		// If we get here cache the AccessControlContext.
+		checked.add(executionContext); // end checkedCache.
 	    }
-	    if ( checked.contains(executionContext)) return; // it's passed before.
-	    executionContext.checkPermission(p); // Throws AccessControlException
-	    // If we get here cache the AccessControlContext.
-	    checked.add(executionContext); // end checkedCache.	    
 	} finally {
 	    rl.unlock();
 	}
     }
 
-    public void end() {
-	// Removal from execution cache.
-	Thread t = Thread.currentThread();
-	rl.lock();
-	try {
-	    association.remove(t);
-	    Set<AccessControlContext> accSet = threadAssociation.remove(t);
-	    Iterator<AccessControlContext> it = accSet.iterator();
-	    while (it.hasNext()){
-		AccessControlContext acc = it.next();
-		executionCache.get(acc).remove(t);
-	    }
-	}finally {
-	    rl.unlock();
-	}	
-    }
+//    public void end() {
+//	// Removal from execution cache.
+//	Thread t = Thread.currentThread();
+//	rl.lock();
+//	try {
+//	    association.remove(t);
+//	    Set<AccessControlContext> accSet = threadAssociation.remove(t);
+//	    Iterator<AccessControlContext> it = accSet.iterator();
+//	    while (it.hasNext()){
+//		AccessControlContext acc = it.next();
+//		executionCache.get(acc).remove(t);
+//	    }
+//	}finally {
+//	    rl.unlock();
+//	}	
+//    }
 }

Added: incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java?rev=986600&view=auto
==============================================================================
--- incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java (added)
+++ incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java Wed Aug 18 09:00:01 2010
@@ -0,0 +1,214 @@
+/*
+ * 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.river.imp.util;
+
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Identity-based sofly referenced hash map, safe for concurrent threads.
+ * 
+ * Based on an underlying ConcurrentHashMap, it doesn't accept null keys.
+ *
+ *
+ * @param K - key
+ * @param V - value
+ * @author Peter Firmstone.
+ *
+ * @since 2.3
+ */
+public class ConcurrentSoftIdentityMap<K, V> implements ConcurrentMap<K, V> {
+    // ConcurrentHashMap must be protected from null values;
+    private final ConcurrentHashMap<Key, V> map = new ConcurrentHashMap<Key, V>();
+    private final ReferenceQueue queue = new ReferenceQueue();
+
+    /**
+     * Associates value with given key, returning value previously associated
+     * with key, or null if none.
+     * @param key - Key
+     * @param value - Value
+     * @return previous value or null
+     */
+    public V put(K key, V value) {
+	processQueue();
+        if (key == null){return null;}
+	return map.put(Key.create(key, queue), value);
+    }
+
+    /**
+     * Returns value associated with given key, or null if none.
+     */
+    public V get(Object key) {
+	processQueue();
+        if (key == null) { return null;}
+	return map.get(Key.create(key, null));
+    }
+
+    /**
+     * Removes association for given key, returning value previously associated
+     * with key, or null if none.
+     */
+    public V remove(Object key) {
+	processQueue();
+        if (key == null) {return null;}
+	return map.remove(Key.create(key, null));
+    }
+
+    /**
+     * Returns collection containing all values currently held in this map.
+     */
+    public Collection<V> values() {
+	processQueue();
+	return map.values();
+    }
+
+    /**
+     * Removes all associations from this map.
+     */
+    public void clear() {
+	processQueue();
+	map.clear();
+    }
+
+    private void processQueue() {
+	Key k;
+	while ((k = (Key) queue.poll()) != null) {
+	    map.remove(k);
+	}
+    }
+
+    private static class Key<T> extends SoftReference<T> {
+	private final int hash;
+
+        @SuppressWarnings("unchecked")
+	static Key create(Object k, ReferenceQueue q) {
+            //if (k == null) {return null;} // Perhaps this is incorrect
+	    if (q == null) {return new Key(k);}
+	    return new Key(k, q);	  
+	}
+
+	private Key(T k) {
+	    super(k);
+	    hash = System.identityHashCode(k);
+	}
+
+	private Key(T k, ReferenceQueue<? super T> q) {
+	    super(k, q);
+	    hash = System.identityHashCode(k);
+	}
+
+        @Override
+	public boolean equals(Object o) {
+	    if (this == o) {
+		return true;
+	    } else if (!(o instanceof Key)) {
+		return false;
+	    }
+	    Object k1 = get(), k2 = ((Key) o).get();
+	    return (k1 != null && k2 != null && k1 == k2);
+	}
+
+        @Override
+	public int hashCode() {
+	    return hash;
+	}
+    }
+
+    public int size() {
+        processQueue();
+        return map.size();
+    }
+
+    public boolean isEmpty() {
+        processQueue();
+        return map.isEmpty();
+    }
+
+    @SuppressWarnings("unchecked")
+    public boolean containsKey(Object key) {
+        processQueue();
+        if (key == null) {return false;}
+        return map.containsKey(new Key(key));
+    }
+
+    public boolean containsValue(Object value) {
+        processQueue();
+        if (value == null) {return false;}
+        return map.containsValue(value);
+    }
+    
+    /**
+     * Unsupported method
+     * @param m
+     */
+    public void putAll(Map<? extends K, ? extends V> m) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+    
+    @SuppressWarnings("unchecked")
+    public Set<K> keySet() {
+        processQueue();
+        Enumeration<Key> keys = map.keys(); //Defensive copy by ConcurrentHashMap
+        Set<K> keySet = new HashSet<K>();
+        while (keys.hasMoreElements()){
+            keySet.add( (K) keys.nextElement().get());
+        }
+        return keySet;
+    }
+    
+    /**
+     * Unsupported method
+     * @return
+     */
+    public Set<Map.Entry<K, V>> entrySet() {
+        throw new UnsupportedOperationException("Not supported yet, ever?");
+    }
+
+    @SuppressWarnings("unchecked")
+    public V putIfAbsent(K key, V value) {
+        processQueue();  //may be a slight delay before atomic putIfAbsent
+        return map.putIfAbsent(new Key(key), value);       
+    }
+
+    @SuppressWarnings("unchecked")
+    public boolean remove(Object key, Object value) {
+        return map.remove(new Key(key), value);
+    }
+
+    @SuppressWarnings("unchecked")
+    public boolean replace(K key, V oldValue, V newValue) {
+        processQueue();
+        return map.replace(new Key(key), oldValue, newValue);
+    }
+
+    @SuppressWarnings("unchecked")
+    public V replace(K key, V value) {
+        processQueue();
+        return map.replace(new Key(key), value);
+    }
+}

Propchange: incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java
------------------------------------------------------------------------------
    svn:eol-style = native