Permalink
Browse files

Allow to plug other cache mechanisms for RestxSession #141 [breaking]

[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 378893bb8402b04cd6b8ec4db1423d2be8e33945
@@ -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);
+ }
+}
@@ -1,60 +0,0 @@
-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.Entry;
-
-import java.util.concurrent.ExecutionException;
-
-/**
- * A session definition entry implementation using Guava Cache.
- *
- * Note that Guava Cache is not distributed, so be very careful with cache invalidation
- * when using this cache.
- */
-public class GuavaCacheSessionDefinitionEntry<T> implements Entry<T> {
- private final LoadingCache<String, T> loadingCache;
- private final Class<T> clazz;
- private final String key;
-
- public GuavaCacheSessionDefinitionEntry(Class<T> clazz, String key, CacheLoader<String, T> loader) {
- this(clazz, key, CacheBuilder.newBuilder().maximumSize(1000).build(loader));
- }
-
- public GuavaCacheSessionDefinitionEntry(Class<T> clazz, String key, LoadingCache<String, T> loadingCache) {
- this.clazz = clazz;
- 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();
- }
-}
@@ -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();
+ }
+ }
+}
@@ -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
@@ -49,8 +53,7 @@
* 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.
*/
@@ -61,6 +64,34 @@
*/
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.
@@ -69,7 +100,7 @@
*
* @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.
@@ -86,13 +117,22 @@
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();
}
@@ -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();
}
}
@@ -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;
@@ -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",
@@ -60,4 +60,9 @@ public int sessionsLimit() {
}
};
}
+
+ @Provides @Named(ENTRY_CACHE_MANAGER)
+ public EntryCacheManager guavaCacheManager() {
+ return new GuavaEntryCacheManager();
+ }
}
@@ -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;
@@ -13,24 +14,24 @@
@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.