App Engine SDK 1.8.4 release.
[gae.git] / java / src / main / com / google / appengine / tools / development / LocalEnvironment.java
blob677e49a307c314ff0e89928c8286b59351b2e9d3
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;
7 import com.google.apphosting.utils.config.WebModule;
8 import com.google.common.base.Charsets;
10 import java.math.BigInteger;
11 import java.nio.ByteBuffer;
12 import java.security.MessageDigest;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.Date;
16 import java.util.Map;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.concurrent.ConcurrentMap;
19 import java.util.concurrent.atomic.AtomicInteger;
20 import java.util.logging.Level;
21 import java.util.logging.Logger;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
25 /**
26 * {@code LocalEnvironment} is a simple
27 * {@link com.google.apphosting.api.ApiProxy.Environment} that reads
28 * application-specific details (e.g. application identifer) directly from the
29 * custom deployment descriptor.
32 abstract public class LocalEnvironment implements ApiProxy.Environment {
33 private static final Logger logger = Logger.getLogger(LocalEnvironment.class.getName());
34 static final Pattern APP_ID_PATTERN = Pattern.compile("([^:.]*)(:([^:.]*))?(.*)?");
36 private static final String APPS_NAMESPACE_KEY =
37 NamespaceManager.class.getName() + ".appsNamespace";
39 public static final Integer TESTING_DEFAULT_PORT = new Integer(8080);
41 public static final String INSTANCE_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.id";
43 public static final String PORT_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.port";
45 /**
46 * {@code ApiProxy.Environment} instances used with {@code
47 * ApiProxyLocalFactory} should have a entry in the map returned by
48 * {@code getAttributes()} with this key, and a {@link
49 * java.util.concurrent.Semaphore} as the value. This is used
50 * internally to track asynchronous API calls.
52 public static final String API_CALL_SEMAPHORE =
53 "com.google.appengine.tools.development.api_call_semaphore";
55 /**
56 * The name of an {@link #getAttributes() attribute} that contains a
57 * (String) unique identifier for the curent request.
59 public static final String REQUEST_ID =
60 "com.google.appengine.runtime.request_log_id";
62 /**
63 * The name of an {@link #getAttributes() attribute} that contains a
64 * a {@link Date} object representing the time this request was
65 * started.
67 public static final String START_TIME_ATTR =
68 "com.google.appengine.tools.development.start_time";
70 /**
71 * The name of an {@link #getAttributes() attribute} that contains a {@code
72 * Set<RequestEndListener>}. The set of {@link RequestEndListener
73 * RequestEndListeners} is populated by from within the service calls. The
74 * listeners are invoked at the end of a user request.
76 public static final String REQUEST_END_LISTENERS =
77 "com.google.appengine.tools.development.request_end_listeners";
79 /**
80 * The name of an {@link #getAttributes() attribute} that contains the {@link
81 * javax.servlet.http.HttpServletRequest} instance.
83 public static final String HTTP_SERVLET_REQUEST =
84 "com.google.appengine.http_servlet_request";
86 private static final String REQUEST_THREAD_FACTORY_ATTR =
87 "com.google.appengine.api.ThreadManager.REQUEST_THREAD_FACTORY";
89 private static final String BACKGROUND_THREAD_FACTORY_ATTR =
90 "com.google.appengine.api.ThreadManager.BACKGROUND_THREAD_FACTORY";
92 /**
93 * For historical and probably compatibility reasons dev appserver assigns all
94 * versions a minor version of 1.
96 private static final String MINOR_VERSION_SUFFIX = ".1";
98 /**
99 * In production, this is a constant that defines the {@link #getAttributes() attribute} name
100 * that contains the hostname on which the default version is listening.
101 * In the local development server, the {@link #getAttributes() attribute} contains the
102 * listening port in addition to the hostname, and is the one and only frontend app instance
103 * hostname and port.
105 public static final String DEFAULT_VERSION_HOSTNAME =
106 "com.google.appengine.runtime.default_version_hostname";
108 private final String appId;
109 private final String versionId;
111 private final Collection<RequestEndListener> requestEndListeners;
113 protected final ConcurrentMap<String, Object> attributes =
114 new ConcurrentHashMap<String, Object>();
116 private final Long endTime;
118 * Instance number for a main instance.
119 * <p>
120 * Clients depend on this literal having the value -1 so do not change this
121 * value without making needed updates to clients.
123 public static final int MAIN_INSTANCE = -1;
126 * Deprecated constructor used by tests.
128 @Deprecated
129 protected LocalEnvironment(String appId, String majorVersionId) {
130 this(appId, WebModule.DEFAULT_MODULE_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE,
131 TESTING_DEFAULT_PORT, null);
135 * Deprecated constructor used by tests.
137 @Deprecated
138 protected LocalEnvironment(String appId, String majorVersionId, Long deadlineMillis) {
139 this(appId, WebModule.DEFAULT_MODULE_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE,
140 TESTING_DEFAULT_PORT, deadlineMillis);
143 protected LocalEnvironment(String appId, String moduleName, String majorVersionId, int instance,
144 Integer port, Long deadlineMillis) {
145 this.appId = appId;
146 if (moduleName == null || WebModule.DEFAULT_MODULE_NAME.equals(moduleName)) {
147 this.versionId = majorVersionId + MINOR_VERSION_SUFFIX;
148 } else {
149 this.versionId = moduleName + ":" + majorVersionId + MINOR_VERSION_SUFFIX;
151 if (deadlineMillis == null) {
152 this.endTime = null;
153 } else if (deadlineMillis < 0) {
154 throw new IllegalArgumentException("deadlineMillis must be a non-negative integer.");
155 } else {
156 this.endTime = System.currentTimeMillis() + deadlineMillis;
158 setInstance(attributes, instance);
159 setPort(attributes, port);
160 requestEndListeners =
161 Collections.newSetFromMap(new ConcurrentHashMap<RequestEndListener, Boolean>(10));
162 attributes.put(REQUEST_ID, generateRequestId());
163 attributes.put(REQUEST_END_LISTENERS, requestEndListeners);
164 attributes.put(START_TIME_ATTR, new Date());
165 attributes.put(REQUEST_THREAD_FACTORY_ATTR, new RequestThreadFactory());
166 attributes.put(BACKGROUND_THREAD_FACTORY_ATTR, new BackgroundThreadFactory(appId,
167 moduleName, majorVersionId));
171 * Sets the instance for the provided attributes.
173 static void setInstance(Map<String, Object> attributes, int instance) {
174 attributes.remove(INSTANCE_ID_ENV_ATTRIBUTE);
175 if (instance != LocalEnvironment.MAIN_INSTANCE) {
176 attributes.put(INSTANCE_ID_ENV_ATTRIBUTE, Integer.toString(instance));
181 * Sets the {@link #PORT_ID_ENV_ATTRIBUTE} value to the provided port value or
182 * clears it if port is null.
184 static void setPort(Map<String, Object> attributes, Integer port) {
185 if (port == null) {
186 attributes.remove(PORT_ID_ENV_ATTRIBUTE);
187 } else {
188 attributes.put(PORT_ID_ENV_ATTRIBUTE, port);
193 * Returns the current instance or {@link #MAIN_INSTANCE} if none is defined.
195 static int getCurrentInstance() {
196 int result = MAIN_INSTANCE;
197 String instance = (String) ApiProxy.getCurrentEnvironment().getAttributes().get(
198 LocalEnvironment.INSTANCE_ID_ENV_ATTRIBUTE);
199 if (instance != null) {
200 result = Integer.parseInt(instance);
202 return result;
206 * Returns the current instance's port or null if none is defined.
208 static Integer getCurrentPort() {
209 return (Integer) ApiProxy.getCurrentEnvironment().getAttributes().get(
210 LocalEnvironment.PORT_ID_ENV_ATTRIBUTE);
213 private static AtomicInteger requestID = new AtomicInteger();
216 * Generates a unique request ID using the same algorithm as the Python dev
217 * appserver. It is similar to the production algorithm, but with less
218 * embedded data. The primary goal is that the IDs be sortable by timestamp,
219 * so the initial bytes consist of seconds then microseconds packed in
220 * big-endian byte order. To ensure uniqueness, a hash of the incrementing
221 * counter is appended. Hexadecimal encoding is used instead of base64 in
222 * order to preserve comparison order.
224 private String generateRequestId(){
225 try {
226 ByteBuffer buf = ByteBuffer.allocate(12);
228 long now = System.currentTimeMillis();
229 buf.putInt((int) (now / 1000));
230 buf.putInt((int) ((now * 1000) % 1000000));
232 String nextID = new Integer(requestID.getAndIncrement()).toString();
233 byte[] hashBytes =
234 MessageDigest.getInstance("SHA-1").digest(nextID.getBytes(Charsets.US_ASCII));
235 buf.put(hashBytes, 0, 4);
237 return String.format("%x", new BigInteger(buf.array()));
238 } catch (Exception e) {
239 return "";
243 @Override
244 public String getAppId() {
245 return appId;
248 @Override
249 public String getVersionId() {
250 return versionId;
253 @Override
254 public String getAuthDomain() {
255 return "gmail.com";
258 @Override
259 @Deprecated
260 public final String getRequestNamespace() {
261 String appsNamespace = (String) getAttributes().get(APPS_NAMESPACE_KEY);
262 return appsNamespace == null ? "" : appsNamespace;
265 @Override
266 public ConcurrentMap<String, Object> getAttributes() {
267 return attributes;
270 public void callRequestEndListeners() {
271 for (RequestEndListener listener : requestEndListeners) {
272 try {
273 listener.onRequestEnd(this);
274 } catch (Exception ex) {
275 logger.log(Level.WARNING,
276 "Exception while attempting to invoke RequestEndListener " + listener.getClass()
277 + ": ", ex);
280 requestEndListeners.clear();
283 @Override
284 public long getRemainingMillis() {
285 if (endTime != null) {
286 return endTime - System.currentTimeMillis();
289 return Long.MAX_VALUE;
293 * Returns the major version component from the provided version id or null
294 * if the provided version id has no major version component.
296 public static String getMajorVersion(String versionId) {
297 Matcher matcher = APP_ID_PATTERN.matcher(versionId);
298 matcher.find();
299 return matcher.group(3) == null ? matcher.group(1) : matcher.group(3);
303 * Returns the module component from the provided version id or
304 * {@link WebModule#DEFAULT_MODULE_NAME} if the provided version id has no
305 * module component.
307 public static String getModuleName(String versionId) {
308 Matcher matcher = APP_ID_PATTERN.matcher(versionId);
309 matcher.find();
310 return matcher.group(3) == null ? WebModule.DEFAULT_MODULE_NAME : matcher.group(1);