Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / ApplicationConfigurationManager.java
blobd4e90d9c705eb76c1f4778c7b932d57ee26ca8fc
1 package com.google.appengine.tools.development;
3 import com.google.appengine.tools.development.EnvironmentVariableChecker.MismatchReportingPolicy;
4 import com.google.apphosting.utils.config.AppEngineConfigException;
5 import com.google.apphosting.utils.config.AppEngineWebXml;
6 import com.google.apphosting.utils.config.BackendsXml;
7 import com.google.apphosting.utils.config.BackendsXmlReader;
8 import com.google.apphosting.utils.config.EarHelper;
9 import com.google.apphosting.utils.config.EarInfo;
10 import com.google.apphosting.utils.config.WebModule;
11 import com.google.common.collect.ImmutableList;
12 import com.google.common.collect.ImmutableSortedSet;
14 import java.io.File;
15 import java.util.List;
16 import java.util.Set;
17 import java.util.logging.Logger;
19 import javax.annotation.concurrent.GuardedBy;
21 /**
22 * Manager for an application's configuration. Supports both single WAR
23 * directory configurations and EAR directory configurations. Also includes
24 * support for rereading configurations.
25 * <p>
27 public class ApplicationConfigurationManager {
28 private static final Logger LOGGER =
29 Logger.getLogger(ApplicationConfigurationManager.class.getName());
31 private final File configurationRoot;
32 private final SystemPropertiesManager systemPropertiesManager;
33 private final String sdkRelease;
34 @GuardedBy("this")
35 private MismatchReportingPolicy environmentVariableMismatchReportingPolicy =
36 MismatchReportingPolicy.EXCEPTION;
37 @GuardedBy("this")
38 private final List<ServerConfigurationHandle> serverConfigurationHandles;
40 /**
41 * Creates a new {@link ApplicationConfigurationManager} from an EAR directory.
43 static ApplicationConfigurationManager newEarConfigurationManager(File earRoot,
44 String sdkVersion)
45 throws AppEngineConfigException {
46 if (!EarHelper.isEar(earRoot.getAbsolutePath())) {
47 String message = String.format(
48 "ApplicationConfigurationManager.newEarConfigurationManager passed an invalid EAR: %s",
49 earRoot.getAbsolutePath());
50 LOGGER.severe(message);
51 throw new AppEngineConfigException(message);
53 return new ApplicationConfigurationManager(earRoot, null, null, null, sdkVersion);
56 /**
57 * Creates a new {@link ApplicationConfigurationManager} from a WAR directory.
58 * <p>
59 * @param warRoot The location of the war directory.
60 * @param externalResourceDirectory If not {@code null}, a resource directory external
61 * to the applicationDirectory. This will be searched before
62 * applicationDirectory when looking for resources.
63 * @param webXmlLocation The location of a file whose format complies with
64 * http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd. If null we will use
65 * <appDir>/WEB-INF/web.xml.
66 * @param appEngineWebXmlLocation The location of the app engine config file.
67 * If null we will use <appDir>/WEB-INF/appengine-web.xml.
68 * @param sdkRelease The sdk version (SdkInfo.getLocalVersion().getRelease()).
70 static ApplicationConfigurationManager newWarConfigurationManager(File warRoot,
71 File appEngineWebXmlLocation, File webXmlLocation, File externalResourceDirectory,
72 String sdkRelease) throws AppEngineConfigException {
73 if (EarHelper.isEar(warRoot.getAbsolutePath())) {
74 String message = String.format(
75 "ApplicationConfigurationManager.newWarConfigurationManager passed an EAR: %s",
76 warRoot.getAbsolutePath());
77 LOGGER.severe(message);
78 throw new AppEngineConfigException(message);
80 return new ApplicationConfigurationManager(warRoot,
81 appEngineWebXmlLocation, webXmlLocation, externalResourceDirectory, sdkRelease);
84 /**
85 * Returns the {@link ServerConfigurationHandle} for the primary server which
86 * will be the only server for configurations read from a single WAR directory
87 * or the first server in applicationDirectory/META-INF/application.xml
88 * module order for configurations read from an EAR directory.
90 ServerConfigurationHandle getPrimaryServerConfigurationHandle() {
91 return serverConfigurationHandles.get(0);
94 /**
95 * Returns {@link List} with a {@link ServerConfigurationHandle} for each
96 * configured server.
97 * <p>
98 * The returned list is immutable.
100 List<ServerConfigurationHandle> getServerConfigurationHandles() {
101 return serverConfigurationHandles;
105 * Constructs an {@link ApplicationConfigurationManager} by reading the
106 * configuration from an exploded WAR directory or EAR directory.
108 * @param configurationRoot the root directory for the applications EAR or WAR.
109 * @param appEngineWebXmlLocation for a WAR configuration a non null value
110 * overrides the default of configurationRoot/WEB-INF/appengine-web.xml and
111 * ignored for an EAR configuration.
112 * @param webXmlLocation for a WAR configuration a non null value
113 * overrides the default of configurationRoot/WEB-INF/web.xml and ignored
114 * for an EAR configuration.
115 * @param externalResourceDirectory for a WAR configuration the optional
116 * external resource directory or null and ignored for an EAR configuration.
117 * @param sdkRelease the SDK verison string.
119 private ApplicationConfigurationManager(File configurationRoot, File appEngineWebXmlLocation,
120 File webXmlLocation, File externalResourceDirectory, String sdkRelease) {
121 this.configurationRoot = configurationRoot;
122 this.systemPropertiesManager = new SystemPropertiesManager();
123 this.sdkRelease = sdkRelease;
124 if (EarHelper.isEar(configurationRoot.getAbsolutePath())) {
125 EarInfo earInfo = readEarConfiguration();
126 ImmutableList.Builder<ServerConfigurationHandle> builder = ImmutableList.builder();
127 for (WebModule module : earInfo.getWebModules()) {
128 builder.add(new EarServerConfigurationHandle(module));
130 serverConfigurationHandles = builder.build();
131 } else {
132 ServerConfigurationHandle warConfigurationHandle = new WarServerConfigurationHandle(
133 appEngineWebXmlLocation, webXmlLocation, externalResourceDirectory);
134 warConfigurationHandle.readConfiguration();
135 serverConfigurationHandles = ImmutableList.of(warConfigurationHandle);
140 * Performs various validations and registers logging configuration and
141 * system properties for a {@link WebModule} so they may be combined with
142 * values from other modules to construct an applications runtime
143 * configuration.
144 * <p>
145 * Though this function provides little or no real abstraction and badly
146 * fails the 'does one thing' test it avoids some code duplication..
148 * @param module module
149 * @param loggingConfigurationManager for validating and combining the
150 * the applications logging configuration.
151 * @param externalResourceDirectory the externalResourceDirectory for
152 * obtaining logging configuration.
154 private synchronized void validateAndRegisterGlobalValues(WebModule module,
155 LoggingConfigurationManager loggingConfigurationManager,
156 File externalResourceDirectory) {
157 module.getWebXml().validate();
158 AppEngineWebXml appEngineWebXml = module.getAppEngineWebXml();
159 loggingConfigurationManager.read(systemPropertiesManager.getOriginalSystemProperties(),
160 appEngineWebXml.getSystemProperties(), module.getApplicationDirectory(),
161 externalResourceDirectory);
162 systemPropertiesManager.setSystemProperties(appEngineWebXml, module.getAppEngineWebXmlFile());
166 * Reads or rereads an application's EAR configuration, performs validations,
167 * and calculate the application's logging configuration and system
168 * properties.
169 * @return the {@link EarInfo} for the configuration.
171 private synchronized EarInfo readEarConfiguration() {
172 if (!EarHelper.isEar(configurationRoot.getAbsolutePath())) {
173 String message = String.format("Unsupported update from EAR to WAR for: %s",
174 configurationRoot.getAbsolutePath());
175 LOGGER.severe(message);
176 throw new AppEngineConfigException(message);
178 EarInfo earInfo = EarHelper.readEarInfo(configurationRoot.getAbsolutePath());
179 String majorVersionId = null;
180 LoggingConfigurationManager loggingConfigurationManager = new LoggingConfigurationManager();
181 for (WebModule module : earInfo.getWebModules()) {
182 module.getWebXml().validate();
183 AppEngineWebXml appEngineWebXml = module.getAppEngineWebXml();
184 if (majorVersionId == null) {
185 majorVersionId = appEngineWebXml.getMajorVersionId();
187 validateAndRegisterGlobalValues(module, loggingConfigurationManager, null);
189 systemPropertiesManager.setAppengineSystemProperties(sdkRelease,
190 earInfo.getAppengineApplicationXml().getApplicationId(), majorVersionId);
191 loggingConfigurationManager.updateLoggingConfiguration();
192 return earInfo;
196 * Checks that the applications combined environment variables are consistent
197 * and reports inconsistencies based on {@link
198 * #getEnvironmentVariableMismatchReportingPolicy}.
200 private synchronized void checkEnvironmentVariables() {
201 EnvironmentVariableChecker environmentVariableChecker =
202 new EnvironmentVariableChecker(environmentVariableMismatchReportingPolicy);
203 for (ServerConfigurationHandle serverConfigurationHandle : serverConfigurationHandles) {
204 WebModule module = serverConfigurationHandle.getModule();
205 environmentVariableChecker.add(module.getAppEngineWebXml(), module.getAppEngineWebXmlFile());
207 environmentVariableChecker.check();
210 synchronized void setEnvironmentVariableMismatchReportingPolicy(
211 MismatchReportingPolicy environmentVariableMismatchReportingPolicy) {
212 this.environmentVariableMismatchReportingPolicy = environmentVariableMismatchReportingPolicy;
215 synchronized MismatchReportingPolicy getEnvironmentVariableMismatchReportingPolicy() {
216 return this.environmentVariableMismatchReportingPolicy;
219 @Override
220 public synchronized String toString() {
221 return "ApplicationConfigurationManager: configurationRoot=" + configurationRoot
222 + " systemPropertiesManager=" + systemPropertiesManager
223 + " sdkVersion=" + sdkRelease
224 + " environmentVariableMismatchReportingPolicy="
225 + environmentVariableMismatchReportingPolicy
226 + " serverConfigurationHandles=" + serverConfigurationHandles;
230 * Handle for accessing and rereading a server configuration.
231 * <p>
232 * A WAR configuration supports a single server with additional
233 * backends specified in war/WEB-INF/backends.xml. All instances
234 * of the single server and all backends instances share a single
235 * {@link ServerConfigurationHandle} so updates made by a call to
236 * {@link #readConfiguration()} will be visible to all server and backend
237 * instances. An EAR configuration supports multiple servers. All instances
238 * of a server share a single {@link ServerConfigurationHandle} so updates
239 * made by a call to {@link #readConfiguration()} for a particular server are
240 * visible to all instances of the server.
241 * <p>
242 * To control when changes become visible clients should keep and refresh
243 * references to values which will be replaced when the configuration is
244 * reread including {@link #getModule()} and for WAR configurations
245 * {@link #getBackendsXml()}.
246 * <p>
247 * Implementations synchronize operations that read or write state
248 * that may be changed by {@link #readConfiguration()} on
249 * ApplicationConfigurationManager.this. Note that configuration updates
250 * involving edits to multiple configuration files are not guaranteed to be
251 * atomic in the case {@link #readConfiguration()} is called after one write
252 * and before another during a multi-write configuration change. Given this
253 * and that backends are about to be deprecated, no synchronized operation
254 * is provided for a client to obtain the combined values returned by calling
255 * {@link #getModule()} and then calling {@link #getBackendsXml()}.
257 interface ServerConfigurationHandle {
259 * Returns the {@link WebModule} for this configuration.
261 WebModule getModule();
263 * Checks if the configuration specifies environment variables that do not
264 * match the JVM's environment variables.
265 * <p>
266 * This check is broken out rather than implemented during construction for
267 * backwards compatibility. The check is deferred until {@link DevAppServer#start()}
268 * to give {@link DevAppServer} clients a chance to call
269 * {@link DevAppServer#setThrowOnEnvironmentVariableMismatch(boolean)}
270 * before reporting errors.
272 void checkEnvironmentVariables();
275 * Returns the {@link BackendsXml} for this configuration.
276 * <p>
277 * For EAR configurations this will return null. For WAR configurations this
278 * will return a value read from the war/WEB-INF/backends.xml if one is
279 * specified or null otherwise.
281 BackendsXml getBackendsXml();
284 * Reads or rereads the configuration from disk to pick up any changes.
285 * Calling this function affects global state visible to all the servers
286 * in the application including:
287 * <ol>
288 * <li> system properties
289 * <li> the logging configuration
290 * </ol>
292 * Because for EAR configurations the global includes information from
293 * all the servers in the EAR, this rereads the configuration for every server.
294 * This does not update the {@link ServerConfigurationHandle} for any other
295 * servers. Certain configuration changes are not currently supp[orted
296 * including changes that
297 * <ol>
298 * <li> Adds entries to {@link ApplicationConfigurationManager#getServerConfigurationHandles()}
299 * <li> removes entries from
300 * {@link ApplicationConfigurationManager#getServerConfigurationHandles()}
301 * <li> Changes the application directory for a {@link ServerConfigurationHandle} returned
302 * by {@link ApplicationConfigurationManager#getServerConfigurationHandles()}
303 * </ol>
304 * @throws AppEngineConfigException if the configuration on disk is not valid
305 * or includes unsupported changes.
307 void readConfiguration() throws AppEngineConfigException;
310 * Clears {link {@link System#getProperties()} values that have been set by this
311 * configuration.
313 void restoreSystemProperties();
316 private class WarServerConfigurationHandle implements ServerConfigurationHandle {
317 private final File rawAppEngineWebXmlLocation;
318 private final File rawWebXmlLocation;
319 private final File externalResourceDirectory;
320 @GuardedBy("ApplicationConfigurationManager.this")
321 private BackendsXml backendsXml;
322 @GuardedBy("ApplicationConfigurationManager.this")
323 private WebModule webModule;
325 * @param appEngineWebXmlLocation absolute paths are accepted, relative
326 * paths are under applicationDirectory and null means to use
327 * applicationDirectory/WEB-INF/appengine-web.xml.
328 * @param webXmlLocation absolute paths are accepted, relative
329 * paths are under applicationDirectory and null means to use
330 * applicationDirectory/WEB-INF/web.xml.
331 * @param externalResourceDirectory If not {@code null}, a resource directory external
332 * to the applicationDirectory. This will be searched before
333 * applicationDirectory when looking for resources.
335 WarServerConfigurationHandle(File appEngineWebXmlLocation, File webXmlLocation,
336 File externalResourceDirectory) {
337 this.rawAppEngineWebXmlLocation = appEngineWebXmlLocation;
338 this.rawWebXmlLocation = webXmlLocation;
339 this.externalResourceDirectory = externalResourceDirectory;
342 @Override
343 public WebModule getModule() {
344 synchronized (ApplicationConfigurationManager.this) {
345 return webModule;
349 @Override
350 public void checkEnvironmentVariables() {
351 ApplicationConfigurationManager.this.checkEnvironmentVariables();
354 @Override
355 public BackendsXml getBackendsXml() {
356 synchronized (ApplicationConfigurationManager.this) {
357 return backendsXml;
361 @Override
362 public void readConfiguration() {
363 synchronized (ApplicationConfigurationManager.this) {
364 if (EarHelper.isEar(configurationRoot.getAbsolutePath())) {
365 String message = String.format("Unsupported update from WAR to EAR for: %s",
366 configurationRoot.getAbsolutePath());
367 LOGGER.severe(message);
368 throw new AppEngineConfigException(message);
370 webModule = EarHelper.readWebModule(null, configurationRoot, rawAppEngineWebXmlLocation,
371 rawWebXmlLocation);
372 backendsXml =
373 new BackendsXmlReader(configurationRoot.getAbsolutePath()).readBackendsXml();
374 AppEngineWebXml appEngineWebXml = webModule.getAppEngineWebXml();
375 if (appEngineWebXml.getAppId() == null || appEngineWebXml.getAppId().length() == 0) {
376 appEngineWebXml.setAppId("no_app_id");
378 LoggingConfigurationManager loggingConfigurationManager = new LoggingConfigurationManager();
379 validateAndRegisterGlobalValues(webModule, loggingConfigurationManager,
380 externalResourceDirectory);
381 systemPropertiesManager.setAppengineSystemProperties(sdkRelease,
382 appEngineWebXml.getAppId(), appEngineWebXml.getMajorVersionId());
383 loggingConfigurationManager.updateLoggingConfiguration();
387 @Override
388 public void restoreSystemProperties() {
389 synchronized (ApplicationConfigurationManager.this) {
390 systemPropertiesManager.restoreSystemProperties();
394 @Override
395 public String toString() {
396 synchronized (ApplicationConfigurationManager.this) {
397 return "WarConfigurationHandle: webModule=" + webModule
398 + " backendsXml=" + backendsXml
399 + " appEngineWebXmlLocation=" + rawAppEngineWebXmlLocation
400 + " webXmlLocation=" + rawWebXmlLocation
401 + " externalResourceDirectory=" + externalResourceDirectory;
406 private class EarServerConfigurationHandle implements ServerConfigurationHandle {
407 @GuardedBy("ApplicationConfigurationManager.this")
408 private WebModule webModule;
410 EarServerConfigurationHandle(WebModule webModule) {
411 this.webModule = webModule;
414 @Override
415 public WebModule getModule() {
416 synchronized (ApplicationConfigurationManager.this) {
417 return webModule;
421 @Override
422 public void checkEnvironmentVariables() {
423 synchronized (ApplicationConfigurationManager.this) {
424 ApplicationConfigurationManager.this.checkEnvironmentVariables();
428 @Override
429 public BackendsXml getBackendsXml() {
430 return null;
433 @Override
434 public void readConfiguration() {
435 synchronized (ApplicationConfigurationManager.this) {
436 EarInfo earInfo = readEarConfiguration();
437 checkServersMatch(earInfo);
438 for (WebModule module : earInfo.getWebModules()) {
439 if (module.getApplicationDirectory().equals(webModule.getApplicationDirectory())) {
440 webModule = module;
441 return;
445 throw new IllegalStateException("Expected web module not found.");
450 * Checks that the servers from the passed in {@link EarInfo} which was
451 * presumably read from the EAR directory matches the servers for this
452 * {@link ApplicationConfigurationManager}.
453 * @throws AppEngineConfigException if the servers do not match.
455 private void checkServersMatch(EarInfo earInfo) throws AppEngineConfigException {
456 ImmutableSortedSet.Builder<File> gotBuilder = ImmutableSortedSet.naturalOrder();
457 for (WebModule module : earInfo.getWebModules()) {
458 gotBuilder.add(module.getApplicationDirectory());
461 ImmutableSortedSet.Builder<File> expectBuilder = ImmutableSortedSet.naturalOrder();
462 for (ServerConfigurationHandle handle : serverConfigurationHandles) {
463 expectBuilder.add(handle.getModule().getApplicationDirectory());
466 Set<File> got = gotBuilder.build();
467 Set<File> expect = expectBuilder.build();
468 if (!expect.equals(got)) {
469 String message = String.format(
470 "Unsupported configuration change of web moudlues from '%s' to '%s'", expect, got);
471 LOGGER.severe(message);
472 throw new AppEngineConfigException(message);
476 @Override
477 public void restoreSystemProperties() {
478 synchronized (ApplicationConfigurationManager.this) {
479 systemPropertiesManager.restoreSystemProperties();
483 @Override
484 public String toString() {
485 synchronized (ApplicationConfigurationManager.this) {
486 return "WarConfigurationHandle: webModule=" + webModule;