App Engine 1.8.4.
[gae.git] / java / src / main / com / google / apphosting / utils / config / AppEngineWebXmlProcessor.java
blobf288d714297318637a30f044a7a9e7519d74a6c0
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;
21 import java.util.Map;
22 import java.util.logging.Level;
23 import java.util.logging.Logger;
25 /**
26 * Constructs an {@link AppEngineWebXml} from an xml document corresponding to
27 * appengine-web.xsd. We use Jetty's {@link XmlParser} utility for
28 * convenience.
30 * @TODO(user): Add a real link to the xsd once it exists and do schema
31 * validation.
34 class AppEngineWebXmlProcessor {
36 enum FileType { STATIC, RESOURCE }
38 private static final Logger logger = Logger.getLogger(AppEngineWebXmlProcessor.class.getName());
40 /**
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)) {
55 continue;
57 XmlParser.Node node = (XmlParser.Node) o;
58 processSecondLevelNode(node, appEngineWebXml);
60 checkScalingConstraints(appEngineWebXml);
61 return appEngineWebXml;
64 /**
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;
73 if (count > 1) {
74 throw new AppEngineConfigException(
75 "There may be only one of 'automatic-scaling', 'manual-scaling' or " +
76 "'basic-scaling' elements.");
80 /**
81 * Given an InputStream, create a Node corresponding to the top level xml
82 * element.
84 * @throws AppEngineConfigException If the input stream cannot be parsed.
86 XmlParser.Node getTopLevelNode(InputStream is) {
87 XmlParser xmlParser = new XmlParser();
88 try {
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);
167 } else {
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);
199 if (node != null) {
200 result = (String) node.get(0);
202 return result;
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,
272 FileType type) {
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);
295 } else {
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);
306 } else {
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);
315 return iterator;
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));
398 String id = null;
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();
454 config.add(entry);
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);
476 if (value == null) {
477 value = "";
479 return value.trim();
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));