Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / apphosting / api / ApiProxy.java
blobea1d81dbec40dcf0cbcf4eae3da7ceeeeb7a477c
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 * Used to create an Environment object to use if no thread local Environment is set.
35 * When the ThreadManager is used to create a thread, an appropriate environment instance will be
36 * created and associated with the thread. This class is used if the thread is created another
37 * way, most likely directly using the Thread constuctor.
39 private static EnvironmentFactory environmentFactory = null;
41 /**
42 * Store a single delegate, to which we proxy all makeSyncCall requests.
44 private static Delegate delegate;
46 /**
47 * Logging records outside the scope of a request are lazily logged.
49 private static List<LogRecord> outOfBandLogs = new ArrayList<LogRecord>();
51 /**
52 * All methods are static. Do not instantiate.
54 private ApiProxy() {
57 /**
58 * @see #makeSyncCall(String,String,byte[],ApiConfig)
60 public static byte[] makeSyncCall(String packageName,
61 String methodName,
62 byte[] request)
63 throws ApiProxyException {
64 return makeSyncCall(packageName, methodName, request, null);
67 /**
68 * Make a synchronous call to the specified method in the specified
69 * API package.
71 * <p>Note: if you have not installed a {@code Delegate} and called
72 * {@code setEnvironmentForCurrentThread} in this thread before
73 * calling this method, it will act like no API calls are available
74 * (i.e. always throw {@code CallNotFoundException}).
76 * @param packageName the name of the API package.
77 * @param methodName the name of the method within the API package.
78 * @param request a byte array containing the serialized form of the
79 * request protocol buffer.
80 * @param apiConfig that specifies API-specific configuration
81 * parameters.
83 * @return a byte array containing the serialized form of the
84 * response protocol buffer.
87 * @throws ApplicationException For any error that is the application's fault.
88 * @throws RPCFailedException If we could not connect to a backend service.
89 * @throws CallNotFoundException If the specified method does not exist.
90 * @throws ArgumentException If the request could not be parsed.
91 * @throws ApiDeadlineExceededException If the request took too long.
92 * @throws CancelledException If the request was explicitly cancelled.
93 * @throws CapabilityDisabledException If the API call is currently
94 * unavailable.
95 * @throws OverQuotaException If the API call required more quota than is
96 * available.
97 * @throws RequestTooLargeException If the request to the API was too large.
98 * @throws ResponseTooLargeException If the response to the API was too large.
99 * @throws UnknownException If any other error occurred.
101 @SuppressWarnings("unchecked")
102 public static byte[] makeSyncCall(String packageName,
103 String methodName,
104 byte[] request,
105 ApiConfig apiConfig)
106 throws ApiProxyException {
107 Environment env = getCurrentEnvironment();
108 if (delegate == null || env == null) {
109 throw new CallNotFoundException(packageName, methodName);
111 if (apiConfig == null || apiConfig.getDeadlineInSeconds() == null) {
112 return delegate.makeSyncCall(env, packageName, methodName, request);
113 } else {
114 Object oldValue = env.getAttributes().put(API_DEADLINE_KEY, apiConfig.getDeadlineInSeconds());
115 try {
116 return delegate.makeSyncCall(env, packageName, methodName, request);
117 } finally {
118 if (oldValue == null) {
119 env.getAttributes().remove(API_DEADLINE_KEY);
120 } else {
121 env.getAttributes().put(API_DEADLINE_KEY, oldValue);
128 * @see #makeAsyncCall(String,String,byte[],ApiConfig)
130 public static Future<byte[]> makeAsyncCall(String packageName,
131 String methodName,
132 byte[] request) {
133 return makeAsyncCall(packageName, methodName, request, new ApiConfig());
137 * Make an asynchronous call to the specified method in the
138 * specified API package.
140 * <p>Note: if you have not installed a {@code Delegate} and called
141 * {@code setEnvironmentForCurrentThread} in this thread before
142 * calling this method, it will act like no API calls are available
143 * (i.e. the returned {@link Future} will throw {@code
144 * CallNotFoundException}).
146 * <p>There is a limit to the number of simultaneous asynchronous
147 * API calls (currently 100). Invoking this method while this number
148 * of API calls are outstanding will block.
150 * @param packageName the name of the API package.
151 * @param methodName the name of the method within the API package.
152 * @param request a byte array containing the serialized form of
153 * the request protocol buffer.
154 * @param apiConfig that specifies API-specific configuration
155 * parameters.
157 * @return a {@link Future} that will resolve to a byte array
158 * containing the serialized form of the response protocol buffer
159 * on success, or throw one of the exceptions documented for
160 * {@link #makeSyncCall(String, String, byte[], ApiConfig)} on failure.
162 @SuppressWarnings("unchecked")
163 public static Future<byte[]> makeAsyncCall(final String packageName,
164 final String methodName,
165 byte[] request,
166 ApiConfig apiConfig) {
167 Environment env = getCurrentEnvironment();
168 if (delegate == null || env == null) {
169 return new Future<byte[]>() {
170 public byte[] get() {
171 throw new CallNotFoundException(packageName, methodName);
174 public byte[] get(long deadline, TimeUnit unit) {
175 throw new CallNotFoundException(packageName, methodName);
178 public boolean isDone() {
179 return true;
182 public boolean isCancelled() {
183 return false;
186 public boolean cancel(boolean shouldInterrupt) {
187 return false;
191 return delegate.makeAsyncCall(env, packageName, methodName, request, apiConfig);
194 @SuppressWarnings("unchecked")
195 public static void log(LogRecord record) {
196 Environment env = getCurrentEnvironment();
197 if (delegate != null && env != null) {
198 delegate.log(env, record);
199 return;
202 synchronized (outOfBandLogs) {
203 outOfBandLogs.add(record);
207 private static void possiblyFlushOutOfBandLogs() {
208 Environment env = getCurrentEnvironment();
209 if (delegate != null && env != null) {
210 List<LogRecord> logsToWrite;
211 synchronized (outOfBandLogs) {
212 logsToWrite = new ArrayList<LogRecord>(outOfBandLogs);
213 outOfBandLogs.clear();
215 for (LogRecord record : logsToWrite) {
216 delegate.log(env, record);
222 * Synchronously flush all pending application logs.
224 @SuppressWarnings("unchecked")
225 public static void flushLogs() {
226 if (delegate != null) {
227 delegate.flushLogs(getCurrentEnvironment());
232 * Gets the environment associated with this thread. This can be used to discover additional
233 * information about the current request.
235 * The value returned is the {@code Environment} that this thread most recently set with
236 * {@link #setEnvironmentForCurrentThread}. If that is null and {@link #setEnvironmentFactory} has
237 * set an {@link EnvironmentFactory}, that {@code EnvironmentFactory} is used to create an
238 * {@code Environment} instance which is returned by this call and future calls. If there is no
239 * {@code EnvironmentFactory} either, then null is returned.
241 public static Environment getCurrentEnvironment() {
242 Environment threadLocalEnvironment = environmentThreadLocal.get();
243 if (threadLocalEnvironment != null) {
244 return threadLocalEnvironment;
246 EnvironmentFactory envFactory = getEnvironmentFactory();
247 if (envFactory != null) {
248 Environment environment = envFactory.newEnvironment();
249 environmentThreadLocal.set(environment);
250 return environment;
252 return null;
256 * Sets a delegate to which we will proxy requests. This should not be
257 * used from user-code.
259 public static void setDelegate(Delegate aDelegate) {
260 delegate = aDelegate;
261 possiblyFlushOutOfBandLogs();
265 * Gets the delegate to which we will proxy requests. This should really
266 * only be called from test-code where, for example, you might want to
267 * downcast and invoke methods on a specific implementation that you happen
268 * to know has been installed.
270 public static Delegate getDelegate() {
271 return delegate;
275 * Sets an environment for the current thread. This should not be
276 * used from user-code.
278 public static void setEnvironmentForCurrentThread(Environment environment) {
279 environmentThreadLocal.set(environment);
280 possiblyFlushOutOfBandLogs();
284 * Removes any environment associated with the current thread. This
285 * should not be used from user-code.
287 public static void clearEnvironmentForCurrentThread() {
288 environmentThreadLocal.set(null);
291 public static synchronized EnvironmentFactory getEnvironmentFactory() {
292 return environmentFactory;
296 * Set the EnvironmentFactory instance to use, which will be used to create Environment instances
297 * when a thread local one is not set. This should not be used from user-code, and it should only
298 * be called once, with a value that must not be null.
300 public static synchronized void setEnvironmentFactory(EnvironmentFactory factory) {
301 if (factory == null) {
302 throw new NullPointerException("factory cannot be null.");
304 if (environmentFactory != null) {
305 throw new IllegalStateException("EnvironmentFactory has already been set.");
307 environmentFactory = factory;
311 * Returns a list of all threads which are currently running requests.
313 public static List<Thread> getRequestThreads() {
314 Environment env = getCurrentEnvironment();
315 if (delegate == null) {
316 return Collections.emptyList();
317 } else {
318 return delegate.getRequestThreads(env);
323 * Environment is a simple data container that provides additional
324 * information about the current request (e.g. who is logged in, are
325 * they an administrator, etc.).
327 public interface Environment {
329 * Gets the application identifier for the current application.
331 String getAppId();
334 * Gets the module identifier for the current application instance.
336 String getModuleId();
339 * Gets the version identifier for the current application version.
340 * Result is of the form {@literal <major>.<minor>} where
341 * {@literal <major>} is the version name supplied at deploy time and
342 * {@literal <minor>} is a timestamp value maintained by App Engine.
344 String getVersionId();
347 * Gets the email address of the currently logged-in user.
349 String getEmail();
352 * Returns true if the user is logged in.
354 boolean isLoggedIn();
357 * Returns true if the currently logged-in user is an administrator.
359 boolean isAdmin();
362 * Returns the domain used for authentication.
364 String getAuthDomain();
367 * @deprecated Use {@link NamespaceManager}.getGoogleAppsNamespace()
369 @Deprecated
370 String getRequestNamespace();
373 * Get a {@code Map} containing any attributes that have been set in this
374 * {@code Environment}. The returned {@code Map} is mutable and is a
375 * useful place to store transient, per-request information.
377 Map<String, Object> getAttributes();
380 * Gets the remaining number of milliseconds left before this request receives a
381 * DeadlineExceededException from App Engine. This API can be used for planning how much work
382 * you can reasonably accomplish before the soft deadline kicks in.
384 * If there is no deadline for the request, then this will reply with Long.MAX_VALUE.
386 long getRemainingMillis();
390 * Used to create an Environment object to use if no thread local Environment is set.
392 public interface EnvironmentFactory {
394 * Creates a new Environment object to use if no thread local Environment is set.
396 Environment newEnvironment();
400 * This interface can be used to provide a class that actually
401 * implements API calls.
403 * @param <E> The concrete class implementing Environment that this
404 * Delegate expects to receive.
406 public interface Delegate<E extends Environment> {
408 * Make a synchronous call to the specified method in the specified
409 * API package.
411 * <p>Note: if you have not installed a {@code Delegate} and called
412 * {@code setEnvironmentForCurrentThread} in this thread before
413 * calling this method, it will act like no API calls are available
414 * (i.e. always throw {@code CallNotFoundException}).
416 * @param environment the current request environment.
417 * @param packageName the name of the API package.
418 * @param methodName the name of the method within the API package.
419 * @param request a byte array containing the serialized form of
420 * the request protocol buffer.
422 * @return a byte array containing the serialized form of the
423 * response protocol buffer.
425 * @throws ApplicationException For any error that is the application's fault.
426 * @throws RPCFailedException If we could not connect to a backend service.
427 * @throws CallNotFoundException If the specified method does not exist.
428 * @throws ArgumentException If the request could not be parsed.
429 * @throws DeadlineExceededException If the request took too long.
430 * @throws CancelledException If the request was explicitly cancelled.
431 * @throws UnknownException If any other error occurred.
433 byte[] makeSyncCall(E environment,
434 String packageName,
435 String methodName,
436 byte[] request)
437 throws ApiProxyException;
440 * Make an asynchronous call to the specified method in the specified API package.
442 * <p>Note: if you have not installed a {@code Delegate} and called
443 * {@code setEnvironmentForCurrentThread} in this thread before
444 * calling this method, it will act like no API calls are available
445 * (i.e. always throw {@code CallNotFoundException}).
447 * @param environment the current request environment.
448 * @param packageName the name of the API package.
449 * @param methodName the name of the method within the API package.
450 * @param request a byte array containing the serialized form of
451 * the request protocol buffer.
452 * @param apiConfig that specifies API-specific configuration
453 * parameters.
455 * @return a {@link Future} that will resolve to a byte array
456 * containing the serialized form of the response protocol buffer
457 * on success, or throw one of the exceptions documented for
458 * {@link #makeSyncCall(Environment, String, String, byte[])} on failure.
460 Future<byte[]> makeAsyncCall(E environment,
461 String packageName,
462 String methodName,
463 byte[] request,
464 ApiConfig apiConfig);
466 void log(E environment, LogRecord record);
468 void flushLogs(E environment);
471 * Returns a list of all threads which are currently running requests.
473 List<Thread> getRequestThreads(E environment);
477 * {@code LogRecord} represents a single apphosting log entry,
478 * including a Java-specific logging level, a timestamp in
479 * microseconds, and a message a formatted string containing the
480 * rest of the logging information (e.g. class and line number
481 * information, the message itself, the stack trace for any
482 * exception associated with the log record, etc.).
484 public static final class LogRecord {
485 private final Level level;
486 private final long timestamp;
487 private final String message;
489 public enum Level {
490 debug,
491 info,
492 warn,
493 error,
494 fatal,
497 public LogRecord(Level level, long timestamp, String message) {
498 this.level = level;
499 this.timestamp = timestamp;
500 this.message = message;
504 * A partial copy constructor.
505 * @param other A {@code LogRecord} from which to
506 * copy the {@link #level} and {@link #timestamp}
507 * but not the {@link #message}
508 * @param message
510 public LogRecord(LogRecord other, String message){
511 this(other.level, other.timestamp, message);
514 public Level getLevel() {
515 return level;
519 * Returns the timestamp of the log message, in microseconds.
521 public long getTimestamp() {
522 return timestamp;
525 public String getMessage() {
526 return message;
531 * {@code ApiConfig} encapsulates one or more configuration
532 * parameters scoped to an individual API call.
534 public static final class ApiConfig {
535 private Double deadlineInSeconds;
538 * Returns the number of seconds that the API call will be allowed
539 * to run, or {@code null} for the default deadline.
541 public Double getDeadlineInSeconds() {
542 return deadlineInSeconds;
546 * Set the number of seconds that the API call will be allowed to
547 * run, or {@code null} for the default deadline.
549 public void setDeadlineInSeconds(Double deadlineInSeconds) {
550 this.deadlineInSeconds = deadlineInSeconds;
553 @Override
554 public boolean equals(Object o) {
555 if (this == o) {
556 return true;
558 if (o == null || getClass() != o.getClass()) {
559 return false;
562 ApiConfig apiConfig = (ApiConfig) o;
564 if (deadlineInSeconds != null ? !deadlineInSeconds.equals(apiConfig.deadlineInSeconds)
565 : apiConfig.deadlineInSeconds != null) {
566 return false;
569 return true;
572 @Override
573 public int hashCode() {
574 return deadlineInSeconds != null ? deadlineInSeconds.hashCode() : 0;
579 * A subtype of {@link Future} that provides more detailed
580 * information about the timing and resource consumption of
581 * particular API calls.
583 * <p>Objects returned from {@link
584 * #makeAsyncCall(String,String,byte[],ApiConfig)} may implement
585 * this interface. However, callers should not currently assume
586 * that all RPCs will.
588 public static interface ApiResultFuture<T> extends Future<T> {
590 * Returns the amount of CPU time consumed across any backend
591 * servers responsible for serving this API call. This quantity
592 * is measured in millions of CPU cycles to avoid suming times
593 * across a hetergeneous machine set with varied CPU clock speeds.
595 * @throws IllegalStateException If the RPC has not yet completed.
597 long getCpuTimeInMegaCycles();
600 * Returns the amount of wallclock time, measured in milliseconds,
601 * that this API call took to complete, as measured from the
602 * client side.
604 * @throws IllegalStateException If the RPC has not yet completed.
606 long getWallclockTimeInMillis();
609 public static class ApiProxyException extends RuntimeException {
610 public ApiProxyException(String message, String packageName, String methodName) {
611 this(String.format(message, packageName, methodName));
614 private ApiProxyException(String message, String packageName, String methodName,
615 Throwable nestedException) {
616 super(String.format(message, packageName, methodName), nestedException);
619 public ApiProxyException(String message) {
620 super(message);
624 * Clones this exception and then sets this Exception as the cause
625 * of the clone and sets the given stack trace in the clone.
627 * @param stackTrace The stack trace to set in the returned clone
628 * @return a clone of this Exception with this Exception as
629 * the cause and with the given stack trace.
631 public ApiProxyException copy(StackTraceElement[] stackTrace) {
632 ApiProxyException theCopy = cloneWithoutStackTrace();
633 theCopy.setStackTrace(stackTrace);
634 theCopy.initCause(this);
635 return theCopy;
638 protected ApiProxyException cloneWithoutStackTrace() {
639 return new ApiProxyException(this.getMessage());
644 public static class ApplicationException extends ApiProxyException {
645 private final int applicationError;
646 private final String errorDetail;
648 public ApplicationException(int applicationError) {
649 this(applicationError, "");
652 public ApplicationException(int applicationError, String errorDetail) {
653 super("ApplicationError: " + applicationError + ": " + errorDetail);
654 this.applicationError = applicationError;
655 this.errorDetail = errorDetail;
658 public int getApplicationError() {
659 return applicationError;
662 public String getErrorDetail() {
663 return errorDetail;
666 @Override
667 protected ApplicationException cloneWithoutStackTrace() {
668 return new ApplicationException(applicationError, errorDetail);
672 public static class RPCFailedException extends ApiProxyException {
673 public RPCFailedException(String packageName, String methodName) {
674 super("The remote RPC to the application server failed for the " +
675 "call %s.%s().",
676 packageName, methodName);
679 private RPCFailedException(String message) {
680 super(message);
683 @Override
684 protected RPCFailedException cloneWithoutStackTrace() {
685 return new RPCFailedException(this.getMessage());
689 public static class CallNotFoundException extends ApiProxyException {
690 public CallNotFoundException(String packageName, String methodName) {
691 super("The API package '%s' or call '%s()' was not found.",
692 packageName, methodName);
695 private CallNotFoundException(String message) {
696 super(message);
699 @Override
700 public CallNotFoundException cloneWithoutStackTrace() {
701 return new CallNotFoundException(this.getMessage());
705 public static class ArgumentException extends ApiProxyException {
706 public ArgumentException(String packageName, String methodName) {
707 super("An error occurred parsing (locally or remotely) the " +
708 "arguments to %S.%s().",
709 packageName, methodName);
712 private ArgumentException(String message) {
713 super(message);
716 @Override
717 public ArgumentException cloneWithoutStackTrace() {
718 return new ArgumentException(this.getMessage());
722 public static class ApiDeadlineExceededException extends ApiProxyException {
723 public ApiDeadlineExceededException(String packageName, String methodName) {
724 super("The API call %s.%s() took too long to respond and was cancelled.",
725 packageName, methodName);
728 private ApiDeadlineExceededException(String message) {
729 super(message);
732 @Override
733 public ApiDeadlineExceededException cloneWithoutStackTrace() {
734 return new ApiDeadlineExceededException(this.getMessage());
738 public static class CancelledException extends ApiProxyException {
739 public CancelledException(String packageName, String methodName) {
740 super("The API call %s.%s() was explicitly cancelled.",
741 packageName, methodName);
744 private CancelledException(String message) {
745 super(message);
748 @Override
749 public CancelledException cloneWithoutStackTrace() {
750 return new CancelledException(this.getMessage());
755 public static class CapabilityDisabledException extends ApiProxyException {
756 public CapabilityDisabledException(String message, String packageName, String methodName) {
757 super("The API call %s.%s() is temporarily unavailable: " + message,
758 packageName, methodName);
761 private CapabilityDisabledException(String message) {
762 super(message);
765 @Override
766 public CapabilityDisabledException cloneWithoutStackTrace() {
767 return new CapabilityDisabledException(this.getMessage());
771 public static class FeatureNotEnabledException extends ApiProxyException {
772 public FeatureNotEnabledException(String message,
773 String packageName,
774 String methodName) {
775 super(message, packageName, methodName);
778 public FeatureNotEnabledException(String message) {
779 super(message);
782 @Override
783 public FeatureNotEnabledException cloneWithoutStackTrace() {
784 return new FeatureNotEnabledException(this.getMessage());
789 public static class OverQuotaException extends ApiProxyException {
790 public OverQuotaException(String packageName, String methodName) {
791 super("The API call %s.%s() required more quota than is available.",
792 packageName, methodName);
795 private OverQuotaException(String message) {
796 super(message);
799 @Override
800 public OverQuotaException cloneWithoutStackTrace() {
801 return new OverQuotaException(this.getMessage());
806 public static class RequestTooLargeException extends ApiProxyException {
807 public RequestTooLargeException(String packageName, String methodName) {
808 super("The request to API call %s.%s() was too large.",
809 packageName, methodName);
812 private RequestTooLargeException(String message) {
813 super(message);
816 @Override
817 public RequestTooLargeException cloneWithoutStackTrace() {
818 return new RequestTooLargeException(this.getMessage());
822 public static class ResponseTooLargeException extends ApiProxyException {
823 public ResponseTooLargeException(String packageName, String methodName) {
824 super("The response from API call %s.%s() was too large.",
825 packageName, methodName);
828 private ResponseTooLargeException(String message) {
829 super(message);
832 @Override
833 public ResponseTooLargeException cloneWithoutStackTrace() {
834 return new ResponseTooLargeException(this.getMessage());
838 public static class UnknownException extends ApiProxyException {
840 public UnknownException(String packageName, String methodName, Throwable nestedException) {
841 super("An error occurred for the API request %s.%s().",
842 packageName, methodName, nestedException);
845 public UnknownException(String packageName, String methodName) {
846 super("An error occurred for the API request %s.%s().",
847 packageName, methodName);
850 public UnknownException(String message) {
851 super(message);
854 @Override
855 public UnknownException cloneWithoutStackTrace() {
856 return new UnknownException(this.getMessage());