Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / apphosting / api / ApiProxy.java
blob521c6add06093b9a81b0ecc0f3be73010c33a974
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 synchronized (outOfBandLogs) {
202 for (LogRecord record : outOfBandLogs) {
203 delegate.log(env, record);
205 outOfBandLogs.clear();
211 * Synchronously flush all pending application logs.
213 @SuppressWarnings("unchecked")
214 public static void flushLogs() {
215 if (delegate != null) {
216 delegate.flushLogs(getCurrentEnvironment());
221 * Gets the environment associated with this thread. This can be used
222 * to discover additional information about the current request.
224 public static Environment getCurrentEnvironment() {
225 return environmentThreadLocal.get();
229 * Sets a delegate to which we will proxy requests. This should not be
230 * used from user-code.
232 public static void setDelegate(Delegate aDelegate) {
233 delegate = aDelegate;
234 possiblyFlushOutOfBandLogs();
238 * Gets the delegate to which we will proxy requests. This should really
239 * only be caled from test-code where, for example, you might want to
240 * downcast and invoke methods on a specific implementation that you happen
241 * to know has been installed.
243 public static Delegate getDelegate() {
244 return delegate;
248 * Sets an environment for the current thread. This should not be
249 * used from user-code.
251 public static void setEnvironmentForCurrentThread(Environment environment) {
252 environmentThreadLocal.set(environment);
253 possiblyFlushOutOfBandLogs();
257 * Removes any environment associated with the current thread. This
258 * should not be used from user-code.
260 public static void clearEnvironmentForCurrentThread() {
261 environmentThreadLocal.set(null);
265 * Returns a list of all threads which are currently running requests.
267 public static List<Thread> getRequestThreads() {
268 Environment env = getCurrentEnvironment();
269 if (delegate == null) {
270 return Collections.emptyList();
271 } else {
272 return delegate.getRequestThreads(env);
277 * Environment is a simple data container that provides additional
278 * information about the current request (e.g. who is logged in, are
279 * they an administrator, etc.).
281 public interface Environment {
283 * Gets the application identifier for the current application.
285 String getAppId();
288 * Gets the version identifier for the current application version.
289 * Result is of the form {@literal <major>.<minor>} where
290 * {@literal <major>} is the version name supplied at deploy time and
291 * {@literal <minor>} is a timestamp value maintained by App Engine.
293 String getVersionId();
296 * Gets the email address of the currently logged-in user.
298 String getEmail();
301 * Returns true if the user is logged in.
303 boolean isLoggedIn();
306 * Returns true if the currently logged-in user is an administrator.
308 boolean isAdmin();
311 * Returns the domain used for authentication.
313 String getAuthDomain();
316 * @deprecated Use {@link NamespaceManager}.getGoogleAppsNamespace()
318 @Deprecated
319 String getRequestNamespace();
322 * Get a {@code Map} containing any attributes that have been set in this
323 * {@code Environment}. The returned {@code Map} is mutable and is a
324 * useful place to store transient, per-request information.
326 Map<String, Object> getAttributes();
329 * Gets the remaining number of milliseconds left before this request receives a
330 * DeadlineExceededException from App Engine. This API can be used for planning how much work
331 * you can reasonably accomplish before the soft deadline kicks in.
333 * If there is no deadline for the request, then this will reply with Long.MAX_VALUE.
335 long getRemainingMillis();
339 * This interface can be used to provide a class that actually
340 * implements API calls.
342 * @param <E> The concrete class implementing Environment that this
343 * Delegate expects to receive.
345 public interface Delegate<E extends Environment> {
347 * Make a synchronous call to the specified method in the specified
348 * API package.
350 * <p>Note: if you have not installed a {@code Delegate} and called
351 * {@code setEnvironmentForCurrentThread} in this thread before
352 * calling this method, it will act like no API calls are available
353 * (i.e. always throw {@code CallNotFoundException}).
355 * @param environment the current request environment.
356 * @param packageName the name of the API package.
357 * @param methodName the name of the method within the API package.
358 * @param request a byte array containing the serialized form of
359 * the request protocol buffer.
361 * @return a byte array containing the serialized form of the
362 * response protocol buffer.
364 * @throws ApplicationException For any error that is the application's fault.
365 * @throws RPCFailedException If we could not connect to a backend service.
366 * @throws CallNotFoundException If the specified method does not exist.
367 * @throws ArgumentException If the request could not be parsed.
368 * @throws DeadlineExceededException If the request took too long.
369 * @throws CancelledException If the request was explicitly cancelled.
370 * @throws UnknownException If any other error occurred.
372 byte[] makeSyncCall(E environment,
373 String packageName,
374 String methodName,
375 byte[] request)
376 throws ApiProxyException;
379 * Make an asynchronous call to the specified method in the specified API package.
381 * <p>Note: if you have not installed a {@code Delegate} and called
382 * {@code setEnvironmentForCurrentThread} in this thread before
383 * calling this method, it will act like no API calls are available
384 * (i.e. always throw {@code CallNotFoundException}).
386 * @param environment the current request environment.
387 * @param packageName the name of the API package.
388 * @param methodName the name of the method within the API package.
389 * @param request a byte array containing the serialized form of
390 * the request protocol buffer.
391 * @param apiConfig that specifies API-specific configuration
392 * parameters.
394 * @return a {@link Future} that will resolve to a byte array
395 * containing the serialized form of the response protocol buffer
396 * on success, or throw one of the exceptions documented for
397 * {@link #makeSyncCall(Environment, String, String, byte[])} on failure.
399 Future<byte[]> makeAsyncCall(E environment,
400 String packageName,
401 String methodName,
402 byte[] request,
403 ApiConfig apiConfig);
405 void log(E environment, LogRecord record);
407 void flushLogs(E environment);
410 * Returns a list of all threads which are currently running requests.
412 List<Thread> getRequestThreads(E environment);
416 * {@code LogRecord} represents a single apphosting log entry,
417 * including a Java-specific logging level, a timestamp in
418 * microseconds, and a message a formatted string containing the
419 * rest of the logging information (e.g. class and line number
420 * information, the message itself, the stack trace for any
421 * exception associated with the log record, etc.).
423 public static final class LogRecord {
424 private final Level level;
425 private final long timestamp;
426 private final String message;
428 public enum Level {
429 debug,
430 info,
431 warn,
432 error,
433 fatal,
436 public LogRecord(Level level, long timestamp, String message) {
437 this.level = level;
438 this.timestamp = timestamp;
439 this.message = message;
443 * A partial copy constructor.
444 * @param other A {@code LogRecord} from which to
445 * copy the {@link #level} and {@link #timestamp}
446 * but not the {@link #message}
447 * @param message
449 public LogRecord(LogRecord other, String message){
450 this(other.level, other.timestamp, message);
453 public Level getLevel() {
454 return level;
458 * Returns the timestamp of the the log message, in microseconds.
460 public long getTimestamp() {
461 return timestamp;
464 public String getMessage() {
465 return message;
470 * {@code ApiConfig} encapsulates one or more configuration
471 * parameters scoped to an individual API call.
473 public static final class ApiConfig {
474 private Double deadlineInSeconds;
477 * Returns the number of seconds that the API call will be allowed
478 * to run, or {@code null} for the default deadline.
480 public Double getDeadlineInSeconds() {
481 return deadlineInSeconds;
485 * Set the number of seconds that the API call will be allowed to
486 * run, or {@code null} for the default deadline.
488 public void setDeadlineInSeconds(Double deadlineInSeconds) {
489 this.deadlineInSeconds = deadlineInSeconds;
492 @Override
493 public boolean equals(Object o) {
494 if (this == o) {
495 return true;
497 if (o == null || getClass() != o.getClass()) {
498 return false;
501 ApiConfig apiConfig = (ApiConfig) o;
503 if (deadlineInSeconds != null ? !deadlineInSeconds.equals(apiConfig.deadlineInSeconds)
504 : apiConfig.deadlineInSeconds != null) {
505 return false;
508 return true;
511 @Override
512 public int hashCode() {
513 return deadlineInSeconds != null ? deadlineInSeconds.hashCode() : 0;
518 * A subtype of {@link Future} that provides more detailed
519 * information about the timing and resource consumption of
520 * particular API calls.
522 * <p>Objects returned from {@link
523 * #makeAsyncCall(String,String,byte[],ApiConfig)} may implement
524 * this interface. However, callers should not currently assume
525 * that all RPCs will.
527 public static interface ApiResultFuture<T> extends Future<T> {
529 * Returns the amount of CPU time consumed across any backend
530 * servers responsible for serving this API call. This quantity
531 * is measured in millions of CPU cycles to avoid suming times
532 * across a hetergeneous machine set with varied CPU clock speeds.
534 * @throws IllegalStateException If the RPC has not yet completed.
536 long getCpuTimeInMegaCycles();
539 * Returns the amount of wallclock time, measured in milliseconds,
540 * that this API call took to complete, as measured from the
541 * client side.
543 * @throws IllegalStateException If the RPC has not yet completed.
545 long getWallclockTimeInMillis();
548 public static class ApiProxyException extends RuntimeException {
549 public ApiProxyException(String message, String packageName, String methodName) {
550 this(String.format(message, packageName, methodName));
553 private ApiProxyException(String message, String packageName, String methodName,
554 Throwable nestedException) {
555 super(String.format(message, packageName, methodName), nestedException);
558 public ApiProxyException(String message) {
559 super(message);
563 * Clones this exception and then sets this Exception as the cause
564 * of the clone and sets the given stack trace in the clone.
566 * @param stackTrace The stack trace to set in the returned clone
567 * @return a clone of this Exception with this Exception as
568 * the cause and with the given stack trace.
570 public ApiProxyException copy(StackTraceElement[] stackTrace) {
571 ApiProxyException theCopy = cloneWithoutStackTrace();
572 theCopy.setStackTrace(stackTrace);
573 theCopy.initCause(this);
574 return theCopy;
577 protected ApiProxyException cloneWithoutStackTrace() {
578 return new ApiProxyException(this.getMessage());
583 public static class ApplicationException extends ApiProxyException {
584 private final int applicationError;
585 private final String errorDetail;
587 public ApplicationException(int applicationError) {
588 this(applicationError, "");
591 public ApplicationException(int applicationError, String errorDetail) {
592 super("ApplicationError: " + applicationError + ": " + errorDetail);
593 this.applicationError = applicationError;
594 this.errorDetail = errorDetail;
597 public int getApplicationError() {
598 return applicationError;
601 public String getErrorDetail() {
602 return errorDetail;
605 @Override
606 protected ApplicationException cloneWithoutStackTrace() {
607 return new ApplicationException(applicationError, errorDetail);
611 public static class RPCFailedException extends ApiProxyException {
612 public RPCFailedException(String packageName, String methodName) {
613 super("The remote RPC to the application server failed for the " +
614 "call %s.%s().",
615 packageName, methodName);
618 private RPCFailedException(String message) {
619 super(message);
622 @Override
623 protected RPCFailedException cloneWithoutStackTrace() {
624 return new RPCFailedException(this.getMessage());
628 public static class CallNotFoundException extends ApiProxyException {
629 public CallNotFoundException(String packageName, String methodName) {
630 super("The API package '%s' or call '%s()' was not found.",
631 packageName, methodName);
634 private CallNotFoundException(String message) {
635 super(message);
638 @Override
639 public CallNotFoundException cloneWithoutStackTrace() {
640 return new CallNotFoundException(this.getMessage());
644 public static class ArgumentException extends ApiProxyException {
645 public ArgumentException(String packageName, String methodName) {
646 super("An error occurred parsing (locally or remotely) the " +
647 "arguments to %S.%s().",
648 packageName, methodName);
651 private ArgumentException(String message) {
652 super(message);
655 @Override
656 public ArgumentException cloneWithoutStackTrace() {
657 return new ArgumentException(this.getMessage());
661 public static class ApiDeadlineExceededException extends ApiProxyException {
662 public ApiDeadlineExceededException(String packageName, String methodName) {
663 super("The API call %s.%s() took too long to respond and was cancelled.",
664 packageName, methodName);
667 private ApiDeadlineExceededException(String message) {
668 super(message);
671 @Override
672 public ApiDeadlineExceededException cloneWithoutStackTrace() {
673 return new ApiDeadlineExceededException(this.getMessage());
677 public static class CancelledException extends ApiProxyException {
678 public CancelledException(String packageName, String methodName) {
679 super("The API call %s.%s() was explicitly cancelled.",
680 packageName, methodName);
683 private CancelledException(String message) {
684 super(message);
687 @Override
688 public CancelledException cloneWithoutStackTrace() {
689 return new CancelledException(this.getMessage());
694 public static class CapabilityDisabledException extends ApiProxyException {
695 public CapabilityDisabledException(String message, String packageName, String methodName) {
696 super("The API call %s.%s() is temporarily unavailable: " + message,
697 packageName, methodName);
700 private CapabilityDisabledException(String message) {
701 super(message);
704 @Override
705 public CapabilityDisabledException cloneWithoutStackTrace() {
706 return new CapabilityDisabledException(this.getMessage());
710 public static class FeatureNotEnabledException extends ApiProxyException {
711 public FeatureNotEnabledException(String message,
712 String packageName,
713 String methodName) {
714 super(message, packageName, methodName);
717 public FeatureNotEnabledException(String message) {
718 super(message);
721 @Override
722 public FeatureNotEnabledException cloneWithoutStackTrace() {
723 return new FeatureNotEnabledException(this.getMessage());
728 public static class OverQuotaException extends ApiProxyException {
729 public OverQuotaException(String packageName, String methodName) {
730 super("The API call %s.%s() required more quota than is available.",
731 packageName, methodName);
734 private OverQuotaException(String message) {
735 super(message);
738 @Override
739 public OverQuotaException cloneWithoutStackTrace() {
740 return new OverQuotaException(this.getMessage());
745 public static class RequestTooLargeException extends ApiProxyException {
746 public RequestTooLargeException(String packageName, String methodName) {
747 super("The request to API call %s.%s() was too large.",
748 packageName, methodName);
751 private RequestTooLargeException(String message) {
752 super(message);
755 @Override
756 public RequestTooLargeException cloneWithoutStackTrace() {
757 return new RequestTooLargeException(this.getMessage());
761 public static class ResponseTooLargeException extends ApiProxyException {
762 public ResponseTooLargeException(String packageName, String methodName) {
763 super("The response from API call %s.%s() was too large.",
764 packageName, methodName);
767 private ResponseTooLargeException(String message) {
768 super(message);
771 @Override
772 public ResponseTooLargeException cloneWithoutStackTrace() {
773 return new ResponseTooLargeException(this.getMessage());
777 public static class UnknownException extends ApiProxyException {
779 public UnknownException(String packageName, String methodName, Throwable nestedException) {
780 super("An error occurred for the API request %s.%s().",
781 packageName, methodName, nestedException);
784 public UnknownException(String packageName, String methodName) {
785 super("An error occurred for the API request %s.%s().",
786 packageName, methodName);
789 public UnknownException(String message) {
790 super(message);
793 @Override
794 public UnknownException cloneWithoutStackTrace() {
795 return new UnknownException(this.getMessage());