Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / LocalEnvironment.java
blob04d26bfa32e86c92b00a0bd93418a2da1dd66f0e
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 String INSTANCE_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.id";
41 /**
42 * {@code ApiProxy.Environment} instances used with {@code
43 * ApiProxyLocalFactory} should have a entry in the map returned by
44 * {@code getAttributes()} with this key, and a {@link
45 * java.util.concurrent.Semaphore} as the value. This is used
46 * internally to track asynchronous API calls.
48 public static final String API_CALL_SEMAPHORE =
49 "com.google.appengine.tools.development.api_call_semaphore";
51 /**
52 * The name of an {@link #getAttributes() attribute} that contains a
53 * (String) unique identifier for the curent request.
55 public static final String REQUEST_ID =
56 "com.google.appengine.runtime.request_log_id";
58 /**
59 * The name of an {@link #getAttributes() attribute} that contains a
60 * a {@link Date} object representing the time this request was
61 * started.
63 public static final String START_TIME_ATTR =
64 "com.google.appengine.tools.development.start_time";
66 /**
67 * The name of an {@link #getAttributes() attribute} that contains a {@code
68 * Set<RequestEndListener>}. The set of {@link RequestEndListener
69 * RequestEndListeners} is populated by from within the service calls. The
70 * listeners are invoked at the end of a user request.
72 public static final String REQUEST_END_LISTENERS =
73 "com.google.appengine.tools.development.request_end_listeners";
75 /**
76 * The name of an {@link #getAttributes() attribute} that contains the {@link
77 * javax.servlet.http.HttpServletRequest} instance.
79 public static final String HTTP_SERVLET_REQUEST =
80 "com.google.appengine.http_servlet_request";
82 private static final String REQUEST_THREAD_FACTORY_ATTR =
83 "com.google.appengine.api.ThreadManager.REQUEST_THREAD_FACTORY";
85 private static final String BACKGROUND_THREAD_FACTORY_ATTR =
86 "com.google.appengine.api.ThreadManager.BACKGROUND_THREAD_FACTORY";
88 /**
89 * For historical and probably compatibility reasons dev appserver assigns all
90 * versions a minor version of 1.
92 private static final String MINOR_VERSION_SUFFIX = ".1";
94 /**
95 * In production, this is a constant that defines the {@link #getAttributes() attribute} name
96 * that contains the hostname on which the default version is listening.
97 * In the local development server, the {@link #getAttributes() attribute} contains the
98 * listening port in addition to the hostname, and is the one and only frontend app instance
99 * hostname and port.
101 public static final String DEFAULT_VERSION_HOSTNAME =
102 "com.google.appengine.runtime.default_version_hostname";
104 private final String appId;
105 private final String versionId;
107 private final Collection<RequestEndListener> requestEndListeners;
109 protected final ConcurrentMap<String, Object> attributes =
110 new ConcurrentHashMap<String, Object>();
112 private final Long endTime;
114 * Instance number for a server's main instance.
115 * <p>
116 * Clients depend on this literal having the value -1 so do not change this
117 * value without making needed updates to clients.
119 public static final int MAIN_INSTANCE = -1;
121 @Deprecated
122 protected LocalEnvironment(String appId, String majorVersionId) {
123 this(appId, WebModule.DEFAULT_SERVER_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE,
124 null);
127 @Deprecated
128 protected LocalEnvironment(String appId, String majorVersionId, Long deadlineMillis) {
129 this(appId, WebModule.DEFAULT_SERVER_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE,
130 deadlineMillis);
133 protected LocalEnvironment(String appId, String serverName, String majorVersionId, int instance,
134 Long deadlineMillis) {
135 this.appId = appId;
136 if (serverName == null || WebModule.DEFAULT_SERVER_NAME.equals(serverName)) {
137 this.versionId = majorVersionId + MINOR_VERSION_SUFFIX;
138 } else {
139 this.versionId = serverName + ":" + majorVersionId + MINOR_VERSION_SUFFIX;
141 if (deadlineMillis == null) {
142 this.endTime = null;
143 } else if (deadlineMillis < 0) {
144 throw new IllegalArgumentException("deadlineMillis must be a non-negative integer.");
145 } else {
146 this.endTime = System.currentTimeMillis() + deadlineMillis;
148 setInstance(attributes, instance);
149 requestEndListeners =
150 Collections.newSetFromMap(new ConcurrentHashMap<RequestEndListener, Boolean>(10));
151 attributes.put(REQUEST_ID, generateRequestId());
152 attributes.put(REQUEST_END_LISTENERS, requestEndListeners);
153 attributes.put(START_TIME_ATTR, new Date());
154 attributes.put(REQUEST_THREAD_FACTORY_ATTR, new RequestThreadFactory());
155 attributes.put(BACKGROUND_THREAD_FACTORY_ATTR, new BackgroundThreadFactory(appId,
156 serverName, majorVersionId));
160 * Sets the instance for the provided attributes.
162 static void setInstance(Map<String, Object> attributes, int instance) {
163 attributes.remove(INSTANCE_ID_ENV_ATTRIBUTE);
164 if (instance != LocalEnvironment.MAIN_INSTANCE) {
165 attributes.put(INSTANCE_ID_ENV_ATTRIBUTE, Integer.toString(instance));
170 * Returns the current instance or {@link #MAIN_INSTANCE} if none is defined.
172 static int getCurrentInstance() {
173 int result = MAIN_INSTANCE;
174 String instance = (String) ApiProxy.getCurrentEnvironment().getAttributes().get(
175 LocalEnvironment.INSTANCE_ID_ENV_ATTRIBUTE);
176 if (instance != null) {
177 result = Integer.parseInt(instance);
179 return result;
182 private static AtomicInteger requestID = new AtomicInteger();
185 * Generates a unique request ID using the same algorithm as the Python dev
186 * appserver. It is similar to the production algorithm, but with less
187 * embedded data. The primary goal is that the IDs be sortable by timestamp,
188 * so the initial bytes consist of seconds then microseconds packed in
189 * big-endian byte order. To ensure uniqueness, a hash of the incrementing
190 * counter is appended. Hexadecimal encoding is used instead of base64 in
191 * order to preserve comparison order.
193 private String generateRequestId(){
194 try {
195 ByteBuffer buf = ByteBuffer.allocate(12);
197 long now = System.currentTimeMillis();
198 buf.putInt((int) (now / 1000));
199 buf.putInt((int) ((now * 1000) % 1000000));
201 String nextID = new Integer(requestID.getAndIncrement()).toString();
202 byte[] hashBytes =
203 MessageDigest.getInstance("SHA-1").digest(
204 nextID.getBytes(Charsets.unsafeDefaultCharset()));
205 buf.put(hashBytes, 0, 4);
207 return String.format("%x", new BigInteger(buf.array()));
208 } catch (Exception e) {
209 return "";
213 @Override
214 public String getAppId() {
215 return appId;
218 @Override
219 public String getVersionId() {
220 return versionId;
223 @Override
224 public String getAuthDomain() {
225 return "gmail.com";
228 @Override
229 @Deprecated
230 public final String getRequestNamespace() {
231 String appsNamespace = (String) getAttributes().get(APPS_NAMESPACE_KEY);
232 return appsNamespace == null ? "" : appsNamespace;
235 @Override
236 public ConcurrentMap<String, Object> getAttributes() {
237 return attributes;
240 public void callRequestEndListeners() {
241 for (RequestEndListener listener : requestEndListeners) {
242 try {
243 listener.onRequestEnd(this);
244 } catch (Exception ex) {
245 logger.log(Level.WARNING,
246 "Exception while attempting to invoke RequestEndListener " + listener.getClass()
247 + ": ", ex);
250 requestEndListeners.clear();
253 @Override
254 public long getRemainingMillis() {
255 if (endTime != null) {
256 return endTime - System.currentTimeMillis();
259 return Long.MAX_VALUE;
263 * Returns the major version component from the provided version id or null
264 * if the provided version id has no major version component.
266 public static String getMajorVersion(String versionId) {
267 Matcher matcher = APP_ID_PATTERN.matcher(versionId);
268 matcher.find();
269 return matcher.group(3) == null ? matcher.group(1) : matcher.group(3);
273 * Returns the server component from the provided version id or
274 * {@link WebModule#DEFAULT_SERVER_NAME} if the provided version id has no
275 * server component.
277 public static String getServerName(String versionId) {
278 Matcher matcher = APP_ID_PATTERN.matcher(versionId);
279 matcher.find();
280 return matcher.group(3) == null ? WebModule.DEFAULT_SERVER_NAME : matcher.group(1);