1.9.22 release.
[gae.git] / java / src / main / com / google / appengine / api / datastore / DatastoreServiceConfig.java
blob33bbe65a2022febc71b689eff07da9a8598daa73
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;
14 /**
15 * User-configurable properties of the datastore.
17 * Notes on usage:<br>
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):
22 * <pre>
23 * import static com.google.appengine.api.datastore.DatastoreServiceConfig.Builder.*;
24 * import com.google.appengine.api.datastore.ReadPolicy.Consistency;
26 * ...
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);
34 * </pre>
37 public final class DatastoreServiceConfig {
39 /**
40 * This is the name of a system property that determines how the Java SDK writes/reads empty lists
41 * to/from Datastore.
42 * <p/>
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.
47 * <p/>
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
51 * store.
52 * <p/>
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
56 * similar queries.
57 * <p/>
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
60 * value.
61 * <p/>
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.
65 * <p/>
66 * When this variable is set to anything other than "true" the system provides legacy behavior.
67 * <p>
68 * <ul>
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.
74 * <ul>
75 * </p>
77 * <p/>
78 * When this variable is set to "true"
79 * <p>
80 * <ul>
81 * <li>Null properties are written as null to Datastore
82 * <li>Empty collections (#{@link Collection#isEmpty()}) are written as empty list to Datastore
83 * <li>A null is read as null from Datastore
84 * <li>An empty collection is read as null
85 * <li>When reading from Datastore an empty list is returned as an empty {@link Collection}.
86 * <ul>
87 * </p>
88 * It is strongly recommended that this property be set to true in order to provide compatibility
89 * with other Datastore SDK's, as well as future versions of Datastore.
91 * To set the flag: <code>
92 * System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT,
93 * Boolean.TRUE.toString());
94 * </code>
96 public static final String DATASTORE_EMPTY_LIST_SUPPORT = "DATASTORE_EMPTY_LIST_SUPPORT";
98 /**
99 * @returns Whether or not empty list support is enabled see
100 * {@link #DATASTORE_EMPTY_LIST_SUPPORT}.
102 public static boolean getEmptyListSupport() {
103 String value = System.getProperty(DATASTORE_EMPTY_LIST_SUPPORT);
104 return value != null && value.equals(Boolean.TRUE.toString());
108 * Datastore service API version.
110 enum ApiVersion {
111 V3(false),
112 CLOUD_DATASTORE_V1_VIA_API_PROXY(false),
113 CLOUD_DATASTORE_V1_REMOTE(true);
115 private final boolean isCloudService;
117 private ApiVersion(boolean isCloudService) {
118 this.isCloudService = isCloudService;
121 boolean isCloudService() {
122 return isCloudService;
127 * The default maximum size a request RPC can be.
129 static final int DEFAULT_RPC_SIZE_LIMIT_BYTES = 1024 * 1024;
132 * The default maximum number of keys allowed in a Get request.
134 static final int DEFAULT_MAX_BATCH_GET_KEYS = 1000;
137 * The default maximum number of entities allowed in a Put or Delete request.
139 static final int DEFAULT_MAX_BATCH_WRITE_ENTITIES = 500;
142 * Name of a system property that users can specify to control the default
143 * max entity groups per rpc.
145 static final String DEFAULT_MAX_ENTITY_GROUPS_PER_RPC_SYS_PROP =
146 "appengine.datastore.defaultMaxEntityGroupsPerRpc";
149 * The default number of max entity groups per rpc.
151 static final int DEFAULT_MAX_ENTITY_GROUPS_PER_RPC = getDefaultMaxEntityGroupsPerRpc();
153 static final String CALLBACKS_CONFIG_SYS_PROP = "appengine.datastore.callbacksConfig";
155 static volatile DatastoreCallbacks CALLBACKS = null;
158 * Keep in sync with
159 * com.google.appengine.tools.compilation.DatastoreCallbacksProcessor.CALLBACKS_CONFIG_FILE
161 private static final String CALLBACKS_CONFIG_FILE = "/META-INF/datastorecallbacks.xml";
163 static int getDefaultMaxEntityGroupsPerRpc() {
164 return getDefaultMaxEntityGroupsPerRpc(DEFAULT_MAX_ENTITY_GROUPS_PER_RPC_SYS_PROP, 10);
167 static final String DATASTORE_SERVICE_VERSION_SYS_PROP = "appengine.datastore.v1service";
168 static final String FORCE_LOCAL_CLOUD_DATASTORE_V1_SYS_PROP_VAL = "force";
171 * Returns the value of the given system property as an int, or return the
172 * default value if there is no property with that name set.
174 static int getDefaultMaxEntityGroupsPerRpc(String sysPropName, int defaultVal) {
175 String sysPropVal = System.getProperty(sysPropName);
176 return sysPropVal == null ? defaultVal : Integer.parseInt(sysPropVal);
179 private static InputStream getCallbacksConfigInputStream() {
180 InputStream is;
181 String callbacksConfig = System.getProperty(CALLBACKS_CONFIG_SYS_PROP);
182 if (callbacksConfig != null) {
183 is = new ByteArrayInputStream(callbacksConfig.getBytes());
184 } else {
185 is = DatastoreServiceConfig.class.getResourceAsStream(CALLBACKS_CONFIG_FILE);
187 return is;
190 private final DatastoreCallbacks instanceDatastoreCallbacks;
192 private ImplicitTransactionManagementPolicy implicitTransactionManagementPolicy =
193 ImplicitTransactionManagementPolicy.NONE;
195 private ReadPolicy readPolicy = new ReadPolicy(Consistency.STRONG);
197 private Double deadline;
199 private AppIdNamespace appIdNamespace;
201 int maxRpcSizeBytes = DEFAULT_RPC_SIZE_LIMIT_BYTES;
202 int maxBatchWriteEntities = DEFAULT_MAX_BATCH_WRITE_ENTITIES;
203 int maxBatchReadEntities = DEFAULT_MAX_BATCH_GET_KEYS;
204 int maxBatchAllocateIdKeys = DEFAULT_MAX_BATCH_WRITE_ENTITIES;
205 int maxEntityGroupsPerRpc = DEFAULT_MAX_ENTITY_GROUPS_PER_RPC;
207 private EntityCacheConfig entityCacheConfig = null;
210 * If set, overrides the service API version.
212 private ApiVersion apiVersion = null;
214 private CloudDatastoreV1Proxy cloudDatastoreV1Proxy = null;
217 * Cannot be directly instantiated, use {@link Builder} instead.
219 * @param datastoreCallbacks the callback methods to invoke on specific datastore operations.
220 * If null the datastore callbacks are derived from {@link #getCallbacksConfigInputStream}.
222 private DatastoreServiceConfig( DatastoreCallbacks datastoreCallbacks) {
223 instanceDatastoreCallbacks = datastoreCallbacks;
227 * Copy constructor
229 DatastoreServiceConfig(DatastoreServiceConfig config) {
230 implicitTransactionManagementPolicy = config.implicitTransactionManagementPolicy;
231 readPolicy = config.readPolicy;
232 deadline = config.deadline;
233 maxRpcSizeBytes = config.maxRpcSizeBytes;
234 maxBatchWriteEntities = config.maxBatchWriteEntities;
235 maxBatchReadEntities = config.maxBatchReadEntities;
236 maxEntityGroupsPerRpc = config.maxEntityGroupsPerRpc;
237 instanceDatastoreCallbacks = config.instanceDatastoreCallbacks;
238 appIdNamespace = config.appIdNamespace;
239 apiVersion = config.apiVersion;
240 cloudDatastoreV1Proxy = config.cloudDatastoreV1Proxy;
244 * Sets the implicit transaction management policy.
245 * @param p the implicit transaction management policy to set.
246 * @return {@code this} (for chaining)
248 public DatastoreServiceConfig implicitTransactionManagementPolicy(
249 ImplicitTransactionManagementPolicy p) {
250 if (p == null) {
251 throw new NullPointerException("implicit transaction management policy must not be null");
253 implicitTransactionManagementPolicy = p;
254 return this;
258 * Sets the read policy.
259 * @param readPolicy the read policy to set.
260 * @return {@code this} (for chaining)
262 public DatastoreServiceConfig readPolicy(ReadPolicy readPolicy) {
263 if (readPolicy == null) {
264 throw new NullPointerException("read policy must not be null");
266 this.readPolicy = readPolicy;
267 return this;
271 * Sets the deadline, in seconds, for all rpcs initiated by the
272 * {@link DatastoreService} with which this config is associated.
273 * @param deadline the deadline to set.
274 * @throws IllegalArgumentException if deadline is not positive
275 * @return {@code this} (for chaining)
277 public DatastoreServiceConfig deadline(double deadline) {
278 if (deadline <= 0.0) {
279 throw new IllegalArgumentException("deadline must be > 0, got " + deadline);
281 this.deadline = deadline;
282 return this;
285 DatastoreServiceConfig appIdNamespace(AppIdNamespace appIdNamespace) {
286 this.appIdNamespace = appIdNamespace;
287 return this;
291 * Sets the maximum number of entities that can be modified in a single RPC.
292 * @param maxBatchWriteEntities the limit to set
293 * @throws IllegalArgumentException if maxBatchWriteEntities is not greater
294 * than zero
295 * @return {@code this} (for chaining)
297 DatastoreServiceConfig maxBatchWriteEntities(int maxBatchWriteEntities) {
298 if (maxBatchWriteEntities <= 0) {
299 throw new IllegalArgumentException("maxBatchWriteEntities must be > 0, got "
300 + maxBatchWriteEntities);
302 this.maxBatchWriteEntities = maxBatchWriteEntities;
303 return this;
307 * Sets the maximum number of entities that can be read in a single RPC.
308 * @param maxBatchReadEntities the limit to set
309 * @throws IllegalArgumentException if maxBatchReadEntities is not greater
310 * than zero
311 * @return {@code this} (for chaining)
313 DatastoreServiceConfig maxBatchReadEntities(int maxBatchReadEntities) {
314 if (maxBatchReadEntities <= 0) {
315 throw new IllegalArgumentException("maxBatchReadEntities must be > 0, got "
316 + maxBatchReadEntities);
318 this.maxBatchReadEntities = maxBatchReadEntities;
319 return this;
323 * Sets the maximum size in bytes an RPC can be.
325 * The size of the request can be exceeded if the RPC cannot be split enough
326 * to respect this limit. However there may be a hard limit on the RPC which,
327 * if exceeded, will cause an exception to be thrown.
329 * @param maxRpcSizeBytes the limit to set
330 * @throws IllegalArgumentException if maxRpcSizeBytes is not positive
331 * @return {@code this} (for chaining)
333 DatastoreServiceConfig maxRpcSizeBytes(int maxRpcSizeBytes) {
334 if (maxRpcSizeBytes < 0) {
335 throw new IllegalArgumentException("maxRpcSizeBytes must be >= 0, got "
336 + maxRpcSizeBytes);
338 this.maxRpcSizeBytes = maxRpcSizeBytes;
339 return this;
343 * Sets the maximum number of entity groups that can be represented in a
344 * single rpc.
346 * For a non-transactional operation that involves more entity groups than the
347 * maximum, the operation will be performed by executing multiple, asynchronous
348 * rpcs to the datastore, each of which has no more entity groups represented
349 * than the maximum. So, if a put() operation has 8 entity groups and the
350 * maximum is 3, we will send 3 rpcs, 2 with 3 entity groups and 1 with 2
351 * entity groups. This is a performance optimization - in many cases
352 * multiple, small, asynchronous rpcs will finish faster than a single large
353 * asynchronous rpc. The optimal value for this property will be
354 * application-specific, so experimentation is encouraged.
356 * @param maxEntityGroupsPerRpc the maximum number of entity groups per rpc
357 * @throws IllegalArgumentException if maxEntityGroupsPerRpc is not greater
358 * than zero
359 * @return {@code this} (for chaining)
361 public DatastoreServiceConfig maxEntityGroupsPerRpc(int maxEntityGroupsPerRpc) {
362 if (maxEntityGroupsPerRpc <= 0) {
363 throw new IllegalArgumentException("maxEntityGroupsPerRpc must be > 0, got "
364 + maxEntityGroupsPerRpc);
366 this.maxEntityGroupsPerRpc = maxEntityGroupsPerRpc;
367 return this;
371 * Sets the entity cache configuration to use for entity caching.
373 * To successfully use entity caching it is important that all application code that updates
374 * or reads the same set of entities uses a {@code DatastoreServiceConfig} with the same entity
375 * cache configuration.
377 * By default the datastore service does not do any entity caching.
379 * @param entityCacheConfig the entity cache configuration to use for entity caching.
380 * @return {@code this} (for chaining)
382 DatastoreServiceConfig entityCacheConfig(EntityCacheConfig entityCacheConfig) {
383 checkNotNull(entityCacheConfig, "The entityCacheConfig argument can not be null");
384 this.entityCacheConfig = entityCacheConfig;
385 return this;
389 * @return The {@code ImplicitTransactionManagementPolicy} to use.
391 public ImplicitTransactionManagementPolicy getImplicitTransactionManagementPolicy() {
392 return implicitTransactionManagementPolicy;
396 * @return The {@code ReadPolicy} to use.
398 public ReadPolicy getReadPolicy() {
399 return readPolicy;
403 * @return The maximum number of entity groups per rpc.
405 public Integer getMaxEntityGroupsPerRpc() {
406 return getMaxEntityGroupsPerRpcInternal();
409 int getMaxEntityGroupsPerRpcInternal() {
410 return maxEntityGroupsPerRpc;
414 * @return The deadline to use. Can be {@code null}.
416 public Double getDeadline() {
417 return deadline;
420 AppIdNamespace getAppIdNamespace() {
421 return appIdNamespace == null ? DatastoreApiHelper.getCurrentAppIdNamespace() : appIdNamespace;
424 DatastoreCallbacks getDatastoreCallbacks() {
425 if (instanceDatastoreCallbacks != null) {
426 return instanceDatastoreCallbacks;
429 if (CALLBACKS == null) {
430 InputStream is = getCallbacksConfigInputStream();
431 if (is == null) {
432 CALLBACKS = DatastoreCallbacks.NoOpDatastoreCallbacks.INSTANCE;
433 } else {
434 CALLBACKS = new DatastoreCallbacksImpl(is, false);
437 return CALLBACKS;
441 * @return the {@link EntityCacheConfig} to use or {@code null} if no entity cache configuration
442 * was specified.
444 EntityCacheConfig getEntityCacheConfig() {
445 return entityCacheConfig;
449 * Datastore service API version to use.
451 ApiVersion getApiVersion() {
452 if (apiVersion != null) {
453 return apiVersion;
456 boolean useLocalCloudDatastoreV1 = FORCE_LOCAL_CLOUD_DATASTORE_V1_SYS_PROP_VAL.equals(
457 System.getProperty(DATASTORE_SERVICE_VERSION_SYS_PROP));
458 boolean useCloudDatastore = Boolean.valueOf(EnvProxy.getenv("DATASTORE_USE_CLOUD_DATASTORE"));
460 ApiVersion localApiVersion = ApiVersion.V3;
461 if (useLocalCloudDatastoreV1 && useCloudDatastore) {
462 throw new IllegalArgumentException("Cannot use both local and remote Cloud Datastore APIs.");
463 } else if (useLocalCloudDatastoreV1) {
464 localApiVersion = ApiVersion.CLOUD_DATASTORE_V1_VIA_API_PROXY;
465 } else if (useCloudDatastore) {
466 localApiVersion = ApiVersion.CLOUD_DATASTORE_V1_REMOTE;
469 apiVersion = localApiVersion;
470 return apiVersion;
473 DatastoreServiceConfig setApiVersion( ApiVersion apiVersion) {
474 this.apiVersion = apiVersion;
475 return this;
478 ApiProxy.ApiConfig constructApiConfig() {
479 ApiProxy.ApiConfig apiConfig = new ApiProxy.ApiConfig();
480 apiConfig.setDeadlineInSeconds(deadline);
481 return apiConfig;
483 CloudDatastoreV1Proxy getCloudDatastoreV1Proxy() {
484 return cloudDatastoreV1Proxy;
488 * Contains static creation methods for {@link DatastoreServiceConfig}.
490 public static final class Builder {
493 * Create a {@link DatastoreServiceConfig} with the given implicit
494 * transaction management policy.
495 * @param p the implicit transaction management policy to set.
496 * @return The newly created DatastoreServiceConfig instance.
498 public static DatastoreServiceConfig withImplicitTransactionManagementPolicy(
499 ImplicitTransactionManagementPolicy p) {
500 return withDefaults().implicitTransactionManagementPolicy(p);
504 * Create a {@link DatastoreServiceConfig} with the given read
505 * policy.
506 * @param readPolicy the read policy to set.
507 * @return The newly created DatastoreServiceConfig instance.
509 public static DatastoreServiceConfig withReadPolicy(ReadPolicy readPolicy) {
510 return withDefaults().readPolicy(readPolicy);
514 * Create a {@link DatastoreServiceConfig} with the given deadline, in
515 * seconds.
516 * @param deadline the deadline to set.
517 * @throws IllegalArgumentException if deadline is not positive
518 * @return The newly created DatastoreServiceConfig instance.
520 public static DatastoreServiceConfig withDeadline(double deadline) {
521 return withDefaults().deadline(deadline);
525 * Create a {@link DatastoreServiceConfig} with the given maximum entity
526 * groups per rpc.
527 * @param maxEntityGroupsPerRpc the maximum entity groups per rpc to set.
528 * @return The newly created DatastoreServiceConfig instance.
530 * @see DatastoreServiceConfig#maxEntityGroupsPerRpc(int)
532 public static DatastoreServiceConfig withMaxEntityGroupsPerRpc(int maxEntityGroupsPerRpc) {
533 return withDefaults().maxEntityGroupsPerRpc(maxEntityGroupsPerRpc);
537 * Create a {@link DatastoreServiceConfig} with the given entity cache configuration.
539 * @param entityCacheConfig the entity cache configuration to use for entity caching.
541 * @see DatastoreServiceConfig#entityCacheConfig
543 static DatastoreServiceConfig withEntityCacheConfig(
544 EntityCacheConfig entityCacheConfig) {
545 return withDefaults().entityCacheConfig(entityCacheConfig);
549 * Helper method for creating a {@link DatastoreServiceConfig} instance with the
550 * specified {@code datastoreCallbacks}. The callbacks defined for the application are
551 * bypassed and the specified callbacks are used instead.
553 * @return The newly created DatastoreServiceConfig instance.
555 static DatastoreServiceConfig withDatastoreCallbacks(DatastoreCallbacks datastoreCallbacks) {
556 Preconditions.checkNotNull(datastoreCallbacks);
557 return new DatastoreServiceConfig(datastoreCallbacks);
561 * Helper method for creating a {@link DatastoreServiceConfig}
562 * instance with default values: Implicit transactions are disabled, reads
563 * execute with {@link Consistency#STRONG}, and no deadline is
564 * provided. When no deadline is provided, datastore rpcs execute with the
565 * system-defined deadline.
567 * @return The newly created DatastoreServiceConfig instance.
569 public static DatastoreServiceConfig withDefaults() {
570 return new DatastoreServiceConfig((DatastoreCallbacks) null);
573 private Builder() {}