App Engine 1.8.4.
[gae.git] / java / src / main / com / google / apphosting / utils / config / AppYaml.java
blobe91c75ec0e15ceac332afac09056e064a7efe1fb
1 // Copyright 2010 Google. All Rights Reserved.
2 package com.google.apphosting.utils.config;
4 import com.google.common.base.Preconditions;
5 import com.google.common.xml.XmlEscapers;
7 import net.sourceforge.yamlbeans.YamlConfig;
8 import net.sourceforge.yamlbeans.YamlException;
9 import net.sourceforge.yamlbeans.YamlReader;
11 import java.io.PrintWriter;
12 import java.io.Reader;
13 import java.io.StringReader;
14 import java.io.Writer;
15 import java.util.LinkedHashMap;
16 import java.util.List;
17 import java.util.Map;
19 /**
20 * JavaBean representation of the Java app.yaml file.
23 public class AppYaml {
25 /**
26 * Plugin service to modify app.yaml with runtime-specific defaults.
28 public static interface Plugin {
29 AppYaml process(AppYaml yaml);
32 /**
33 * A {@code Handler} element from app.yaml. Maps to {@code servlet}, {@code servlet-mapping},
34 * {@code filter}, and {@code filter-mapping} elements in web.xml
37 public static class Handler {
39 public static enum Type {SERVLET, JSP, FILTER, NONE}
41 private String url;
42 private String jsp;
43 private String servlet;
44 private String filter;
45 private LoginType login;
46 private Security secure;
47 private Map<String, String> init_params;
48 private String name;
49 private boolean load_on_startup;
51 public enum LoginType { admin, required }
52 public enum Security { always, optional, never }
53 private boolean api_endpoint = false;
55 private String script;
57 private static final String MULTIPLE_HANDLERS = "Cannot set both %s and %s for the same url.";
59 public String getUrl() {
60 return url;
63 public void setUrl(String url) {
64 YamlUtils.validateUrl(url);
65 this.url = url;
68 public String getJsp() {
69 return jsp;
72 public void setJsp(String jsp) {
73 this.jsp = jsp;
74 checkHandlers();
77 public String getServlet() {
78 return servlet;
81 public void setServlet(String servlet) {
82 this.servlet = servlet;
83 checkHandlers();
86 public String getFilter() {
87 return filter;
90 public void setFilter(String filter) {
91 this.filter = filter;
92 checkHandlers();
95 public Type getType() {
96 if (servlet != null) {
97 return Type.SERVLET;
99 if (filter != null) {
100 return Type.FILTER;
102 if (jsp != null) {
103 return Type.JSP;
105 return Type.NONE;
108 public String getTarget() {
109 if (servlet != null) {
110 return servlet;
112 if (filter != null) {
113 return filter;
115 if (jsp != null) {
116 return jsp;
118 return null;
121 public void setScript(String script) {
122 this.script = script;
125 public String getScript() {
126 return this.script;
129 public LoginType getLogin() {
130 return login;
133 public void setLogin(LoginType login) {
134 this.login = login;
137 public Security getSecure() {
138 return secure;
141 public void setSecure(Security secure) {
142 if (secure == Security.never) {
143 throw new AppEngineConfigException("Java does not support secure: never");
145 this.secure = secure;
148 public Map<String, String> getInit_params() {
149 return init_params;
152 public void setInit_params(Map<String, String> init_params) {
153 this.init_params = init_params;
156 public String getName() {
157 return (name == null ? getTarget() : name);
160 public void setLoad_on_startup(boolean loadOnStartup) {
161 this.load_on_startup = loadOnStartup;
164 public boolean getLoad_on_startup() {
165 return this.load_on_startup;
168 public void setName(String name) {
169 this.name = name;
172 public String getApi_endpoint() {
173 return "" + this.api_endpoint;
176 public void setApi_endpoint(String api_endpoint) {
177 this.api_endpoint = YamlUtils.parseBoolean(api_endpoint);
180 public boolean isApiEndpoint() {
181 return this.api_endpoint;
184 private void checkHandlers() {
185 if (jsp != null && servlet != null) {
186 throw new AppEngineConfigException(String.format(MULTIPLE_HANDLERS, "jsp", "servlet"));
188 if (jsp != null && filter != null) {
189 throw new AppEngineConfigException(String.format(MULTIPLE_HANDLERS, "jsp", "filter"));
191 if (filter != null && servlet != null) {
192 throw new AppEngineConfigException(String.format(MULTIPLE_HANDLERS, "filter", "servlet"));
197 * Generates the {@code servlet} or {@code filter} element of web.xml
198 * corresponding to this handler.
200 private void generateDefinitionXml(XmlWriter xml) {
201 if (getServlet() != null || getJsp() != null) {
202 generateServletDefinition(xml);
203 } else if (getFilter() != null) {
204 generateFilterDefintion(xml);
208 private void generateMappingXml(XmlWriter xml) {
209 if (getServlet() != null || getJsp() != null) {
210 generateServletMapping(xml);
211 } else if (getFilter() != null) {
212 generateFilterMapping(xml);
214 generateSecurityConstraints(xml);
217 private void generateSecurityConstraints(XmlWriter xml) {
218 if (secure == Security.always || login == LoginType.required || login == LoginType.admin) {
219 xml.startElement("security-constraint");
220 xml.startElement("web-resource-collection");
221 xml.simpleElement("web-resource-name", "aname");
222 xml.simpleElement("url-pattern", getUrl());
223 xml.endElement("web-resource-collection");
224 if (login == LoginType.required) {
225 securityConstraint(xml, "auth", "role-name", "*");
226 } else if (login == LoginType.admin) {
227 securityConstraint(xml, "auth", "role-name", "admin");
229 if (secure == Security.always) {
230 securityConstraint(xml, "user-data", "transport-guarantee", "CONFIDENTIAL");
232 xml.endElement("security-constraint");
236 private void securityConstraint(XmlWriter xml, String type, String name, String value) {
237 type = type + "-constraint";
238 xml.startElement(type);
239 xml.simpleElement(name, value);
240 xml.endElement(type);
244 * Generates a {@code filter} element of web.xml corresponding to this handler.
246 private void generateFilterDefintion(XmlWriter xml) {
247 xml.startElement("filter");
248 xml.simpleElement("filter-name", getName());
249 xml.simpleElement("filter-class", getFilter());
250 generateInitParams(xml);
251 xml.endElement("filter");
255 * Generates a {@code filter-mapping} element of web.xml corresponding to this handler.
257 private void generateFilterMapping(XmlWriter xml) {
258 xml.startElement("filter-mapping");
259 xml.simpleElement("filter-name", getName());
260 xml.simpleElement("url-pattern", getUrl());
261 xml.endElement("filter-mapping");
265 * Generates a {@code servlet} or {@code jsp-file} element of web.xml corresponding to this
266 * handler.
268 private void generateServletDefinition(XmlWriter xml) {
269 xml.startElement("servlet");
270 xml.simpleElement("servlet-name", getName());
271 if (getJsp() == null) {
272 xml.simpleElement("servlet-class", getServlet());
273 } else {
274 xml.simpleElement("jsp-file", getJsp());
276 generateInitParams(xml);
277 if (load_on_startup) {
278 xml.simpleElement("load-on-startup", "1");
280 xml.endElement("servlet");
284 * Merges another handler into this handler, assuming that the other handler
285 * has the same name, type and target. This operation is intended to be
286 * used for generating a Servlet or Filter *definition* as opposed to a
287 * mapping, and therefore the urls of this handler and the other handler
288 * are not involved in the merge operation. The load_on_startup values
289 * of the two handlers will be OR'd and the init_params will be unioned.
291 public void mergeDefinitions(Handler otherHandler) {
292 Preconditions.checkArgument(this.getName().equals(otherHandler.getName()),
293 "Cannot merge handler named " + this.getName() + " with handler named " +
294 otherHandler.getName());
295 Preconditions.checkArgument(this.getType() == otherHandler.getType(),
296 "Cannot merge handler of type " + this.getType() + " with handler of type "
297 + otherHandler.getType());
298 Preconditions.checkArgument(this.getTarget().equals(otherHandler.getTarget()),
299 "Cannont merge handler with target " + this.getTarget() + " with handler with target "
300 + otherHandler.getTarget());
301 this.load_on_startup = this.load_on_startup || otherHandler.load_on_startup;
302 Map<String, String> mergedInitParams = new LinkedHashMap<String, String>();
303 if (this.init_params != null) {
304 mergedInitParams.putAll(this.init_params);
306 if (otherHandler.init_params != null) {
307 for (String key : otherHandler.init_params.keySet()) {
308 String thisValue = mergedInitParams.get(key);
309 String otherValue = otherHandler.init_params.get(key);
310 if (thisValue == null) {
311 mergedInitParams.put(key, otherValue);
312 } else if (!thisValue.equals(otherValue)) {
313 throw new IllegalArgumentException(
314 "Cannot merge handlers with conflicting values for the init_param: " + key + " : "
315 + thisValue + " vs " + otherValue);
319 if (mergedInitParams.size() != 0) {
320 this.init_params = mergedInitParams;
325 * Generates a {@code servlet-mapping} element of web.xml corresponding to this handler.
327 private void generateServletMapping(XmlWriter xml) {
328 if (isApiEndpoint()) {
329 xml.startElement("servlet-mapping", "id", xml.nextApiEndpointId());
330 } else {
331 xml.startElement("servlet-mapping");
333 xml.simpleElement("servlet-name", getName());
334 xml.simpleElement("url-pattern", getUrl());
335 xml.endElement("servlet-mapping");
338 private void generateInitParams(XmlWriter xml) {
339 if (init_params != null) {
340 for (Map.Entry<String, String> param : init_params.entrySet()) {
341 xml.startElement("init-param");
342 xml.simpleElement("param-name", param.getKey());
343 xml.simpleElement("param-value", param.getValue());
344 xml.endElement("init-param");
349 private void generateEndpointServletMappingId(XmlWriter xml) {
350 if (isApiEndpoint()) {
351 xml.simpleElement("endpoint-servlet-mapping-id", xml.nextApiEndpointId());
356 public static class ResourceFile {
357 private static final String EMPTY_MESSAGE = "Missing include or exclude.";
358 private static final String BOTH_MESSAGE = "Cannot specify both include and exclude.";
360 protected String include;
361 protected String exclude;
362 protected Map<String, String> httpHeaders;
364 public String getInclude() {
365 if (exclude == null && include == null) {
366 throw new AppEngineConfigException(EMPTY_MESSAGE);
368 return include;
371 public void setInclude(String include) {
372 if (exclude != null) {
373 throw new AppEngineConfigException(BOTH_MESSAGE);
375 this.include = include;
378 public String getExclude() {
379 if (exclude == null && include == null) {
380 throw new AppEngineConfigException(EMPTY_MESSAGE);
382 return exclude;
385 public void setExclude(String exclude) {
386 if (include != null) {
387 throw new AppEngineConfigException(BOTH_MESSAGE);
389 this.exclude = exclude;
392 public Map<String, String> getHttp_headers() {
393 if (include == null) {
394 throw new AppEngineConfigException("Missing include.");
397 return httpHeaders;
400 public void setHttp_headers(Map<String, String> httpHeaders) {
401 if (include == null) {
402 throw new AppEngineConfigException("Missing include.");
405 this.httpHeaders = httpHeaders;
409 public static class StaticFile extends ResourceFile {
410 private static final String NO_INCLUDE = "Missing include.";
411 private static final String INCLUDE_ONLY = "Expiration can only be specified with include.";
412 private String expiration;
414 public String getExpiration() {
415 if (expiration != null && include == null) {
416 throw new AppEngineConfigException(NO_INCLUDE);
418 return expiration;
421 public void setExpiration(String expiration) {
422 if (exclude != null) {
423 throw new AppEngineConfigException(INCLUDE_ONLY);
425 this.expiration = expiration;
428 @Override
429 public void setExclude(String exclude) {
430 if (expiration != null) {
431 throw new AppEngineConfigException(INCLUDE_ONLY);
433 super.setExclude(exclude);
437 public static class AdminConsole {
438 private List<AdminPage> pages;
440 public List<AdminPage> getPages() {
441 return pages;
444 public void setPages(List<AdminPage> pages) {
445 this.pages = pages;
449 public static class AdminPage {
450 private String name;
451 private String url;
453 public String getName() {
454 return name;
457 public void setName(String name) {
458 this.name = name;
461 public String getUrl() {
462 return url;
465 public void setUrl(String url) {
466 this.url = url;
470 public static class AsyncSessionPersistence {
471 private boolean enabled = false;
472 private String queue_name;
474 public String getEnabled() {
475 return "" + enabled;
478 public void setEnabled(String enabled) {
479 this.enabled = YamlUtils.parseBoolean(enabled);
482 public String getQueue_name() {
483 return this.queue_name;
486 public void setQueue_name(String queue_name) {
487 this.queue_name = queue_name;
491 public static class ErrorHandler {
492 private String file;
493 private String errorCode;
495 public String getFile() {
496 return file;
499 public void setFile(String file) {
500 this.file = file;
503 public String getError_code() {
504 return errorCode;
507 public void setError_code(String errorCode) {
508 this.errorCode = errorCode;
513 * AutomaticScaling bean.
515 public static class AutomaticScaling {
516 private String minPendingLatency;
517 private String maxPendingLatency;
518 private String minIdleInstances;
519 private String maxIdleInstances;
520 private String maxConcurrentRequests;
522 public String getMin_pending_latency() {
523 return minPendingLatency;
526 public void setMin_pending_latency(String minPendingLatency) {
527 this.minPendingLatency = minPendingLatency;
530 public String getMax_pending_latency() {
531 return maxPendingLatency;
534 public void setMax_pending_latency(String maxPendingLatency) {
535 this.maxPendingLatency = maxPendingLatency;
538 public String getMin_idle_instances() {
539 return minIdleInstances;
542 public void setMin_idle_instances(String minIdleInstances) {
543 this.minIdleInstances = minIdleInstances;
546 public String getMax_idle_instances() {
547 return maxIdleInstances;
550 public void setMax_idle_instances(String maxIdleInstances) {
551 this.maxIdleInstances = maxIdleInstances;
554 public String getMax_concurrent_requests() {
555 return maxConcurrentRequests;
558 public void setMax_concurrent_requests(String maxConcurrentRequests) {
559 this.maxConcurrentRequests = maxConcurrentRequests;
564 * ManualScaling bean.
566 public static class ManualScaling {
567 private String instances;
569 public String getInstances() {
570 return instances;
573 public void setInstances(String instances) {
574 this.instances = instances;
579 * BasicScaling bean.
581 public static class BasicScaling {
582 private String maxInstances;
583 private String idleTimeout;
585 public String getMax_instances() {
586 return maxInstances;
589 public void setMax_instances(String maxInstances) {
590 this.maxInstances = maxInstances;
592 public String getIdle_timeout() {
593 return idleTimeout;
596 public void setIdle_timeout(String idleTimeout) {
597 this.idleTimeout = idleTimeout;
601 private String application;
602 private String version;
603 private String source_language;
604 private String module;
605 private String instanceClass;
606 private AutomaticScaling automatic_scaling;
607 private ManualScaling manual_scaling;
608 private BasicScaling basic_scaling;
609 private String runtime;
610 private List<Handler> handlers;
611 private String public_root;
612 private List<StaticFile> static_files;
613 private List<ResourceFile> resource_files;
614 private boolean ssl_enabled = true;
615 private boolean precompilation_enabled = true;
616 private boolean sessions_enabled = false;
617 private AsyncSessionPersistence async_session_persistence;
618 private boolean threadsafe = false;
619 private String auto_id_policy;
620 private boolean threadsafeWasSet = false;
621 private boolean codeLock = false;
622 private Map<String, String> system_properties;
623 private Map<String, String> env_variables;
624 private Map<String, String> context_params;
625 private List<String> welcome_files;
626 private List<String> listeners;
627 private List<String> inbound_services;
628 private AdminConsole admin_console;
629 private List<ErrorHandler> error_handlers;
630 private ApiConfig api_config;
631 private Pagespeed pagespeed;
632 private String web_xml;
634 private static final String REQUIRED_FIELD = "Missing required element '%s'.";
636 public String getApplication() {
637 if (application == null) {
638 throw new AppEngineConfigException(String.format(REQUIRED_FIELD, "application"));
640 return application;
643 public void setApplication(String application) {
644 this.application = application;
647 public String getVersion() {
648 return version;
651 public void setVersion(String version) {
652 this.version = version;
655 public void setSource_language(String sourceLanguage) {
656 this.source_language = sourceLanguage;
659 public String getSource_language() {
660 return source_language;
663 public String getModule() {
664 return module;
667 public void setModule(String module) {
668 this.module = module;
671 public String getInstance_class() {
672 return instanceClass;
675 public void setInstance_class(String instanceClass) {
676 this.instanceClass = instanceClass;
679 public AutomaticScaling getAutomatic_scaling() {
680 return automatic_scaling;
683 public void setAutomatic_scaling(AutomaticScaling automaticScaling) {
684 this.automatic_scaling = automaticScaling;
687 public ManualScaling getManual_scaling() {
688 return manual_scaling;
691 public void setManual_scaling(ManualScaling manualScaling) {
692 this.manual_scaling = manualScaling;
695 public BasicScaling getBasic_scaling() {
696 return basic_scaling;
699 public void setBasic_scaling(BasicScaling basicScaling) {
700 this.basic_scaling = basicScaling;
703 public String getRuntime() {
704 return runtime;
707 public void setRuntime(String runtime) {
708 this.runtime = runtime;
711 public List<Handler> getHandlers() {
712 return handlers;
715 public void setHandlers(List<Handler> handlers) {
716 this.handlers = handlers;
717 if (this.api_config != null) {
718 this.api_config.setHandlers(handlers);
722 public String getPublic_root() {
723 return public_root;
726 public void setPublic_root(String public_root) {
727 this.public_root = public_root;
730 public List<StaticFile> getStatic_files() {
731 return static_files;
734 public void setStatic_files(List<StaticFile> static_files) {
735 this.static_files = static_files;
738 public List<ResourceFile> getResource_files() {
739 return resource_files;
742 public void setResource_files(List<ResourceFile> resource_files) {
743 this.resource_files = resource_files;
746 public String getSsl_enabled() {
747 return "" + ssl_enabled;
750 public void setSsl_enabled(String ssl_enabled) {
751 this.ssl_enabled = YamlUtils.parseBoolean(ssl_enabled);
754 public boolean isSslEnabled() {
755 return ssl_enabled;
758 public String getPrecompilation_enabled() {
759 return "" + precompilation_enabled;
762 public boolean isPrecompilationEnabled() {
763 return precompilation_enabled;
766 public void setPrecompilation_enabled(String precompilation_enabled) {
767 this.precompilation_enabled = YamlUtils.parseBoolean(precompilation_enabled);
770 public String getSessions_enabled() {
771 return "" + sessions_enabled;
774 public boolean isSessionsEnabled() {
775 return sessions_enabled;
778 public void setSessions_enabled(String sessions_enabled) {
779 this.sessions_enabled = YamlUtils.parseBoolean(sessions_enabled);
782 public AsyncSessionPersistence getAsync_session_persistence() {
783 return async_session_persistence;
786 public void setAsync_session_persistence(AsyncSessionPersistence async_session_persistence) {
787 this.async_session_persistence = async_session_persistence;
790 public String getThreadsafe() {
791 return "" + threadsafe;
794 public boolean isThreadsafeSet() {
795 return threadsafeWasSet;
798 public void setThreadsafe(String threadsafe) {
799 this.threadsafe = YamlUtils.parseBoolean(threadsafe);
800 threadsafeWasSet = true;
803 public String getAuto_id_policy() {
804 return auto_id_policy;
807 public void setAuto_id_policy(String policy) {
808 auto_id_policy = policy;
811 public String getCode_lock() {
812 return "" + codeLock;
815 public void setCode_lock(String codeLock) {
816 this.codeLock = YamlUtils.parseBoolean(codeLock);
819 public Map<String, String> getSystem_properties() {
820 return system_properties;
823 public void setSystem_properties(Map<String, String> system_properties) {
824 this.system_properties = system_properties;
827 public Map<String, String> getEnv_variables() {
828 return env_variables;
831 public void setEnv_variables(Map<String, String> env_variables) {
832 this.env_variables = env_variables;
835 public List<String> getWelcome_files() {
836 return welcome_files;
839 public void setWelcome_files(List<String> welcome_files) {
840 this.welcome_files = welcome_files;
843 public Map<String, String> getContext_params() {
844 return context_params;
847 public void setContext_params(Map<String, String> context_params) {
848 this.context_params = context_params;
851 public List<String> getListeners() {
852 return listeners;
855 public void setListeners(List<String> listeners) {
856 this.listeners = listeners;
859 public String getWeb_xml() {
860 return web_xml;
863 public void setWeb_xml(String web_xml) {
864 this.web_xml = web_xml;
867 public List<String> getInbound_services() {
868 return inbound_services;
871 public void setInbound_services(List<String> inbound_services) {
872 this.inbound_services = inbound_services;
875 public AdminConsole getAdmin_console() {
876 return admin_console;
879 public void setAdmin_console(AdminConsole admin_console) {
880 this.admin_console = admin_console;
883 public List<ErrorHandler> getError_handlers() {
884 return error_handlers;
887 public void setError_handlers(List<ErrorHandler> error_handlers) {
888 this.error_handlers = error_handlers;
891 public ApiConfig getApi_config() {
892 return api_config;
895 public void setApi_config(ApiConfig api_config) {
896 this.api_config = api_config;
897 if (handlers != null) {
898 this.api_config.setHandlers(handlers);
902 public Pagespeed getPagespeed() {
903 return pagespeed;
906 public void setPagespeed(Pagespeed pagespeed) {
907 this.pagespeed = pagespeed;
910 public AppYaml applyPlugins() {
911 AppYaml yaml = this;
912 for (Plugin plugin : PluginLoader.loadPlugins(Plugin.class)) {
913 AppYaml modified = plugin.process(yaml);
914 if (modified != null) {
915 yaml = modified;
918 return yaml;
922 * Represents an api-config: top level app.yaml stanza
923 * This is a singleton specifying url: and servlet: for the api config server.
925 public static class ApiConfig {
926 private String url;
927 private String servlet;
928 private List<Handler> handlers;
930 public void setHandlers(List<Handler> handlers) {
931 this.handlers = handlers;
934 public String getUrl() {
935 return url;
938 public void setUrl(String url) {
939 YamlUtils.validateUrl(url);
940 this.url = url;
943 public String getServlet() {
944 return servlet;
947 public void setServlet(String servlet) {
948 this.servlet = servlet;
951 private void generateXml(XmlWriter xml) {
952 xml.startElement("api-config", "servlet-class", getServlet(), "url-pattern", getUrl());
953 if (handlers != null) {
954 for (Handler handler : handlers) {
955 handler.generateEndpointServletMappingId(xml);
958 xml.endElement("api-config");
963 * Represents a &lt;pagespeed&gt; element. This is used to specify configuration for the Page
964 * Speed Service, which can be used to automatically optimize the loading speed of app engine
965 * sites.
967 public static class Pagespeed {
968 private List<String> urlBlacklist;
969 private List<String> domainsToRewrite;
970 private List<String> enabledRewriters;
971 private List<String> disabledRewriters;
973 public void setUrl_blacklist(List<String> urls) {
974 urlBlacklist = urls;
977 public List<String> getUrl_blacklist() {
978 return urlBlacklist;
981 public void setDomains_to_rewrite(List<String> domains) {
982 domainsToRewrite = domains;
985 public List<String> getDomains_to_rewrite() {
986 return domainsToRewrite;
989 public void setEnabled_rewriters(List<String> rewriters) {
990 enabledRewriters = rewriters;
993 public List<String> getEnabled_rewriters() {
994 return enabledRewriters;
997 public void setDisabled_rewriters(List<String> rewriters) {
998 disabledRewriters = rewriters;
1001 public List<String> getDisabled_rewriters() {
1002 return disabledRewriters;
1005 private void generateXml(XmlWriter xml) {
1006 if (!isEmpty()) {
1007 xml.startElement("pagespeed");
1008 appendElements(xml, "url-blacklist", urlBlacklist);
1009 appendElements(xml, "domain-to-rewrite", domainsToRewrite);
1010 appendElements(xml, "enabled-rewriter", enabledRewriters);
1011 appendElements(xml, "disabled-rewriter", disabledRewriters);
1012 xml.endElement("pagespeed");
1016 private void appendElements(XmlWriter xml, String name, List<String> l) {
1017 if (l != null) {
1018 for (String elt : l) {
1019 xml.simpleElement(name, elt);
1024 private boolean isEmpty() {
1025 return !hasElements(urlBlacklist) && !hasElements(domainsToRewrite)
1026 && !hasElements(enabledRewriters) && !hasElements(disabledRewriters);
1029 private boolean hasElements(List<?> coll) {
1030 return coll != null && !coll.isEmpty();
1034 private class XmlWriter {
1035 private static final String XML_HEADER = "<!-- Generated from app.yaml. Do not edit. -->";
1036 private final PrintWriter writer;
1037 private int indent = 0;
1038 private int apiEndpointId = 0;
1040 public XmlWriter(Writer w) {
1041 writer = new PrintWriter(w);
1042 writer.println(XML_HEADER);
1045 public void startElement(String name, String... attributes) {
1046 startElement(name, false, attributes);
1047 writer.println();
1050 public void startElement(String name, boolean empty, String... attributes) {
1051 indent();
1052 writer.print("<");
1053 writer.print(name);
1054 for (int i = 0; i < attributes.length; i += 2) {
1055 String attributeName = attributes[i];
1056 String value = attributes[i + 1];
1057 if (value != null) {
1058 writer.print(" ");
1059 writer.print(attributeName);
1060 writer.print("='");
1061 writer.print(escapeAttribute(value));
1062 writer.print("'");
1065 if (empty) {
1066 writer.println("/>");
1067 } else {
1068 writer.print(">");
1069 indent += 2;
1073 public void endElement(String name) {
1074 endElement(name, true);
1077 public void endElement(String name, boolean needIndent) {
1078 indent -= 2;
1079 if (needIndent) {
1080 indent();
1082 writer.print("</");
1083 writer.print(name);
1084 writer.println(">");
1087 public void emptyElement(String name, String... attributes) {
1088 startElement(name, true, attributes);
1091 public void simpleElement(String name, String value, String... attributes) {
1092 startElement(name, false, attributes);
1093 writer.print(escapeContent(value));
1094 endElement(name, false);
1097 public void writeUnescaped(String xmlContent) {
1098 writer.println(xmlContent);
1101 private void indent() {
1102 for (int i = 0; i < indent; i++) {
1103 writer.print(" ");
1107 private String escapeContent(String value) {
1108 if (value == null) {
1109 return null;
1111 return XmlEscapers.xmlContentEscaper().escape(value);
1114 private String escapeAttribute(String value) {
1115 if (value == null) {
1116 return null;
1118 return XmlEscapers.xmlAttributeEscaper().escape(value);
1121 private String nextApiEndpointId() {
1122 return String.format("endpoint-%1$d", ++apiEndpointId);
1126 private void addOptionalElement(XmlWriter xml, String name, String value) {
1127 if (value != null) {
1128 xml.simpleElement(name, value);
1132 public void generateAppEngineWebXml(Writer writer) {
1133 XmlWriter xml = new XmlWriter(writer);
1134 xml.startElement("appengine-web-app", "xmlns", "http://appengine.google.com/ns/1.0");
1135 xml.simpleElement("application", getApplication());
1136 addOptionalElement(xml, "version", getVersion());
1137 addOptionalElement(xml, "source-language", getSource_language());
1138 addOptionalElement(xml, "module", getModule());
1139 addOptionalElement(xml, "instance-class", getInstance_class());
1140 addOptionalElement(xml, "public-root", public_root);
1141 addOptionalElement(xml, "auto-id-policy", getAuto_id_policy());
1142 if (automatic_scaling != null) {
1143 xml.startElement("automatic-scaling");
1144 addOptionalElement(xml, "min-pending-latency", automatic_scaling.getMin_pending_latency());
1145 addOptionalElement(xml, "max-pending-latency", automatic_scaling.getMax_pending_latency());
1146 addOptionalElement(xml, "min-idle-instances", automatic_scaling.getMin_idle_instances());
1147 addOptionalElement(xml, "max-idle-instances", automatic_scaling.getMax_idle_instances());
1148 addOptionalElement(xml, "max-concurrent-requests",
1149 automatic_scaling.getMax_concurrent_requests());
1150 xml.endElement("automatic-scaling");
1152 if (manual_scaling != null) {
1153 xml.startElement("manual-scaling");
1154 xml.simpleElement("instances", manual_scaling.getInstances());
1155 xml.endElement("manual-scaling");
1157 if (basic_scaling != null) {
1158 xml.startElement("basic-scaling");
1159 xml.simpleElement("max-instances", basic_scaling.getMax_instances());
1160 addOptionalElement(xml, "idle-timeout", basic_scaling.getIdle_timeout());
1161 xml.endElement("basic-scaling");
1163 xml.startElement("static-files");
1164 if (static_files != null) {
1165 for (StaticFile file : static_files) {
1166 String name, path;
1167 if (file.getInclude() != null) {
1168 generateInclude(file, xml);
1169 } else {
1170 xml.emptyElement("exclude", "path", file.getExclude());
1174 xml.endElement("static-files");
1175 xml.startElement("resource-files");
1176 if (resource_files != null) {
1177 for (ResourceFile file : resource_files) {
1178 String name, path;
1179 if (file.getInclude() != null) {
1180 name = "include";
1181 path = file.getInclude();
1182 } else {
1183 name = "exclude";
1184 path = file.getExclude();
1186 xml.emptyElement(name, "path", path);
1189 xml.endElement("resource-files");
1190 xml.simpleElement("ssl-enabled", getSsl_enabled());
1191 xml.simpleElement("precompilation-enabled", getPrecompilation_enabled());
1192 if (isThreadsafeSet()) {
1193 xml.simpleElement("threadsafe", getThreadsafe());
1195 xml.simpleElement("code-lock", getCode_lock());
1196 xml.simpleElement("sessions-enabled", getSessions_enabled());
1197 if (async_session_persistence != null) {
1198 xml.simpleElement("async-session-persistence", null,
1199 "enabled", getAsync_session_persistence().getEnabled(),
1200 "queue-name", getAsync_session_persistence().getQueue_name());
1202 if (system_properties != null) {
1203 xml.startElement("system-properties");
1204 for (Map.Entry<String, String> entry : system_properties.entrySet()) {
1205 xml.emptyElement("property", "name", entry.getKey(), "value", entry.getValue());
1207 xml.endElement("system-properties");
1209 if (env_variables != null) {
1210 xml.startElement("env-variables");
1211 for (Map.Entry<String, String> entry : env_variables.entrySet()) {
1212 xml.emptyElement("env-var", "name", entry.getKey(), "value", entry.getValue());
1214 xml.endElement("env-variables");
1216 boolean warmupService = false;
1217 if (inbound_services != null) {
1218 xml.startElement("inbound-services");
1219 for (String service : inbound_services) {
1220 if (AppEngineWebXml.WARMUP_SERVICE.equals(service)) {
1221 warmupService = true;
1222 } else {
1223 xml.simpleElement("service", service);
1226 xml.endElement("inbound-services");
1228 xml.simpleElement("warmup-requests-enabled", Boolean.toString(warmupService));
1229 if (admin_console != null && admin_console.getPages() != null) {
1230 xml.startElement("admin-console");
1231 for (AdminPage page : admin_console.getPages()) {
1232 xml.emptyElement("page", "name", page.getName(), "url", page.getUrl());
1234 xml.endElement("admin-console");
1236 if (error_handlers != null) {
1237 xml.startElement("static-error-handlers");
1238 for (ErrorHandler handler : error_handlers) {
1239 xml.emptyElement("handler",
1240 "file", handler.getFile(),
1241 "error-code", handler.getError_code());
1243 xml.endElement("static-error-handlers");
1245 if (api_config != null) {
1246 api_config.generateXml(xml);
1248 if (pagespeed != null) {
1249 pagespeed.generateXml(xml);
1251 xml.endElement("appengine-web-app");
1255 * Generates the {@code servlet}, {@code servlet-mapping}, {@code filter}, and
1256 * {@code filter-mapping} elements of web.xml corresponding to the {@link #handlers} list. There
1257 * may be multiple {@link Handler handlers} corresponding to the same servlet or filter, because a
1258 * single handler can only specify one URL pattern and the user may wish to map several URL
1259 * patterns to the same servlet or filter. In this case we want to have multiple
1260 * {@code servlet-mapping} or {@code filter-mapping} elements but only a single {@code servlet} or
1261 * {@code filter} element.
1263 private void generateHandlerXml(XmlWriter xmlWriter) {
1264 if (handlers == null) {
1265 return;
1267 Map<String, Handler> servletsByName = new LinkedHashMap<String, Handler>(handlers.size());
1268 Map<String, Handler> filtersByName = new LinkedHashMap<String, Handler>(handlers.size());
1269 for (Handler handler : handlers) {
1270 String name = handler.getName();
1271 if (name != null) {
1272 Handler.Type type = handler.getType();
1273 boolean isServlet = (type == Handler.Type.SERVLET || type == Handler.Type.JSP);
1274 boolean isFilter = (type == Handler.Type.FILTER);
1275 Handler existing = (isServlet ? servletsByName.get(name) : filtersByName.get(name));
1276 if (existing != null) {
1277 existing.mergeDefinitions(handler);
1278 } else {
1279 if (isServlet) {
1280 servletsByName.put(name, handler);
1282 if (isFilter) {
1283 filtersByName.put(name, handler);
1288 for (Handler handler : servletsByName.values()) {
1289 handler.generateDefinitionXml(xmlWriter);
1291 for (Handler handler : filtersByName.values()) {
1292 handler.generateDefinitionXml(xmlWriter);
1294 for (Handler handler : handlers) {
1295 handler.generateMappingXml(xmlWriter);
1299 public void generateWebXml(Writer writer) {
1300 XmlWriter xml = new XmlWriter(writer);
1301 xml.startElement("web-app", "version", "2.5",
1302 "xmlns", "http://java.sun.com/xml/ns/javaee",
1303 "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance",
1304 "xsi:schemaLocation",
1305 "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
1307 generateHandlerXml(xml);
1308 if (context_params != null) {
1309 for (Map.Entry<String, String> entry : context_params.entrySet()) {
1310 xml.startElement("context-param");
1311 xml.simpleElement("param-name", entry.getKey());
1312 xml.simpleElement("param-value", entry.getValue());
1313 xml.endElement("context-param");
1316 if (welcome_files != null) {
1317 xml.startElement("welcome-file-list");
1318 for (String file : welcome_files) {
1319 xml.simpleElement("welcome-file", file);
1321 xml.endElement("welcome-file-list");
1323 if (listeners != null) {
1324 for (String listener : listeners) {
1325 xml.startElement("listener");
1326 xml.simpleElement("listener-class", listener);
1327 xml.endElement("listener");
1330 if (web_xml != null) {
1331 xml.writeUnescaped(web_xml);
1333 xml.endElement("web-app");
1336 public static AppYaml parse(Reader reader) {
1337 YamlReader yaml = new YamlReader(reader);
1338 prepareParser(yaml.getConfig());
1339 try {
1340 AppYaml appYaml = yaml.read(AppYaml.class);
1341 if (appYaml == null) {
1342 throw new YamlException("Unable to parse yaml file");
1344 return appYaml.applyPlugins();
1345 } catch (YamlException e) {
1346 Throwable innerException = e.getCause();
1348 while (innerException != null) {
1349 if (innerException instanceof AppEngineConfigException) {
1350 throw (AppEngineConfigException) innerException;
1352 innerException = innerException.getCause();
1355 throw new AppEngineConfigException(e.getMessage(), e);
1359 public static AppYaml parse(String yaml) {
1360 return parse(new StringReader(yaml));
1363 public static void prepareParser(YamlConfig config) {
1364 config.setPropertyElementType(AppYaml.class, "handlers", Handler.class);
1365 config.setPropertyElementType(AppYaml.class, "static_files", StaticFile.class);
1366 config.setPropertyElementType(AppYaml.class, "resource_files", ResourceFile.class);
1367 config.setPropertyElementType(AppYaml.class, "system_properties", String.class);
1368 config.setPropertyElementType(AppYaml.class, "context_params", String.class);
1369 config.setPropertyElementType(AppYaml.class, "env_variables", String.class);
1370 config.setPropertyElementType(AppYaml.class, "welcome_files", String.class);
1371 config.setPropertyElementType(AppYaml.class, "listeners", String.class);
1372 config.setPropertyElementType(AppYaml.class, "inbound_services", String.class);
1373 config.setPropertyElementType(Handler.class, "init_params", String.class);
1374 config.setPropertyElementType(AdminConsole.class, "pages", AdminPage.class);
1375 config.setPropertyElementType(AppYaml.class, "error_handlers", ErrorHandler.class);
1376 config.setPropertyElementType(Pagespeed.class, "url_blacklist", String.class);
1377 config.setPropertyElementType(Pagespeed.class, "domains_to_rewrite", String.class);
1378 config.setPropertyElementType(Pagespeed.class, "enabled_rewriters", String.class);
1379 config.setPropertyElementType(Pagespeed.class, "disabled_rewriters", String.class);
1382 private void generateInclude(StaticFile include, XmlWriter xml) {
1383 String path = include.getInclude();
1384 Map<String, String> httpHeaders = include.getHttp_headers();
1385 if (httpHeaders == null || httpHeaders.isEmpty()) {
1386 xml.emptyElement("include",
1387 "path", include.getInclude(),
1388 "expiration", include.getExpiration());
1389 } else {
1390 xml.startElement("include",
1391 false,
1392 "path", include.getInclude(),
1393 "expiration", include.getExpiration());
1394 for (Map.Entry<String, String> entry : httpHeaders.entrySet()) {
1395 xml.emptyElement("http-header",
1396 "name", entry.getKey(),
1397 "value", entry.getValue());
1399 xml.endElement("include");