Skip to content

Commit

Permalink
Allow to plug other cache mechanisms for RestxSession #141 [breaking]
Browse files Browse the repository at this point in the history
[breaking] RestxSession.Definition.Entry is now an interface.
Use DefaultSessionDefinitionEntry to get similar behavior if you were instanciating
RestxSession.Definition.Entry before.

[breaking] RestxSession#cleanUpCaches has been removed
It was misleading, was not invalidating the cache and very implementation dependent.
2 invalidate methods have been added to replace it.

---
this commit review the responsibilities introduced in previous commit, to make the cache
pluggable without having to rewrite all Definition.Entry. Now the Entry knows how to load,
and the EntryCacheManager knows how to cache, so there is a better separation of responsibilities.
  • Loading branch information
xhanin committed Jan 20, 2015
1 parent 19cf4a2 commit 378893b
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package restx.security;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import restx.security.RestxSession.Definition.Entry;

/**
* Date: 20/1/15
* Time: 20:56
*/
public class DefaultSessionDefinitionEntry<T> implements Entry<T> {
private final String sessionDefKey;
private final Function<String, Optional<? extends T>> function;

public DefaultSessionDefinitionEntry(Class<T> clazz, String sessionDefKey, Function<String, Optional<? extends T>> function) {
this.sessionDefKey = sessionDefKey;
this.function = function;
}

@Override
public String getKey() {
return sessionDefKey;
}

@Override
public Optional<? extends T> getValueForId(String valueId) {
return function.apply(valueId);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package restx.security;

import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import restx.security.RestxSession.Definition.CachedEntry;
import restx.security.RestxSession.Definition.Entry;
import restx.security.RestxSession.Definition.EntryCacheManager;

import java.util.concurrent.ExecutionException;

/**
* A restx session entry cache manager based on guava cache.
*
* You can override the cache settings by overriding the getCacheBuilder() method.
*
* Note that Guava Cache is not distributed, so be very careful with cache invalidation
* when using this cache.
*
* This is the default EntryCacheManager, see SecurityModule which provides one.
*/
public class GuavaEntryCacheManager implements EntryCacheManager {
@Override
public <T> CachedEntry<T> getCachedEntry(Entry<T> entry) {
return new GuavaCacheSessionDefinitionEntry<T>(entry.getKey(), getLoadingCacheFor(entry));
}

protected <T> LoadingCache<String, T> getLoadingCacheFor(final Entry<T> entry) {
return getCacheBuilder(entry).build(getCacheLoaderFor(entry));
}

protected <T> CacheLoader<String, T> getCacheLoaderFor(final Entry<T> entry) {
return new CacheLoader<String, T>() {
@Override
public T load(String key) throws Exception {
return entry.getValueForId(key).orNull();
}
};
}

protected <T> CacheBuilder<Object, Object> getCacheBuilder(Entry<T> entry) {
return CacheBuilder.newBuilder().maximumSize(1000);
}

/**
* A session definition entry implementation using Guava Cache.
*/
public static class GuavaCacheSessionDefinitionEntry<T> implements CachedEntry<T> {
private final LoadingCache<String, T> loadingCache;
private final String key;

public GuavaCacheSessionDefinitionEntry(String key, LoadingCache<String, T> loadingCache) {
this.key = key;
this.loadingCache = loadingCache;
}

@Override
public String getKey() {
return key;
}

@Override
public Optional<T> getValueForId(String valueId) {
try {
return Optional.fromNullable(loadingCache.get(valueId));
} catch (CacheLoader.InvalidCacheLoadException e) {
// this exception is raised when cache loader returns null, which may happen if the object behind the key
// is deleted. Therefore we just return an absent value
return Optional.absent();
} catch (ExecutionException e) {
throw new RuntimeException(
"impossible to load object from cache using valueid " + valueId + " for " + key + ": " + e.getMessage(), e);
}
}

@Override
public void invalidateCacheFor(String valueId) {
loadingCache.invalidate(valueId);
}

@Override
public void invalidateCache() {
loadingCache.invalidateAll();
}
}
}
60 changes: 50 additions & 10 deletions restx-core/src/main/java/restx/security/RestxSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
* With such a restx session, when you call a #get(User.class, "USER"), the session will first check its
* valueIdsByKeys map to find the corresponding valueId ("johndoe@acme.com"). Then it will check the cache for
* this valueId, and in case of cache miss will use the provided cache loader which will load the user from db.
*
* If you want to define your own session keys, you should define a Definition.Entry component for it allowing
* to load values based on their ids. You don't have to take care of caching in the Entry, caching is performed
* by EntryCacheManager.
*/
public class RestxSession {
@Component
Expand All @@ -49,8 +53,7 @@ public static class Definition {
* A session definition entry is responsible for loading session values by session value id, for a
* particular definition key.
*
* It most of the time rely on a Cache to perform that efficiently, and therefore also offers
* minimalistic cache related APIs to allow to invalidate it.
* They don't implement caching themselves, see CachedEntry for entries supporting caching.
*
* @param <T> the type of values this Entry handles.
*/
Expand All @@ -61,6 +64,34 @@ public static interface Entry<T> {
*/
String getKey();

/**
* Gives the value corresponding to the given valueId.
*
* @param valueId the id of the value to get.
*
* @return the value, or absent if not found.
*/
Optional<? extends T> getValueForId(String valueId);
}

/**
* A cached version of session definition entry.
*
* This does not derive from Entry, because its its getting method does not have the same semantic as the
* original one: here it returns a value which may not be the freshest one, while an Entry should always
* return current one.
*
* CachedEntry instances are usually created from a Entry instance using a EntryCacheManager.
*
* @param <T> the type of values this CachedEntry handles.
*/
public static interface CachedEntry<T> {
/**
* Returns the definition key that this entry handles.
* @return the definition key that this entry handles.
*/
String getKey();

/**
* Gives the value corresponding to the given valueId.
* This value may come from a cache, so its freshness depends on configuration and implementation.
Expand All @@ -69,7 +100,7 @@ public static interface Entry<T> {
*
* @return the value, or absent if not found.
*/
Optional<T> getValueForId(String valueId);
Optional<? extends T> getValueForId(String valueId);

/**
* Invalidates the cache for a single value id.
Expand All @@ -86,13 +117,22 @@ public static interface Entry<T> {
void invalidateCache();
}

private final ImmutableMap<String, Entry<?>> entries;
/**
* A cache manager for session definition entry.
*
* It transforms Entry into CachedEntry
*/
public static interface EntryCacheManager {
<T> CachedEntry<T> getCachedEntry(Entry<T> entry);
}

private final ImmutableMap<String, CachedEntry<?>> entries;

@SuppressWarnings("unchecked") // can't use Iterable<Entry<?> as parameter in injectable constructor ATM
public Definition(Iterable<Entry> entries) {
ImmutableMap.Builder<String, Entry<?>> builder = ImmutableMap.builder();
public Definition(EntryCacheManager cacheManager, Iterable<Entry> entries) {
ImmutableMap.Builder<String, CachedEntry<?>> builder = ImmutableMap.builder();
for (Entry<?> entry : entries) {
builder.put(entry.getKey(), entry);
builder.put(entry.getKey(), cacheManager.getCachedEntry(entry));
}
this.entries = builder.build();
}
Expand All @@ -106,12 +146,12 @@ public boolean hasEntryForKey(String key) {
}

@SuppressWarnings("unchecked")
public <T> Optional<Entry<T>> getEntry(String key) {
return Optional.fromNullable((Entry<T>) entries.get(key));
public <T> Optional<CachedEntry<T>> getEntry(String key) {
return Optional.fromNullable((CachedEntry<T>) entries.get(key));
}

public void invalidateAllCaches() {
for (Entry<?> entry : entries.values()) {
for (CachedEntry<?> entry : entries.values()) {
entry.invalidateCache();
}
}
Expand Down
9 changes: 7 additions & 2 deletions restx-core/src/main/java/restx/security/SecurityModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import restx.common.RestxConfig;
import restx.config.Settings;
import restx.config.SettingsKey;
import restx.factory.AutoStartable;
import restx.factory.Module;
import restx.factory.Provides;
import restx.security.RestxSession.Definition.EntryCacheManager;

import javax.inject.Named;

Expand All @@ -19,6 +18,7 @@
@Module(priority = 100)
public class SecurityModule {
private static final Logger logger = LoggerFactory.getLogger(SecurityModule.class);
public static final String ENTRY_CACHE_MANAGER = "EntryCacheManager";

public static interface SecuritySettings {
@SettingsKey(key = "restx.sessions.stats.limit", defaultValue = "100",
Expand Down Expand Up @@ -60,4 +60,9 @@ public int sessionsLimit() {
}
};
}

@Provides @Named(ENTRY_CACHE_MANAGER)
public EntryCacheManager guavaCacheManager() {
return new GuavaEntryCacheManager();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package restx.security;

import com.google.common.cache.CacheLoader;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import restx.factory.Module;
import restx.factory.Provides;

Expand All @@ -13,24 +14,24 @@ public class BasicSecurityModule {
@Provides
@Named(RestxPrincipal.SESSION_DEF_KEY)
public RestxSession.Definition.Entry principalSessionEntry(final BasicPrincipalAuthenticator authenticator) {
return new GuavaCacheSessionDefinitionEntry<>(RestxPrincipal.class, RestxPrincipal.SESSION_DEF_KEY,
new CacheLoader<String, RestxPrincipal>() {
@Override
public RestxPrincipal load(String key) throws Exception {
return authenticator.findByName(key).orNull();
}
return new DefaultSessionDefinitionEntry<>(RestxPrincipal.class, RestxPrincipal.SESSION_DEF_KEY,
new Function<String, Optional<? extends RestxPrincipal>>() {
@Override
public Optional<? extends RestxPrincipal> apply(String key) {
return authenticator.findByName(key);
}
});
}

@Provides
@Named(Session.SESSION_DEF_KEY)
public RestxSession.Definition.Entry sessionKeySessionEntry() {
return new GuavaCacheSessionDefinitionEntry<>(String.class, Session.SESSION_DEF_KEY,
new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return key;
}
});
return new DefaultSessionDefinitionEntry<>(String.class, Session.SESSION_DEF_KEY,
new Function<String, Optional<? extends String>>() {
@Override
public Optional<? extends String> apply(String key) {
return Optional.fromNullable(key);
}
});
}
}

0 comments on commit 378893b

Please sign in to comment.