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
;
20 * JavaBean representation of the Java app.yaml file.
23 public class AppYaml
{
26 * Plugin service to modify app.yaml with runtime-specific defaults.
28 public static interface Plugin
{
29 AppYaml
process(AppYaml yaml
);
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
}
43 private String servlet
;
44 private String filter
;
45 private LoginType login
;
46 private Security secure
;
47 private Map
<String
, String
> init_params
;
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() {
63 public void setUrl(String url
) {
64 YamlUtils
.validateUrl(url
);
68 public String
getJsp() {
72 public void setJsp(String jsp
) {
77 public String
getServlet() {
81 public void setServlet(String servlet
) {
82 this.servlet
= servlet
;
86 public String
getFilter() {
90 public void setFilter(String filter
) {
95 public Type
getType() {
96 if (servlet
!= null) {
108 public String
getTarget() {
109 if (servlet
!= null) {
112 if (filter
!= null) {
121 public void setScript(String script
) {
122 this.script
= script
;
125 public String
getScript() {
129 public LoginType
getLogin() {
133 public void setLogin(LoginType login
) {
137 public Security
getSecure() {
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() {
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
) {
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
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());
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());
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
);
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
);
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.");
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
);
421 public void setExpiration(String expiration
) {
422 if (exclude
!= null) {
423 throw new AppEngineConfigException(INCLUDE_ONLY
);
425 this.expiration
= expiration
;
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() {
444 public void setPages(List
<AdminPage
> pages
) {
449 public static class AdminPage
{
453 public String
getName() {
457 public void setName(String name
) {
461 public String
getUrl() {
465 public void setUrl(String url
) {
470 public static class AsyncSessionPersistence
{
471 private boolean enabled
= false;
472 private String queue_name
;
474 public String
getEnabled() {
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
{
493 private String errorCode
;
495 public String
getFile() {
499 public void setFile(String file
) {
503 public String
getError_code() {
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() {
573 public void setInstances(String instances
) {
574 this.instances
= instances
;
581 public static class BasicScaling
{
582 private String maxInstances
;
583 private String idleTimeout
;
585 public String
getMax_instances() {
589 public void setMax_instances(String maxInstances
) {
590 this.maxInstances
= maxInstances
;
592 public String
getIdle_timeout() {
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"));
643 public void setApplication(String application
) {
644 this.application
= application
;
647 public String
getVersion() {
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() {
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() {
707 public void setRuntime(String runtime
) {
708 this.runtime
= runtime
;
711 public List
<Handler
> getHandlers() {
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() {
726 public void setPublic_root(String public_root
) {
727 this.public_root
= public_root
;
730 public List
<StaticFile
> getStatic_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() {
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() {
855 public void setListeners(List
<String
> listeners
) {
856 this.listeners
= listeners
;
859 public String
getWeb_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() {
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() {
906 public void setPagespeed(Pagespeed pagespeed
) {
907 this.pagespeed
= pagespeed
;
910 public AppYaml
applyPlugins() {
912 for (Plugin plugin
: PluginLoader
.loadPlugins(Plugin
.class)) {
913 AppYaml modified
= plugin
.process(yaml
);
914 if (modified
!= null) {
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
{
927 private String servlet
;
928 private List
<Handler
> handlers
;
930 public void setHandlers(List
<Handler
> handlers
) {
931 this.handlers
= handlers
;
934 public String
getUrl() {
938 public void setUrl(String url
) {
939 YamlUtils
.validateUrl(url
);
943 public String
getServlet() {
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 <pagespeed> 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
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
) {
977 public List
<String
> getUrl_blacklist() {
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
) {
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
) {
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
);
1050 public void startElement(String name
, boolean empty
, String
... attributes
) {
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) {
1059 writer
.print(attributeName
);
1061 writer
.print(escapeAttribute(value
));
1066 writer
.println("/>");
1073 public void endElement(String name
) {
1074 endElement(name
, true);
1077 public void endElement(String name
, boolean needIndent
) {
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
++) {
1107 private String
escapeContent(String value
) {
1108 if (value
== null) {
1111 return XmlEscapers
.xmlContentEscaper().escape(value
);
1114 private String
escapeAttribute(String value
) {
1115 if (value
== 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
) {
1167 if (file
.getInclude() != null) {
1168 generateInclude(file
, xml
);
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
) {
1179 if (file
.getInclude() != null) {
1181 path
= file
.getInclude();
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;
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) {
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();
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
);
1280 servletsByName
.put(name
, handler
);
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());
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());
1390 xml
.startElement("include",
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");