1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com
.google
.apphosting
.utils
.config
;
4 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.AdminConsolePage
;
5 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.ApiConfig
;
6 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.AutomaticScaling
;
7 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.BasicScaling
;
8 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.ClassLoaderConfig
;
9 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.ErrorHandler
;
10 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.ManualScaling
;
11 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.Pagespeed
;
12 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.PrioritySpecifierEntry
;
14 import org
.mortbay
.xml
.XmlParser
;
15 import org
.mortbay
.xml
.XmlParser
.Node
;
16 import org
.xml
.sax
.SAXException
;
18 import java
.io
.IOException
;
19 import java
.io
.InputStream
;
20 import java
.util
.Iterator
;
22 import java
.util
.logging
.Level
;
23 import java
.util
.logging
.Logger
;
26 * Constructs an {@link AppEngineWebXml} from an xml document corresponding to
27 * appengine-web.xsd. We use Jetty's {@link XmlParser} utility for
30 * @TODO(user): Add a real link to the xsd once it exists and do schema
34 class AppEngineWebXmlProcessor
{
36 enum FileType
{ STATIC
, RESOURCE
}
38 private static final Logger logger
= Logger
.getLogger(AppEngineWebXmlProcessor
.class.getName());
41 * Construct an {@link AppEngineWebXml} from the xml document
42 * identified by the provided {@link InputStream}.
44 * @param is The InputStream containing the xml we want to parse and process.
46 * @return Object representation of the xml document.
47 * @throws AppEngineConfigException If the input stream cannot be parsed.
49 public AppEngineWebXml
processXml(InputStream is
) {
50 XmlParser
.Node config
= getTopLevelNode(is
);
51 AppEngineWebXml appEngineWebXml
= new AppEngineWebXml();
52 appEngineWebXml
.setWarmupRequestsEnabled(true);
53 for (Object o
: config
) {
54 if (!(o
instanceof XmlParser
.Node
)) {
57 XmlParser
.Node node
= (XmlParser
.Node
) o
;
58 processSecondLevelNode(node
, appEngineWebXml
);
60 checkScalingConstraints(appEngineWebXml
);
61 return appEngineWebXml
;
65 * Given an AppEngineWebXml, ensure it has no more than one of the scaling options available.
67 * @throws AppEngineConfigException If there is more than one scaling option selected.
69 private static void checkScalingConstraints(AppEngineWebXml appEngineWebXml
) {
70 int count
= appEngineWebXml
.getManualScaling().isEmpty() ?
0 : 1;
71 count
+= appEngineWebXml
.getBasicScaling().isEmpty() ?
0 : 1;
72 count
+= appEngineWebXml
.getAutomaticScaling().isEmpty() ?
0 : 1;
74 throw new AppEngineConfigException(
75 "There may be only one of 'automatic-scaling', 'manual-scaling' or " +
76 "'basic-scaling' elements.");
81 * Given an InputStream, create a Node corresponding to the top level xml
84 * @throws AppEngineConfigException If the input stream cannot be parsed.
86 XmlParser
.Node
getTopLevelNode(InputStream is
) {
87 XmlParser xmlParser
= new XmlParser();
89 return xmlParser
.parse(is
);
90 } catch (IOException e
) {
91 String msg
= "Received IOException parsing the input stream.";
92 logger
.log(Level
.SEVERE
, msg
, e
);
93 throw new AppEngineConfigException(msg
, e
);
94 } catch (SAXException e
) {
95 String msg
= "Received SAXException parsing the input stream.";
96 logger
.log(Level
.SEVERE
, msg
, e
);
97 throw new AppEngineConfigException(msg
, e
);
101 private void processSecondLevelNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
102 String elementName
= node
.getTag();
103 if (elementName
.equals("system-properties")) {
104 processSystemPropertiesNode(node
, appEngineWebXml
);
105 } else if (elementName
.equals("vm-settings")) {
106 processVmSettingsNode(node
, appEngineWebXml
);
107 } else if (elementName
.equals("env-variables")) {
108 processEnvironmentVariablesNode(node
, appEngineWebXml
);
109 } else if (elementName
.equals("application")) {
110 processApplicationNode(node
, appEngineWebXml
);
111 } else if (elementName
.equals("version")) {
112 processVersionNode(node
, appEngineWebXml
);
113 } else if (elementName
.equals("source-language")) {
114 processSourceLanguageNode(node
, appEngineWebXml
);
115 } else if (elementName
.equals("module")) {
116 processModuleNode(node
, appEngineWebXml
);
117 } else if (elementName
.equals("instance-class")) {
118 processInstanceClassNode(node
, appEngineWebXml
);
119 } else if (elementName
.equals("automatic-scaling")) {
120 processAutomaticScalingNode(node
, appEngineWebXml
);
121 } else if (elementName
.equals("manual-scaling")) {
122 processManualScalingNode(node
, appEngineWebXml
);
123 } else if (elementName
.equals("basic-scaling")) {
124 processBasicScalingNode(node
, appEngineWebXml
);
125 } else if (elementName
.equals("static-files")) {
126 processFilesetNode(node
, appEngineWebXml
, FileType
.STATIC
);
127 } else if (elementName
.equals("resource-files")) {
128 processFilesetNode(node
, appEngineWebXml
, FileType
.RESOURCE
);
129 } else if (elementName
.equals("ssl-enabled")) {
130 processSslEnabledNode(node
, appEngineWebXml
);
131 } else if (elementName
.equals("sessions-enabled")) {
132 processSessionsEnabledNode(node
, appEngineWebXml
);
133 } else if (elementName
.equals("async-session-persistence")) {
134 processAsyncSessionPersistenceNode(node
, appEngineWebXml
);
135 } else if (elementName
.equals("user-permissions")) {
136 processPermissionsNode(node
, appEngineWebXml
);
137 } else if (elementName
.equals("public-root")) {
138 processPublicRootNode(node
, appEngineWebXml
);
139 } else if (elementName
.equals("inbound-services")) {
140 processInboundServicesNode(node
, appEngineWebXml
);
141 } else if (elementName
.equals("precompilation-enabled")) {
142 processPrecompilationEnabledNode(node
, appEngineWebXml
);
143 } else if (elementName
.equals("admin-console")) {
144 processAdminConsoleNode(node
, appEngineWebXml
);
145 } else if (elementName
.equals("static-error-handlers")) {
146 processErrorHandlerNode(node
, appEngineWebXml
);
147 } else if (elementName
.equals("warmup-requests-enabled")) {
148 processWarmupRequestsEnabledNode(node
, appEngineWebXml
);
149 } else if (elementName
.equals("threadsafe")) {
150 processThreadsafeNode(node
, appEngineWebXml
);
151 } else if (elementName
.equals("auto-id-policy")) {
152 processAutoIdPolicyNode(node
, appEngineWebXml
);
153 } else if (elementName
.equals("code-lock")) {
154 processCodeLockNode(node
, appEngineWebXml
);
155 } else if (elementName
.equals("vm")) {
156 processVmNode(node
, appEngineWebXml
);
157 } else if (elementName
.equals("api-config")) {
158 processApiConfigNode(node
, appEngineWebXml
);
159 } else if (elementName
.equals("pagespeed")) {
160 processPagespeedNode(node
, appEngineWebXml
);
161 } else if (elementName
.equals("class-loader-config")) {
162 processClassLoaderConfig(node
, appEngineWebXml
);
163 } else if (elementName
.equals("url-stream-handler")) {
164 processUrlStreamHandler(node
, appEngineWebXml
);
165 } else if (elementName
.equals("use-google-connector-j")) {
166 processUseGoogleConnectorJNode(node
, appEngineWebXml
);
168 throw new AppEngineConfigException("Unrecognized element <" + elementName
+ ">");
172 private void processApplicationNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
173 appEngineWebXml
.setAppId(getTextNode(node
));
176 private void processPublicRootNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
177 appEngineWebXml
.setPublicRoot(getTextNode(node
));
180 private void processVersionNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
181 appEngineWebXml
.setMajorVersionId(getTextNode(node
));
184 private void processSourceLanguageNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
185 appEngineWebXml
.setSourceLanguage(getTextNode(node
));
188 private void processModuleNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
189 appEngineWebXml
.setModule(getTextNode(node
));
192 private void processInstanceClassNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
193 appEngineWebXml
.setInstanceClass(getTextNode(node
));
196 private String
getChildNodeText(XmlParser
.Node parentNode
, String childTag
) {
197 String result
= null;
198 XmlParser
.Node node
= parentNode
.get(childTag
);
200 result
= (String
) node
.get(0);
205 private void processAutomaticScalingNode(XmlParser
.Node settingsNode
,
206 AppEngineWebXml appEngineWebXml
) {
207 AutomaticScaling automaticScaling
= appEngineWebXml
.getAutomaticScaling();
208 automaticScaling
.setMinPendingLatency(getChildNodeText(settingsNode
, "min-pending-latency"));
209 automaticScaling
.setMaxPendingLatency(getChildNodeText(settingsNode
, "max-pending-latency"));
210 automaticScaling
.setMinIdleInstances(getChildNodeText(settingsNode
, "min-idle-instances"));
211 automaticScaling
.setMaxIdleInstances(getChildNodeText(settingsNode
, "max-idle-instances"));
212 automaticScaling
.setMaxConcurrentRequests(getChildNodeText(settingsNode
,
213 "max-concurrent-requests"));
216 private void processManualScalingNode(XmlParser
.Node settingsNode
,
217 AppEngineWebXml appEngineWebXml
) {
218 ManualScaling manualScaling
= appEngineWebXml
.getManualScaling();
219 manualScaling
.setInstances(getChildNodeText(settingsNode
, "instances"));
222 private void processBasicScalingNode(XmlParser
.Node settingsNode
,
223 AppEngineWebXml appEngineWebXml
) {
224 BasicScaling basicScaling
= appEngineWebXml
.getBasicScaling();
225 basicScaling
.setMaxInstances(getChildNodeText(settingsNode
, "max-instances"));
226 basicScaling
.setIdleTimeout(getChildNodeText(settingsNode
, "idle-timeout"));
229 private void processSslEnabledNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
230 appEngineWebXml
.setSslEnabled(getBooleanValue(node
));
233 private void processSessionsEnabledNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
234 appEngineWebXml
.setSessionsEnabled(getBooleanValue(node
));
237 private void processAsyncSessionPersistenceNode(
238 XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
239 boolean enabled
= getBooleanAttributeValue(node
, "enabled");
240 appEngineWebXml
.setAsyncSessionPersistence(enabled
);
241 String queueName
= trim(node
.getAttribute("queue-name"));
242 appEngineWebXml
.setAsyncSessionPersistenceQueueName(queueName
);
245 private void processPrecompilationEnabledNode(XmlParser
.Node node
,
246 AppEngineWebXml appEngineWebXml
) {
247 appEngineWebXml
.setPrecompilationEnabled(getBooleanValue(node
));
250 private void processWarmupRequestsEnabledNode(XmlParser
.Node node
,
251 AppEngineWebXml appEngineWebXml
) {
252 appEngineWebXml
.setWarmupRequestsEnabled(getBooleanValue(node
));
255 private void processThreadsafeNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
256 appEngineWebXml
.setThreadsafe(getBooleanValue(node
));
259 private void processAutoIdPolicyNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
260 appEngineWebXml
.setAutoIdPolicy(getTextNode(node
));
263 private void processCodeLockNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
264 appEngineWebXml
.setCodeLock(getBooleanValue(node
));
267 private void processVmNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
268 appEngineWebXml
.setUseVm(getBooleanValue(node
));
271 private void processFilesetNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
,
273 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "include");
274 while (nodeIter
.hasNext()) {
275 XmlParser
.Node includeNode
= nodeIter
.next();
276 String path
= trim(includeNode
.getAttribute("path"));
277 if (type
== FileType
.STATIC
) {
278 String expiration
= trim(includeNode
.getAttribute("expiration"));
279 AppEngineWebXml
.StaticFileInclude staticFileInclude
=
280 appEngineWebXml
.includeStaticPattern(path
, expiration
);
282 Map
<String
, String
> httpHeaders
= staticFileInclude
.getHttpHeaders();
283 Iterator
<XmlParser
.Node
> httpHeaderIter
= getNodeIterator(includeNode
, "http-header");
284 while (httpHeaderIter
.hasNext()) {
285 XmlParser
.Node httpHeaderNode
= httpHeaderIter
.next();
286 String name
= httpHeaderNode
.getAttribute("name");
287 String value
= httpHeaderNode
.getAttribute("value");
289 if (httpHeaders
.containsKey(name
)) {
290 throw new AppEngineConfigException("Two http-header elements have the same name.");
293 httpHeaders
.put(name
, value
);
296 appEngineWebXml
.includeResourcePattern(path
);
300 nodeIter
= getNodeIterator(node
, "exclude");
301 while (nodeIter
.hasNext()) {
302 XmlParser
.Node subNode
= nodeIter
.next();
303 String path
= trim(subNode
.getAttribute("path"));
304 if (type
== FileType
.STATIC
) {
305 appEngineWebXml
.excludeStaticPattern(path
);
307 appEngineWebXml
.excludeResourcePattern(path
);
312 private Iterator
<XmlParser
.Node
> getNodeIterator(XmlParser
.Node node
, String filter
) {
313 @SuppressWarnings("unchecked")
314 Iterator
<XmlParser
.Node
> iterator
= node
.iterator(filter
);
318 private void processSystemPropertiesNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
319 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "property");
320 while (nodeIter
.hasNext()) {
321 XmlParser
.Node subNode
= nodeIter
.next();
322 String propertyName
= trim(subNode
.getAttribute("name"));
323 String propertyValue
= trim(subNode
.getAttribute("value"));
324 appEngineWebXml
.addSystemProperty(propertyName
, propertyValue
);
328 private void processVmSettingsNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
329 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "setting");
330 while (nodeIter
.hasNext()) {
331 XmlParser
.Node subNode
= nodeIter
.next();
332 String name
= trim(subNode
.getAttribute("name"));
333 String value
= trim(subNode
.getAttribute("value"));
334 appEngineWebXml
.addVmSetting(name
, value
);
338 private void processEnvironmentVariablesNode(
339 XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
340 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "env-var");
341 while (nodeIter
.hasNext()) {
342 XmlParser
.Node subNode
= nodeIter
.next();
343 String propertyName
= trim(subNode
.getAttribute("name"));
344 String propertyValue
= trim(subNode
.getAttribute("value"));
345 appEngineWebXml
.addEnvironmentVariable(propertyName
, propertyValue
);
349 private void processPermissionsNode(
350 XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
351 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "permission");
352 while (nodeIter
.hasNext()) {
353 XmlParser
.Node subNode
= nodeIter
.next();
354 String className
= trim(subNode
.getAttribute("class"));
355 String name
= trim(subNode
.getAttribute("name"));
356 String actions
= trim(subNode
.getAttribute("actions"));
357 appEngineWebXml
.addUserPermission(className
, name
, actions
);
361 private void processInboundServicesNode(
362 XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
363 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "service");
364 while (nodeIter
.hasNext()) {
365 XmlParser
.Node subNode
= nodeIter
.next();
366 String service
= getTextNode(subNode
);
367 appEngineWebXml
.addInboundService(service
);
371 private void processAdminConsoleNode(
372 XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
373 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "page");
374 while (nodeIter
.hasNext()) {
375 XmlParser
.Node subNode
= nodeIter
.next();
376 String name
= trim(subNode
.getAttribute("name"));
377 String url
= trim(subNode
.getAttribute("url"));
378 appEngineWebXml
.addAdminConsolePage(new AdminConsolePage(name
, url
));
382 private void processErrorHandlerNode(
383 XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
384 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "handler");
385 while (nodeIter
.hasNext()) {
386 XmlParser
.Node subNode
= nodeIter
.next();
387 String file
= trim(subNode
.getAttribute("file"));
388 String errorCode
= trim(subNode
.getAttribute("error-code"));
389 appEngineWebXml
.addErrorHandler(new ErrorHandler(file
, errorCode
));
393 private void processApiConfigNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
394 String servlet
= trim(node
.getAttribute("servlet-class"));
395 String url
= trim(node
.getAttribute("url-pattern"));
396 appEngineWebXml
.setApiConfig(new ApiConfig(servlet
, url
));
399 String endpoint
= null;
400 Iterator
<XmlParser
.Node
> subNodeIter
= getNodeIterator(node
, "endpoint-servlet-mapping-id");
401 while (subNodeIter
.hasNext()) {
402 XmlParser
.Node subNode
= subNodeIter
.next();
403 id
= trim(getTextNode(subNode
));
404 if (id
!= null && id
.length() > 0) {
405 appEngineWebXml
.addApiEndpoint(id
);
410 private void processPagespeedNode(XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
411 Pagespeed pagespeed
= new Pagespeed();
412 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "url-blacklist");
413 while (nodeIter
.hasNext()) {
414 XmlParser
.Node subNode
= nodeIter
.next();
415 String urlMatcher
= getTextNode(subNode
);
416 pagespeed
.addUrlBlacklist(urlMatcher
);
418 nodeIter
= getNodeIterator(node
, "domain-to-rewrite");
419 while (nodeIter
.hasNext()) {
420 XmlParser
.Node subNode
= nodeIter
.next();
421 String domain
= getTextNode(subNode
);
422 pagespeed
.addDomainToRewrite(domain
);
424 nodeIter
= getNodeIterator(node
, "enabled-rewriter");
425 while (nodeIter
.hasNext()) {
426 XmlParser
.Node subNode
= nodeIter
.next();
427 String rewriter
= getTextNode(subNode
);
428 pagespeed
.addEnabledRewriter(rewriter
);
430 nodeIter
= getNodeIterator(node
, "disabled-rewriter");
431 while (nodeIter
.hasNext()) {
432 XmlParser
.Node subNode
= nodeIter
.next();
433 String rewriter
= getTextNode(subNode
);
434 pagespeed
.addDisabledRewriter(rewriter
);
436 appEngineWebXml
.setPagespeed(pagespeed
);
439 private void processClassLoaderConfig(
440 XmlParser
.Node node
, AppEngineWebXml appEngineWebXml
) {
441 ClassLoaderConfig config
= new ClassLoaderConfig();
442 appEngineWebXml
.setClassLoaderConfig(config
);
443 Iterator
<XmlParser
.Node
> nodeIter
= getNodeIterator(node
, "priority-specifier");
444 while (nodeIter
.hasNext()) {
445 processClassPathPrioritySpecifier(nodeIter
.next(), config
);
449 private void processClassPathPrioritySpecifier(Node node
, ClassLoaderConfig config
) {
450 PrioritySpecifierEntry entry
= new PrioritySpecifierEntry();
451 entry
.setFilename(node
.getAttribute("filename"));
452 entry
.setPriority(node
.getAttribute("priority"));
453 entry
.checkClassLoaderConfig();
457 private void processUrlStreamHandler(Node node
, AppEngineWebXml appEngineWebXml
) {
458 appEngineWebXml
.setUrlStreamHandlerType(getTextNode(node
));
461 private boolean getBooleanValue(XmlParser
.Node node
) {
462 return toBoolean(getTextNode(node
));
465 private boolean getBooleanAttributeValue(XmlParser
.Node node
, String attribute
) {
466 return toBoolean(node
.getAttribute(attribute
));
469 private boolean toBoolean(String value
) {
470 value
= value
.trim();
471 return (value
.equalsIgnoreCase("true") || value
.equals("1"));
474 private String
getTextNode(XmlParser
.Node node
) {
475 String value
= (String
) node
.get(0);
482 private String
trim(String attribute
) {
483 return attribute
== null ?
null : attribute
.trim();
486 private void processUseGoogleConnectorJNode(XmlParser
.Node node
,
487 AppEngineWebXml appEngineWebXml
) {
488 appEngineWebXml
.setUseGoogleConnectorJ(getBooleanValue(node
));