Skip to content

Commit 378893b

Browse files
committed
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.
1 parent 19cf4a2 commit 378893b

File tree

6 files changed

+188
-86
lines changed

6 files changed

+188
-86
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package restx.security;
2+
3+
import com.google.common.base.Function;
4+
import com.google.common.base.Optional;
5+
import restx.security.RestxSession.Definition.Entry;
6+
7+
/**
8+
* Date: 20/1/15
9+
* Time: 20:56
10+
*/
11+
public class DefaultSessionDefinitionEntry<T> implements Entry<T> {
12+
private final String sessionDefKey;
13+
private final Function<String, Optional<? extends T>> function;
14+
15+
public DefaultSessionDefinitionEntry(Class<T> clazz, String sessionDefKey, Function<String, Optional<? extends T>> function) {
16+
this.sessionDefKey = sessionDefKey;
17+
this.function = function;
18+
}
19+
20+
@Override
21+
public String getKey() {
22+
return sessionDefKey;
23+
}
24+
25+
@Override
26+
public Optional<? extends T> getValueForId(String valueId) {
27+
return function.apply(valueId);
28+
}
29+
}

restx-core/src/main/java/restx/security/GuavaCacheSessionDefinitionEntry.java

Lines changed: 0 additions & 60 deletions
This file was deleted.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package restx.security;
2+
3+
import com.google.common.base.Optional;
4+
import com.google.common.cache.CacheBuilder;
5+
import com.google.common.cache.CacheLoader;
6+
import com.google.common.cache.LoadingCache;
7+
import restx.security.RestxSession.Definition.CachedEntry;
8+
import restx.security.RestxSession.Definition.Entry;
9+
import restx.security.RestxSession.Definition.EntryCacheManager;
10+
11+
import java.util.concurrent.ExecutionException;
12+
13+
/**
14+
* A restx session entry cache manager based on guava cache.
15+
*
16+
* You can override the cache settings by overriding the getCacheBuilder() method.
17+
*
18+
* Note that Guava Cache is not distributed, so be very careful with cache invalidation
19+
* when using this cache.
20+
*
21+
* This is the default EntryCacheManager, see SecurityModule which provides one.
22+
*/
23+
public class GuavaEntryCacheManager implements EntryCacheManager {
24+
@Override
25+
public <T> CachedEntry<T> getCachedEntry(Entry<T> entry) {
26+
return new GuavaCacheSessionDefinitionEntry<T>(entry.getKey(), getLoadingCacheFor(entry));
27+
}
28+
29+
protected <T> LoadingCache<String, T> getLoadingCacheFor(final Entry<T> entry) {
30+
return getCacheBuilder(entry).build(getCacheLoaderFor(entry));
31+
}
32+
33+
protected <T> CacheLoader<String, T> getCacheLoaderFor(final Entry<T> entry) {
34+
return new CacheLoader<String, T>() {
35+
@Override
36+
public T load(String key) throws Exception {
37+
return entry.getValueForId(key).orNull();
38+
}
39+
};
40+
}
41+
42+
protected <T> CacheBuilder<Object, Object> getCacheBuilder(Entry<T> entry) {
43+
return CacheBuilder.newBuilder().maximumSize(1000);
44+
}
45+
46+
/**
47+
* A session definition entry implementation using Guava Cache.
48+
*/
49+
public static class GuavaCacheSessionDefinitionEntry<T> implements CachedEntry<T> {
50+
private final LoadingCache<String, T> loadingCache;
51+
private final String key;
52+
53+
public GuavaCacheSessionDefinitionEntry(String key, LoadingCache<String, T> loadingCache) {
54+
this.key = key;
55+
this.loadingCache = loadingCache;
56+
}
57+
58+
@Override
59+
public String getKey() {
60+
return key;
61+
}
62+
63+
@Override
64+
public Optional<T> getValueForId(String valueId) {
65+
try {
66+
return Optional.fromNullable(loadingCache.get(valueId));
67+
} catch (CacheLoader.InvalidCacheLoadException e) {
68+
// this exception is raised when cache loader returns null, which may happen if the object behind the key
69+
// is deleted. Therefore we just return an absent value
70+
return Optional.absent();
71+
} catch (ExecutionException e) {
72+
throw new RuntimeException(
73+
"impossible to load object from cache using valueid " + valueId + " for " + key + ": " + e.getMessage(), e);
74+
}
75+
}
76+
77+
@Override
78+
public void invalidateCacheFor(String valueId) {
79+
loadingCache.invalidate(valueId);
80+
}
81+
82+
@Override
83+
public void invalidateCache() {
84+
loadingCache.invalidateAll();
85+
}
86+
}
87+
}

