package org.dcache.auth;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.UncheckedExecutionException;
import javax.security.auth.Subject;
import java.io.PrintWriter;
import java.security.Principal;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.TimeoutCacheException;
import dmg.cells.nucleus.CellCommandListener;
import dmg.cells.nucleus.CellInfo;
import dmg.cells.nucleus.CellInfoProvider;
import org.dcache.util.Args;
/**
* Caching implementation of {@link LoginStrategy}.
*/
public class CachingLoginStrategy implements LoginStrategy, CellCommandListener, CellInfoProvider
{
private final LoginStrategy _inner;
private final LoadingCache<Principal,CheckedFuture<Principal, CacheException>> _forwardCache;
private final LoadingCache<Principal,CheckedFuture<Set<Principal>, CacheException>> _reverseCache;
private final LoadingCache<Subject, CheckedFuture<LoginReply, CacheException>> _loginCache;
private final long _time;
private final TimeUnit _unit;
private final int _size;
/**
* Create an instance of LoginStrategy
*
* @param inner {@link LoginStrategy} used for fetching data.
* @param size maximal size of cached entries per cache table
* @param timeout cache entry life time.
* @param unit the time unit of the timeout argument
*/
public CachingLoginStrategy(LoginStrategy inner, int size, long timeout, TimeUnit unit) {
_inner = inner;
_forwardCache = CacheBuilder.newBuilder()
.expireAfterWrite(timeout, unit)
.maximumSize(size)
.softValues()
.recordStats()
.build( new ForwardFetcher());
_reverseCache = CacheBuilder.newBuilder()
.expireAfterWrite(timeout, unit)
.maximumSize(size)
.softValues()
.recordStats()
.build( new ReverseFetcher());
_loginCache = CacheBuilder.newBuilder()
.expireAfterWrite(timeout, unit)
.maximumSize(size)
.softValues()
.recordStats()
.build( new LoginFetcher());
_time = timeout;
_unit = unit;
_size = size;
}
@Override
public LoginReply login(Subject subject) throws CacheException {
try {
return _loginCache.get(subject).checkedGet();
} catch (ExecutionException e) {
Throwables.propagateIfPossible(e.getCause(), CacheException.class);
throw new RuntimeException(e.getCause());
} catch (UncheckedExecutionException e) {
Throwables.throwIfUnchecked(e.getCause());
throw new RuntimeException(e.getCause());
}
}
@Override
public Principal map(Principal principal) throws CacheException {
try {
return _forwardCache.get(principal).checkedGet();
} catch (ExecutionException e) {
Throwables.propagateIfPossible(e.getCause(), CacheException.class);
throw new RuntimeException(e.getCause());
} catch (UncheckedExecutionException e) {
Throwables.throwIfUnchecked(e.getCause());
throw new RuntimeException(e.getCause());
}
}
@Override
public Set<Principal> reverseMap(Principal principal) throws CacheException {
try {
return _reverseCache.get(principal).checkedGet();
} catch (ExecutionException e) {
Throwables.propagateIfPossible(e.getCause(), CacheException.class);
throw new RuntimeException(e.getCause());
} catch (UncheckedExecutionException e) {
Throwables.throwIfUnchecked(e.getCause());
throw new RuntimeException(e.getCause());
}
}
private class ForwardFetcher extends CacheLoader<Principal, CheckedFuture<Principal, CacheException>> {
@Override
public CheckedFuture<Principal, CacheException> load(Principal f) throws TimeoutCacheException
{
try {
Principal p = _inner.map(f);
return Futures.immediateCheckedFuture(p);
} catch (TimeoutCacheException e) {
throw e;
} catch (CacheException e) {
return Futures.immediateFailedCheckedFuture(e);
}
}
}
private class ReverseFetcher extends CacheLoader<Principal, CheckedFuture<Set<Principal>, CacheException>> {
@Override
public CheckedFuture<Set<Principal>, CacheException> load(Principal f) throws TimeoutCacheException
{
try {
Set<Principal> s = _inner.reverseMap(f);
return Futures.immediateCheckedFuture(s);
} catch (TimeoutCacheException e) {
throw e;
} catch (CacheException e) {
return Futures.immediateFailedCheckedFuture(e);
}
}
}
private class LoginFetcher extends CacheLoader<Subject, CheckedFuture<LoginReply, CacheException>> {
@Override
public CheckedFuture<LoginReply, CacheException> load(Subject f) throws TimeoutCacheException
{
try {
LoginReply s = _inner.login(f);
return Futures.immediateCheckedFuture(s);
} catch (TimeoutCacheException e) {
throw e;
} catch (CacheException e) {
return Futures.immediateFailedCheckedFuture(e);
}
}
}
public static final String hh_login_clear_cache = " # clear cached result of login and identity mapping oprations";
public String ac_login_clear_cache(Args args) {
_forwardCache.invalidateAll();
_loginCache.invalidateAll();
_reverseCache.invalidateAll();
return "";
}
public static final String hh_login_dump_cache = " # dump cached result of login and identity mapping oprations";
public String ac_login_dump_cache(Args args) {
StringBuilder sb = new StringBuilder();
sb.append("Max Cache size: ").append(_size).append("\n");
sb.append("Max Cache time: ").append(_time).append(" ")
.append(_unit.name().toLowerCase()).append("\n");
sb.append("Login:\n");
for (Subject s : _loginCache.asMap().keySet()) {
try {
CheckedFuture<LoginReply, CacheException> out = _loginCache.getIfPresent(s);
if (out != null) {
sb.append(" ").append(s.getPrincipals()).append(" => ");
sb.append(out.checkedGet()).append('\n');
}
} catch (CacheException e) {
sb.append(e.toString()).append('\n');
}
}
sb.append("Map:\n");
for (Principal p : _forwardCache.asMap().keySet()) {
try {
CheckedFuture<Principal, CacheException> out = _forwardCache.getIfPresent(p);
if (out != null) {
sb.append(" ").append(p).append(" => ");
sb.append(out.checkedGet()).append('\n');
}
} catch (CacheException e) {
sb.append(e.toString()).append('\n');
}
}
sb.append("ReverseMap:\n");
for (Principal p : _reverseCache.asMap().keySet()) {
try {
CheckedFuture<Set<Principal>, CacheException> out = _reverseCache.getIfPresent(p);
if (out != null) {
sb.append(" ").append(p).append(" => ");
sb.append(out.checkedGet()).append('\n');
}
} catch (CacheException e) {
sb.append(e.toString()).append('\n');
}
}
return sb.toString();
}
@Override
public void getInfo(PrintWriter pw)
{
pw.append("gPlazma login cache: ").println(_loginCache.stats());
pw.append("gPlazma map cache: ").println(_forwardCache.stats());
pw.append("gPlazma reverse map cache: ").println(_reverseCache.stats());
}
@Override
public CellInfo getCellInfo(CellInfo info)
{
return info;
}
}