Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / apphosting / api / ApiProxy.java
blobc7e3bef2d98e73e49f63d9cf0a0a2fd94063e7e7
1 // Copyright 2007 Google Inc. All rights reserved.
3 package com.google.apphosting.api;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.concurrent.Future;
10 import java.util.concurrent.TimeUnit;
12 /**
13 * ApiProxy is a static class that serves as the collection point for
14 * all API calls from user code into the application server.
16 * It is responsible for proxying makeSyncCall() calls to a delegate,
17 * which actually implements the API calls. It also stores an
18 * Environment for each thread, which contains additional user-visible
19 * information about the request.
22 public class ApiProxy {
23 private static final String API_DEADLINE_KEY =
24 "com.google.apphosting.api.ApiProxy.api_deadline_key";
26 /**
27 * Store an environment object for each thread.
29 private static final ThreadLocal<Environment> environmentThreadLocal =
30 new ThreadLocal<Environment>();
32 /**
33 * Store a single delegate, to which we proxy all makeSyncCall requests.
35 private static Delegate delegate;
37 /**
38 * Logging records outside the scope of a request are lazily logged.
40 private static List<LogRecord> outOfBandLogs = new ArrayList<LogRecord>();
42 /**
43 * All methods are static. Do not instantiate.
45 private ApiProxy() {
48 /**
49 * @see #makeSyncCall(String,String,byte[],ApiConfig)
51 public static byte[] makeSyncCall(String packageName,
52 String methodName,
53 byte[] request)
54 throws ApiProxyException {
55 return makeSyncCall(packageName, methodName, request, null);
58 /**
59 * Make a synchronous call to the specified method in the specified
60 * API package.
62 * <p>Note: if you have not installed a {@code Delegate} and called
63 * {@code setEnvironmentForCurrentThread} in this thread before
64 * calling this method, it will act like no API calls are available
65 * (i.e. always throw {@code CallNotFoundException}).
67 * @param packageName the name of the API package.
68 * @param methodName the name of the method within the API package.
69 * @param request a byte array containing the serialized form of the
70 * request protocol buffer.
71 * @param apiConfig that specifies API-specific configuration
72 * parameters.
74 * @return a byte array containing the serialized form of the
75 * response protocol buffer.
78 * @throws ApplicationException For any error that is the application's fault.
79 * @throws RPCFailedException If we could not connect to a backend service.
80 * @throws CallNotFoundException If the specified method does not exist.
81 * @throws ArgumentException If the request could not be parsed.
82 * @throws ApiDeadlineExceededException If the request took too long.
83 * @throws CancelledException If the request was explicitly cancelled.
84 * @throws CapabilityDisabledException If the API call is currently
85 * unavailable.
86 * @throws OverQuotaException If the API call required more quota than is
87 * available.
88 * @throws RequestTooLargeException If the request to the API was too large.
89 * @throws ResponseTooLargeException If the response to the API was too large.
90 * @throws UnknownException If any other error occurred.
92 @SuppressWarnings("unchecked")
93 public static byte[] makeSyncCall(String packageName,
94 String methodName,
95 byte[] request,
96 ApiConfig apiConfig)
97 throws ApiProxyException {
98 Environment env = getCurrentEnvironment();
99 if (delegate == null || env == null) {
100 throw new CallNotFoundException(packageName, methodName);
102 if (apiConfig == null || apiConfig.getDeadlineInSeconds() == null) {
103 return delegate.makeSyncCall(env, packageName, methodName, request);
104 } else {
105 Object oldValue = env.getAttributes().put(API_DEADLINE_KEY, apiConfig.getDeadlineInSeconds());
106 try {
107 return delegate.makeSyncCall(env, packageName, methodName, request);
108 } finally {
109 if (oldValue == null) {
110 env.getAttributes().remove(API_DEADLINE_KEY);
111 } else {
112 env.getAttributes().put(API_DEADLINE_KEY, oldValue);
119 * @see #makeAsyncCall(String,String,byte[],ApiConfig)
121 public static Future<byte[]> makeAsyncCall(String packageName,
122 String methodName,
123 byte[] request) {
124 return makeAsyncCall(packageName, methodName, request, new ApiConfig());
128 * Make an asynchronous call to the specified method in the
129 * specified API package.
131 * <p>Note: if you have not installed a {@code Delegate} and called
132 * {@code setEnvironmentForCurrentThread} in this thread before
133 * calling this method, it will act like no API calls are available
134 * (i.e. the returned {@link Future} will throw {@code
135 * CallNotFoundException}).
137 * <p>There is a limit to the number of simultaneous asynchronous
138 * API calls (currently 100). Invoking this method while this number
139 * of API calls are outstanding will block.
141 * @param packageName the name of the API package.
142 * @param methodName the name of the method within the API package.
143 * @param request a byte array containing the serialized form of
144 * the request protocol buffer.
145 * @param apiConfig that specifies API-specific configuration
146 * parameters.
148 * @return a {@link Future} that will resolve to a byte array
149 * containing the serialized form of the response protocol buffer
150 * on success, or throw one of the exceptions documented for
151 * {@link #makeSyncCall(String, String, byte[], ApiConfig)} on failure.
153 @SuppressWarnings("unchecked")
154 public static Future<byte[]> makeAsyncCall(final String packageName,
155 final String methodName,
156 byte[] request,
157 ApiConfig apiConfig) {
158 Environment env = getCurrentEnvironment();
159 if (delegate == null || env == null) {
160 return new Future<byte[]>() {
161 public byte[] get() {
162 throw new CallNotFoundException(packageName, methodName);
165 public byte[] get(long deadline, TimeUnit unit) {
166 throw new CallNotFoundException(packageName, methodName);
169 public boolean isDone() {
170 return true;
173 public boolean isCancelled() {
174 return false;
177 public boolean cancel(boolean shouldInterrupt) {
178 return false;
182 return delegate.makeAsyncCall(env, packageName, methodName, request, apiConfig);
185 @SuppressWarnings("unchecked")
186 public static void log(LogRecord record) {
187 Environment env = getCurrentEnvironment();
188 if (delegate != null && env != null) {
189 delegate.log(env, record);
190 return;
193 synchronized (outOfBandLogs) {
194 outOfBandLogs.add(record);
198 private static void possiblyFlushOutOfBandLogs() {
199 Environment env = getCurrentEnvironment();
200 if (delegate != null && env != null) {
201 List<LogRecord> logsToWrite;
202 synchronized (outOfBandLogs) {
203 logsToWrite = new ArrayList<LogRecord>(outOfBandLogs);
204 outOfBandLogs.clear();
206 for (LogRecord record : logsToWrite) {
207 delegate.log(env, record);
213 * Synchronously flush all pending application logs.
215 @SuppressWarnings("unchecked")
216 public static void flushLogs() {
217 if (delegate != null) {
218 delegate.flushLogs(getCurrentEnvironment());
223 * Gets the environment associated with this thread. This can be used
224 * to discover additional information about the current request.
226 public static Environment getCurrentEnvironment() {
227 return environmentThreadLocal.get();
231 * Sets a delegate to which we will proxy requests. This should not be
232 * used from user-code.
234 public static void setDelegate(Delegate aDelegate) {
235 delegate = aDelegate;
236 possiblyFlushOutOfBandLogs();
240 * Gets the delegate to which we will proxy requests. This should really
241 * only be called from test-code where, for example, you might want to
242 * downcast and invoke methods on a specific implementation that you happen
243 * to know has been installed.
245 public static Delegate getDelegate() {
246 return delegate;
250 * Sets an environment for the current thread. This should not be
251 * used from user-code.
253 public static void setEnvironmentForCurrentThread(Environment environment) {
254 environmentThreadLocal.set(environment);
255 possiblyFlushOutOfBandLogs();
259 * Removes any environment associated with the current thread. This
260 * should not be used from user-code.
262 public static void clearEnvironmentForCurrentThread() {
263 environmentThreadLocal.set(null);
267 * Returns a list of all threads which are currently running requests.
269 public static List<Thread> getRequestThreads() {
270 Environment env = getCurrentEnvironment();
271 if (delegate == null) {
272 return Collections.emptyList();
273 } else {
274 return delegate.getRequestThreads(env);
279 * Environment is a simple data container that provides additional
280 * information about the current request (e.g. who is logged in, are
281 * they an administrator, etc.).
283 public interface Environment {
285 * Gets the application identifier for the current application.
287 String getAppId();
290 * Gets the module identifier for the current application instance.
292 String getModuleId();
295 * Gets the version identifier for the current application version.
296 * Result is of the form {@literal <major>.<minor>} where
297 * {@literal <major>} is the version name supplied at deploy time and
298 * {@literal <minor>} is a timestamp value maintained by App Engine.
300 String getVersionId();
303 * Gets the email address of the currently logged-in user.
305 String getEmail();
308 * Returns true if the user is logged in.
310 boolean isLoggedIn();
313 * Returns true if the currently logged-in user is an administrator.
315 boolean isAdmin();
318 * Returns the domain used for authentication.
320 String getAuthDomain();
323 * @deprecated Use {@link NamespaceManager}.getGoogleAppsNamespace()
325 @Deprecated
326 String getRequestNamespace();
329 * Get a {@code Map} containing any attributes that have been set in this
330 * {@code Environment}. The returned {@code Map} is mutable and is a
331 * useful place to store transient, per-request information.
333 Map<String, Object> getAttributes();
336 * Gets the remaining number of milliseconds left before this request receives a
337 * DeadlineExceededException from App Engine. This API can be used for planning how much work
338 * you can reasonably accomplish before the soft deadline kicks in.
340 * If there is no deadline for the request, then this will reply with Long.MAX_VALUE.
342 long getRemainingMillis();
346 * This interface can be used to provide a class that actually
347 * implements API calls.
349 * @param <E> The concrete class implementing Environment that this
350 * Delegate expects to receive.
352 public interface Delegate<E extends Environment> {
354 * Make a synchronous call to the specified method in the specified
355 * API package.
357 * <p>Note: if you have not installed a {@code Delegate} and called
358 * {@code setEnvironmentForCurrentThread} in this thread before
359 * calling this method, it will act like no API calls are available
360 * (i.e. always throw {@code CallNotFoundException}).
362 * @param environment the current request environment.
363 * @param packageName the name of the API package.
364 * @param methodName the name of the method within the API package.
365 * @param request a byte array containing the serialized form of
366 * the request protocol buffer.
368 * @return a byte array containing the serialized form of the
369 * response protocol buffer.
371 * @throws ApplicationException For any error that is the application's fault.
372 * @throws RPCFailedException If we could not connect to a backend service.
373 * @throws CallNotFoundException If the specified method does not exist.
374 * @throws ArgumentException If the request could not be parsed.
375 * @throws DeadlineExceededException If the request took too long.
376 * @throws CancelledException If the request was explicitly cancelled.
377 * @throws UnknownException If any other error occurred.
379 byte[] makeSyncCall(E environment,
380 String packageName,
381 String methodName,
382 byte[] request)
383 throws ApiProxyException;
386 * Make an asynchronous call to the specified method in the specified API package.
388 * <p>Note: if you have not installed a {@code Delegate} and called
389 * {@code setEnvironmentForCurrentThread} in this thread before
390 * calling this method, it will act like no API calls are available
391 * (i.e. always throw {@code CallNotFoundException}).
393 * @param environment the current request environment.
394 * @param packageName the name of the API package.
395 * @param methodName the name of the method within the API package.
396 * @param request a byte array containing the serialized form of
397 * the request protocol buffer.
398 * @param apiConfig that specifies API-specific configuration
399 * parameters.
401 * @return a {@link Future} that will resolve to a byte array
402 * containing the serialized form of the response protocol buffer
403 * on success, or throw one of the exceptions documented for
404 * {@link #makeSyncCall(Environment, String, String, byte[])} on failure.
406 Future<byte[]> makeAsyncCall(E environment,
407 String packageName,
408 String methodName,
409 byte[] request,
410 ApiConfig apiConfig);
412 void log(E environment, LogRecord record);
414 void flushLogs(E environment);
417 * Returns a list of all threads which are currently running requests.
419 List<Thread> getRequestThreads(E environment);
423 * {@code LogRecord} represents a single apphosting log entry,
424 * including a Java-specific logging level, a timestamp in
425 * microseconds, and a message a formatted string containing the
426 * rest of the logging information (e.g. class and line number
427 * information, the message itself, the stack trace for any
428 * exception associated with the log record, etc.).
430 public static final class LogRecord {
431 private final Level level;
432 private final long timestamp;
433 private final String message;
435 public enum Level {
436 debug,
437 info,
438 warn,
439 error,
440 fatal,
443 public LogRecord(Level level, long timestamp, String message) {
444 this.level = level;
445 this.timestamp = timestamp;
446 this.message = message;
450 * A partial copy constructor.
451 * @param other A {@code LogRecord} from which to
452 * copy the {@link #level} and {@link #timestamp}
453 * but not the {@link #message}
454 * @param message
456 public LogRecord(LogRecord other, String message){
457 this(other.level, other.timestamp, message);
460 public Level getLevel() {
461 return level;
465 * Returns the timestamp of the log message, in microseconds.
467 public long getTimestamp() {
468 return timestamp;
471 public String getMessage() {
472 return message;
477 * {@code ApiConfig} encapsulates one or more configuration
478 * parameters scoped to an individual API call.
480 public static final class ApiConfig {
481 private Double deadlineInSeconds;
484 * Returns the number of seconds that the API call will be allowed
485 * to run, or {@code null} for the default deadline.
487 public Double getDeadlineInSeconds() {
488 return deadlineInSeconds;
492 * Set the number of seconds that the API call will be allowed to
493 * run, or {@code null} for the default deadline.
495 public void setDeadlineInSeconds(Double deadlineInSeconds) {
496 this.deadlineInSeconds = deadlineInSeconds;
499 @Override
500 public boolean equals(Object o) {
501 if (this == o) {
502 return true;
504 if (o == null || getClass() != o.getClass()) {
505 return false;
508 ApiConfig apiConfig = (ApiConfig) o;
510 if (deadlineInSeconds != null ? !deadlineInSeconds.equals(apiConfig.deadlineInSeconds)
511 : apiConfig.deadlineInSeconds != null) {
512 return false;
515 return true;
518 @Override
519 public int hashCode() {
520 return deadlineInSeconds != null ? deadlineInSeconds.hashCode() : 0;
525 * A subtype of {@link Future} that provides more detailed
526 * information about the timing and resource consumption of
527 * particular API calls.
529 * <p>Objects returned from {@link
530 * #makeAsyncCall(String,String,byte[],ApiConfig)} may implement
531 * this interface. However, callers should not currently assume
532 * that all RPCs will.
534 public static interface ApiResultFuture<T> extends Future<T> {
536 * Returns the amount of CPU time consumed across any backend
537 * servers responsible for serving this API call. This quantity
538 * is measured in millions of CPU cycles to avoid suming times
539 * across a hetergeneous machine set with varied CPU clock speeds.
541 * @throws IllegalStateException If the RPC has not yet completed.
543 long getCpuTimeInMegaCycles();
546 * Returns the amount of wallclock time, measured in milliseconds,
547 * that this API call took to complete, as measured from the
548 * client side.
550 * @throws IllegalStateException If the RPC has not yet completed.
552 long getWallclockTimeInMillis();
555 public static class ApiProxyException extends RuntimeException {
556 public ApiProxyException(String message, String packageName, String methodName) {
557 this(String.format(message, packageName, methodName));
560 private ApiProxyException(String message, String packageName, String methodName,
561 Throwable nestedException) {
562 super(String.format(message, packageName, methodName), nestedException);
565 public ApiProxyException(String message) {
566 super(message);
570 * Clones this exception and then sets this Exception as the cause
571 * of the clone and sets the given stack trace in the clone.
573 * @param stackTrace The stack trace to set in the returned clone
574 * @return a clone of this Exception with this Exception as
575 * the cause and with the given stack trace.
577 public ApiProxyException copy(StackTraceElement[] stackTrace) {
578 ApiProxyException theCopy = cloneWithoutStackTrace();
579 theCopy.setStackTrace(stackTrace);
580 theCopy.initCause(this);
581 return theCopy;
584 protected ApiProxyException cloneWithoutStackTrace() {
585 return new ApiProxyException(this.getMessage());
590 public static class ApplicationException extends ApiProxyException {
591 private final int applicationError;
592 private final String errorDetail;
594 public ApplicationException(int applicationError) {
595 this(applicationError, "");
598 public ApplicationException(int applicationError, String errorDetail) {
599 super("ApplicationError: " + applicationError + ": " + errorDetail);
600 this.applicationError = applicationError;
601 this.errorDetail = errorDetail;
604 public int getApplicationError() {
605 return applicationError;
608 public String getErrorDetail() {
609 return errorDetail;
612 @Override
613 protected ApplicationException cloneWithoutStackTrace() {
614 return new ApplicationException(applicationError, errorDetail);
618 public static class RPCFailedException extends ApiProxyException {
619 public RPCFailedException(String packageName, String methodName) {
620 super("The remote RPC to the application server failed for the " +
621 "call %s.%s().",
622 packageName, methodName);
625 private RPCFailedException(String message) {
626 super(message);
629 @Override
630 protected RPCFailedException cloneWithoutStackTrace() {
631 return new RPCFailedException(this.getMessage());
635 public static class CallNotFoundException extends ApiProxyException {
636 public CallNotFoundException(String packageName, String methodName) {
637 super("The API package '%s' or call '%s()' was not found.",
638 packageName, methodName);
641 private CallNotFoundException(String message) {
642 super(message);
645 @Override
646 public CallNotFoundException cloneWithoutStackTrace() {
647 return new CallNotFoundException(this.getMessage());
651 public static class ArgumentException extends ApiProxyException {
652 public ArgumentException(String packageName, String methodName) {
653 super("An error occurred parsing (locally or remotely) the " +
654 "arguments to %S.%s().",
655 packageName, methodName);
658 private ArgumentException(String message) {
659 super(message);
662 @Override
663 public ArgumentException cloneWithoutStackTrace() {
664 return new ArgumentException(this.getMessage());
668 public static class ApiDeadlineExceededException extends ApiProxyException {
669 public ApiDeadlineExceededException(String packageName, String methodName) {
670 super("The API call %s.%s() took too long to respond and was cancelled.",
671 packageName, methodName);
674 private ApiDeadlineExceededException(String message) {
675 super(message);
678 @Override
679 public ApiDeadlineExceededException cloneWithoutStackTrace() {
680 return new ApiDeadlineExceededException(this.getMessage());
684 public static class CancelledException extends ApiProxyException {
685 public CancelledException(String packageName, String methodName) {
686 super("The API call %s.%s() was explicitly cancelled.",
687 packageName, methodName);
690 private CancelledException(String message) {
691 super(message);
694 @Override
695 public CancelledException cloneWithoutStackTrace() {
696 return new CancelledException(this.getMessage());
701 public static class CapabilityDisabledException extends ApiProxyException {
702 public CapabilityDisabledException(String message, String packageName, String methodName) {
703 super("The API call %s.%s() is temporarily unavailable: " + message,
704 packageName, methodName);
707 private CapabilityDisabledException(String message) {
708 super(message);
711 @Override
712 public CapabilityDisabledException cloneWithoutStackTrace() {
713 return new CapabilityDisabledException(this.getMessage());
717 public static class FeatureNotEnabledException extends ApiProxyException {
718 public FeatureNotEnabledException(String message,
719 String packageName,
720 String methodName) {
721 super(message, packageName, methodName);
724 public FeatureNotEnabledException(String message) {
725 super(message);
728 @Override
729 public FeatureNotEnabledException cloneWithoutStackTrace() {
730 return new FeatureNotEnabledException(this.getMessage());
735 public static class OverQuotaException extends ApiProxyException {
736 public OverQuotaException(String packageName, String methodName) {
737 super("The API call %s.%s() required more quota than is available.",
738 packageName, methodName);
741 private OverQuotaException(String message) {
742 super(message);
745 @Override
746 public OverQuotaException cloneWithoutStackTrace() {
747 return new OverQuotaException(this.getMessage());
752 public static class RequestTooLargeException extends ApiProxyException {
753 public RequestTooLargeException(String packageName, String methodName) {
754 super("The request to API call %s.%s() was too large.",
755 packageName, methodName);
758 private RequestTooLargeException(String message) {
759 super(message);
762 @Override
763 public RequestTooLargeException cloneWithoutStackTrace() {
764 return new RequestTooLargeException(this.getMessage());
768 public static class ResponseTooLargeException extends ApiProxyException {
769 public ResponseTooLargeException(String packageName, String methodName) {
770 super("The response from API call %s.%s() was too large.",
771 packageName, methodName);
774 private ResponseTooLargeException(String message) {
775 super(message);
778 @Override
779 public ResponseTooLargeException cloneWithoutStackTrace() {
780 return new ResponseTooLargeException(this.getMessage());
784 public static class UnknownException extends ApiProxyException {
786 public UnknownException(String packageName, String methodName, Throwable nestedException) {
787 super("An error occurred for the API request %s.%s().",
788 packageName, methodName, nestedException);
791 public UnknownException(String packageName, String methodName) {
792 super("An error occurred for the API request %s.%s().",
793 packageName, methodName);
796 public UnknownException(String message) {
797 super(message);
800 @Override
801 public UnknownException cloneWithoutStackTrace() {
802 return new UnknownException(this.getMessage());