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
;
20 import java
.io
.IOException
;
21 import java
.net
.InetAddress
;
23 import java
.net
.UnknownHostException
;
24 import java
.security
.Permissions
;
25 import java
.util
.Collection
;
26 import java
.util
.Collections
;
28 import java
.util
.concurrent
.CountDownLatch
;
29 import java
.util
.logging
.Level
;
30 import java
.util
.logging
.Logger
;
32 import javax
.annotation
.concurrent
.GuardedBy
;
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
;
61 * The location of web.xml. If not provided, defaults to
62 * <appDir>/WEB-INF/web.xml
64 protected File webXmlLocation
;
67 * The hostname on which the module instance is listening for http requests.
69 protected String hostName
;
72 * The network address on which the module instance is listening for http requests.
74 protected String address
;
77 * The port on which the module instance is listening for http requests.
82 * The 0 based index for this instance or {@link LocalEnvironment#MAIN_INSTANCE}.
84 protected int instance
;
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() {
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
;
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
;
137 this.moduleInitLatch
= new CountDownLatch(1);
138 this.hostName
= "localhost";
139 this.devAppServer
= devAppServer
;
140 if ("0.0.0.0".equals(address
)) {
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
);
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
+ ")");
165 this.portMappingProvider
= callersPortMappingProvider
;
168 this.instance
= instance
;
170 return new LocalServerEnvironment() {
172 public File
getAppDir() {
173 return moduleConfigurationHandle
.getModule().getApplicationDirectory();
177 public String
getAddress() {
182 public String
getHostName() {
187 public int getPort() {
188 return AbstractContainerService
.this.port
;
192 public void waitForServerToStart() throws InterruptedException
{
193 moduleInitLatch
.await();
197 public boolean simulateProductionLatencies() {
202 public boolean enforceApiDeadlines() {
203 return !Boolean
.getBoolean("com.google.appengine.disable_api_deadlines");
209 public void setApiProxyDelegate(ApiProxy
.Delegate
<?
> apiProxyDelegate
) {
210 this.apiProxyDelegate
= apiProxyDelegate
;
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")
235 public Collection
<URL
> getUserCodeClasspath(File root
) {
236 return (Collection
<URL
>) userCodeClasspathManagerProps
.get(USER_CODE_CLASSPATH
);
240 public boolean requiresWebInf() {
241 return (Boolean
) userCodeClasspathManagerProps
.get(USER_CODE_REQUIRES_WEB_INF
);
245 return new WebAppUserCodeClasspathManager();
249 public final void createConnection() throws Exception
{
254 public final void startup() throws Exception
{
255 Environment prevEnvironment
= ApiProxy
.getCurrentEnvironment();
258 if (appEngineWebXml
== null) {
259 throw new IllegalStateException("initContext failed to initialize appEngineWebXml.");
263 startHotDeployScanner();
264 moduleInitLatch
.countDown();
266 ApiProxy
.setEnvironmentForCurrentThread(prevEnvironment
);
271 public final void shutdown() throws Exception
{
272 stopHotDeployScanner();
274 moduleConfigurationHandle
.restoreSystemProperties();
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
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
;
326 public String
getAddress() {
331 public AppEngineWebXml
getAppEngineWebXmlConfig(){
332 return appEngineWebXml
;
336 public int getPort() {
341 public String
getHostName() {
345 protected Permissions
getUserPermissions() {
346 return appEngineWebXml
.getUserPermissions();
350 * Installs a {@link LocalInitializationEnvironment} with
351 * {@link ApiProxy#setEnvironmentForCurrentThread}.
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.
358 * This depends on port which may not be set to its final value until {@link #connectContainer()}
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
);
397 * Sets up an {@link com.google.apphosting.api.ApiProxy.Environment} for container
400 public static void installLocalInitializationEnvironment(AppEngineWebXml appEngineWebXml
,
401 int instance
, int port
,
402 int defaultModuleMainPort
,
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
,
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);
427 public String
getEmail() {
432 public boolean isLoggedIn() {
437 public boolean isAdmin() {
443 * Provider for the 'portMapping'.
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();