restx-core/src/main/java/restx/security/RestxSession.java

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
* With such a restx session, when you call a #get(User.class, "USER"), the session will first check its
4242
* valueIdsByKeys map to find the corresponding valueId ("johndoe@acme.com"). Then it will check the cache for
4343
* this valueId, and in case of cache miss will use the provided cache loader which will load the user from db.
44+
*
45+
* If you want to define your own session keys, you should define a Definition.Entry component for it allowing
46+
* to load values based on their ids. You don't have to take care of caching in the Entry, caching is performed
47+
* by EntryCacheManager.
4448
*/
4549
public class RestxSession {
4650
@Component
@@ -49,8 +53,7 @@ public static class Definition {
4953
* A session definition entry is responsible for loading session values by session value id, for a
5054
* particular definition key.
5155
*
52-
* It most of the time rely on a Cache to perform that efficiently, and therefore also offers
53-
* minimalistic cache related APIs to allow to invalidate it.
56+
* They don't implement caching themselves, see CachedEntry for entries supporting caching.
5457
*
5558
* @param <T> the type of values this Entry handles.
5659
*/
@@ -61,6 +64,34 @@ public static interface Entry<T> {
6164
*/
6265
String getKey();
6366

67+
/**
68+
* Gives the value corresponding to the given valueId.
69+
*
70+
* @param valueId the id of the value to get.
71+
*
72+
* @return the value, or absent if not found.
73+
*/
74+
Optional<? extends T> getValueForId(String valueId);
75+
}
76+
77+
/**
78+
* A cached version of session definition entry.
79+
*
80+
* This does not derive from Entry, because its its getting method does not have the same semantic as the
81+
* original one: here it returns a value which may not be the freshest one, while an Entry should always
82+
* return current one.
83+
*
84+
* CachedEntry instances are usually created from a Entry instance using a EntryCacheManager.
85+
*
86+
* @param <T> the type of values this CachedEntry handles.
87+
*/
88+
public static interface CachedEntry<T> {
89+
/**
90+
* Returns the definition key that this entry handles.
91+
* @return the definition key that this entry handles.
92+
*/
93+
String getKey();
94+
6495
/**
6596
* Gives the value corresponding to the given valueId.
6697
* This value may come from a cache, so its freshness depends on configuration and implementation.
@@ -69,7 +100,7 @@ public static interface Entry<T> {
69100
*
70101
* @return the value, or absent if not found.
71102
*/
72-
Optional<T> getValueForId(String valueId);
103+
Optional<? extends T> getValueForId(String valueId);
73104

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

89-
private final ImmutableMap<String, Entry<?>> entries;
120+
/**
121+
* A cache manager for session definition entry.
122+
*
123+
* It transforms Entry into CachedEntry
124+
*/
125+
public static interface EntryCacheManager {
126+
<T> CachedEntry<T> getCachedEntry(Entry<T> entry);
127+
}
128+
129+
private final ImmutableMap<String, CachedEntry<?>> entries;
90130

91131
@SuppressWarnings("unchecked") // can't use Iterable<Entry<?> as parameter in injectable constructor ATM
92-
public Definition(Iterable<Entry> entries) {
93-
ImmutableMap.Builder<String, Entry<?>> builder = ImmutableMap.builder();
132+
public Definition(EntryCacheManager cacheManager, Iterable<Entry> entries) {
133+
ImmutableMap.Builder<String, CachedEntry<?>> builder = ImmutableMap.builder();
94134
for (Entry<?> entry : entries) {
95-
builder.put(entry.getKey(), entry);
135+
builder.put(entry.getKey(), cacheManager.getCachedEntry(entry));
96136
}
97137
this.entries = builder.build();
98138
}
@@ -106,12 +146,12 @@ public boolean hasEntryForKey(String key) {
106146
}
107147

108148
@SuppressWarnings("unchecked")
109-
public <T> Optional<Entry<T>> getEntry(String key) {
110-
return Optional.fromNullable((Entry<T>) entries.get(key));
149+
public <T> Optional<CachedEntry<T>> getEntry(String key) {
150+
return Optional.fromNullable((CachedEntry<T>) entries.get(key));
111151
}
112152

113153
public void invalidateAllCaches() {
114-
for (Entry<?> entry : entries.values()) {
154+
for (CachedEntry<?> entry : entries.values()) {
115155
entry.invalidateCache();
116156
}
117157
}

restx-core/src/main/java/restx/security/SecurityModule.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22

33
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
5-
65
import restx.common.RestxConfig;
7-
import restx.config.Settings;
86
import restx.config.SettingsKey;
97
import restx.factory.AutoStartable;
108
import restx.factory.Module;
119
import restx.factory.Provides;
10+
import restx.security.RestxSession.Definition.EntryCacheManager;
1211

1312
import javax.inject.Named;
1413

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

2323
public static interface SecuritySettings {
2424
@SettingsKey(key = "restx.sessions.stats.limit", defaultValue = "100",
@@ -60,4 +60,9 @@ public int sessionsLimit() {
6060
}
6161
};
6262
}
63+
64+
@Provides @Named(ENTRY_CACHE_MANAGER)
65+
public EntryCacheManager guavaCacheManager() {
66+
return new GuavaEntryCacheManager();
67+
}
6368
}
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package restx.security;
22

3-
import com.google.common.cache.CacheLoader;
3+
import com.google.common.base.Function;
4+
import com.google.common.base.Optional;
45
import restx.factory.Module;
56
import restx.factory.Provides;
67

@@ -13,24 +14,24 @@ public class BasicSecurityModule {
1314
@Provides
1415
@Named(RestxPrincipal.SESSION_DEF_KEY)
1516
public RestxSession.Definition.Entry principalSessionEntry(final BasicPrincipalAuthenticator authenticator) {
16-
return new GuavaCacheSessionDefinitionEntry<>(RestxPrincipal.class, RestxPrincipal.SESSION_DEF_KEY,
17-
new CacheLoader<String, RestxPrincipal>() {
18-
@Override
19-
public RestxPrincipal load(String key) throws Exception {
20-
return authenticator.findByName(key).orNull();
21-
}
17+
return new DefaultSessionDefinitionEntry<>(RestxPrincipal.class, RestxPrincipal.SESSION_DEF_KEY,
18+
new Function<String, Optional<? extends RestxPrincipal>>() {
19+
@Override
20+
public Optional<? extends RestxPrincipal> apply(String key) {
21+
return authenticator.findByName(key);
22+
}
2223
});
2324
}
2425

2526
@Provides
2627
@Named(Session.SESSION_DEF_KEY)
2728
public RestxSession.Definition.Entry sessionKeySessionEntry() {
28-
return new GuavaCacheSessionDefinitionEntry<>(String.class, Session.SESSION_DEF_KEY,
29-
new CacheLoader<String, String>() {
30-
@Override
31-
public String load(String key) throws Exception {
32-
return key;
33-
}
34-
});
29+
return new DefaultSessionDefinitionEntry<>(String.class, Session.SESSION_DEF_KEY,
30+
new Function<String, Optional<? extends String>>() {
31+
@Override
32+
public Optional<? extends String> apply(String key) {
33+
return Optional.fromNullable(key);
34+
}
35+
});
3536
}
3637
}

0 commit comments

Comments
 (0)