1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.api
.datastore
;
4 import com
.google
.appengine
.api
.NamespaceManager
;
5 import com
.google
.appengine
.api
.appidentity
.AppIdentityService
;
6 import com
.google
.appengine
.api
.appidentity
.AppIdentityService
.ParsedAppId
;
7 import com
.google
.appengine
.api
.appidentity
.AppIdentityServiceFactory
;
8 import com
.google
.appengine
.api
.utils
.FutureWrapper
;
9 import com
.google
.apphosting
.api
.ApiProxy
;
10 import com
.google
.apphosting
.api
.ApiProxy
.ApiConfig
;
11 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.DatastoreService_3
;
12 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.Error
;
13 import com
.google
.datastore
.v1beta3
.Datastore
;
14 import com
.google
.io
.protocol
.ProtocolMessage
;
15 import com
.google
.protobuf
.InvalidProtocolBufferException
;
16 import com
.google
.protobuf
.MessageLite
;
17 import com
.google
.protobuf
.Parser
;
18 import com
.google
.rpc
.Code
;
20 import java
.util
.ConcurrentModificationException
;
21 import java
.util
.concurrent
.Future
;
24 * Helper methods and constants shared by classes that implement the Java api
25 * on top of the datastore.
27 * <p>Note: users should not access this class directly.
30 public final class DatastoreApiHelper
{
32 static final String DATASTORE_V3_PACKAGE
= "datastore_v3";
33 static final String CLOUD_DATASTORE_V1_PACKAGE
= "cloud_datastore_v1beta3";
36 * Key to put in {@link ApiProxy.Environment#getAttributes()} to override the app id used by the
37 * datastore api. If absent, {@link ApiProxy.Environment#getAppId()} will be used.
39 @SuppressWarnings("javadoc")
40 static final String APP_ID_OVERRIDE_KEY
= "com.google.appengine.datastore.AppIdOverride";
42 private static final AppIdentityService appIdentityService
=
43 AppIdentityServiceFactory
.getAppIdentityService();
45 private DatastoreApiHelper() {}
47 public static RuntimeException
translateError(ApiProxy
.ApplicationException exception
) {
48 Error
.ErrorCode errorCode
= Error
.ErrorCode
.valueOf(exception
.getApplicationError());
49 if (errorCode
== null) {
50 return new DatastoreFailureException(exception
.getErrorDetail());
54 return new IllegalArgumentException(exception
.getErrorDetail());
56 case CONCURRENT_TRANSACTION
:
57 return new ConcurrentModificationException(exception
.getErrorDetail());
60 return new DatastoreNeedIndexException(exception
.getErrorDetail());
64 return new DatastoreTimeoutException(exception
.getErrorDetail());
66 case COMMITTED_BUT_STILL_APPLYING
:
67 return new CommittedButStillApplyingException(exception
.getErrorDetail());
71 return new DatastoreFailureException(exception
.getErrorDetail());
75 static RuntimeException
createV1Exception(Code code
, String message
, Datastore
.Method method
) {
77 return new DatastoreFailureException(message
);
80 case INVALID_ARGUMENT
:
81 return new IllegalArgumentException(message
);
83 return new ConcurrentModificationException(message
);
84 case FAILED_PRECONDITION
:
85 return new DatastoreNeedIndexException(message
);
86 case DEADLINE_EXCEEDED
:
87 return new DatastoreTimeoutException(message
);
88 case PERMISSION_DENIED
:
89 return new IllegalArgumentException(message
);
91 return new ApiProxy
.RPCFailedException(CLOUD_DATASTORE_V1_PACKAGE
, method
.name());
92 case RESOURCE_EXHAUSTED
:
93 return new ApiProxy
.OverQuotaException(CLOUD_DATASTORE_V1_PACKAGE
, method
.name());
96 return new DatastoreFailureException(message
);
100 private static RuntimeException
translateV1CanonicalCodeError(
101 ApiProxy
.ApplicationException exception
, Datastore
.Method method
) {
102 return createV1Exception(Code
.valueOf(exception
.getApplicationError()),
103 exception
.getErrorDetail(), method
);
106 static <T
extends ProtocolMessage
<T
>> Future
<T
> makeAsyncCall(ApiConfig apiConfig
,
107 final DatastoreService_3
.Method method
, ProtocolMessage
<?
> request
, final T responseProto
) {
108 Future
<byte[]> response
= ApiProxy
.makeAsyncCall(DATASTORE_V3_PACKAGE
, method
.name(),
109 request
.toByteArray(), apiConfig
);
110 return new FutureWrapper
<byte[], T
>(response
) {
112 protected T
wrap(byte[] responseBytes
) throws InvalidProtocolBufferException
{
113 if (responseBytes
!= null) {
114 if (!responseProto
.parseFrom(responseBytes
)) {
115 throw new InvalidProtocolBufferException(String
.format("Invalid %s.%s response",
116 DATASTORE_V3_PACKAGE
, method
.name()));
118 String initializationError
= responseProto
.findInitializationError();
119 if (initializationError
!= null) {
120 throw new InvalidProtocolBufferException(initializationError
);
123 return responseProto
;
127 protected Throwable
convertException(Throwable cause
) {
128 if (cause
instanceof ApiProxy
.ApplicationException
) {
129 return translateError((ApiProxy
.ApplicationException
) cause
);
136 static <T
extends MessageLite
> Future
<T
> makeV1AsyncCall(ApiProxy
.ApiConfig apiConfig
,
137 Datastore
.Method method
, MessageLite request
, Parser
<T
> responseParser
) {
138 return makeV1AsyncCall(apiConfig
, method
, request
.toByteArray(), responseParser
);
141 static <T
extends MessageLite
> Future
<T
> makeV1AsyncCall(ApiProxy
.ApiConfig apiConfig
,
142 final Datastore
.Method method
, byte[] request
, final Parser
<T
> responseParser
) {
143 Future
<byte[]> response
= ApiProxy
.makeAsyncCall(CLOUD_DATASTORE_V1_PACKAGE
, method
.name(),
145 return new FutureWrapper
<byte[], T
>(response
) {
147 protected T
wrap(byte[] responseBytes
) throws Exception
{
148 if (responseBytes
!= null) {
149 return responseParser
.parseFrom(responseBytes
);
151 return responseParser
.parsePartialFrom(new byte[0]);
155 protected Throwable
convertException(Throwable cause
) {
156 if (cause
instanceof ApiProxy
.ApplicationException
) {
157 return translateV1CanonicalCodeError((ApiProxy
.ApplicationException
) cause
, method
);
164 static String
getCurrentProjectId() {
165 return toProjectId(getCurrentAppId());
168 static String
toProjectId(String appId
) {
169 ParsedAppId parsedAppId
= appIdentityService
.parseFullAppId(appId
);
170 if (parsedAppId
.getDomain().isEmpty()) {
171 return parsedAppId
.getId();
173 return String
.format("%s:%s", parsedAppId
.getDomain(), parsedAppId
.getId());
177 static String
getCurrentAppId() {
178 ApiProxy
.Environment environment
= ApiProxy
.getCurrentEnvironment();
179 if (environment
== null) {
180 throw new NullPointerException("No API environment is registered for this thread.");
183 Object appIdOverride
= environment
.getAttributes().get(APP_ID_OVERRIDE_KEY
);
184 if (appIdOverride
!= null) {
185 return (String
) appIdOverride
;
188 return environment
.getAppId();
192 * Returns a new {@link AppIdNamespace} with the current appId and the namespace
193 * registered with the {@link NamespaceManager}
195 static AppIdNamespace
getCurrentAppIdNamespace() {
196 return getCurrentAppIdNamespace(getCurrentAppId());
200 * Returns a new {@link AppIdNamespace} with the namespace currently
201 * registered with the {@link NamespaceManager} for a given appid.
203 static AppIdNamespace
getCurrentAppIdNamespace(String appId
) {
204 String namespace
= NamespaceManager
.get();
205 namespace
= namespace
== null ?
"" : namespace
;
206 return new AppIdNamespace(appId
, namespace
);