forked from geoserver/geoserver
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[GEOS-11525] GeoFence: refactor cache classes
- Loading branch information
Showing
10 changed files
with
494 additions
and
491 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 151 additions & 0 deletions
151
src/extension/geofence/geofence/src/main/java/org/geoserver/geofence/cache/CacheManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/* (c) 2018 Open Source Geospatial Foundation - all rights reserved | ||
* This code is licensed under the GPL 2.0 license, available at the root | ||
* application directory. | ||
*/ | ||
package org.geoserver.geofence.cache; | ||
|
||
import com.google.common.cache.CacheBuilder; | ||
import com.google.common.cache.LoadingCache; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import org.geoserver.geofence.cache.CachedRuleLoaders.NamePw; | ||
import org.geoserver.geofence.config.GeoFenceConfigurationManager; | ||
import org.geoserver.geofence.services.dto.AccessInfo; | ||
import org.geoserver.geofence.services.dto.AuthUser; | ||
import org.geoserver.geofence.services.dto.RuleFilter; | ||
import org.geotools.util.logging.Logging; | ||
|
||
/** | ||
* A centralized point of cache control for GeoFence auth calls | ||
* | ||
* <p>Cache eviction policy is LRU.<br> | ||
* Cache coherence is handled by entry timeout.<br> | ||
* | ||
* <p> | ||
* | ||
* @author Emanuele Tajariol (etj at geo-solutions.it) | ||
*/ | ||
public class CacheManager { | ||
|
||
static final Logger LOGGER = Logging.getLogger(CacheManager.class); | ||
|
||
private CachedRuleLoaders cachedRuleLoaders; | ||
|
||
private LoadingCache<RuleFilter, AccessInfo> ruleCache; | ||
private LoadingCache<NamePw, AuthUser> userCache; | ||
private LoadingCache<RuleFilter, AccessInfo> authCache; | ||
|
||
private final GeoFenceConfigurationManager configurationManager; | ||
|
||
/** Latest configuration used */ | ||
private CacheConfiguration cacheConfiguration = new CacheConfiguration(); | ||
|
||
public CacheManager( | ||
GeoFenceConfigurationManager configurationManager, | ||
CachedRuleLoaders cachedRuleLoaders) { | ||
|
||
this.configurationManager = configurationManager; | ||
this.cachedRuleLoaders = cachedRuleLoaders; | ||
|
||
// pull config when initializing | ||
init(); | ||
} | ||
|
||
/** | ||
* (Re)Init the cache, pulling the configuration from the configurationManager. | ||
* | ||
* <p>Please use {@link #getCacheInitParams() } to set the cache parameters before <code>init() | ||
* </code>ting the cache | ||
*/ | ||
public final void init() { | ||
|
||
cacheConfiguration = configurationManager.getCacheConfiguration(); | ||
|
||
ruleCache = getCacheBuilder().build(cachedRuleLoaders.new RuleLoader()); | ||
userCache = getCacheBuilder().build(cachedRuleLoaders.new UserLoader()); | ||
authCache = getCacheBuilder().build(cachedRuleLoaders.new AuthLoader()); | ||
} | ||
|
||
protected CacheBuilder<Object, Object> getCacheBuilder() { | ||
CacheBuilder<Object, Object> builder = | ||
CacheBuilder.newBuilder() | ||
.maximumSize(cacheConfiguration.getSize()) | ||
.refreshAfterWrite( | ||
cacheConfiguration.getRefreshMilliSec(), | ||
TimeUnit.MILLISECONDS) // reloadable after x time | ||
.expireAfterWrite( | ||
cacheConfiguration.getExpireMilliSec(), | ||
TimeUnit.MILLISECONDS) // throw away entries too old | ||
.recordStats(); | ||
// .expireAfterAccess(timeoutMillis, TimeUnit.MILLISECONDS) | ||
// .removalListener(MY_LISTENER) | ||
// this should only be used while testing | ||
if (cacheConfiguration.getCustomTicker() != null) { | ||
LOGGER.log( | ||
Level.SEVERE, | ||
"Setting a custom Ticker in the cache {0}", | ||
cacheConfiguration.getCustomTicker().getClass().getName()); | ||
builder.ticker(cacheConfiguration.getCustomTicker()); | ||
} | ||
return builder; | ||
} | ||
|
||
public void invalidateAll() { | ||
if (LOGGER.isLoggable(Level.WARNING)) | ||
LOGGER.log(Level.WARNING, "Forcing cache invalidation"); | ||
ruleCache.invalidateAll(); | ||
userCache.invalidateAll(); | ||
authCache.invalidateAll(); | ||
} | ||
|
||
private AtomicLong dumpCnt = new AtomicLong(0); | ||
|
||
public void logStats() { | ||
|
||
if (LOGGER.isLoggable(Level.INFO)) | ||
if (dumpCnt.incrementAndGet() % 10 == 0) { | ||
LOGGER.info("Rules :" + ruleCache.stats()); | ||
LOGGER.info("Users :" + userCache.stats()); | ||
LOGGER.info("Auth :" + authCache.stats()); | ||
LOGGER.fine("params :" + cacheConfiguration); | ||
} | ||
} | ||
|
||
// -------------------------------------------------------------------------- | ||
|
||
public CacheConfiguration getCacheInitParams() { | ||
return cacheConfiguration; | ||
} | ||
|
||
public LoadingCache<RuleFilter, AccessInfo> getRuleCache() { | ||
logStats(); | ||
return ruleCache; | ||
} | ||
|
||
public LoadingCache<NamePw, AuthUser> getUserCache() { | ||
logStats(); | ||
return userCache; | ||
} | ||
|
||
public LoadingCache<RuleFilter, AccessInfo> getAuthCache() { | ||
logStats(); | ||
return authCache; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return getClass().getSimpleName() | ||
+ "[" | ||
+ "Rule:" | ||
+ ruleCache.stats() | ||
+ " User:" | ||
+ userCache.stats() | ||
+ " Auth:" | ||
+ authCache.stats() | ||
+ " " | ||
+ cacheConfiguration | ||
+ "]"; | ||
} | ||
} |
188 changes: 188 additions & 0 deletions
188
...nsion/geofence/geofence/src/main/java/org/geoserver/geofence/cache/CachedRuleLoaders.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* (c) 2018 Open Source Geospatial Foundation - all rights reserved | ||
* This code is licensed under the GPL 2.0 license, available at the root | ||
* application directory. | ||
*/ | ||
package org.geoserver.geofence.cache; | ||
|
||
import com.google.common.cache.CacheLoader; | ||
import com.google.common.util.concurrent.Futures; | ||
import com.google.common.util.concurrent.ListenableFuture; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import org.geoserver.geofence.services.RuleReaderService; | ||
import org.geoserver.geofence.services.dto.AccessInfo; | ||
import org.geoserver.geofence.services.dto.AuthUser; | ||
import org.geoserver.geofence.services.dto.RuleFilter; | ||
import org.geotools.util.logging.Logging; | ||
|
||
/** | ||
* CacheLoaders for calls to RuleReadService | ||
* | ||
* @author Emanuele Tajariol (etj at geo-solutions.it) | ||
*/ | ||
public class CachedRuleLoaders { | ||
|
||
static final Logger LOGGER = Logging.getLogger(CachedRuleLoaders.class); | ||
|
||
private RuleReaderService realRuleReaderService; | ||
|
||
public CachedRuleLoaders(RuleReaderService realRuleReaderService) { | ||
this.realRuleReaderService = realRuleReaderService; | ||
} | ||
|
||
class RuleLoader extends CacheLoader<RuleFilter, AccessInfo> { | ||
|
||
@Override | ||
public AccessInfo load(RuleFilter filter) throws Exception { | ||
if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "Loading {0}", filter); | ||
// the service, when integrated, may modify the filter | ||
RuleFilter clone = filter.clone(); | ||
return realRuleReaderService.getAccessInfo(clone); | ||
} | ||
|
||
@Override | ||
public ListenableFuture<AccessInfo> reload(final RuleFilter filter, AccessInfo accessInfo) | ||
throws Exception { | ||
if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "Reloading {0}", filter); | ||
|
||
// the service, when integrated, may modify the filter | ||
RuleFilter clone = filter.clone(); | ||
|
||
// this is a sync implementation | ||
AccessInfo ret = realRuleReaderService.getAccessInfo(clone); | ||
return Futures.immediateFuture(ret); | ||
|
||
// next there is an asynchronous implementation, but in tests it seems to hang | ||
// return ListenableFutureTask.create(new Callable<AccessInfo>() { | ||
// @Override | ||
// public AccessInfo call() throws Exception { | ||
// if(LOGGER.isLoggable(Level.FINE)) | ||
// LOGGER.log(Level.FINE, "Asynch reloading {0}", filter); | ||
// return realRuleReaderService.getAccessInfo(filter); | ||
// } | ||
// }); | ||
} | ||
} | ||
|
||
class AuthLoader extends CacheLoader<RuleFilter, AccessInfo> { | ||
|
||
@Override | ||
public AccessInfo load(RuleFilter filter) throws Exception { | ||
if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "Loading {0}", filter); | ||
// the service, when integrated, may modify the filter | ||
RuleFilter clone = filter.clone(); | ||
return realRuleReaderService.getAdminAuthorization(clone); | ||
} | ||
|
||
@Override | ||
public ListenableFuture<AccessInfo> reload(final RuleFilter filter, AccessInfo accessInfo) | ||
throws Exception { | ||
if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "Reloading {0}", filter); | ||
|
||
// the service, when integrated, may modify the filter | ||
RuleFilter clone = filter.clone(); | ||
|
||
// this is a sync implementation | ||
AccessInfo ret = realRuleReaderService.getAdminAuthorization(clone); | ||
return Futures.immediateFuture(ret); | ||
} | ||
} | ||
|
||
class UserLoader extends CacheLoader<NamePw, AuthUser> { | ||
|
||
@Override | ||
public AuthUser load(NamePw user) throws NoAuthException { | ||
if (LOGGER.isLoggable(Level.FINE)) | ||
LOGGER.log(Level.FINE, "Loading user '" + user.getName() + "'"); | ||
AuthUser auth = realRuleReaderService.authorize(user.getName(), user.getPw()); | ||
if (auth == null) throw new NoAuthException("Can't auth user [" + user.getName() + "]"); | ||
return auth; | ||
} | ||
|
||
@Override | ||
public ListenableFuture<AuthUser> reload(final NamePw user, AuthUser authUser) | ||
throws NoAuthException { | ||
if (LOGGER.isLoggable(Level.FINE)) | ||
LOGGER.log(Level.FINE, "Reloading user '" + user.getName() + "'"); | ||
|
||
// this is a sync implementation | ||
AuthUser auth = realRuleReaderService.authorize(user.getName(), user.getPw()); | ||
if (auth == null) throw new NoAuthException("Can't auth user [" + user.getName() + "]"); | ||
return Futures.immediateFuture(auth); | ||
|
||
// todo: we may want a asynchronous implementation | ||
} | ||
} | ||
|
||
public static class NamePw { | ||
private String name; | ||
|
||
private String pw; | ||
|
||
public NamePw() {} | ||
|
||
public NamePw(String name, String pw) { | ||
this.name = name; | ||
this.pw = pw; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public void setName(String name) { | ||
this.name = name; | ||
} | ||
|
||
public String getPw() { | ||
return pw; | ||
} | ||
|
||
public void setPw(String pw) { | ||
this.pw = pw; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int hash = 7; | ||
hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0); | ||
hash = 89 * hash + (this.pw != null ? this.pw.hashCode() : 0); | ||
return hash; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (obj == null) { | ||
return false; | ||
} | ||
if (getClass() != obj.getClass()) { | ||
return false; | ||
} | ||
final NamePw other = (NamePw) obj; | ||
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { | ||
return false; | ||
} | ||
if ((this.pw == null) ? (other.pw != null) : !this.pw.equals(other.pw)) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
} | ||
|
||
static class NoAuthException extends Exception { | ||
|
||
public NoAuthException() {} | ||
|
||
public NoAuthException(String message) { | ||
super(message); | ||
} | ||
|
||
public NoAuthException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
|
||
public NoAuthException(Throwable cause) { | ||
super(cause); | ||
} | ||
} | ||
} |
Oops, something went wrong.