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
;
9 import java
.math
.BigInteger
;
10 import java
.nio
.ByteBuffer
;
11 import java
.security
.MessageDigest
;
12 import java
.util
.Collection
;
13 import java
.util
.Collections
;
14 import java
.util
.Date
;
16 import java
.util
.concurrent
.ConcurrentHashMap
;
17 import java
.util
.concurrent
.ConcurrentMap
;
18 import java
.util
.concurrent
.atomic
.AtomicInteger
;
19 import java
.util
.logging
.Level
;
20 import java
.util
.logging
.Logger
;
21 import java
.util
.regex
.Matcher
;
22 import java
.util
.regex
.Pattern
;
25 * {@code LocalEnvironment} is a simple
26 * {@link com.google.apphosting.api.ApiProxy.Environment} that reads
27 * application-specific details (e.g. application identifer) directly from the
28 * custom deployment descriptor.
31 abstract public class LocalEnvironment
implements ApiProxy
.Environment
{
32 private static final Logger logger
= Logger
.getLogger(LocalEnvironment
.class.getName());
33 static final Pattern APP_ID_PATTERN
= Pattern
.compile("([^:.]*)(:([^:.]*))?(.*)?");
35 private static final String APPS_NAMESPACE_KEY
=
36 NamespaceManager
.class.getName() + ".appsNamespace";
38 public static final String INSTANCE_ID_ENV_ATTRIBUTE
= "com.google.appengine.instance.id";
41 * {@code ApiProxy.Environment} instances used with {@code
42 * ApiProxyLocalFactory} should have a entry in the map returned by
43 * {@code getAttributes()} with this key, and a {@link
44 * java.util.concurrent.Semaphore} as the value. This is used
45 * internally to track asynchronous API calls.
47 public static final String API_CALL_SEMAPHORE
=
48 "com.google.appengine.tools.development.api_call_semaphore";
51 * The name of an {@link #getAttributes() attribute} that contains a
52 * (String) unique identifier for the curent request.
54 public static final String REQUEST_ID
=
55 "com.google.appengine.runtime.request_log_id";
58 * The name of an {@link #getAttributes() attribute} that contains a
59 * a {@link Date} object representing the time this request was
62 public static final String START_TIME_ATTR
=
63 "com.google.appengine.tools.development.start_time";
66 * The name of an {@link #getAttributes() attribute} that contains a {@code
67 * Set<RequestEndListener>}. The set of {@link RequestEndListener
68 * RequestEndListeners} is populated by from within the service calls. The
69 * listeners are invoked at the end of a user request.
71 public static final String REQUEST_END_LISTENERS
=
72 "com.google.appengine.tools.development.request_end_listeners";
75 * The name of an {@link #getAttributes() attribute} that contains the {@link
76 * javax.servlet.http.HttpServletRequest} instance.
78 public static final String HTTP_SERVLET_REQUEST
=
79 "com.google.appengine.http_servlet_request";
81 private static final String REQUEST_THREAD_FACTORY_ATTR
=
82 "com.google.appengine.api.ThreadManager.REQUEST_THREAD_FACTORY";
84 private static final String BACKGROUND_THREAD_FACTORY_ATTR
=
85 "com.google.appengine.api.ThreadManager.BACKGROUND_THREAD_FACTORY";
88 * For historical and probably compatibility reasons dev appserver assigns all
89 * versions a minor version of 1.
91 private static final String MINOR_VERSION_SUFFIX
= ".1";
94 * In production, this is a constant that defines the {@link #getAttributes() attribute} name
95 * that contains the hostname on which the default version is listening.
96 * In the local development server, the {@link #getAttributes() attribute} contains the
97 * listening port in addition to the hostname, and is the one and only frontend app instance
100 public static final String DEFAULT_VERSION_HOSTNAME
=
101 "com.google.appengine.runtime.default_version_hostname";
103 private final String appId
;
104 private final String versionId
;
106 private final Collection
<RequestEndListener
> requestEndListeners
;
108 protected final ConcurrentMap
<String
, Object
> attributes
=
109 new ConcurrentHashMap
<String
, Object
>();
111 private final Long endTime
;
113 * Instance number for a server's main instance.
115 * Clients depend on this literal having the value -1 so do not change this
116 * value without making needed updates to clients.
118 public static final int MAIN_INSTANCE
= -1;
121 protected LocalEnvironment(String appId
, String majorVersionId
) {
122 this(appId
, WebModule
.DEFAULT_SERVER_NAME
, majorVersionId
, LocalEnvironment
.MAIN_INSTANCE
,
127 protected LocalEnvironment(String appId
, String majorVersionId
, Long deadlineMillis
) {
128 this(appId
, WebModule
.DEFAULT_SERVER_NAME
, majorVersionId
, LocalEnvironment
.MAIN_INSTANCE
,
132 protected LocalEnvironment(String appId
, String serverName
, String majorVersionId
, int instance
,
133 Long deadlineMillis
) {
135 if (serverName
== null || WebModule
.DEFAULT_SERVER_NAME
.equals(serverName
)) {
136 this.versionId
= majorVersionId
+ MINOR_VERSION_SUFFIX
;
138 this.versionId
= serverName
+ ":" + majorVersionId
+ MINOR_VERSION_SUFFIX
;
140 if (deadlineMillis
== null) {
142 } else if (deadlineMillis
< 0) {
143 throw new IllegalArgumentException("deadlineMillis must be a non-negative integer.");
145 this.endTime
= System
.currentTimeMillis() + deadlineMillis
;
147 setInstance(attributes
, instance
);
148 requestEndListeners
=
149 Collections
.newSetFromMap(new ConcurrentHashMap
<RequestEndListener
, Boolean
>(10));
150 attributes
.put(REQUEST_ID
, generateRequestId());
151 attributes
.put(REQUEST_END_LISTENERS
, requestEndListeners
);
152 attributes
.put(START_TIME_ATTR
, new Date());
153 attributes
.put(REQUEST_THREAD_FACTORY_ATTR
, new RequestThreadFactory());
154 attributes
.put(BACKGROUND_THREAD_FACTORY_ATTR
, new BackgroundThreadFactory(appId
,
155 serverName
, majorVersionId
));
159 * Sets the instance for the provided attributes.
161 static void setInstance(Map
<String
, Object
> attributes
, int instance
) {
162 attributes
.remove(INSTANCE_ID_ENV_ATTRIBUTE
);
163 if (instance
!= LocalEnvironment
.MAIN_INSTANCE
) {
164 attributes
.put(INSTANCE_ID_ENV_ATTRIBUTE
, Integer
.toString(instance
));
169 * Returns the current instance or {@link #MAIN_INSTANCE} if none is defined.
171 static int getCurrentInstance() {
172 int result
= MAIN_INSTANCE
;
173 String instance
= (String
) ApiProxy
.getCurrentEnvironment().getAttributes().get(
174 LocalEnvironment
.INSTANCE_ID_ENV_ATTRIBUTE
);
175 if (instance
!= null) {
176 result
= Integer
.parseInt(instance
);
181 private static AtomicInteger requestID
= new AtomicInteger();
184 * Generates a unique request ID using the same algorithm as the Python dev
185 * appserver. It is similar to the production algorithm, but with less
186 * embedded data. The primary goal is that the IDs be sortable by timestamp,
187 * so the initial bytes consist of seconds then microseconds packed in
188 * big-endian byte order. To ensure uniqueness, a hash of the incrementing
189 * counter is appended. Hexadecimal encoding is used instead of base64 in
190 * order to preserve comparison order.
192 private String
generateRequestId(){
194 ByteBuffer buf
= ByteBuffer
.allocate(12);
196 long now
= System
.currentTimeMillis();
197 buf
.putInt((int) (now
/ 1000));
198 buf
.putInt((int) ((now
* 1000) % 1000000));
200 String nextID
= new Integer(requestID
.getAndIncrement()).toString();
201 byte[] hashBytes
= MessageDigest
.getInstance("SHA-1").digest(nextID
.getBytes());
202 buf
.put(hashBytes
, 0, 4);
204 return String
.format("%x", new BigInteger(buf
.array()));
205 } catch (Exception e
) {
211 public String
getAppId() {
216 public String
getVersionId() {
221 public String
getAuthDomain() {
227 public final String
getRequestNamespace() {
228 String appsNamespace
= (String
) getAttributes().get(APPS_NAMESPACE_KEY
);
229 return appsNamespace
== null ?
"" : appsNamespace
;
233 public ConcurrentMap
<String
, Object
> getAttributes() {
237 public void callRequestEndListeners() {
238 for (RequestEndListener listener
: requestEndListeners
) {
240 listener
.onRequestEnd(this);
241 } catch (Exception ex
) {
242 logger
.log(Level
.WARNING
,
243 "Exception while attempting to invoke RequestEndListener " + listener
.getClass()
247 requestEndListeners
.clear();
251 public long getRemainingMillis() {
252 if (endTime
!= null) {
253 return endTime
- System
.currentTimeMillis();
256 return Long
.MAX_VALUE
;
260 * Returns the major version component from the provided version id or null
261 * if the provided version id has no major version component.
263 public static String
getMajorVersion(String versionId
) {
264 Matcher matcher
= APP_ID_PATTERN
.matcher(versionId
);
266 return matcher
.group(3) == null ? matcher
.group(1) : matcher
.group(3);
270 * Returns the server component from the provided version id or
271 * {@link WebModule#DEFAULT_SERVER_NAME} if the provided version id has no
274 public static String
getServerName(String versionId
) {
275 Matcher matcher
= APP_ID_PATTERN
.matcher(versionId
);
277 return matcher
.group(3) == null ? WebModule
.DEFAULT_SERVER_NAME
: matcher
.group(1);