1.9.30 sync.
[gae.git] / java / src / main / com / google / appengine / tools / development / LocalEnvironment.java
blob1f72105004e4836329cbe57d79acaa158db45614
1 // Copyright 2009 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.development;
5 import static java.nio.charset.StandardCharsets.US_ASCII;
7 import com.google.appengine.api.NamespaceManager;
8 import com.google.apphosting.api.ApiProxy;
9 import com.google.apphosting.utils.config.WebModule;
11 import java.math.BigInteger;
12 import java.nio.ByteBuffer;
13 import java.security.MessageDigest;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Date;
17 import java.util.Map;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ConcurrentMap;
20 import java.util.concurrent.atomic.AtomicInteger;
21 import java.util.logging.Level;
22 import java.util.logging.Logger;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
26 /**
27 * {@code LocalEnvironment} is a simple
28 * {@link com.google.apphosting.api.ApiProxy.Environment} that reads
29 * application-specific details (e.g. application identifer) directly from the
30 * custom deployment descriptor.
33 abstract public class LocalEnvironment implements ApiProxy.Environment {
34 private static final Logger logger = Logger.getLogger(LocalEnvironment.class.getName());
35 static final Pattern APP_ID_PATTERN = Pattern.compile("([^:.]*)(:([^:.]*))?(.*)?");
37 private static final String APPS_NAMESPACE_KEY =
38 NamespaceManager.class.getName() + ".appsNamespace";
40 public static final Integer TESTING_DEFAULT_PORT = new Integer(8080);
42 public static final String INSTANCE_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.id";
44 public static final String PORT_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.port";
46 /**
47 * {@code ApiProxy.Environment} instances used with {@code
48 * ApiProxyLocalFactory} should have a entry in the map returned by
49 * {@code getAttributes()} with this key, and a {@link
50 * java.util.concurrent.Semaphore} as the value. This is used
51 * internally to track asynchronous API calls.
53 public static final String API_CALL_SEMAPHORE =
54 "com.google.appengine.tools.development.api_call_semaphore";
56 /**
57 * The name of an {@link #getAttributes() attribute} that contains a
58 * (String) unique identifier for the curent request.
60 public static final String REQUEST_ID =
61 "com.google.appengine.runtime.request_log_id";
63 /**
64 * The name of an {@link #getAttributes() attribute} that contains a
65 * a {@link Date} object representing the time this request was
66 * started.
68 public static final String START_TIME_ATTR =
69 "com.google.appengine.tools.development.start_time";
71 /**
72 * The name of an {@link #getAttributes() attribute} that contains a {@code
73 * Set<RequestEndListener>}. The set of {@link RequestEndListener
74 * RequestEndListeners} is populated by from within the service calls. The
75 * listeners are invoked at the end of a user request.
77 public static final String REQUEST_END_LISTENERS =
78 "com.google.appengine.tools.development.request_end_listeners";
80 /**
81 * The name of an {@link #getAttributes() attribute} that contains the {@link
82 * javax.servlet.http.HttpServletRequest} instance.
84 public static final String HTTP_SERVLET_REQUEST =
85 "com.google.appengine.http_servlet_request";
87 private static final String REQUEST_THREAD_FACTORY_ATTR =
88 "com.google.appengine.api.ThreadManager.REQUEST_THREAD_FACTORY";
90 private static final String BACKGROUND_THREAD_FACTORY_ATTR =
91 "com.google.appengine.api.ThreadManager.BACKGROUND_THREAD_FACTORY";
93 /**
94 * For historical and probably compatibility reasons dev appserver assigns all
95 * versions a minor version of 1.
97 private static final String MINOR_VERSION_SUFFIX = ".1";
99 /**
100 * In production, this is a constant that defines the {@link #getAttributes() attribute} name
101 * that contains the hostname on which the default version is listening.
102 * In the local development server, the {@link #getAttributes() attribute} contains the
103 * listening port in addition to the hostname, and is the one and only frontend app instance
104 * hostname and port.
106 public static final String DEFAULT_VERSION_HOSTNAME =
107 "com.google.appengine.runtime.default_version_hostname";
109 public static final String FILESAPI_WAS_USED =
110 "com.google.appengine.api.files.filesapi_was_used";
112 private final String appId;
113 private final String moduleId;
114 private final String versionId;
116 private final Collection<RequestEndListener> requestEndListeners;
118 protected final ConcurrentMap<String, Object> attributes =
119 new ConcurrentHashMap<String, Object>();
121 private final Long endTime;
123 * Instance number for a main instance.
124 * <p>
125 * Clients depend on this literal having the value -1 so do not change this
126 * value without making needed updates to clients.
128 public static final int MAIN_INSTANCE = -1;
131 * Deprecated constructor used by tests.
133 @Deprecated
134 protected LocalEnvironment(String appId, String majorVersionId) {
135 this(appId, WebModule.DEFAULT_MODULE_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE,
136 TESTING_DEFAULT_PORT, null);
140 * Deprecated constructor used by tests.
142 @Deprecated
143 protected LocalEnvironment(String appId, String majorVersionId, Long deadlineMillis) {
144 this(appId, WebModule.DEFAULT_MODULE_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE,
145 TESTING_DEFAULT_PORT, deadlineMillis);
148 protected LocalEnvironment(String appId, String moduleName, String majorVersionId, int instance,
149 Integer port, Long deadlineMillis) {
150 this.appId = appId;
151 this.moduleId = moduleName;
152 this.versionId = (majorVersionId != null ? majorVersionId : "no_version")
153 + MINOR_VERSION_SUFFIX;
155 if (deadlineMillis == null) {
156 this.endTime = null;
157 } else if (deadlineMillis < 0) {
158 throw new IllegalArgumentException("deadlineMillis must be a non-negative integer.");
159 } else {
160 this.endTime = System.currentTimeMillis() + deadlineMillis;
162 setInstance(attributes, instance);
163 setPort(attributes, port);
164 requestEndListeners =
165 Collections.newSetFromMap(new ConcurrentHashMap<RequestEndListener, Boolean>(10));
166 attributes.put(REQUEST_ID, generateRequestId());
167 attributes.put(REQUEST_END_LISTENERS, requestEndListeners);
168 attributes.put(START_TIME_ATTR, new Date());
169 attributes.put(REQUEST_THREAD_FACTORY_ATTR, new RequestThreadFactory());
170 attributes.put(BACKGROUND_THREAD_FACTORY_ATTR, new BackgroundThreadFactory(appId,
171 moduleName, majorVersionId));
175 * Sets the instance for the provided attributes.
177 static void setInstance(Map<String, Object> attributes, int instance) {
178 attributes.remove(INSTANCE_ID_ENV_ATTRIBUTE);
179 if (instance != LocalEnvironment.MAIN_INSTANCE) {
180 attributes.put(INSTANCE_ID_ENV_ATTRIBUTE, Integer.toString(instance));
185 * Sets the {@link #PORT_ID_ENV_ATTRIBUTE} value to the provided port value or
186 * clears it if port is null.
188 static void setPort(Map<String, Object> attributes, Integer port) {
189 if (port == null) {
190 attributes.remove(PORT_ID_ENV_ATTRIBUTE);
191 } else {
192 attributes.put(PORT_ID_ENV_ATTRIBUTE, port);
197 * Returns the current instance or {@link #MAIN_INSTANCE} if none is defined.
199 static int getCurrentInstance() {
200 int result = MAIN_INSTANCE;
201 String instance = (String) ApiProxy.getCurrentEnvironment().getAttributes().get(
202 LocalEnvironment.INSTANCE_ID_ENV_ATTRIBUTE);
203 if (instance != null) {
204 result = Integer.parseInt(instance);
206 return result;
210 * Returns the current instance's port or null if none is defined.
212 static Integer getCurrentPort() {
213 return (Integer) ApiProxy.getCurrentEnvironment().getAttributes().get(
214 LocalEnvironment.PORT_ID_ENV_ATTRIBUTE);
217 private static AtomicInteger requestID = new AtomicInteger();
220 * Generates a unique request ID using the same algorithm as the Python dev
221 * appserver. It is similar to the production algorithm, but with less
222 * embedded data. The primary goal is that the IDs be sortable by timestamp,
223 * so the initial bytes consist of seconds then microseconds packed in
224 * big-endian byte order. To ensure uniqueness, a hash of the incrementing
225 * counter is appended. Hexadecimal encoding is used instead of base64 in
226 * order to preserve comparison order.
228 private String generateRequestId(){
229 try {
230 ByteBuffer buf = ByteBuffer.allocate(12);
232 long now = System.currentTimeMillis();
233 buf.putInt((int) (now / 1000));
234 buf.putInt((int) ((now * 1000) % 1000000));
236 String nextID = Integer.toString(requestID.getAndIncrement());
237 byte[] hashBytes =
238 MessageDigest.getInstance("SHA-1").digest(nextID.getBytes(US_ASCII));
239 buf.put(hashBytes, 0, 4);
241 return String.format("%x", new BigInteger(buf.array()));
242 } catch (Exception e) {
243 return "";
247 @Override
248 public String getAppId() {
249 return appId;
252 @Override
253 public String getModuleId() {
254 return moduleId;
257 @Override
258 public String getVersionId() {
259 return versionId;
262 @Override
263 public String getAuthDomain() {
264 return "gmail.com";
267 @Override
268 @Deprecated
269 public final String getRequestNamespace() {
270 String appsNamespace = (String) getAttributes().get(APPS_NAMESPACE_KEY);
271 return appsNamespace == null ? "" : appsNamespace;
274 @Override
275 public ConcurrentMap<String, Object> getAttributes() {
276 return attributes;
279 public void callRequestEndListeners() {
280 for (RequestEndListener listener : requestEndListeners) {
281 try {
282 listener.onRequestEnd(this);
283 } catch (Exception ex) {
284 logger.log(Level.WARNING,
285 "Exception while attempting to invoke RequestEndListener " + listener.getClass()
286 + ": ", ex);
289 requestEndListeners.clear();
292 @Override
293 public long getRemainingMillis() {
294 if (endTime != null) {
295 return endTime - System.currentTimeMillis();
298 return Long.MAX_VALUE;
302 * Returns the major version component from the provided version id or null
303 * if the provided version id has no major version component.
305 public static String getMajorVersion(String versionId) {
306 Matcher matcher = APP_ID_PATTERN.matcher(versionId);
307 matcher.find();
308 return matcher.group(3) == null ? matcher.group(1) : matcher.group(3);