1 // Copyright 2010 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.api
.datastore
;
4 import static com
.google
.common
.base
.Preconditions
.checkNotNull
;
6 import com
.google
.appengine
.api
.datastore
.ReadPolicy
.Consistency
;
7 import com
.google
.apphosting
.api
.ApiProxy
;
8 import com
.google
.common
.base
.Preconditions
;
10 import java
.io
.ByteArrayInputStream
;
11 import java
.io
.InputStream
;
12 import java
.util
.Collection
;
15 * User-configurable properties of the datastore.
18 * The recommended way to instantiate a {@code DatastoreServiceConfig} object
19 * is to statically import {@link Builder}.* and invoke a static creation
20 * method followed by an instance mutator (if needed):
23 * import static com.google.appengine.api.datastore.DatastoreServiceConfig.Builder.*;
24 * import com.google.appengine.api.datastore.ReadPolicy.Consistency;
28 * // eventually consistent reads
29 * DatastoreServiceConfig config = withReadPolicy(new ReadPolicy(Consistency.EVENTUAL));
31 * // eventually consistent reads with a 5 second deadline
32 * DatastoreServiceConfig config =
33 * withReadPolicy(new ReadPolicy(Consistency.EVENTUAL)).deadline(5.0);
37 public final class DatastoreServiceConfig
{
40 * This is the name of a system property that determines how the Java SDK writes/reads empty lists
43 * Historically Datastore has not had the ability to represent an empty list in its persistent
44 * store. Different SDKs have made different decisions on what to write to Datastore when a client
45 * attempts to insert an empty list to the persistent store. The Java SDK has historically written
46 * both empty lists and null values as null values to the persistent store.
48 * With the release of this flag, Datastore can now represent empty lists within its persistent
49 * store. This means that Datastore SDKs can distinguish between empty lists and null values. This
50 * property controls whether this SDK writes an empty list as empty list or null to the persistent
53 * A note on queries: Null values can be indexed by Datastore which means they can interact with
54 * queries. For example queries that find all entities with null values or order by a property
55 * will include null values. Empty lists are not indexable by Datastore and so cannot appear in
58 * Thus, if this flag was unset (the old behavior) and an empty list was stored into the
59 * persistent store, it could appear in query results because it was really stored as a null
62 * If this flag is set (the new behavior) and an empty list is stored into the persistent store,
63 * it will not appear it query results because it is stored as an empty list.
66 * When this variable is set to anything other than "true" the system provides legacy behavior.
69 * <li>Null properties are written as null to Datastore
70 * <li>Empty collections are written as null to Datastore
71 * <li>A null is read as null from Datastore
72 * <li>An empty collection is read as null. <b>Note that a read modify write of an entity with an
73 * empty list will cause that list to be turned into a null value.</b>
77 * When this variable is set to "true":
79 * <li>Null properties are written as null to Datastore
80 * <li>Empty collections (#{@link Collection#isEmpty()}) are written as empty list to Datastore
81 * <li>A null is read as null from Datastore
82 * <li>An empty collection is read as null
83 * <li>When reading from Datastore an empty list is returned as an empty {@link Collection}.
87 * It is strongly recommended that this property be set to true in order to provide compatibility
88 * with other Datastore SDK's, as well as future versions of Datastore.
90 * To set the flag: <code>
91 * System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT,
92 * Boolean.TRUE.toString());
95 public static final String DATASTORE_EMPTY_LIST_SUPPORT
= "DATASTORE_EMPTY_LIST_SUPPORT";
98 * Returns whether or not empty list support is enabled; see
99 * {@link #DATASTORE_EMPTY_LIST_SUPPORT}.
101 public static boolean getEmptyListSupport() {
102 String value
= System
.getProperty(DATASTORE_EMPTY_LIST_SUPPORT
);
103 return value
!= null && value
.equals(Boolean
.TRUE
.toString());
107 * Datastore service API version.
111 CLOUD_DATASTORE_V1_VIA_API_PROXY(false),
112 CLOUD_DATASTORE_V1_REMOTE(true);
114 private final boolean isCloudService
;
116 private ApiVersion(boolean isCloudService
) {
117 this.isCloudService
= isCloudService
;
120 boolean isCloudService() {
121 return isCloudService
;
126 * The default maximum size a request RPC can be.
128 static final int DEFAULT_RPC_SIZE_LIMIT_BYTES
= 1024 * 1024;
131 * The default maximum number of keys allowed in a Get request.
133 static final int DEFAULT_MAX_BATCH_GET_KEYS
= 1000;
136 * The default maximum number of entities allowed in a Put or Delete request.
138 static final int DEFAULT_MAX_BATCH_WRITE_ENTITIES
= 500;
141 * Name of a system property that users can specify to control the default
142 * max entity groups per rpc.
144 static final String DEFAULT_MAX_ENTITY_GROUPS_PER_RPC_SYS_PROP
=
145 "appengine.datastore.defaultMaxEntityGroupsPerRpc";
148 * The default number of max entity groups per rpc.
150 static final int DEFAULT_MAX_ENTITY_GROUPS_PER_RPC
= getDefaultMaxEntityGroupsPerRpc();
152 static final String CALLBACKS_CONFIG_SYS_PROP
= "appengine.datastore.callbacksConfig";
154 static volatile DatastoreCallbacks CALLBACKS
= null;
158 * com.google.appengine.tools.compilation.DatastoreCallbacksProcessor.CALLBACKS_CONFIG_FILE
160 private static final String CALLBACKS_CONFIG_FILE
= "/META-INF/datastorecallbacks.xml";
162 static int getDefaultMaxEntityGroupsPerRpc() {
163 return getDefaultMaxEntityGroupsPerRpc(DEFAULT_MAX_ENTITY_GROUPS_PER_RPC_SYS_PROP
, 10);
166 static final String DATASTORE_SERVICE_VERSION_SYS_PROP
= "appengine.datastore.v1service";
167 static final String FORCE_LOCAL_CLOUD_DATASTORE_V1_SYS_PROP_VAL
= "force";
170 * Returns the value of the given system property as an int, or return the
171 * default value if there is no property with that name set.
173 static int getDefaultMaxEntityGroupsPerRpc(String sysPropName
, int defaultVal
) {
174 String sysPropVal
= System
.getProperty(sysPropName
);
175 return sysPropVal
== null ? defaultVal
: Integer
.parseInt(sysPropVal
);
178 private static InputStream
getCallbacksConfigInputStream() {
180 String callbacksConfig
= System
.getProperty(CALLBACKS_CONFIG_SYS_PROP
);
181 if (callbacksConfig
!= null) {
182 is
= new ByteArrayInputStream(callbacksConfig
.getBytes());
184 is
= DatastoreServiceConfig
.class.getResourceAsStream(CALLBACKS_CONFIG_FILE
);
189 private final DatastoreCallbacks instanceDatastoreCallbacks
;
191 private ImplicitTransactionManagementPolicy implicitTransactionManagementPolicy
=
192 ImplicitTransactionManagementPolicy
.NONE
;
194 private ReadPolicy readPolicy
= new ReadPolicy(Consistency
.STRONG
);
196 private Double deadline
;
198 private AppIdNamespace appIdNamespace
;
200 int maxRpcSizeBytes
= DEFAULT_RPC_SIZE_LIMIT_BYTES
;
201 int maxBatchWriteEntities
= DEFAULT_MAX_BATCH_WRITE_ENTITIES
;
202 int maxBatchReadEntities
= DEFAULT_MAX_BATCH_GET_KEYS
;
203 int maxBatchAllocateIdKeys
= DEFAULT_MAX_BATCH_WRITE_ENTITIES
;
204 int maxEntityGroupsPerRpc
= DEFAULT_MAX_ENTITY_GROUPS_PER_RPC
;
206 private EntityCacheConfig entityCacheConfig
= null;
209 * If set, overrides the service API version.
211 private ApiVersion apiVersion
= null;
213 private CloudDatastoreV1Proxy cloudDatastoreV1Proxy
= null;
216 * Cannot be directly instantiated, use {@link Builder} instead.
218 * @param datastoreCallbacks the callback methods to invoke on specific datastore operations.
219 * If null the datastore callbacks are derived from {@link #getCallbacksConfigInputStream}.
221 private DatastoreServiceConfig( DatastoreCallbacks datastoreCallbacks
) {
222 instanceDatastoreCallbacks
= datastoreCallbacks
;
228 DatastoreServiceConfig(DatastoreServiceConfig config
) {
229 implicitTransactionManagementPolicy
= config
.implicitTransactionManagementPolicy
;
230 readPolicy
= config
.readPolicy
;
231 deadline
= config
.deadline
;
232 maxRpcSizeBytes
= config
.maxRpcSizeBytes
;
233 maxBatchWriteEntities
= config
.maxBatchWriteEntities
;
234 maxBatchReadEntities
= config
.maxBatchReadEntities
;
235 maxEntityGroupsPerRpc
= config
.maxEntityGroupsPerRpc
;
236 instanceDatastoreCallbacks
= config
.instanceDatastoreCallbacks
;
237 appIdNamespace
= config
.appIdNamespace
;
238 apiVersion
= config
.apiVersion
;
239 cloudDatastoreV1Proxy
= config
.cloudDatastoreV1Proxy
;
243 * Sets the implicit transaction management policy.
244 * @param p the implicit transaction management policy to set.
245 * @return {@code this} (for chaining)
247 public DatastoreServiceConfig
implicitTransactionManagementPolicy(
248 ImplicitTransactionManagementPolicy p
) {
250 throw new NullPointerException("implicit transaction management policy must not be null");
252 implicitTransactionManagementPolicy
= p
;
257 * Sets the read policy.
258 * @param readPolicy the read policy to set.
259 * @return {@code this} (for chaining)
261 public DatastoreServiceConfig
readPolicy(ReadPolicy readPolicy
) {
262 if (readPolicy
== null) {
263 throw new NullPointerException("read policy must not be null");
265 this.readPolicy
= readPolicy
;
270 * Sets the deadline, in seconds, for all rpcs initiated by the
271 * {@link DatastoreService} with which this config is associated.
272 * @param deadline the deadline to set.
273 * @throws IllegalArgumentException if deadline is not positive
274 * @return {@code this} (for chaining)
276 public DatastoreServiceConfig
deadline(double deadline
) {
277 if (deadline
<= 0.0) {
278 throw new IllegalArgumentException("deadline must be > 0, got " + deadline
);
280 this.deadline
= deadline
;
284 DatastoreServiceConfig
appIdNamespace(AppIdNamespace appIdNamespace
) {
285 this.appIdNamespace
= appIdNamespace
;
290 * Sets the maximum number of entities that can be modified in a single RPC.
291 * @param maxBatchWriteEntities the limit to set
292 * @throws IllegalArgumentException if maxBatchWriteEntities is not greater
294 * @return {@code this} (for chaining)
296 DatastoreServiceConfig
maxBatchWriteEntities(int maxBatchWriteEntities
) {
297 if (maxBatchWriteEntities
<= 0) {
298 throw new IllegalArgumentException("maxBatchWriteEntities must be > 0, got "
299 + maxBatchWriteEntities
);
301 this.maxBatchWriteEntities
= maxBatchWriteEntities
;
306 * Sets the maximum number of entities that can be read in a single RPC.
307 * @param maxBatchReadEntities the limit to set
308 * @throws IllegalArgumentException if maxBatchReadEntities is not greater
310 * @return {@code this} (for chaining)
312 DatastoreServiceConfig
maxBatchReadEntities(int maxBatchReadEntities
) {
313 if (maxBatchReadEntities
<= 0) {
314 throw new IllegalArgumentException("maxBatchReadEntities must be > 0, got "
315 + maxBatchReadEntities
);
317 this.maxBatchReadEntities
= maxBatchReadEntities
;
322 * Sets the maximum size in bytes an RPC can be.
324 * The size of the request can be exceeded if the RPC cannot be split enough
325 * to respect this limit. However there may be a hard limit on the RPC which,
326 * if exceeded, will cause an exception to be thrown.
328 * @param maxRpcSizeBytes the limit to set
329 * @throws IllegalArgumentException if maxRpcSizeBytes is not positive
330 * @return {@code this} (for chaining)
332 DatastoreServiceConfig
maxRpcSizeBytes(int maxRpcSizeBytes
) {
333 if (maxRpcSizeBytes
< 0) {
334 throw new IllegalArgumentException("maxRpcSizeBytes must be >= 0, got "
337 this.maxRpcSizeBytes
= maxRpcSizeBytes
;
342 * Sets the maximum number of entity groups that can be represented in a
345 * For a non-transactional operation that involves more entity groups than the
346 * maximum, the operation will be performed by executing multiple, asynchronous
347 * rpcs to the datastore, each of which has no more entity groups represented
348 * than the maximum. So, if a put() operation has 8 entity groups and the
349 * maximum is 3, we will send 3 rpcs, 2 with 3 entity groups and 1 with 2
350 * entity groups. This is a performance optimization - in many cases
351 * multiple, small, asynchronous rpcs will finish faster than a single large
352 * asynchronous rpc. The optimal value for this property will be
353 * application-specific, so experimentation is encouraged.
355 * @param maxEntityGroupsPerRpc the maximum number of entity groups per rpc
356 * @throws IllegalArgumentException if maxEntityGroupsPerRpc is not greater
358 * @return {@code this} (for chaining)
360 public DatastoreServiceConfig
maxEntityGroupsPerRpc(int maxEntityGroupsPerRpc
) {
361 if (maxEntityGroupsPerRpc
<= 0) {
362 throw new IllegalArgumentException("maxEntityGroupsPerRpc must be > 0, got "
363 + maxEntityGroupsPerRpc
);
365 this.maxEntityGroupsPerRpc
= maxEntityGroupsPerRpc
;
370 * Sets the entity cache configuration to use for entity caching.
372 * To successfully use entity caching it is important that all application code that updates
373 * or reads the same set of entities uses a {@code DatastoreServiceConfig} with the same entity
374 * cache configuration.
376 * By default the datastore service does not do any entity caching.
378 * @param entityCacheConfig the entity cache configuration to use for entity caching.
379 * @return {@code this} (for chaining)
381 DatastoreServiceConfig
entityCacheConfig(EntityCacheConfig entityCacheConfig
) {
382 checkNotNull(entityCacheConfig
, "The entityCacheConfig argument can not be null");
383 this.entityCacheConfig
= entityCacheConfig
;
388 * @return The {@code ImplicitTransactionManagementPolicy} to use.
390 public ImplicitTransactionManagementPolicy
getImplicitTransactionManagementPolicy() {
391 return implicitTransactionManagementPolicy
;
395 * @return The {@code ReadPolicy} to use.
397 public ReadPolicy
getReadPolicy() {
402 * @return The maximum number of entity groups per rpc.
404 public Integer
getMaxEntityGroupsPerRpc() {
405 return getMaxEntityGroupsPerRpcInternal();
408 int getMaxEntityGroupsPerRpcInternal() {
409 return maxEntityGroupsPerRpc
;
413 * @return The deadline to use. Can be {@code null}.
415 public Double
getDeadline() {
419 AppIdNamespace
getAppIdNamespace() {
420 return appIdNamespace
== null ? DatastoreApiHelper
.getCurrentAppIdNamespace() : appIdNamespace
;
423 DatastoreCallbacks
getDatastoreCallbacks() {
424 if (instanceDatastoreCallbacks
!= null) {
425 return instanceDatastoreCallbacks
;
428 if (CALLBACKS
== null) {
429 InputStream is
= getCallbacksConfigInputStream();
431 CALLBACKS
= DatastoreCallbacks
.NoOpDatastoreCallbacks
.INSTANCE
;
433 CALLBACKS
= new DatastoreCallbacksImpl(is
, false);
440 * @return the {@link EntityCacheConfig} to use or {@code null} if no entity cache configuration
443 EntityCacheConfig
getEntityCacheConfig() {
444 return entityCacheConfig
;
448 * Datastore service API version to use.
450 ApiVersion
getApiVersion() {
451 if (apiVersion
!= null) {
455 boolean useLocalCloudDatastoreV1
= FORCE_LOCAL_CLOUD_DATASTORE_V1_SYS_PROP_VAL
.equals(
456 System
.getProperty(DATASTORE_SERVICE_VERSION_SYS_PROP
));
457 boolean useCloudDatastore
= Boolean
.valueOf(EnvProxy
.getenv("DATASTORE_USE_CLOUD_DATASTORE"));
459 ApiVersion localApiVersion
= ApiVersion
.V3
;
460 if (useLocalCloudDatastoreV1
&& useCloudDatastore
) {
461 throw new IllegalArgumentException("Cannot use both local and remote Cloud Datastore APIs.");
462 } else if (useLocalCloudDatastoreV1
) {
463 localApiVersion
= ApiVersion
.CLOUD_DATASTORE_V1_VIA_API_PROXY
;
464 } else if (useCloudDatastore
) {
465 localApiVersion
= ApiVersion
.CLOUD_DATASTORE_V1_REMOTE
;
468 apiVersion
= localApiVersion
;
472 DatastoreServiceConfig
setApiVersion( ApiVersion apiVersion
) {
473 this.apiVersion
= apiVersion
;
477 ApiProxy
.ApiConfig
constructApiConfig() {
478 ApiProxy
.ApiConfig apiConfig
= new ApiProxy
.ApiConfig();
479 apiConfig
.setDeadlineInSeconds(deadline
);
482 CloudDatastoreV1Proxy
getCloudDatastoreV1Proxy() {
483 return cloudDatastoreV1Proxy
;
487 * Contains static creation methods for {@link DatastoreServiceConfig}.
489 public static final class Builder
{
492 * Create a {@link DatastoreServiceConfig} with the given implicit
493 * transaction management policy.
494 * @param p the implicit transaction management policy to set.
495 * @return The newly created DatastoreServiceConfig instance.
497 public static DatastoreServiceConfig
withImplicitTransactionManagementPolicy(
498 ImplicitTransactionManagementPolicy p
) {
499 return withDefaults().implicitTransactionManagementPolicy(p
);
503 * Create a {@link DatastoreServiceConfig} with the given read
505 * @param readPolicy the read policy to set.
506 * @return The newly created DatastoreServiceConfig instance.
508 public static DatastoreServiceConfig
withReadPolicy(ReadPolicy readPolicy
) {
509 return withDefaults().readPolicy(readPolicy
);
513 * Create a {@link DatastoreServiceConfig} with the given deadline, in
515 * @param deadline the deadline to set.
516 * @throws IllegalArgumentException if deadline is not positive
517 * @return The newly created DatastoreServiceConfig instance.
519 public static DatastoreServiceConfig
withDeadline(double deadline
) {
520 return withDefaults().deadline(deadline
);
524 * Create a {@link DatastoreServiceConfig} with the given maximum entity
526 * @param maxEntityGroupsPerRpc the maximum entity groups per rpc to set.
527 * @return The newly created DatastoreServiceConfig instance.
529 * @see DatastoreServiceConfig#maxEntityGroupsPerRpc(int)
531 public static DatastoreServiceConfig
withMaxEntityGroupsPerRpc(int maxEntityGroupsPerRpc
) {
532 return withDefaults().maxEntityGroupsPerRpc(maxEntityGroupsPerRpc
);
536 * Create a {@link DatastoreServiceConfig} with the given entity cache configuration.
538 * @param entityCacheConfig the entity cache configuration to use for entity caching.
540 * @see DatastoreServiceConfig#entityCacheConfig
542 static DatastoreServiceConfig
withEntityCacheConfig(
543 EntityCacheConfig entityCacheConfig
) {
544 return withDefaults().entityCacheConfig(entityCacheConfig
);
548 * Helper method for creating a {@link DatastoreServiceConfig} instance with the
549 * specified {@code datastoreCallbacks}. The callbacks defined for the application are
550 * bypassed and the specified callbacks are used instead.
552 * @return The newly created DatastoreServiceConfig instance.
554 static DatastoreServiceConfig
withDatastoreCallbacks(DatastoreCallbacks datastoreCallbacks
) {
555 Preconditions
.checkNotNull(datastoreCallbacks
);
556 return new DatastoreServiceConfig(datastoreCallbacks
);
560 * Helper method for creating a {@link DatastoreServiceConfig}
561 * instance with default values: Implicit transactions are disabled, reads
562 * execute with {@link Consistency#STRONG}, and no deadline is
563 * provided. When no deadline is provided, datastore rpcs execute with the
564 * system-defined deadline.
566 * @return The newly created DatastoreServiceConfig instance.
568 public static DatastoreServiceConfig
withDefaults() {
569 return new DatastoreServiceConfig((DatastoreCallbacks
) null);