Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / LocalEnvironment.java
blob8fc247d52045386dc02ef5d495f164aa8a8cb2bd
1 // Copyright 2009 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.development;
5 import com.google.appengine.api.NamespaceManager;
6 import com.google.apphosting.api.ApiProxy;
8 import java.math.BigInteger;
9 import java.nio.ByteBuffer;
10 import java.security.MessageDigest;
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.Date;
14 import java.util.concurrent.ConcurrentHashMap;
15 import java.util.concurrent.ConcurrentMap;
16 import java.util.concurrent.atomic.AtomicInteger;
17 import java.util.logging.Level;
18 import java.util.logging.Logger;
20 /**
21 * {@code LocalEnvironment} is a simple {@link ApiProxy.Environment} that reads
22 * application-specific details (e.g. application identifer) directly from the
23 * custom deployment descriptor.
26 abstract public class LocalEnvironment implements ApiProxy.Environment {
27 private static final Logger logger = Logger.getLogger(LocalEnvironment.class.getName());
28 private static final String APPS_NAMESPACE_KEY =
29 NamespaceManager.class.getName() + ".appsNamespace";
30 /**
31 * {@code ApiProxy.Environment} instances used with {@code
32 * ApiProxyLocalFactory} should have a entry in the map returned by
33 * {@code getAttributes()} with this key, and a {@link
34 * java.util.concurrent.Semaphore} as the value. This is used
35 * internally to track asynchronous API calls.
37 public static final String API_CALL_SEMAPHORE =
38 "com.google.appengine.tools.development.api_call_semaphore";
40 /**
41 * The name of an {@link #getAttributes() attribute} that contains a
42 * (String) unique identifier for the curent request to the
43 * Dev App Server.
45 public static final String REQUEST_ID = "com.google.appengine.tools.development.request_id";
47 /**
48 * The name of an {@link #getAttributes() attribute} that contains a
49 * (String) unique identifier for the curent request.
51 public static final String REQUEST_LOG_ID =
52 "com.google.appengine.runtime.request_log_id";
54 /**
55 * The name of an {@link #getAttributes() attribute} that contains a
56 * a {@link Date} object representing the time this request was
57 * started.
59 public static final String START_TIME_ATTR =
60 "com.google.appengine.tools.development.start_time";
62 /**
63 * The name of an {@link #getAttributes() attribute} that contains a {@code
64 * Set<RequestEndListener>}. The set of {@link RequestEndListener
65 * RequestEndListeners} is populated by from within the service calls. The
66 * listeners are invoked at the end of a user request.
68 public static final String REQUEST_END_LISTENERS =
69 "com.google.appengine.tools.development.request_end_listeners";
71 private static final String REQUEST_THREAD_FACTORY_ATTR =
72 "com.google.appengine.api.ThreadManager.REQUEST_THREAD_FACTORY";
74 private static final String BACKGROUND_THREAD_FACTORY_ATTR =
75 "com.google.appengine.api.ThreadManager.BACKGROUND_THREAD_FACTORY";
77 /**
78 * In production, this is a constant that defines the {@link #getAttributes() attribute} name
79 * that contains the hostname on which the default version is listening.
80 * In the local development server, the {@link #getAttributes() attribute} contains the
81 * listening port in addition to the hostname, and is the one and only frontend app instance
82 * hostname and port.
84 public static final String DEFAULT_VERSION_HOSTNAME =
85 "com.google.appengine.runtime.default_version_hostname";
87 private final String appId;
88 private final String versionId;
90 private final Collection<RequestEndListener> requestEndListeners;
92 protected final ConcurrentMap<String, Object> attributes =
93 new ConcurrentHashMap<String, Object>();
95 private final Long endTime;
97 protected LocalEnvironment(String appId, String majorVersionId) {
98 this(appId, majorVersionId, null);
101 protected LocalEnvironment(String appId, String majorVersionId, Long deadlineMillis) {
102 this.appId = appId;
103 this.versionId = majorVersionId + ".1";
104 if (deadlineMillis == null) {
105 this.endTime = null;
106 } else if (deadlineMillis < 0) {
107 throw new IllegalArgumentException("deadlineMillis must be a non-negative integer.");
108 } else {
109 this.endTime = System.currentTimeMillis() + deadlineMillis;
111 requestEndListeners =
112 Collections.newSetFromMap(new ConcurrentHashMap<RequestEndListener, Boolean>(10));
113 attributes.put(REQUEST_ID, generateRequestId());
114 attributes.put(REQUEST_LOG_ID, generateRequestLogId());
115 attributes.put(REQUEST_END_LISTENERS, requestEndListeners);
116 attributes.put(START_TIME_ATTR, new Date());
117 attributes.put(REQUEST_THREAD_FACTORY_ATTR, new RequestThreadFactory());
118 attributes.put(BACKGROUND_THREAD_FACTORY_ATTR, new BackgroundThreadFactory(appId,
119 majorVersionId));
122 private static final String REQUEST_ID_PREFIX = "" + System.currentTimeMillis();
123 private static AtomicInteger requestID = new AtomicInteger();
124 private String generateRequestId(){
125 return REQUEST_ID_PREFIX + "," + requestID.getAndIncrement();
129 * Generates a unique request ID using the same algorithm as the Python dev
130 * appserver. It is similar to the production algorithm, but with less
131 * embedded data. The primary goal is that the IDs be sortable by timestamp,
132 * so the initial bytes consist of seconds then microseconds packed in
133 * big-endian byte order. To ensure uniqueness, a hash of the incrementing
134 * counter is appended. Hexadecimal encoding is used instead of base64 in
135 * order to preserve comparison order.
137 private String generateRequestLogId(){
138 try {
139 ByteBuffer buf = ByteBuffer.allocate(12);
141 long now = System.currentTimeMillis();
142 buf.putInt((int) (now / 1000));
143 buf.putInt((int) ((now * 1000) % 1000000));
145 byte[] hashBytes = MessageDigest.getInstance("SHA-1").digest(
146 requestID.toString().getBytes());
147 buf.put(hashBytes, 0, 4);
149 return String.format("%x", new BigInteger(buf.array()));
150 } catch (Exception e) {
151 return "";
155 @Override
156 public String getAppId() {
157 return appId;
160 @Override
161 public String getVersionId() {
162 return versionId;
165 @Override
166 public String getAuthDomain() {
167 return "gmail.com";
170 @Override
171 @Deprecated
172 public final String getRequestNamespace() {
173 String appsNamespace = (String) getAttributes().get(APPS_NAMESPACE_KEY);
174 return appsNamespace == null ? "" : appsNamespace;
177 @Override
178 public ConcurrentMap<String, Object> getAttributes() {
179 return attributes;
182 public void callRequestEndListeners() {
183 for (RequestEndListener listener : requestEndListeners) {
184 try {
185 listener.onRequestEnd(this);
186 } catch (Exception ex) {
187 logger.log(Level.WARNING,
188 "Exception while attempting to invoke RequestEndListener " + listener.getClass()
189 + ": ", ex);
192 requestEndListeners.clear();
195 @Override
196 public long getRemainingMillis() {
197 if (endTime != null) {
198 return endTime - System.currentTimeMillis();
201 return Long.MAX_VALUE;