1.9.5
[gae.git] / java / src / main / com / google / appengine / tools / development / AbstractContainerService.java
bloba02e22c6c98221cebd4144866d0f45c9ebe3b59d
1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.development;
5 import static com.google.appengine.tools.development.LocalEnvironment.DEFAULT_VERSION_HOSTNAME;
7 import com.google.appengine.api.backends.BackendService;
8 import com.google.appengine.tools.development.ApplicationConfigurationManager.ModuleConfigurationHandle;
9 import com.google.appengine.tools.info.SdkImplInfo;
10 import com.google.apphosting.api.ApiProxy;
11 import com.google.apphosting.api.ApiProxy.Environment;
12 import com.google.apphosting.utils.config.AppEngineWebXml;
13 import com.google.apphosting.utils.config.ClassPathBuilder;
14 import com.google.apphosting.utils.config.WebModule;
15 import com.google.apphosting.utils.config.WebXml;
16 import com.google.common.base.Joiner;
17 import com.google.common.collect.ImmutableMap;
19 import java.io.File;
20 import java.io.IOException;
21 import java.net.InetAddress;
22 import java.net.URL;
23 import java.net.UnknownHostException;
24 import java.security.Permissions;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Map;
28 import java.util.concurrent.CountDownLatch;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
32 import javax.annotation.concurrent.GuardedBy;
34 /**
35 * Common implementation for the {@link ContainerService} interface.
37 * There should be no reference to any third-party servlet container from here.
40 abstract class AbstractContainerService implements ContainerService {
42 private static final Logger log = Logger.getLogger(AbstractContainerService.class.getName());
44 protected static final String _AH_URL_RELOAD = "/_ah/reloadwebapp";
46 private static final String USER_CODE_CLASSPATH_MANAGER_PROP =
47 "devappserver.userCodeClasspathManager";
49 private static final String USER_CODE_CLASSPATH = USER_CODE_CLASSPATH_MANAGER_PROP + ".classpath";
50 private static final String USER_CODE_REQUIRES_WEB_INF =
51 USER_CODE_CLASSPATH_MANAGER_PROP + ".requiresWebInf";
53 public static final String PORT_MAPPING_PROVIDER_PROP = "devappserver.portMappingProvider";
55 protected ModuleConfigurationHandle moduleConfigurationHandle;
56 protected String devAppServerVersion;
57 protected File appDir;
58 protected File externalResourceDir;
60 /**
61 * The location of web.xml. If not provided, defaults to
62 * <appDir>/WEB-INF/web.xml
64 protected File webXmlLocation;
66 /**
67 * The hostname on which the module instance is listening for http requests.
69 protected String hostName;
71 /**
72 * The network address on which the module instance is listening for http requests.
74 protected String address;
76 /**
77 * The port on which the module instance is listening for http requests.
79 protected int port;
81 /**
82 * The 0 based index for this instance or {@link LocalEnvironment#MAIN_INSTANCE}.
84 protected int instance;
86 /**
87 * A reference to the parent DevAppServer that configured this container.
89 protected DevAppServer devAppServer;
91 protected AppEngineWebXml appEngineWebXml;
93 protected WebXml webXml;
95 private String backendName;
97 private int backendInstance;
99 private PortMappingProvider portMappingProvider = new PortMappingProvider() {
100 @Override
101 public Map<String, String> getPortMapping() {
102 return Collections.emptyMap();
106 @GuardedBy("AbstractContainerService.class")
107 private static SystemPropertiesManager systemPropertiesManager;
110 * Latch that will open once the module instance is fully initialized.
111 * TODO(user): This is used by some services but only for the
112 * default instance of the default module. Investigate. Does module
113 * start/stop cause issues? There is some issue with tasks during
114 * Servlet initialization.
116 private CountDownLatch moduleInitLatch;
119 * Not initialized until {@link #startup()} has been called.
121 protected ApiProxy.Delegate<?> apiProxyDelegate;
123 protected UserCodeClasspathManager userCodeClasspathManager;
125 protected ModulesFilterHelper modulesFilterHelper;
127 @Override
128 public final LocalServerEnvironment configure(String devAppServerVersion, final String address,
129 int port, final ModuleConfigurationHandle moduleConfigurationHandle, File externalResourceDir,
130 Map<String, Object> containerConfigProperties, int instance, DevAppServer devAppServer) {
131 this.devAppServerVersion = devAppServerVersion;
132 this.moduleConfigurationHandle = moduleConfigurationHandle;
133 extractFieldsFromWebModule(moduleConfigurationHandle.getModule());
134 this.externalResourceDir = externalResourceDir;
135 this.address = address;
136 this.port = port;
137 this.moduleInitLatch = new CountDownLatch(1);
138 this.hostName = "localhost";
139 this.devAppServer = devAppServer;
140 if ("0.0.0.0".equals(address)) {
141 try {
142 InetAddress localhost = InetAddress.getLocalHost();
143 this.hostName = localhost.getHostName();
144 } catch (UnknownHostException ex) {
145 log.log(Level.WARNING,
146 "Unable to determine hostname - defaulting to localhost.");
150 this.userCodeClasspathManager = newUserCodeClasspathProvider(containerConfigProperties);
151 this.modulesFilterHelper = (ModulesFilterHelper)
152 containerConfigProperties.get(DevAppServerImpl.MODULES_FILTER_HELPER_PROPERTY);
153 this.backendName =
154 (String) containerConfigProperties.get(BackendService.BACKEND_ID_ENV_ATTRIBUTE);
155 Object rawBackendInstance =
156 containerConfigProperties.get(BackendService.INSTANCE_ID_ENV_ATTRIBUTE);
157 this.backendInstance =
158 rawBackendInstance == null ? -1 : ((Integer) rawBackendInstance).intValue();
159 PortMappingProvider callersPortMappingProvider =
160 (PortMappingProvider) containerConfigProperties.get(PORT_MAPPING_PROVIDER_PROP);
161 if (callersPortMappingProvider == null) {
162 log.warning("Null value for containerConfigProperties.get("
163 + PORT_MAPPING_PROVIDER_PROP + ")");
164 } else {
165 this.portMappingProvider = callersPortMappingProvider;
168 this.instance = instance;
170 return new LocalServerEnvironment() {
171 @Override
172 public File getAppDir() {
173 return moduleConfigurationHandle.getModule().getApplicationDirectory();
176 @Override
177 public String getAddress() {
178 return address;
181 @Override
182 public String getHostName() {
183 return hostName;
186 @Override
187 public int getPort() {
188 return AbstractContainerService.this.port;
191 @Override
192 public void waitForServerToStart() throws InterruptedException {
193 moduleInitLatch.await();
196 @Override
197 public boolean simulateProductionLatencies() {
198 return false;
201 @Override
202 public boolean enforceApiDeadlines() {
203 return !Boolean.getBoolean("com.google.appengine.disable_api_deadlines");
208 @Override
209 public void setApiProxyDelegate(ApiProxy.Delegate<?> apiProxyDelegate) {
210 this.apiProxyDelegate = apiProxyDelegate;
214 * @param webModule
216 protected void extractFieldsFromWebModule(WebModule webModule) {
217 this.appDir = webModule.getApplicationDirectory();
218 this.webXml = webModule.getWebXml();
219 this.webXmlLocation = webModule.getWebXmlFile();
220 this.appEngineWebXml = webModule.getAppEngineWebXml();
224 * Constructs a {@link UserCodeClasspathManager} from the given properties.
226 private static UserCodeClasspathManager newUserCodeClasspathProvider(
227 Map<String, Object> containerConfigProperties) {
228 if (containerConfigProperties.containsKey(USER_CODE_CLASSPATH_MANAGER_PROP)) {
229 @SuppressWarnings("unchecked")
230 final Map<String, Object> userCodeClasspathManagerProps =
231 (Map<String, Object>) containerConfigProperties.get(USER_CODE_CLASSPATH_MANAGER_PROP);
232 return new UserCodeClasspathManager() {
233 @SuppressWarnings("unchecked")
234 @Override
235 public Collection<URL> getUserCodeClasspath(File root) {
236 return (Collection<URL>) userCodeClasspathManagerProps.get(USER_CODE_CLASSPATH);
239 @Override
240 public boolean requiresWebInf() {
241 return (Boolean) userCodeClasspathManagerProps.get(USER_CODE_REQUIRES_WEB_INF);
245 return new WebAppUserCodeClasspathManager();
248 @Override
249 public final void createConnection() throws Exception {
250 connectContainer();
253 @Override
254 public final void startup() throws Exception {
255 Environment prevEnvironment = ApiProxy.getCurrentEnvironment();
256 try {
257 initContext();
258 if (appEngineWebXml == null) {
259 throw new IllegalStateException("initContext failed to initialize appEngineWebXml.");
262 startContainer();
263 startHotDeployScanner();
264 moduleInitLatch.countDown();
265 } finally {
266 ApiProxy.setEnvironmentForCurrentThread(prevEnvironment);
270 @Override
271 public final void shutdown() throws Exception {
272 stopHotDeployScanner();
273 stopContainer();
274 moduleConfigurationHandle.restoreSystemProperties();
277 @Override
278 public Map<String, String> getServiceProperties() {
279 return ImmutableMap.of("appengine.dev.inbound-services",
280 Joiner.on(",").useForNull("null").join(appEngineWebXml.getInboundServices()));
284 * Set up the webapp context in a container specific way.
285 * <p>Note that {@link #initContext()} is required to call
286 * {@link #installLocalInitializationEnvironment()} for the service to be correctly
287 * initialized.
289 * @return the effective webapp directory.
291 protected abstract File initContext() throws IOException;
294 * Creates the servlet container's network connections.
296 protected abstract void connectContainer() throws Exception;
299 * Start up the servlet container runtime.
301 protected abstract void startContainer() throws Exception;
304 * Stop the servlet container runtime.
306 protected abstract void stopContainer() throws Exception;
309 * Start up the hot-deployment scanner.
311 protected abstract void startHotDeployScanner() throws Exception;
314 * Stop the hot-deployment scanner.
316 protected abstract void stopHotDeployScanner() throws Exception;
319 * Re-deploy the current webapp context in a container specific way,
320 * while taking into account possible appengine-web.xml change too,
321 * without restarting the module instance.
323 protected abstract void reloadWebApp() throws Exception;
325 @Override
326 public String getAddress() {
327 return address;
330 @Override
331 public AppEngineWebXml getAppEngineWebXmlConfig(){
332 return appEngineWebXml;
335 @Override
336 public int getPort() {
337 return port;
340 @Override
341 public String getHostName() {
342 return hostName;
345 protected Permissions getUserPermissions() {
346 return appEngineWebXml.getUserPermissions();
350 * Installs a {@link LocalInitializationEnvironment} with
351 * {@link ApiProxy#setEnvironmentForCurrentThread}.
352 * <p>
353 * Filters and servlets get initialized when we call server.start(). If any of
354 * those filters and servlets need access to the current execution environment
355 * they'll call ApiProxy.getCurrentEnvironment(). We set a special initialization
356 * environment so that there is an environment available when this happens.
357 * <p>
358 * This depends on port which may not be set to its final value until {@link #connectContainer()}
359 * is called.
361 protected void installLocalInitializationEnvironment() {
362 installLocalInitializationEnvironment(appEngineWebXml, instance, port, devAppServer.getPort(),
363 backendName, backendInstance, portMappingProvider.getPortMapping());
366 /** Returns {@code true} if appengine-web.xml <sessions-enabled> is true. */
367 protected boolean isSessionsEnabled() {
368 return appEngineWebXml.getSessionsEnabled();
372 * Gets all of the URLs that should be added to the classpath for an
373 * application located at {@code root}.
375 protected URL[] getClassPathForApp(File root) {
376 ClassPathBuilder classPathBuilder =
377 new ClassPathBuilder(appEngineWebXml.getClassLoaderConfig());
379 classPathBuilder.addUrls(SdkImplInfo.getAgentRuntimeLibs());
381 classPathBuilder.addUrls(userCodeClasspathManager.getUserCodeClasspath(root));
382 classPathBuilder.addUrls(SdkImplInfo.getUserJspLibs());
383 classPathBuilder.addUrls(SdkImplInfo.getWebApiToolLibs());
384 return getUrls(classPathBuilder);
387 private static URL[] getUrls(ClassPathBuilder classPathBuilder) {
388 URL[] urls = classPathBuilder.getUrls();
389 String message = classPathBuilder.getLogMessage();
390 if (!message.isEmpty()) {
391 log.warning(message);
393 return urls;
397 * Sets up an {@link com.google.apphosting.api.ApiProxy.Environment} for container
398 * initialization.
400 public static void installLocalInitializationEnvironment(AppEngineWebXml appEngineWebXml,
401 int instance, int port,
402 int defaultModuleMainPort,
403 String backendName,
404 int backendInstance,
405 Map<String, String> portMapping) {
406 Environment environment = new LocalInitializationEnvironment(
407 appEngineWebXml.getAppId(), WebModule.getModuleName(appEngineWebXml),
408 appEngineWebXml.getMajorVersionId(), instance, port);
409 environment.getAttributes().put(DEFAULT_VERSION_HOSTNAME, "localhost:"
410 + defaultModuleMainPort);
411 ApiProxy.setEnvironmentForCurrentThread(environment);
412 DevAppServerModulesFilter.injectBackendServiceCurrentApiInfo(backendName, backendInstance,
413 portMapping);
417 * A fake {@link LocalEnvironment} implementation that is used during the
418 * initialization of the Development AppServer.
420 public static class LocalInitializationEnvironment extends LocalEnvironment {
421 public LocalInitializationEnvironment(String appId, String moduleName, String majorVersionId,
422 int instance, int port) {
423 super(appId, moduleName, majorVersionId, instance, port, null);
426 @Override
427 public String getEmail() {
428 return null;
431 @Override
432 public boolean isLoggedIn() {
433 return false;
436 @Override
437 public boolean isAdmin() {
438 return false;
443 * Provider for the 'portMapping'.
444 * <p>
445 * The provided map contains an entry for every backend instance.
446 * For the main instance the key is the backend name and the value is
447 * the hostname:port for sending http requests to the instance (i.e.
448 * bob->127.0.0.1:1234). For other instances the key is
449 * instance-id.hostname and the value is again the hostname:port for
450 * sending http requests to the instance (i.e. 2.bob->127.0.0.1:1234).
452 interface PortMappingProvider {
453 Map<String, String> getPortMapping();