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
;
15 import java
.util
.List
;
17 import java
.util
.logging
.Logger
;
19 import javax
.annotation
.concurrent
.GuardedBy
;
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.
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
;
35 private MismatchReportingPolicy environmentVariableMismatchReportingPolicy
=
36 MismatchReportingPolicy
.EXCEPTION
;
38 private final List
<ServerConfigurationHandle
> serverConfigurationHandles
;
41 * Creates a new {@link ApplicationConfigurationManager} from an EAR directory.
43 static ApplicationConfigurationManager
newEarConfigurationManager(File earRoot
,
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
);
57 * Creates a new {@link ApplicationConfigurationManager} from a WAR directory.
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
);
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);
95 * Returns {@link List} with a {@link ServerConfigurationHandle} for each
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();
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
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
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();
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
;
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.
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.
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()}.
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.
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.
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:
288 * <li> system properties
289 * <li> the logging configuration
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
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()}
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
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
;
343 public WebModule
getModule() {
344 synchronized (ApplicationConfigurationManager
.this) {
350 public void checkEnvironmentVariables() {
351 ApplicationConfigurationManager
.this.checkEnvironmentVariables();
355 public BackendsXml
getBackendsXml() {
356 synchronized (ApplicationConfigurationManager
.this) {
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
,
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();
388 public void restoreSystemProperties() {
389 synchronized (ApplicationConfigurationManager
.this) {
390 systemPropertiesManager
.restoreSystemProperties();
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
;
415 public WebModule
getModule() {
416 synchronized (ApplicationConfigurationManager
.this) {
422 public void checkEnvironmentVariables() {
423 synchronized (ApplicationConfigurationManager
.this) {
424 ApplicationConfigurationManager
.this.checkEnvironmentVariables();
429 public BackendsXml
getBackendsXml() {
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())) {
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
);
477 public void restoreSystemProperties() {
478 synchronized (ApplicationConfigurationManager
.this) {
479 systemPropertiesManager
.restoreSystemProperties();
484 public String
toString() {
485 synchronized (ApplicationConfigurationManager
.this) {
486 return "WarConfigurationHandle: webModule=" + webModule
;