Fred,
Does this look more like it?
Multiple policy, multiple Permission checks, and an execution cache to
minimise re checking during revocation.
it's used like this:
ecm.begin(reaper);
try{
ecm.checkPermission(p);
ecm.checkPermission(q);
// Do something briefly
return something;
} finally {
ecm.end();
}
/*
* 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.security.policy.se;
import java.security.AccessControlContext;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.Permission;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.river.api.security.ExecutionContextManager;
import org.apache.river.api.security.Reaper;
import org.apache.river.imp.util.ConcurrentWeakIdentityMap;
/**
* Only a Single instance of ECM is required per policy, it is threadsafe.
* Threads will wait until revoke is complete.
*
* @author Peter Firmstone
*/
public 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 ReadWriteLock blockLock;
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.
ECM(){
checkedCache = new ConcurrentWeakIdentityMap<Permission,
Set<AccessControlContext>>();
executionCache = new ConcurrentHashMap<AccessControlContext,
Set<Thread>>();
threadAssociation = new ConcurrentHashMap<Thread,
Set<AccessControlContext>>();
association = new ConcurrentHashMap<Thread, Reaper>();
blockLock = new ReentrantReadWriteLock();
rl = blockLock.readLock();
wl = blockLock.writeLock();
}
Set<Reaper> revoke(Set<Permission> perms){
wl.lock();
try {
/* This is where we determine what needs to be revoked, we first
* get all the AccessControlContexts for the Permission class,
* these are removed from the checkedCache, then we narrow
* down the AccessControlContext's to only those in the
* execution cache, each AccessControlContext in the execution
cache
* has checkPermission(Permission) called for each revoked
* permission. Any that throw AccessControlException will be
* caught and have their Reaper run, for the Threads referenced
* from that AccessControlContext.
*
* The RevokeableDynamicPolicy will have updated the current
* Permission's after revocation, so the ProtectionDomain's in
* the AccessControlContext will now throw an
AccessControlException
* if the revocation applied to them.
*
* The wl lock will be released, any threads that were interrupted
* will exit through the finally block and remove themselves
* from the execution cache. Those that aren't may throw
* an exception and bubble up the stack, due to closing sockets
* etc.
*
* The execution cache is comprised of the following fields:
* executionCache
* threadAssociation
*/
// Identify Permission's with matching class files to those
revoked.
Set<Class> permClasses = new HashSet<Class>();
Iterator<Permission> itp = perms.iterator();
while (itp.hasNext()){
permClasses.add(itp.next().getClass());
}
// Remove Permission's and AccessControlContexts from the
checked cache.
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<Reaper> reapers = new HashSet<Reaper>();
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);
r.put(t);
reapers.add(r);
} }
return reapers;
} finally {
wl.unlock();
}
}
public void begin(Reaper r) {
Thread currentThread = Thread.currentThread();
association.put(currentThread, r); }
public void checkPermission(Permission p) throws
AccessControlException {
Thread currentThread = Thread.currentThread();
AccessControlContext executionContext = AccessController.getContext();
rl.lock();
try {
// execution cache.
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
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.
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)) 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() {
// Teardown.
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();
}
}
}