Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / AbstractContainerService.java
blob8aa6eb55a857a08904253529b846ff07817496f5
1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.development;
5 import com.google.appengine.api.log.dev.LocalLogService;
6 import com.google.appengine.api.utils.SystemProperty;
7 import com.google.appengine.tools.info.SdkImplInfo;
8 import com.google.appengine.tools.info.SdkInfo;
9 import com.google.appengine.tools.plugins.AppYamlProcessor;
10 import com.google.apphosting.api.ApiProxy;
11 import com.google.apphosting.utils.config.AppEngineConfigException;
12 import com.google.apphosting.utils.config.AppEngineWebXml;
13 import com.google.apphosting.utils.config.AppEngineWebXmlReader;
14 import com.google.apphosting.utils.config.BackendsXml;
15 import com.google.apphosting.utils.config.BackendsXmlReader;
16 import com.google.apphosting.utils.config.WebXml;
17 import com.google.apphosting.utils.config.WebXmlReader;
18 import com.google.common.base.Join;
19 import com.google.common.collect.ImmutableMap;
20 import com.google.common.collect.Maps;
22 import java.io.BufferedInputStream;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.net.InetAddress;
30 import java.net.URL;
31 import java.net.UnknownHostException;
32 import java.security.Permissions;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Properties;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.logging.Handler;
40 import java.util.logging.Level;
41 import java.util.logging.LogManager;
42 import java.util.logging.Logger;
44 /**
45 * Common implementation for the {@link ContainerService} interface.
47 * There should be no reference to any third-party servlet container from here.
50 public abstract class AbstractContainerService implements ContainerService {
52 private static final Logger log = Logger.getLogger(AbstractContainerService.class.getName());
54 protected static final String _AH_URL_RELOAD = "/_ah/reloadwebapp";
56 private static final String LOGGING_CONFIG_FILE = "java.util.logging.config.file";
58 private static final String USER_CODE_CLASSPATH_MANAGER_PROP =
59 "devappserver.userCodeClasspathManager";
61 private static final String USER_CODE_CLASSPATH = USER_CODE_CLASSPATH_MANAGER_PROP + ".classpath";
62 private static final String USER_CODE_REQUIRES_WEB_INF =
63 USER_CODE_CLASSPATH_MANAGER_PROP + ".requiresWebInf";
65 protected String devAppServerVersion;
66 protected File appDir;
67 protected File externalResourceDir;
69 /**
70 * The location of web.xml. If not provided, defaults to
71 * <appDir>/WEB-INF/web.xml
73 protected File webXmlLocation;
75 /**
76 * The hostname on which the server is listening for http requests.
78 protected String hostName;
80 /**
81 * The location of appengine-web.xml. If not provided, defaults to
82 * <appDir>/WEB-INF/appengine-web.xml
84 protected File appEngineWebXmlLocation;
86 /**
87 * The network address on which the server is listening for http requests.
89 protected String address;
91 /**
92 * The port on which the server is listening for http requests.
94 protected int port;
96 /**
97 * A reference to the parent DevAppServer that configured this container.
99 protected DevAppServer devAppServer;
101 protected AppEngineWebXml appEngineWebXml;
103 protected WebXml webXml;
105 protected BackendsXml backendsXml;
108 * We modify the system properties when the dev appserver is
109 * launched using key/value pairs defined in appengine-web.xml.
110 * This can make it very easy to leak state across tests that launch
111 * instances of the dev appserver, so we use this member to keep
112 * track of the original values all system properties at start-up.
113 * We then restore the values when we shutdown the server.
115 private static Properties originalSysProps = null;
118 * The severity of an environment variable mismatch.
120 private static EnvironmentVariableMismatchSeverity envVarMismatchSeverity =
121 EnvironmentVariableMismatchSeverity.ERROR;
124 * Latch that will open once the server is fully initialized.
126 private CountDownLatch serverInitLatch;
129 * Not initialized until {@link #startup()} has been called.
131 protected ApiProxyLocal apiProxyLocal;
133 protected UserCodeClasspathManager userCodeClasspathManager;
135 public final LocalServerEnvironment configure(String devAppServerVersion, final File appDir,
136 final File externalResourceDir, File webXmlLocation, File appEngineWebXmlLocation,
137 final String address, int port, Map<String, Object> containerConfigProperties,
138 DevAppServer devAppServer) {
139 this.devAppServerVersion = devAppServerVersion;
140 this.appDir = appDir;
141 this.externalResourceDir = externalResourceDir;
142 this.webXmlLocation = webXmlLocation;
143 this.appEngineWebXmlLocation = appEngineWebXmlLocation;
144 this.address = address;
145 this.port = port;
146 this.serverInitLatch = new CountDownLatch(1);
147 this.hostName = "localhost";
148 this.devAppServer = devAppServer;
149 if ("0.0.0.0".equals(address)) {
150 try {
151 InetAddress localhost = InetAddress.getLocalHost();
152 this.hostName = localhost.getHostName();
153 } catch (UnknownHostException ex) {
154 log.log(Level.WARNING,
155 "Unable to determine hostname - defaulting to localhost.");
159 this.userCodeClasspathManager = newUserCodeClasspathProvider(containerConfigProperties);
160 return new LocalServerEnvironment() {
161 @Override
162 public File getAppDir() {
163 return appDir;
166 @Override
167 public String getAddress() {
168 return address;
171 @Override
172 public String getHostName() {
173 return hostName;
176 @Override
177 public int getPort() {
178 return AbstractContainerService.this.port;
181 @Override
182 public void waitForServerToStart() throws InterruptedException {
183 serverInitLatch.await();
186 @Override
187 public boolean simulateProductionLatencies() {
188 return false;
191 @Override
192 public boolean enforceApiDeadlines() {
193 return !Boolean.getBoolean("com.google.appengine.disable_api_deadlines");
199 * Constructs a {@link UserCodeClasspathManager} from the given properties.
201 private static UserCodeClasspathManager newUserCodeClasspathProvider(
202 Map<String, Object> containerConfigProperties) {
203 if (containerConfigProperties.containsKey(USER_CODE_CLASSPATH_MANAGER_PROP)) {
204 final Map<String, Object> userCodeClasspathManagerProps =
205 (Map<String, Object>) containerConfigProperties.get(USER_CODE_CLASSPATH_MANAGER_PROP);
206 return new UserCodeClasspathManager() {
207 @Override
208 public Collection<URL> getUserCodeClasspath(File root) {
209 return (Collection<URL>) userCodeClasspathManagerProps.get(USER_CODE_CLASSPATH);
212 @Override
213 public boolean requiresWebInf() {
214 return (Boolean) userCodeClasspathManagerProps.get(USER_CODE_REQUIRES_WEB_INF);
218 return new WebAppUserCodeClasspathManager();
222 * This is made final, and detail implementation (that is specific to any
223 * particular servlet container) goes to individual "template" methods.
225 @Override
226 public final void startup() throws Exception {
227 apiProxyLocal = (ApiProxyLocal) ApiProxy.getDelegate();
228 File webAppDir = initContext();
229 loadAppEngineWebXml(webAppDir);
231 startContainer();
232 startHotDeployScanner();
233 serverInitLatch.countDown();
236 @Override
237 public final void shutdown() throws Exception {
238 stopHotDeployScanner();
239 stopContainer();
240 restoreSystemProperties();
243 /** {@inheritdoc} */
244 @Override
245 public Map<String, String> getServiceProperties() {
246 return ImmutableMap.of("appengine.dev.inbound-services",
247 Join.join(",", appEngineWebXml.getInboundServices()));
251 * Set up the webapp context in a container specific way.
253 * @return the effective webapp directory.
255 protected abstract File initContext() throws IOException;
258 * Start up the servlet container runtime.
260 protected abstract void startContainer() throws Exception;
263 * Stop the servlet container runtime.
265 protected abstract void stopContainer() throws Exception;
268 * Start up the hot-deployment scanner.
270 protected abstract void startHotDeployScanner() throws Exception;
273 * Stop the hot-deployment scanner.
275 protected abstract void stopHotDeployScanner() throws Exception;
278 * Re-deploy the current webapp context in a container specific way,
279 * while taking into account possible appengine-web.xml change too,
280 * without restarting the server.
282 protected abstract void reloadWebApp() throws Exception;
284 @Override
285 public String getAddress() {
286 return address;
289 @Override
290 public AppEngineWebXml getAppEngineWebXmlConfig(){
291 return appEngineWebXml;
294 @Override
295 public BackendsXml getBackendsXml() {
296 return backendsXml;
299 @Override
300 public int getPort() {
301 return port;
304 @Override
305 public String getHostName() {
306 return hostName;
309 @Override
310 public void setEnvironmentVariableMismatchSeverity(EnvironmentVariableMismatchSeverity val) {
311 envVarMismatchSeverity = val;
314 protected Permissions getUserPermissions() {
315 return appEngineWebXml.getUserPermissions();
318 protected void loadAppEngineWebXml(File webAppDir) {
320 WebXmlReader webXmlReader;
321 if (webXmlLocation == null) {
322 webXmlReader = new WebXmlReader(webAppDir.getAbsolutePath());
323 webXmlLocation = new File(webXmlReader.getFilename());
324 } else {
325 webXmlReader = new WebXmlReader(webXmlLocation.getParent(), webXmlLocation.getName());
328 AppEngineWebXmlReader appEngineWebXmlReader;
329 if (appEngineWebXmlLocation == null) {
330 appEngineWebXmlReader = new AppEngineWebXmlReader(webAppDir.getAbsolutePath());
331 appEngineWebXmlLocation = new File(appEngineWebXmlReader.getFilename());
332 } else {
333 appEngineWebXmlReader = new AppEngineWebXmlReader(
334 appEngineWebXmlLocation.getParent(), appEngineWebXmlLocation.getName());
337 AppYamlProcessor.convert(new File(appDir, "WEB-INF"),
338 appEngineWebXmlReader.getFilename(), webXmlLocation.getAbsolutePath());
340 appEngineWebXml = appEngineWebXmlReader.readAppEngineWebXml();
341 if (appEngineWebXml.getAppId() == null || appEngineWebXml.getAppId().length() == 0) {
342 appEngineWebXml.setAppId("no_app_id");
344 webXml = webXmlReader.readWebXml();
345 staticInitialize(appEngineWebXml, appDir, externalResourceDir);
346 webXml.validate();
348 backendsXml = new BackendsXmlReader(webAppDir.getAbsolutePath()).readBackendsXml();
350 ApiProxy.setEnvironmentForCurrentThread(new LocalInitializationEnvironment(
351 appEngineWebXml.getAppId(), appEngineWebXml.getMajorVersionId()));
354 private static synchronized void staticInitialize(
355 AppEngineWebXml appEngineWebXml, File appDir, File externalResourceDir) {
356 setSystemProperties(appEngineWebXml);
357 checkEnvironmentVariables(appEngineWebXml);
358 updateLoggingConfiguration(
359 originalSysProps, appEngineWebXml.getSystemProperties(), appDir, externalResourceDir);
362 protected void restoreSystemProperties() {
363 for (String key : appEngineWebXml.getSystemProperties().keySet()) {
364 System.clearProperty(key);
366 System.getProperties().putAll(originalSysProps);
369 /** Returns {@code true} if appengine-web.xml <sessions-enabled> is true. */
370 protected boolean isSessionsEnabled() {
371 return appEngineWebXml.getSessionsEnabled();
375 * Gets all of the URLs that should be added to the classpath for an
376 * application located at {@code root}.
378 protected URL[] getClassPathForApp(File root) {
379 List<URL> appUrls = new ArrayList<URL>();
381 appUrls.addAll(SdkImplInfo.getAgentRuntimeLibs());
382 appUrls.addAll(userCodeClasspathManager.getUserCodeClasspath(root));
383 for (URL url : SdkImplInfo.getUserJspLibs()) {
384 appUrls.add(url);
386 for (URL url : SdkImplInfo.getWebApiToolLibs()) {
387 appUrls.add(url);
389 return appUrls.toArray(new URL[appUrls.size()]);
393 * Sets system properties that are defined in {@link AppEngineWebXml}.
395 private static void setSystemProperties(AppEngineWebXml appEngineWebXml) {
396 SystemProperty.environment.set(SystemProperty.Environment.Value.Development);
397 String release = SdkInfo.getLocalVersion().getRelease();
398 if (release == null) {
399 release = "null";
401 SystemProperty.version.set(release);
402 SystemProperty.applicationId.set(appEngineWebXml.getAppId());
403 SystemProperty.applicationVersion.set(appEngineWebXml.getMajorVersionId() + ".1");
405 synchronized (AbstractContainerService.class) {
406 if (null == originalSysProps){
407 originalSysProps = new Properties();
408 originalSysProps.putAll(System.getProperties());
411 System.getProperties().putAll(appEngineWebXml.getSystemProperties());
415 * Updates the JVM's logging configuration to include both the
416 * user's custom logging configuration and the SDK's internal
417 * logging configurations.
419 * @param systemProperties
420 * @param userSystemProperties
422 private static void updateLoggingConfiguration(Properties systemProperties,
423 Map<String, String> userSystemProperties, File appDir, File externalResourceDir) {
424 String userConfigFile = userSystemProperties.get(LOGGING_CONFIG_FILE);
426 boolean shouldLogWarning = (externalResourceDir == null);
427 Properties userProperties = loadPropertiesFile(userConfigFile, appDir, shouldLogWarning);
428 if (userProperties == null && externalResourceDir != null) {
429 userProperties = loadPropertiesFile(userConfigFile, externalResourceDir, true);
431 String sdkConfigFile = systemProperties.getProperty(LOGGING_CONFIG_FILE);
432 Properties sdkProperties = loadPropertiesFile(sdkConfigFile, appDir, true);
433 Properties allProperties = new Properties();
434 if (sdkProperties != null) {
435 allProperties.putAll(sdkProperties);
437 if (userProperties != null) {
438 allProperties.putAll(userProperties);
441 ByteArrayOutputStream out = new ByteArrayOutputStream();
443 try {
444 allProperties.store(out, null);
445 LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(out.toByteArray()));
447 Logger root = Logger.getLogger("");
449 ApiProxyLocal proxy = (ApiProxyLocal) ApiProxy.getDelegate();
450 LocalLogService logService = (LocalLogService) proxy.getService(LocalLogService.PACKAGE);
451 root.addHandler(logService.getLogHandler());
453 Handler[] handlers = root.getHandlers();
454 if (handlers != null) {
455 for (Handler handler : handlers) {
456 handler.setLevel(Level.FINEST);
459 } catch (IOException e) {
460 log.log(Level.WARNING, "Unable to configure logging properties.", e);
464 private static Properties loadPropertiesFile(String file, File appDir, boolean logWarning) {
465 if (file == null) {
466 return null;
468 file = file.replace('/', File.separatorChar);
469 File f = new File(file);
470 if (!f.isAbsolute()) {
471 f = new File(appDir + File.separator + f.getPath());
473 InputStream inputStream = null;
474 try {
475 inputStream = new BufferedInputStream(new FileInputStream(f));
476 Properties props = new Properties();
477 props.load(inputStream);
478 return props;
479 } catch (IOException e) {
480 if (logWarning) {
481 log.log(Level.WARNING, "Unable to load properties file, " + f.getAbsolutePath(), e);
483 return null;
484 } finally {
485 if (inputStream != null) {
486 try {
487 inputStream.close();
488 } catch (IOException e) {
494 private static void checkEnvironmentVariables(AppEngineWebXml appEngineWebXml) {
495 Map<String, String> missingEnvEntries = Maps.newHashMap();
496 for (Map.Entry<String, String> entry : appEngineWebXml.getEnvironmentVariables().entrySet()) {
497 if (!entry.getValue().equals(System.getenv(entry.getKey()))) {
498 missingEnvEntries.put(entry.getKey(), entry.getValue());
501 if (!missingEnvEntries.isEmpty()) {
502 String msg =
503 "One or more environment variables have been configured in appengine-web.xml that have "
504 + "missing or different values in your local environment. We recommend you use system "
505 + "properties instead, but if you are interacting with legacy code that requires "
506 + "specific environment variables to have specific values, please set these environment "
507 + "variables in your environment before running.\n"
508 + Join.join("\n", missingEnvEntries);
510 if (envVarMismatchSeverity == EnvironmentVariableMismatchSeverity.WARNING) {
511 log.warning(msg);
512 } else if (envVarMismatchSeverity == EnvironmentVariableMismatchSeverity.ERROR) {
513 throw new IncorrectEnvironmentVariableException(msg, missingEnvEntries);
518 static class IncorrectEnvironmentVariableException extends
519 AppEngineConfigException {
521 private final Map<String, String> missingEnvEntries;
522 private IncorrectEnvironmentVariableException(String msg,
523 Map<String, String> missingEnvEntries) {
524 super(msg);
525 this.missingEnvEntries = missingEnvEntries;
528 public Map<String, String> getMissingEnvEntries() {
529 return missingEnvEntries;
534 * A fake {@link LocalEnvironment} implementation that is used during the
535 * initialization of the Development AppServer.
537 public static class LocalInitializationEnvironment extends LocalEnvironment {
538 public LocalInitializationEnvironment(String appId, String majorVersionId) {
539 super(appId, majorVersionId);
542 @Override
543 public String getEmail() {
544 return null;
547 @Override
548 public boolean isLoggedIn() {
549 return false;
552 @Override
553 public boolean isAdmin() {
554 return false;