Skip to content

Commit

Permalink
[GEOS-11525] GeoFence: refactor cache classes
Browse files Browse the repository at this point in the history
  • Loading branch information
etj authored and aaime committed Sep 19, 2024
1 parent c5fd49d commit 4c94d99
Show file tree
Hide file tree
Showing 10 changed files with 494 additions and 491 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.geoserver.geofence.cache.CachedRuleReader;
import org.geoserver.geofence.cache.CacheManager;
import org.geoserver.geofence.core.model.AdminRule;
import org.geoserver.geofence.core.model.GSInstance;
import org.geoserver.geofence.core.model.IPAddressRange;
Expand Down Expand Up @@ -52,8 +52,8 @@ public GeofenceIntegrationTestSupport(Supplier<GeoServerTestApplicationContext>
public @Override void after() {
deleteRules();
// this is odd, RuleService.delete() should invalidate
CachedRuleReader cacheRuleReader = GeoServerExtensions.bean(CachedRuleReader.class);
cacheRuleReader.invalidateAll();
CacheManager cacheManager = GeoServerExtensions.bean(CacheManager.class);
cacheManager.invalidateAll();
}

public long addAdminRule(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import org.geoserver.data.test.MockData;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.geofence.cache.CachedRuleReader;
import org.geoserver.geofence.cache.CacheManager;
import org.geoserver.geofence.config.GeoFenceConfigurationManager;
import org.geoserver.geofence.core.model.enums.GrantType;
import org.geoserver.geofence.services.RuleAdminService;
Expand Down Expand Up @@ -130,7 +130,7 @@ public void testOperationAccess() throws Exception {
MockData.BUILDINGS.getPrefix(),
MockData.BUILDINGS.getLocalPart(),
2);
GeoServerExtensions.bean(CachedRuleReader.class).invalidateAll();
GeoServerExtensions.bean(CacheManager.class).invalidateAll();

dom = runBuildingsRequest();
// dumpAllRules();
Expand Down Expand Up @@ -160,7 +160,7 @@ public void testChainedExecute() throws Exception {
MockData.BASIC_POLYGONS.getPrefix(),
MockData.BASIC_POLYGONS.getLocalPart(),
10);
GeoServerExtensions.bean(CachedRuleReader.class).invalidateAll();
GeoServerExtensions.bean(CacheManager.class).invalidateAll();

dom = runChainedRequest();
assertEquals("1", xp.evaluate("count(//wps:ProcessFailed)", dom));
Expand All @@ -176,7 +176,7 @@ public void testChainedExecute() throws Exception {
MockData.BASIC_POLYGONS.getPrefix(),
MockData.BASIC_POLYGONS.getLocalPart(),
20);
GeoServerExtensions.bean(CachedRuleReader.class).invalidateAll();
GeoServerExtensions.bean(CacheManager.class).invalidateAll();

dom = runChainedRequest();

Expand Down
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
+ "]";
}
}
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);
}
}
}
Loading

0 comments on commit 4c94d99

Please sign in to comment.