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
;
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";
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";
41 * The name of an {@link #getAttributes() attribute} that contains a
42 * (String) unique identifier for the curent request to the
45 public static final String REQUEST_ID
= "com.google.appengine.tools.development.request_id";
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";
55 * The name of an {@link #getAttributes() attribute} that contains a
56 * a {@link Date} object representing the time this request was
59 public static final String START_TIME_ATTR
=
60 "com.google.appengine.tools.development.start_time";
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";
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
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
) {
103 this.versionId
= majorVersionId
+ ".1";
104 if (deadlineMillis
== null) {
106 } else if (deadlineMillis
< 0) {
107 throw new IllegalArgumentException("deadlineMillis must be a non-negative integer.");
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
,
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(){
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
) {
156 public String
getAppId() {
161 public String
getVersionId() {
166 public String
getAuthDomain() {
172 public final String
getRequestNamespace() {
173 String appsNamespace
= (String
) getAttributes().get(APPS_NAMESPACE_KEY
);
174 return appsNamespace
== null ?
"" : appsNamespace
;
178 public ConcurrentMap
<String
, Object
> getAttributes() {
182 public void callRequestEndListeners() {
183 for (RequestEndListener listener
: requestEndListeners
) {
185 listener
.onRequestEnd(this);
186 } catch (Exception ex
) {
187 logger
.log(Level
.WARNING
,
188 "Exception while attempting to invoke RequestEndListener " + listener
.getClass()
192 requestEndListeners
.clear();
196 public long getRemainingMillis() {
197 if (endTime
!= null) {
198 return endTime
- System
.currentTimeMillis();
201 return Long
.MAX_VALUE
;