1 package com
.google
.appengine
.api
.datastore
;
3 import static com
.google
.common
.base
.Preconditions
.checkArgument
;
5 import com
.google
.appengine
.api
.memcache
.AsyncMemcacheService
;
6 import com
.google
.appengine
.api
.memcache
.ErrorHandlers
;
7 import com
.google
.appengine
.api
.memcache
.Expiration
;
8 import com
.google
.appengine
.api
.memcache
.InvalidValueException
;
9 import com
.google
.appengine
.api
.memcache
.MemcacheService
.CasValues
;
10 import com
.google
.appengine
.api
.memcache
.MemcacheService
.IdentifiableValue
;
11 import com
.google
.appengine
.api
.memcache
.MemcacheService
.SetPolicy
;
12 import com
.google
.appengine
.api
.memcache
.MemcacheServiceFactory
;
13 import com
.google
.appengine
.api
.utils
.FutureWrapper
;
14 import com
.google
.common
.collect
.ImmutableMap
;
16 import java
.util
.Collection
;
19 import java
.util
.concurrent
.Future
;
20 import java
.util
.logging
.Level
;
21 import java
.util
.logging
.Logger
;
24 * A memcache backed cache used for entity caching.
26 * If an RPC error or de-serialization error happens during a caching operation the error is
27 * logged and the cache operation completes with no results (an empty set or map).
30 class MemcacheEntityCache
{
32 static Logger logger
= Logger
.getLogger(MemcacheEntityCache
.class.getName());
33 private static final Level LOG_LEVEL
= Level
.INFO
;
35 static final String NAMESPACE
= "__ah-datastore-l2-v1__";
37 private Double rpcDeadlineSecs
;
38 private Expiration defaultValueExpirationTime
;
39 private final AsyncMemcacheService memcacheService
;
41 private MemcacheEntityCache() {
42 memcacheService
= MemcacheServiceFactory
.getAsyncMemcacheService(NAMESPACE
);
43 memcacheService
.setErrorHandler(ErrorHandlers
.getConsistentLogAndContinue(Level
.INFO
));
46 MemcacheEntityCache(AsyncMemcacheService memcacheService
) {
47 this.memcacheService
= memcacheService
;
51 * Updates the memcache operation RPC deadline to use for memcache RPCs.
53 * @param rpcDeadlineSecs the memcache operation RPC deadline in seconds.
54 * @return {@code this} (for chaining).
55 * @throws IllegalArgumentException if the {@code rpcDeadlineSecs} is not greater than zero.
57 public MemcacheEntityCache
rpcDeadlineSecs(double rpcDeadlineSecs
) {
58 checkArgument(rpcDeadlineSecs
> 0, "The rpcDeadlineSecs argument must be greater than 0");
59 this.rpcDeadlineSecs
= rpcDeadlineSecs
;
64 * @return the memcache operation RPC deadline in seconds or {@code null} if no deadline has
67 public Double
getRpcDeadlineSecs() {
68 return rpcDeadlineSecs
;
72 * Updates the expiration time for values stored in the cache without an explicit expiration time.
73 * By default, values without an explicit expiration time are cached for as long of a duration as
76 * @param defaultValueExpirationTime the default value expiration time. Or {@code null} to specify
77 * an indefinite default expiration time.
78 * @return {@code this} (for chaining).
80 public MemcacheEntityCache
defaultValueExpirationTime( Expiration defaultValueExpirationTime
) {
81 this.defaultValueExpirationTime
= defaultValueExpirationTime
;
86 * @return the default value expiration time for values stored in the cache without
87 * an explicit expiration time or {@code null} if no default expiration time has been
90 public Expiration
getDefaultValueExpirationTime() {
91 return defaultValueExpirationTime
;
95 * Fetches previously-stored cache values. The values returned can be used in subsequent calls
96 * to {@link #putIfUntouched}.
98 * @param keys a collection of keys for which values should be retrieved
99 * @return a mapping from keys to values of any entries found. If a requested
100 * key is not found in the cache the key will not be in the returned Map.
101 * @throws IllegalArgumentException if any element of {@code keys} is {@code null}
102 * or not {@link Serializable}.
104 public <K
> Future
<Map
<K
, IdentifiableValue
>> getIdentifiable(Collection
<K
> keys
) {
105 return new GetIdentifiablesFutureWrapper
<K
>(
106 memcacheService
.getIdentifiables(keys
));
110 * Sets the value in the cache for each key/value pair in the {@code values} map if the update
111 * satisfies the cache update {@code policy}.
113 * The expiration time for each value stored is specified by
114 * {@link #defaultValueExpirationTime(Expiration)}.
116 * @param values the key/value mappings to add to the cache
117 * @param policy what to do if the cache entry is or is not already present
118 * @return the set of keys for which entries were created. Keys in {@code values} may not be
119 * in the returned set because of the {@code policy} regarding pre-existing entries.
120 * @throws IllegalArgumentException if any of the keys or values are {@code null} or
121 * or are not {@link Serializable}.
123 public <K
> Future
<Set
<K
>> put(Map
<K
, ?
> values
, SetPolicy policy
) {
124 return put(values
, policy
, defaultValueExpirationTime
);
128 * Sets the value in the cache for each key/value pair in the {@code values} map if the update
129 * satisfies the cache update {@code policy}.
131 * @param values the key/value mappings to add to the cache
132 * @param policy what to do if the cache entry is or is not already present
133 * @param valueExpirationTime the memcache value expiration time to use for each value updated
134 * in the cache. This overrides the default value expiration time. If {@code null} the
135 * expiration time is indefinite.
136 * @return the set of keys for which entries were created. Keys in {@code values} may not be
137 * in the returned set because of the {@code policy} regarding pre-existing entries.
138 * @throws IllegalArgumentException if any of the keys or values are {@code null} or
139 * or are not {@link Serializable}.
141 public <K
> Future
<Set
<K
>> put(Map
<K
, ?
> values
, SetPolicy policy
, Expiration valueExpirationTime
) {
142 return memcacheService
.putAll(values
, valueExpirationTime
, policy
);
146 * Atomically stores the new value of each {@link CasValues} object in the {@code values} map if
147 * no other value has been stored in the cache since the {@code CasValues} object's old value
148 * was retrieved from {@link #getIdentifiable}. If another value in the cache for {@code key}
149 * has been stored, or if the cache entry has been evicted then nothing is stored by this call.
151 * The expiration time for each {@CasValues} object with a {@code null} expiration time is
152 * specified by {@link #defaultValueExpirationTime(Expiration)}.
154 * @param values the key/values mappings to compare and swap.
155 * @return the set of keys for which the new value was stored.
156 * @throws IllegalArgumentException If any of the keys or newValues are {@code null} or
157 * or are not {@link Serializable}. Also throws IllegalArgumentException if {@code values} has
160 public <K
> Future
<Set
<K
>> putIfUntouched(Map
<K
, CasValues
> values
) {
161 return putIfUntouched(values
, defaultValueExpirationTime
);
165 * Atomically stores the new value of each {@link CasValues} object in the {@code values} map if
166 * no other value has been stored in the cache since the {@code CasValues} object's old value
167 * was retrieved from {@link #getIdentifiable}. If another value in the cache for {@code key}
168 * has been stored, or if the cache entry has been evicted then nothing is stored by this call.
171 * @param values the key/values mappings to compare and swap.
172 * @param valueExpirationTime the expiration time to use for all {@code values} with a
173 * {@code null} {@link Expiration expiration} value in the {@code CasValues} object.
174 * This overrides the default value expiration time. If this is {@code null} and the
175 * {@code CasValues} object's expiration time is {@code null} then an indefinite expiration
176 * time will be specified for that {@code CasValue} object.
177 * @return the set of keys for which the new value was stored.
178 * @throws IllegalArgumentException If any of the keys or newValues are {@code null} or
179 * or are not {@link Serializable}. Also throws IllegalArgumentException if {@code values} has
182 public <K
> Future
<Set
<K
>> putIfUntouched(Map
<K
, CasValues
> values
, Expiration valueExpirationTime
) {
183 return memcacheService
.putIfUntouched(values
, valueExpirationTime
);
187 * This class logs and absorbs de-serialization errors encountered when invoking
188 * {@link AsyncMemcacheService#getIdentifiables(Collection)}.
190 private static final class GetIdentifiablesFutureWrapper
<K
>
191 extends FutureWrapper
<Map
<K
, IdentifiableValue
>, Map
<K
, IdentifiableValue
>> {
193 private GetIdentifiablesFutureWrapper(Future
<Map
<K
, IdentifiableValue
>> parent
) {
198 protected Map
<K
, IdentifiableValue
> wrap(Map
<K
, IdentifiableValue
> wrapped
) throws Exception
{
203 protected Map
<K
, IdentifiableValue
> absorbParentException(Throwable cause
) throws Throwable
{
204 if (cause
instanceof InvalidValueException
) {
205 logger
.log(LOG_LEVEL
, "Deserialization error in memcache entity cache", cause
);
206 return ImmutableMap
.of();
213 protected Throwable
convertException(Throwable cause
) {
219 * Contains static creation methods for {@link MemcacheEntityCache} instances.
221 public static final class Builder
{
224 * Creates a {@link MemcacheEntityCache} instance with the specified memcache RPC deadline to
225 * use for memcache RPCs.
227 * @param rpcDeadlineSecs the memcache operation RPC deadline in seconds.
228 * @return the newly created {@code MemcacheEntityCache} instance.
229 * @throws IllegalArgumentException if the {@code rpcDeadlineSecs} is not greater than zero.
231 public static MemcacheEntityCache
withRpcDeadlineSecs(double rpcDeadlineSecs
) {
232 return new MemcacheEntityCache().rpcDeadlineSecs(rpcDeadlineSecs
);
236 * Creates a {@link MemcacheEntityCache} instance with the specified expiration time for values
237 * stored in the cache without an explicit expiration time. By default, values without an
238 * explicit expiration time are cached for as long of a duration as possible.
240 * @param defaultValueExpirationTime the default value expiration time. Or {@code null} to
241 * specify an indefinite default expiration time.
242 * @return the newly created {@code MemcacheEntityCache} instance.
244 public static MemcacheEntityCache
withDefaultValueExpirationTime( Expiration defaultValueExpirationTime
) {
245 return new MemcacheEntityCache().defaultValueExpirationTime(defaultValueExpirationTime
);
249 * Creates a {@link MemcacheEntityCache} instance with the default configuration.
251 public static MemcacheEntityCache
withDefaults() {
252 return new MemcacheEntityCache();