1 package com
.google
.appengine
.api
.labs
.datastore
.overlay
;
3 import static com
.google
.common
.base
.Preconditions
.checkArgument
;
4 import static com
.google
.common
.base
.Preconditions
.checkNotNull
;
6 import com
.google
.appengine
.api
.NamespaceManager
;
7 import com
.google
.appengine
.api
.datastore
.AsyncDatastoreService
;
8 import com
.google
.appengine
.api
.datastore
.BaseDatastoreService
;
9 import com
.google
.appengine
.api
.datastore
.DatastoreAttributes
;
10 import com
.google
.appengine
.api
.datastore
.Entity
;
11 import com
.google
.appengine
.api
.datastore
.Index
;
12 import com
.google
.appengine
.api
.datastore
.Key
;
13 import com
.google
.appengine
.api
.datastore
.KeyRange
;
14 import com
.google
.appengine
.api
.datastore
.PreparedQuery
;
15 import com
.google
.appengine
.api
.datastore
.Projection
;
16 import com
.google
.appengine
.api
.datastore
.Query
;
17 import com
.google
.appengine
.api
.datastore
.Query
.FilterPredicate
;
18 import com
.google
.appengine
.api
.datastore
.Query
.SortPredicate
;
19 import com
.google
.appengine
.api
.datastore
.Transaction
;
20 import com
.google
.appengine
.api
.datastore
.TransactionOptions
;
21 import com
.google
.common
.annotations
.VisibleForTesting
;
22 import com
.google
.common
.base
.Strings
;
23 import com
.google
.common
.collect
.ImmutableList
;
24 import com
.google
.common
.collect
.Lists
;
25 import com
.google
.common
.collect
.Maps
;
27 import java
.util
.Collection
;
28 import java
.util
.List
;
30 import java
.util
.concurrent
.Future
;
33 * A thin wrapper around {@link AsyncDatastoreService}, where all operations are redirected to use
34 * an alternate namespace.
36 * <p>For operations which use the ambient namespace from the {@link NamespaceManager} (such as
37 * {@code allocateIds}), {@code namespacePrefix} is applied to the current namespace
38 * before completing the operation.
40 * <p>For operations whose arguments contain namespaces in some form (such as keys),
41 * {@code namespacePrefix} is applied to the relevant namespaces before completing the
44 * <p>In all cases, there are no lasting effects on the ambient namespace; it is restored to the
45 * original value after the operation is done.
47 final class NamespacePinnedAsyncDatastoreServiceImpl
implements AsyncDatastoreService
{
48 private final AsyncDatastoreService datastore
;
49 private final String namespacePrefix
;
50 private final boolean shouldReserveIds
;
53 * Constructs a new {@link NamespacePinnedAsyncDatastoreServiceImpl}.
55 * @param datastore the underlying {@link AsyncDatastoreService}
56 * @param namespacePrefix the prefix to apply to all namespaces
57 * @param shouldReserveIds a flag that indicates whether calling {@code put()} should explicitly
58 * reserve (not allocate) the numeric IDs used in any complete keys before storing them
60 NamespacePinnedAsyncDatastoreServiceImpl(AsyncDatastoreService datastore
, String namespacePrefix
,
61 boolean shouldReserveIds
) {
62 this.datastore
= checkNotNull(datastore
);
63 this.namespacePrefix
= checkNotNull(namespacePrefix
);
64 this.shouldReserveIds
= shouldReserveIds
;
68 public Future
<Entity
> get(Key key
) {
70 return getImpl(datastore
, key
);
74 public Future
<Entity
> get( Transaction txn
, Key key
) {
76 return getImpl(getTxnAsyncDatastore(txn
), key
);
79 private Future
<Entity
> getImpl(AsyncDatastoreService datastore
, Key key
) {
80 checkNotNull(datastore
);
82 return new RethrowingFutureWrapper
<Entity
, Entity
>(
83 datastore
.get(getAlternateNamespaceKey(key
))) {
85 protected Entity
wrap(Entity entity
) throws Exception
{
86 return getOriginalNamespaceEntity(entity
);
92 public Future
<Map
<Key
, Entity
>> get(Iterable
<Key
> keys
) {
94 return getImpl(datastore
, keys
);
98 public Future
<Map
<Key
, Entity
>> get( Transaction txn
, Iterable
<Key
> keys
) {
100 return getImpl(getTxnAsyncDatastore(txn
), keys
);
103 private Future
<Map
<Key
, Entity
>> getImpl(AsyncDatastoreService datastore
, Iterable
<Key
> keys
) {
104 checkNotNull(datastore
);
106 return new RethrowingFutureWrapper
<Map
<Key
, Entity
>, Map
<Key
, Entity
>>(
107 datastore
.get(getAlternateNamespaceKeys(keys
))) {
109 protected Map
<Key
, Entity
> wrap(Map
<Key
, Entity
> entityMap
) throws Exception
{
110 return getOriginalNamespaceEntityMap(entityMap
);
116 public Future
<Key
> put(Entity entity
) {
117 checkNotNull(entity
);
118 return putImpl(datastore
, entity
);
122 public Future
<Key
> put( Transaction txn
, Entity entity
) {
123 checkNotNull(entity
);
124 return putImpl(getTxnAsyncDatastore(txn
), entity
);
127 private Future
<Key
> putImpl(final AsyncDatastoreService datastore
, Entity entity
) {
128 checkNotNull(datastore
);
129 checkNotNull(entity
);
130 final Entity alternateEntity
= getAlternateNamespaceEntity(entity
);
131 return new RethrowingFutureWrapper
<Key
, Key
>(datastore
.put(alternateEntity
)) {
133 protected Key
wrap(Key key
) throws Exception
{
134 return getOriginalNamespaceKey(key
);
140 public Future
<List
<Key
>> put(Iterable
<Entity
> entities
) {
141 checkNotNull(entities
);
142 return putImpl(datastore
, entities
);
146 public Future
<List
<Key
>> put( Transaction txn
, Iterable
<Entity
> entities
) {
147 checkNotNull(entities
);
148 return putImpl(getTxnAsyncDatastore(txn
), entities
);
151 private Future
<List
<Key
>> putImpl(final AsyncDatastoreService datastore
,
152 Iterable
<Entity
> entities
) {
153 checkNotNull(datastore
);
154 checkNotNull(entities
);
155 final List
<Entity
> alternateEntities
= getAlternateNamespaceEntities(entities
);
156 return new RethrowingFutureWrapper
<List
<Key
>, List
<Key
>>(datastore
.put(alternateEntities
)) {
158 protected List
<Key
> wrap(List
<Key
> keys
) throws Exception
{
159 return getOriginalNamespaceKeys(keys
);
165 public Future
<Void
> delete(Key
... keys
) {
167 return delete(ImmutableList
.copyOf(keys
));
171 public Future
<Void
> delete( Transaction txn
, Key
... keys
) {
173 return delete(txn
, ImmutableList
.copyOf(keys
));
177 public Future
<Void
> delete(Iterable
<Key
> keys
) {
179 return datastore
.delete(getAlternateNamespaceKeys(keys
));
183 public Future
<Void
> delete( Transaction txn
, Iterable
<Key
> keys
) {
185 return datastore
.delete(txn
, getAlternateNamespaceKeys(keys
));
189 public Future
<Transaction
> beginTransaction() {
190 return datastore
.beginTransaction();
194 public Future
<Transaction
> beginTransaction(TransactionOptions options
) {
195 checkNotNull(options
);
196 return datastore
.beginTransaction(options
);
200 public Future
<KeyRange
> allocateIds(String kind
, long num
) {
202 String currentNamespace
= NamespaceManager
.get();
204 NamespaceManager
.set(getAlternateNamespace(currentNamespace
));
205 return new RethrowingFutureWrapper
<KeyRange
, KeyRange
>(datastore
.allocateIds(kind
, num
)) {
207 protected KeyRange
wrap(KeyRange range
) throws Exception
{
208 return getOriginalNamespaceKeyRange(range
);
212 NamespaceManager
.set(currentNamespace
);
217 public Future
<KeyRange
> allocateIds(Key parent
, String kind
, long num
) {
218 checkNotNull(parent
);
220 String currentNamespace
= NamespaceManager
.get();
222 NamespaceManager
.set(getAlternateNamespace(parent
.getNamespace()));
223 return new RethrowingFutureWrapper
<KeyRange
, KeyRange
>(
224 datastore
.allocateIds(getAlternateNamespaceKey(parent
), kind
, num
)) {
226 protected KeyRange
wrap(KeyRange range
) throws Exception
{
227 return getOriginalNamespaceKeyRange(range
);
231 NamespaceManager
.set(currentNamespace
);
236 public Future
<DatastoreAttributes
> getDatastoreAttributes() {
237 return datastore
.getDatastoreAttributes();
241 public Future
<Map
<Index
, Index
.IndexState
>> getIndexes() {
242 return datastore
.getIndexes();
246 public Collection
<Transaction
> getActiveTransactions() {
247 return datastore
.getActiveTransactions();
251 public Transaction
getCurrentTransaction() {
252 return datastore
.getCurrentTransaction();
256 public Transaction
getCurrentTransaction(Transaction returnedIfNoTxn
) {
257 return datastore
.getCurrentTransaction(returnedIfNoTxn
);
261 public PreparedQuery
prepare(Query query
) {
263 return prepareImpl(datastore
, query
);
267 public PreparedQuery
prepare(Transaction txn
, Query query
) {
269 return prepareImpl(getTxnAsyncDatastore(txn
), query
);
272 private PreparedQuery
prepareImpl(BaseDatastoreService datastore
, Query query
) {
273 checkNotNull(datastore
);
275 Query alternateQuery
= getAlternateNamespaceQuery(query
);
276 return new NamespacePinnedPreparedQueryImpl(this, datastore
.prepare(alternateQuery
));
280 * Returns {@code namespace} combined with the namespace prefix for this instance.
283 private String
getAlternateNamespace(String namespace
) {
284 return namespacePrefix
+ Strings
.nullToEmpty(namespace
);
288 * Returns {@code namespace} with the prefix removed.
291 private String
getOriginalNamespace(String namespace
) {
292 checkNotNull(namespace
);
293 checkArgument(namespace
.startsWith(namespacePrefix
),
294 "%s must start with %s", namespace
, namespacePrefix
);
295 return namespace
.substring(namespacePrefix
.length());
299 * Creates a copy of {@code entities}, where each entity is replaced with a copy whose key is in
300 * the alternate namespace.
302 private List
<Entity
> getAlternateNamespaceEntities(Iterable
<Entity
> entities
) {
303 checkNotNull(entities
);
304 List
<Entity
> newEntities
= Lists
.newArrayList();
305 for (Entity entity
: entities
) {
306 newEntities
.add(getAlternateNamespaceEntity(entity
));
312 * Creates a copy of {@code entity}, where the key is in the alternate namespace.
314 private Entity
getAlternateNamespaceEntity(Entity entity
) {
315 checkNotNull(entity
);
316 return cloneEntityWithNewKey(entity
, getAlternateNamespaceKey(entity
.getKey()));
320 * Creates a copy of {@code entity}, where the key is in the original namespace.
322 Entity
getOriginalNamespaceEntity(Entity entity
) {
323 checkNotNull(entity
);
324 return cloneEntityWithNewKey(entity
, getOriginalNamespaceKey(entity
.getKey()));
328 * Creates a copy of {@code entity}, where the key is replaced with {@code newKey}.
330 private Entity
cloneEntityWithNewKey(Entity entity
, Key newKey
) {
331 Entity newEntity
= new Entity(newKey
);
332 newEntity
.setPropertiesFrom(entity
);
337 * Creates a copy of {@code entityMap}, where all the keys are replaced with the equivalent keys
338 * in the original namespace.
340 private Map
<Key
, Entity
> getOriginalNamespaceEntityMap(Map
<Key
, Entity
> entityMap
) {
341 checkNotNull(entityMap
);
342 Map
<Key
, Entity
> newMap
= Maps
.newHashMapWithExpectedSize(entityMap
.size());
343 for (Map
.Entry
<Key
, Entity
> entry
: entityMap
.entrySet()) {
344 newMap
.put(getOriginalNamespaceKey(entry
.getKey()),
345 getOriginalNamespaceEntity(entry
.getValue()));
351 * Creates a copy of {@code key}, but in the alternate namespace.
353 private Key
getAlternateNamespaceKey(Key key
) {
355 String currentNamespace
= NamespaceManager
.get();
357 NamespaceManager
.set(getAlternateNamespace(key
.getNamespace()));
358 return cloneKey(key
);
360 NamespaceManager
.set(currentNamespace
);
365 * Creates a copy of {@code key} in the original namespace. {@code key} must be in the alternate
366 * namespace to begin with.
368 private Key
getOriginalNamespaceKey(Key key
) {
370 String currentNamespace
= NamespaceManager
.get();
372 NamespaceManager
.set(getOriginalNamespace(key
.getNamespace()));
373 return cloneKey(key
);
375 NamespaceManager
.set(currentNamespace
);
380 * Creates a copy of {@code keys}, where each key is replaced with a copy that is in the alternate
383 private List
<Key
> getAlternateNamespaceKeys(Iterable
<Key
> keys
) {
385 List
<Key
> newKeys
= Lists
.newArrayList();
386 for (Key key
: keys
) {
387 newKeys
.add(getAlternateNamespaceKey(key
));
393 * Creates a copy of {@code keys}, where each key is replaced with a copy that is in the original
394 * namespace. The elements of {@code keys} must be in the alternate namespace to begin with.
396 private List
<Key
> getOriginalNamespaceKeys(Iterable
<Key
> keys
) {
398 List
<Key
> newKeys
= Lists
.newArrayList();
399 for (Key key
: keys
) {
400 newKeys
.add(getOriginalNamespaceKey(key
));
406 * Creates a copy of {@code query}, where each component key is replaced with the equivalent key
407 * in the alternate namespace.
409 private Query
getAlternateNamespaceQuery(Query query
) {
411 Query alternateQuery
;
412 String currentNamespace
= NamespaceManager
.get();
414 NamespaceManager
.set(getAlternateNamespace(query
.getNamespace()));
415 alternateQuery
= new Query(query
.getKind());
417 NamespaceManager
.set(currentNamespace
);
420 Key ancestor
= query
.getAncestor();
421 if (ancestor
!= null) {
422 alternateQuery
.setAncestor(getAlternateNamespaceKey(ancestor
));
424 for (SortPredicate sp
: query
.getSortPredicates()) {
425 alternateQuery
.addSort(sp
.getPropertyName(), sp
.getDirection());
427 alternateQuery
.setFilter(query
.getFilter());
428 for (FilterPredicate fp
: query
.getFilterPredicates()) {
429 alternateQuery
.addFilter(fp
.getPropertyName(), fp
.getOperator(), fp
.getValue());
431 if (query
.isKeysOnly()) {
432 alternateQuery
.setKeysOnly();
434 for (Projection p
: query
.getProjections()) {
435 alternateQuery
.addProjection(p
);
437 alternateQuery
.setDistinct(query
.getDistinct());
439 return alternateQuery
;
442 * Creates a copy of {@code keyRange}, where the returned keys are in the alternate namespace.
446 * Creates a copy of {@code keyRange}, where the returned keys are in the original namespace,
447 * rather than the alternate namespace.
449 private KeyRange
getOriginalNamespaceKeyRange(KeyRange keyRange
) {
450 Key alternateParent
= keyRange
.getStart().getParent();
451 Key originalParent
= alternateParent
== null ?
null : getOriginalNamespaceKey(alternateParent
);
452 String currentNamespace
= NamespaceManager
.get();
454 NamespaceManager
.set(getOriginalNamespace(keyRange
.getStart().getNamespace()));
455 return new KeyRange(originalParent
, keyRange
.getStart().getKind(),
456 keyRange
.getStart().getId(), keyRange
.getEnd().getId());
458 NamespaceManager
.set(currentNamespace
);
463 * Gets a version of the Datastore that is linked to {@code txn}.
465 private AsyncDatastoreService
getTxnAsyncDatastore( Transaction txn
) {
466 return new TransactionLinkedAsyncDatastoreServiceImpl(datastore
, txn
);
470 * Creates a copy of {@code key}, where the copy (and each of its components) takes its namespace
471 * from the {@link NamespaceManager}.
473 private static Key
cloneKey(Key key
) {
475 Key parentKey
= key
.getParent();
476 String name
= key
.getName();
477 long id
= key
.getId();
478 if (parentKey
== null) {
480 return new Entity(key
.getKind(), name
).getKey();
481 } else if (id
!= 0L) {
482 return new Entity(key
.getKind(), key
.getId()).getKey();
484 return new Entity(key
.getKind()).getKey();
488 return new Entity(key
.getKind(), name
, cloneKey(parentKey
)).getKey();
489 } else if (id
!= 0L) {
490 return new Entity(key
.getKind(), key
.getId(), cloneKey(parentKey
)).getKey();
492 return new Entity(key
.getKind(), cloneKey(parentKey
)).getKey();