Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / apphosting / api / ApiProxy.java
blob11dee64be19b84bd0e8eb5e2caccc9a9428be850
1 // Copyright 2007 Google Inc. All rights reserved.
3 package com.google.apphosting.api;
5 import java.util.Collections;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.concurrent.Future;
9 import java.util.concurrent.TimeUnit;
11 /**
12 * ApiProxy is a static class that serves as the collection point for
13 * all API calls from user code into the application server.
15 * It is responsible for proxying makeSyncCall() calls to a delegate,
16 * which actually implements the API calls. It also stores an
17 * Environment for each thread, which contains additional user-visible
18 * information about the request.
21 public class ApiProxy {
22 private static final String API_DEADLINE_KEY =
23 "com.google.apphosting.api.ApiProxy.api_deadline_key";
25 /**
26 * Store an environment object for each thread.
28 private static final ThreadLocal<Environment> environmentThreadLocal =
29 new ThreadLocal<Environment>();
31 /**
32 * Store a single delegate, to which we proxy all makeSyncCall requests.
34 private static Delegate delegate;
36 /**
37 * All methods are static. Do not instantiate.
39 private ApiProxy() {
42 /**
43 * @see #makeSyncCall(String,String,byte[],ApiConfig)
45 public static byte[] makeSyncCall(String packageName,
46 String methodName,
47 byte[] request)
48 throws ApiProxyException {
49 return makeSyncCall(packageName, methodName, request, null);
52 /**
53 * Make a synchronous call to the specified method in the specified
54 * API package.
56 * <p>Note: if you have not installed a {@code Delegate} and called
57 * {@code setEnvironmentForCurrentThread} in this thread before
58 * calling this method, it will act like no API calls are available
59 * (i.e. always throw {@code CallNotFoundException}).
61 * @param packageName the name of the API package.
62 * @param methodName the name of the method within the API package.
63 * @param request a byte array containing the serialized form of the
64 * request protocol buffer.
65 * @param apiConfig that specifies API-specific configuration
66 * parameters.
68 * @return a byte array containing the serialized form of the
69 * response protocol buffer.
72 * @throws ApplicationException For any error that is the application's fault.
73 * @throws RPCFailedException If we could not connect to a backend service.
74 * @throws CallNotFoundException If the specified method does not exist.
75 * @throws ArgumentException If the request could not be parsed.
76 * @throws ApiDeadlineExceededException If the request took too long.
77 * @throws CancelledException If the request was explicitly cancelled.
78 * @throws CapabilityDisabledException If the API call is currently
79 * unavailable.
80 * @throws OverQuotaException If the API call required more quota than is
81 * available.
82 * @throws RequestTooLargeException If the request to the API was too large.
83 * @throws ResponseTooLargeException If the response to the API was too large.
84 * @throws UnknownException If any other error occurred.
86 @SuppressWarnings("unchecked")
87 public static byte[] makeSyncCall(String packageName,
88 String methodName,
89 byte[] request,
90 ApiConfig apiConfig)
91 throws ApiProxyException {
92 Environment env = getCurrentEnvironment();
93 if (delegate == null || env == null) {
94 throw new CallNotFoundException(packageName, methodName);
96 if (apiConfig == null || apiConfig.getDeadlineInSeconds() == null) {
97 return delegate.makeSyncCall(env, packageName, methodName, request);
98 } else {
99 Object oldValue = env.getAttributes().put(API_DEADLINE_KEY, apiConfig.getDeadlineInSeconds());
100 try {
101 return delegate.makeSyncCall(env, packageName, methodName, request);
102 } finally {
103 if (oldValue == null) {
104 env.getAttributes().remove(API_DEADLINE_KEY);
105 } else {
106 env.getAttributes().put(API_DEADLINE_KEY, oldValue);
113 * @see #makeAsyncCall(String,String,byte[],ApiConfig)
115 public static Future<byte[]> makeAsyncCall(String packageName,
116 String methodName,
117 byte[] request) {
118 return makeAsyncCall(packageName, methodName, request, new ApiConfig());
122 * Make an asynchronous call to the specified method in the
123 * specified API package.
125 * <p>Note: if you have not installed a {@code Delegate} and called
126 * {@code setEnvironmentForCurrentThread} in this thread before
127 * calling this method, it will act like no API calls are available
128 * (i.e. the returned {@link Future} will throw {@code
129 * CallNotFoundException}).
131 * <p>There is a limit to the number of simultaneous asynchronous
132 * API calls (currently 10). Invoking this method while this number
133 * of API calls are outstanding will block.
135 * @param packageName the name of the API package.
136 * @param methodName the name of the method within the API package.
137 * @param request a byte array containing the serialized form of
138 * the request protocol buffer.
139 * @param apiConfig that specifies API-specific configuration
140 * parameters.
142 * @return a {@link Future} that will resolve to a byte array
143 * containing the serialized form of the response protocol buffer
144 * on success, or throw one of the exceptions documented for
145 * {@link #makeSyncCall(String, String, byte[], ApiConfig)} on failure.
147 @SuppressWarnings("unchecked")
148 public static Future<byte[]> makeAsyncCall(final String packageName,
149 final String methodName,
150 byte[] request,
151 ApiConfig apiConfig) {
152 Environment env = getCurrentEnvironment();
153 if (delegate == null || env == null) {
154 return new Future<byte[]>() {
155 public byte[] get() {
156 throw new CallNotFoundException(packageName, methodName);
159 public byte[] get(long deadline, TimeUnit unit) {
160 throw new CallNotFoundException(packageName, methodName);
163 public boolean isDone() {
164 return true;
167 public boolean isCancelled() {
168 return false;
171 public boolean cancel(boolean shouldInterrupt) {
172 return false;
176 return delegate.makeAsyncCall(env, packageName, methodName, request, apiConfig);
179 @SuppressWarnings("unchecked")
180 public static void log(LogRecord record) {
181 if (delegate != null) {
182 delegate.log(getCurrentEnvironment(), record);
187 * Synchronously flush all pending application logs.
189 @SuppressWarnings("unchecked")
190 public static void flushLogs() {
191 if (delegate != null) {
192 delegate.flushLogs(getCurrentEnvironment());
197 * Gets the environment associated with this thread. This can be used
198 * to discover additional information about the current request.
200 public static Environment getCurrentEnvironment() {
201 return environmentThreadLocal.get();
205 * Sets a delegate to which we will proxy requests. This should not be
206 * used from user-code.
208 public static void setDelegate(Delegate aDelegate) {
209 delegate = aDelegate;
213 * Gets the delegate to which we will proxy requests. This should really
214 * only be caled from test-code where, for example, you might want to
215 * downcast and invoke methods on a specific implementation that you happen
216 * to know has been installed.
218 public static Delegate getDelegate() {
219 return delegate;
223 * Sets an environment for the current thread. This should not be
224 * used from user-code.
226 public static void setEnvironmentForCurrentThread(Environment environment) {
227 environmentThreadLocal.set(environment);
231 * Removes any environment associated with the current thread. This
232 * should not be used from user-code.
234 public static void clearEnvironmentForCurrentThread() {
235 environmentThreadLocal.set(null);
239 * Returns a list of all threads which are currently running requests.
241 public static List<Thread> getRequestThreads() {
242 Environment env = getCurrentEnvironment();
243 if (delegate == null) {
244 return Collections.emptyList();
245 } else {
246 return delegate.getRequestThreads(env);
251 * Environment is a simple data container that provides additional
252 * information about the current request (e.g. who is logged in, are
253 * they an administrator, etc.).
255 public interface Environment {
257 * Gets the application identifier for the current application.
259 String getAppId();
262 * Gets the version identifier for the current application version.
263 * Result is of the form {@literal <major>.<minor>} where
264 * {@literal <major>} is the version name supplied at deploy time and
265 * {@literal <minor>} is a timestamp value maintained by App Engine.
267 String getVersionId();
270 * Gets the email address of the currently logged-in user.
272 String getEmail();
275 * Returns true if the user is logged in.
277 boolean isLoggedIn();
280 * Returns true if the currently logged-in user is an administrator.
282 boolean isAdmin();
285 * Returns the domain used for authentication.
287 String getAuthDomain();
290 * @deprecated Use {@link NamespaceManager}.getGoogleAppsNamespace()
292 @Deprecated
293 String getRequestNamespace();
296 * Get a {@code Map} containing any attributes that have been set in this
297 * {@code Environment}. The returned {@code Map} is mutable and is a
298 * useful place to store transient, per-request information.
300 Map<String, Object> getAttributes();
303 * Gets the remaining number of milliseconds left before this request receives a
304 * DeadlineExceededException from App Engine. This API can be used for planning how much work
305 * you can reasonably accomplish before the soft deadline kicks in.
307 * If there is no deadline for the request, then this will reply with Long.MAX_VALUE.
309 long getRemainingMillis();
313 * This interface can be used to provide a class that actually
314 * implements API calls.
316 * @param <E> The concrete class implementing Environment that this
317 * Delegate expects to receive.
319 public interface Delegate<E extends Environment> {
321 * Make a synchronous call to the specified method in the specified
322 * API package.
324 * <p>Note: if you have not installed a {@code Delegate} and called
325 * {@code setEnvironmentForCurrentThread} in this thread before
326 * calling this method, it will act like no API calls are available
327 * (i.e. always throw {@code CallNotFoundException}).
329 * @param environment the current request environment.
330 * @param packageName the name of the API package.
331 * @param methodName the name of the method within the API package.
332 * @param request a byte array containing the serialized form of
333 * the request protocol buffer.
335 * @return a byte array containing the serialized form of the
336 * response protocol buffer.
338 * @throws ApplicationException For any error that is the application's fault.
339 * @throws RPCFailedException If we could not connect to a backend service.
340 * @throws CallNotFoundException If the specified method does not exist.
341 * @throws ArgumentException If the request could not be parsed.
342 * @throws DeadlineExceededException If the request took too long.
343 * @throws CancelledException If the request was explicitly cancelled.
344 * @throws UnknownException If any other error occurred.
346 byte[] makeSyncCall(E environment,
347 String packageName,
348 String methodName,
349 byte[] request)
350 throws ApiProxyException;
353 * Make an asynchronous call to the specified method in the specified API package.
355 * <p>Note: if you have not installed a {@code Delegate} and called
356 * {@code setEnvironmentForCurrentThread} in this thread before
357 * calling this method, it will act like no API calls are available
358 * (i.e. always throw {@code CallNotFoundException}).
360 * @param environment the current request environment.
361 * @param packageName the name of the API package.
362 * @param methodName the name of the method within the API package.
363 * @param request a byte array containing the serialized form of
364 * the request protocol buffer.
365 * @param apiConfig that specifies API-specific configuration
366 * parameters.
368 * @return a {@link Future} that will resolve to a byte array
369 * containing the serialized form of the response protocol buffer
370 * on success, or throw one of the exceptions documented for
371 * {@link #makeSyncCall(Environment, String, String, byte[])} on failure.
373 Future<byte[]> makeAsyncCall(E environment,
374 String packageName,
375 String methodName,
376 byte[] request,
377 ApiConfig apiConfig);
379 void log(E environment, LogRecord record);
381 void flushLogs(E environment);
384 * Returns a list of all threads which are currently running requests.
386 List<Thread> getRequestThreads(E environment);
390 * {@code LogRecord} represents a single apphosting log entry,
391 * including a Java-specific logging level, a timestamp in
392 * microseconds, and a message a formatted string containing the
393 * rest of the logging information (e.g. class and line number
394 * information, the message itself, the stack trace for any
395 * exception associated with the log record, etc.).
397 public static final class LogRecord {
398 private final Level level;
399 private final long timestamp;
400 private final String message;
402 public enum Level {
403 debug,
404 info,
405 warn,
406 error,
407 fatal,
410 public LogRecord(Level level, long timestamp, String message) {
411 this.level = level;
412 this.timestamp = timestamp;
413 this.message = message;
417 * A partial copy constructor.
418 * @param other A {@code LogRecord} from which to
419 * copy the {@link #level} and {@link #timestamp}
420 * but not the {@link #message}
421 * @param message
423 public LogRecord(LogRecord other, String message){
424 this(other.level, other.timestamp, message);
427 public Level getLevel() {
428 return level;
432 * Returns the timestamp of the the log message, in microseconds.
434 public long getTimestamp() {
435 return timestamp;
438 public String getMessage() {
439 return message;
444 * {@code ApiConfig} encapsulates one or more configuration
445 * parameters scoped to an individual API call.
447 public static final class ApiConfig {
448 private Double deadlineInSeconds;
451 * Returns the number of seconds that the API call will be allowed
452 * to run, or {@code null} for the default deadline.
454 public Double getDeadlineInSeconds() {
455 return deadlineInSeconds;
459 * Set the number of seconds that the API call will be allowed to
460 * run, or {@code null} for the default deadline.
462 public void setDeadlineInSeconds(Double deadlineInSeconds) {
463 this.deadlineInSeconds = deadlineInSeconds;
466 @Override
467 public boolean equals(Object o) {
468 if (this == o) {
469 return true;
471 if (o == null || getClass() != o.getClass()) {
472 return false;
475 ApiConfig apiConfig = (ApiConfig) o;
477 if (deadlineInSeconds != null ? !deadlineInSeconds.equals(apiConfig.deadlineInSeconds)
478 : apiConfig.deadlineInSeconds != null) {
479 return false;
482 return true;
485 @Override
486 public int hashCode() {
487 return deadlineInSeconds != null ? deadlineInSeconds.hashCode() : 0;
492 * A subtype of {@link Future} that provides more detailed
493 * information about the timing and resource consumption of
494 * particular API calls.
496 * <p>Objects returned from {@link
497 * #makeAsyncCall(String,String,byte[],ApiConfig)} may implement
498 * this interface. However, callers should not currently assume
499 * that all RPCs will.
501 public static interface ApiResultFuture<T> extends Future<T> {
503 * Returns the amount of CPU time consumed across any backend
504 * servers responsible for serving this API call. This quantity
505 * is measured in millions of CPU cycles to avoid suming times
506 * across a hetergeneous machine set with varied CPU clock speeds.
508 * @throws IllegalStateException If the RPC has not yet completed.
510 long getCpuTimeInMegaCycles();
513 * Returns the amount of wallclock time, measured in milliseconds,
514 * that this API call took to complete, as measured from the
515 * client side.
517 * @throws IllegalStateException If the RPC has not yet completed.
519 long getWallclockTimeInMillis();
522 public static class ApiProxyException extends RuntimeException {
523 public ApiProxyException(String message, String packageName, String methodName) {
524 this(String.format(message, packageName, methodName));
527 private ApiProxyException(String message, String packageName, String methodName,
528 Throwable nestedException) {
529 super(String.format(message, packageName, methodName), nestedException);
532 public ApiProxyException(String message) {
533 super(message);
537 * Clones this exception and then sets this Exception as the cause
538 * of the clone and sets the given stack trace in the clone.
540 * @param stackTrace The stack trace to set in the returned clone
541 * @return a clone of this Exception with this Exception as
542 * the cause and with the given stack trace.
544 public ApiProxyException copy(StackTraceElement[] stackTrace) {
545 ApiProxyException theCopy = cloneWithoutStackTrace();
546 theCopy.setStackTrace(stackTrace);
547 theCopy.initCause(this);
548 return theCopy;
551 protected ApiProxyException cloneWithoutStackTrace() {
552 return new ApiProxyException(this.getMessage());
557 public static class ApplicationException extends ApiProxyException {
558 private final int applicationError;
559 private final String errorDetail;
561 public ApplicationException(int applicationError) {
562 this(applicationError, "");
565 public ApplicationException(int applicationError, String errorDetail) {
566 super("ApplicationError: " + applicationError + ": " + errorDetail);
567 this.applicationError = applicationError;
568 this.errorDetail = errorDetail;
571 public int getApplicationError() {
572 return applicationError;
575 public String getErrorDetail() {
576 return errorDetail;
579 @Override
580 protected ApplicationException cloneWithoutStackTrace() {
581 return new ApplicationException(applicationError, errorDetail);
585 public static class RPCFailedException extends ApiProxyException {
586 public RPCFailedException(String packageName, String methodName) {
587 super("The remote RPC to the application server failed for the " +
588 "call %s.%s().",
589 packageName, methodName);
592 private RPCFailedException(String message) {
593 super(message);
596 @Override
597 protected RPCFailedException cloneWithoutStackTrace() {
598 return new RPCFailedException(this.getMessage());
602 public static class CallNotFoundException extends ApiProxyException {
603 public CallNotFoundException(String packageName, String methodName) {
604 super("The API package '%s' or call '%s()' was not found.",
605 packageName, methodName);
608 private CallNotFoundException(String message) {
609 super(message);
612 @Override
613 public CallNotFoundException cloneWithoutStackTrace() {
614 return new CallNotFoundException(this.getMessage());
618 public static class ArgumentException extends ApiProxyException {
619 public ArgumentException(String packageName, String methodName) {
620 super("An error occurred parsing (locally or remotely) the " +
621 "arguments to %S.%s().",
622 packageName, methodName);
625 private ArgumentException(String message) {
626 super(message);
629 @Override
630 public ArgumentException cloneWithoutStackTrace() {
631 return new ArgumentException(this.getMessage());
635 public static class ApiDeadlineExceededException extends ApiProxyException {
636 public ApiDeadlineExceededException(String packageName, String methodName) {
637 super("The API call %s.%s() took too long to respond and was cancelled.",
638 packageName, methodName);
641 private ApiDeadlineExceededException(String message) {
642 super(message);
645 @Override
646 public ApiDeadlineExceededException cloneWithoutStackTrace() {
647 return new ApiDeadlineExceededException(this.getMessage());
651 public static class CancelledException extends ApiProxyException {
652 public CancelledException(String packageName, String methodName) {
653 super("The API call %s.%s() was explicitly cancelled.",
654 packageName, methodName);
657 private CancelledException(String message) {
658 super(message);
661 @Override
662 public CancelledException cloneWithoutStackTrace() {
663 return new CancelledException(this.getMessage());
668 public static class CapabilityDisabledException extends ApiProxyException {
669 public CapabilityDisabledException(String message, String packageName, String methodName) {
670 super("The API call %s.%s() is temporarily unavailable: " + message,
671 packageName, methodName);
674 private CapabilityDisabledException(String message) {
675 super(message);
678 @Override
679 public CapabilityDisabledException cloneWithoutStackTrace() {
680 return new CapabilityDisabledException(this.getMessage());
684 public static class FeatureNotEnabledException extends ApiProxyException {
685 public FeatureNotEnabledException(String message,
686 String packageName,
687 String methodName) {
688 super(message, packageName, methodName);
691 public FeatureNotEnabledException(String message) {
692 super(message);
695 @Override
696 public FeatureNotEnabledException cloneWithoutStackTrace() {
697 return new FeatureNotEnabledException(this.getMessage());
702 public static class OverQuotaException extends ApiProxyException {
703 public OverQuotaException(String packageName, String methodName) {
704 super("The API call %s.%s() required more quota than is available.",
705 packageName, methodName);
708 private OverQuotaException(String message) {
709 super(message);
712 @Override
713 public OverQuotaException cloneWithoutStackTrace() {
714 return new OverQuotaException(this.getMessage());
719 public static class RequestTooLargeException extends ApiProxyException {
720 public RequestTooLargeException(String packageName, String methodName) {
721 super("The request to API call %s.%s() was too large.",
722 packageName, methodName);
725 private RequestTooLargeException(String message) {
726 super(message);
729 @Override
730 public RequestTooLargeException cloneWithoutStackTrace() {
731 return new RequestTooLargeException(this.getMessage());
735 public static class ResponseTooLargeException extends ApiProxyException {
736 public ResponseTooLargeException(String packageName, String methodName) {
737 super("The response from API call %s.%s() was too large.",
738 packageName, methodName);
741 private ResponseTooLargeException(String message) {
742 super(message);
745 @Override
746 public ResponseTooLargeException cloneWithoutStackTrace() {
747 return new ResponseTooLargeException(this.getMessage());
751 public static class UnknownException extends ApiProxyException {
753 public UnknownException(String packageName, String methodName, Throwable nestedException) {
754 super("An error occurred for the API request %s.%s().",
755 packageName, methodName, nestedException);
758 public UnknownException(String packageName, String methodName) {
759 super("An error occurred for the API request %s.%s().",
760 packageName, methodName);
763 public UnknownException(String message) {
764 super(message);
767 @Override
768 public UnknownException cloneWithoutStackTrace() {
769 return new UnknownException(this.getMessage());