1 // Copyright 2010 Google. All Rights Reserved.
2 package com
.google
.apphosting
.utils
.config
;
4 import static com
.google
.common
.collect
.Maps
.newLinkedHashMapWithExpectedSize
;
6 import com
.google
.common
.base
.Preconditions
;
7 import com
.google
.common
.xml
.XmlEscapers
;
9 import net
.sourceforge
.yamlbeans
.YamlConfig
;
10 import net
.sourceforge
.yamlbeans
.YamlException
;
11 import net
.sourceforge
.yamlbeans
.YamlReader
;
13 import java
.io
.PrintWriter
;
14 import java
.io
.Reader
;
15 import java
.io
.StringReader
;
16 import java
.io
.Writer
;
17 import java
.util
.LinkedHashMap
;
18 import java
.util
.List
;
20 import java
.util
.TreeMap
;
23 * JavaBean representation of the Java app.yaml file.
26 public class AppYaml
{
29 * Plugin service to modify app.yaml with runtime-specific defaults.
31 public static interface Plugin
{
32 AppYaml
process(AppYaml yaml
);
36 * A {@code Handler} element from app.yaml. Maps to {@code servlet}, {@code servlet-mapping},
37 * {@code filter}, and {@code filter-mapping} elements in web.xml
40 public static class Handler
{
42 public static enum Type
{SERVLET
, JSP
, FILTER
, NONE
}
46 private String servlet
;
47 private String filter
;
48 private LoginType login
;
49 private Security secure
;
50 private Map
<String
, String
> init_params
;
52 private boolean load_on_startup
;
54 public enum LoginType
{ admin
, required
}
55 public enum Security
{ always
, optional
, never
}
56 private boolean api_endpoint
= false;
58 private String script
;
60 private static final String MULTIPLE_HANDLERS
= "Cannot set both %s and %s for the same url.";
62 public String
getUrl() {
66 public void setUrl(String url
) {
67 YamlUtils
.validateUrl(url
);
71 public String
getJsp() {
75 public void setJsp(String jsp
) {
80 public String
getServlet() {
84 public void setServlet(String servlet
) {
85 this.servlet
= servlet
;
89 public String
getFilter() {
93 public void setFilter(String filter
) {
98 public Type
getType() {
99 if (servlet
!= null) {
102 if (filter
!= null) {
111 public String
getTarget() {
112 if (servlet
!= null) {
115 if (filter
!= null) {
124 public void setScript(String script
) {
125 this.script
= script
;
128 public String
getScript() {
132 public LoginType
getLogin() {
136 public void setLogin(LoginType login
) {
140 public Security
getSecure() {
144 public void setSecure(Security secure
) {
145 if (secure
== Security
.never
) {
146 throw new AppEngineConfigException("Java does not support secure: never");
148 this.secure
= secure
;
151 public Map
<String
, String
> getInit_params() {
155 public void setInit_params(Map
<String
, String
> init_params
) {
156 if (init_params
== null) {
157 this.init_params
= null;
159 this.init_params
= new TreeMap
<String
, String
>(init_params
);
163 public String
getName() {
164 return (name
== null ?
getTarget() : name
);
167 public void setLoad_on_startup(boolean loadOnStartup
) {
168 this.load_on_startup
= loadOnStartup
;
171 public boolean getLoad_on_startup() {
172 return this.load_on_startup
;
175 public void setName(String name
) {
179 public String
getApi_endpoint() {
180 return "" + this.api_endpoint
;
183 public void setApi_endpoint(String api_endpoint
) {
184 this.api_endpoint
= YamlUtils
.parseBoolean(api_endpoint
);
187 public boolean isApiEndpoint() {
188 return this.api_endpoint
;
191 private void checkHandlers() {
192 if (jsp
!= null && servlet
!= null) {
193 throw new AppEngineConfigException(String
.format(MULTIPLE_HANDLERS
, "jsp", "servlet"));
195 if (jsp
!= null && filter
!= null) {
196 throw new AppEngineConfigException(String
.format(MULTIPLE_HANDLERS
, "jsp", "filter"));
198 if (filter
!= null && servlet
!= null) {
199 throw new AppEngineConfigException(String
.format(MULTIPLE_HANDLERS
, "filter", "servlet"));
204 * Generates the {@code servlet} or {@code filter} element of web.xml
205 * corresponding to this handler.
207 private void generateDefinitionXml(XmlWriter xml
) {
208 if (getServlet() != null || getJsp() != null) {
209 generateServletDefinition(xml
);
210 } else if (getFilter() != null) {
211 generateFilterDefintion(xml
);
215 private void generateMappingXml(XmlWriter xml
) {
216 if (getServlet() != null || getJsp() != null) {
217 generateServletMapping(xml
);
218 } else if (getFilter() != null) {
219 generateFilterMapping(xml
);
221 generateSecurityConstraints(xml
);
224 private void generateSecurityConstraints(XmlWriter xml
) {
225 if (secure
== Security
.always
|| login
== LoginType
.required
|| login
== LoginType
.admin
) {
226 xml
.startElement("security-constraint");
227 xml
.startElement("web-resource-collection");
228 xml
.simpleElement("web-resource-name", "aname");
229 xml
.simpleElement("url-pattern", getUrl());
230 xml
.endElement("web-resource-collection");
231 if (login
== LoginType
.required
) {
232 securityConstraint(xml
, "auth", "role-name", "*");
233 } else if (login
== LoginType
.admin
) {
234 securityConstraint(xml
, "auth", "role-name", "admin");
236 if (secure
== Security
.always
) {
237 securityConstraint(xml
, "user-data", "transport-guarantee", "CONFIDENTIAL");
239 xml
.endElement("security-constraint");
243 private void securityConstraint(XmlWriter xml
, String type
, String name
, String value
) {
244 type
= type
+ "-constraint";
245 xml
.startElement(type
);
246 xml
.simpleElement(name
, value
);
247 xml
.endElement(type
);
251 * Generates a {@code filter} element of web.xml corresponding to this handler.
253 private void generateFilterDefintion(XmlWriter xml
) {
254 xml
.startElement("filter");
255 xml
.simpleElement("filter-name", getName());
256 xml
.simpleElement("filter-class", getFilter());
257 generateInitParams(xml
);
258 xml
.endElement("filter");
262 * Generates a {@code filter-mapping} element of web.xml corresponding to this handler.
264 private void generateFilterMapping(XmlWriter xml
) {
265 xml
.startElement("filter-mapping");
266 xml
.simpleElement("filter-name", getName());
267 xml
.simpleElement("url-pattern", getUrl());
268 xml
.endElement("filter-mapping");
272 * Generates a {@code servlet} or {@code jsp-file} element of web.xml corresponding to this
275 private void generateServletDefinition(XmlWriter xml
) {
276 xml
.startElement("servlet");
277 xml
.simpleElement("servlet-name", getName());
278 if (getJsp() == null) {
279 xml
.simpleElement("servlet-class", getServlet());
281 xml
.simpleElement("jsp-file", getJsp());
283 generateInitParams(xml
);
284 if (load_on_startup
) {
285 xml
.simpleElement("load-on-startup", "1");
287 xml
.endElement("servlet");
291 * Merges another handler into this handler, assuming that the other handler
292 * has the same name, type and target. This operation is intended to be
293 * used for generating a Servlet or Filter *definition* as opposed to a
294 * mapping, and therefore the urls of this handler and the other handler
295 * are not involved in the merge operation. The load_on_startup values
296 * of the two handlers will be OR'd and the init_params will be unioned.
298 public void mergeDefinitions(Handler otherHandler
) {
299 Preconditions
.checkArgument(this.getName().equals(otherHandler
.getName()),
300 "Cannot merge handler named " + this.getName() + " with handler named " +
301 otherHandler
.getName());
302 Preconditions
.checkArgument(this.getType() == otherHandler
.getType(),
303 "Cannot merge handler of type " + this.getType() + " with handler of type "
304 + otherHandler
.getType());
305 Preconditions
.checkArgument(this.getTarget().equals(otherHandler
.getTarget()),
306 "Cannont merge handler with target " + this.getTarget() + " with handler with target "
307 + otherHandler
.getTarget());
308 this.load_on_startup
= this.load_on_startup
|| otherHandler
.load_on_startup
;
309 Map
<String
, String
> mergedInitParams
= new LinkedHashMap
<String
, String
>();
310 if (this.init_params
!= null) {
311 mergedInitParams
.putAll(this.init_params
);
313 if (otherHandler
.init_params
!= null) {
314 for (String key
: otherHandler
.init_params
.keySet()) {
315 String thisValue
= mergedInitParams
.get(key
);
316 String otherValue
= otherHandler
.init_params
.get(key
);
317 if (thisValue
== null) {
318 mergedInitParams
.put(key
, otherValue
);
319 } else if (!thisValue
.equals(otherValue
)) {
320 throw new IllegalArgumentException(
321 "Cannot merge handlers with conflicting values for the init_param: " + key
+ " : "
322 + thisValue
+ " vs " + otherValue
);
326 if (mergedInitParams
.size() != 0) {
327 this.init_params
= mergedInitParams
;
332 * Generates a {@code servlet-mapping} element of web.xml corresponding to this handler.
334 private void generateServletMapping(XmlWriter xml
) {
335 if (isApiEndpoint()) {
336 xml
.startElement("servlet-mapping", "id", xml
.nextApiEndpointId());
338 xml
.startElement("servlet-mapping");
340 xml
.simpleElement("servlet-name", getName());
341 xml
.simpleElement("url-pattern", getUrl());
342 xml
.endElement("servlet-mapping");
345 private void generateInitParams(XmlWriter xml
) {
346 if (init_params
!= null) {
347 for (Map
.Entry
<String
, String
> param
: init_params
.entrySet()) {
348 xml
.startElement("init-param");
349 xml
.simpleElement("param-name", param
.getKey());
350 xml
.simpleElement("param-value", param
.getValue());
351 xml
.endElement("init-param");
356 private void generateEndpointServletMappingId(XmlWriter xml
) {
357 if (isApiEndpoint()) {
358 xml
.simpleElement("endpoint-servlet-mapping-id", xml
.nextApiEndpointId());
363 public static class ResourceFile
{
364 private static final String EMPTY_MESSAGE
= "Missing include or exclude.";
365 private static final String BOTH_MESSAGE
= "Cannot specify both include and exclude.";
367 protected String include
;
368 protected String exclude
;
369 protected Map
<String
, String
> httpHeaders
;
371 public String
getInclude() {
372 if (exclude
== null && include
== null) {
373 throw new AppEngineConfigException(EMPTY_MESSAGE
);
378 public void setInclude(String include
) {
379 if (exclude
!= null) {
380 throw new AppEngineConfigException(BOTH_MESSAGE
);
382 this.include
= include
;
385 public String
getExclude() {
386 if (exclude
== null && include
== null) {
387 throw new AppEngineConfigException(EMPTY_MESSAGE
);
392 public void setExclude(String exclude
) {
393 if (include
!= null) {
394 throw new AppEngineConfigException(BOTH_MESSAGE
);
396 this.exclude
= exclude
;
399 public Map
<String
, String
> getHttp_headers() {
400 if (include
== null) {
401 throw new AppEngineConfigException("Missing include.");
407 public void setHttp_headers(Map
<String
, String
> httpHeaders
) {
408 if (include
== null) {
409 throw new AppEngineConfigException("Missing include.");
412 this.httpHeaders
= httpHeaders
;
416 public static class StaticFile
extends ResourceFile
{
417 private static final String NO_INCLUDE
= "Missing include.";
418 private static final String INCLUDE_ONLY
= "Expiration can only be specified with include.";
419 private String expiration
;
421 public String
getExpiration() {
422 if (expiration
!= null && include
== null) {
423 throw new AppEngineConfigException(NO_INCLUDE
);
428 public void setExpiration(String expiration
) {
429 if (exclude
!= null) {
430 throw new AppEngineConfigException(INCLUDE_ONLY
);
432 this.expiration
= expiration
;
436 public void setExclude(String exclude
) {
437 if (expiration
!= null) {
438 throw new AppEngineConfigException(INCLUDE_ONLY
);
440 super.setExclude(exclude
);
444 public static class AdminConsole
{
445 private List
<AdminPage
> pages
;
447 public List
<AdminPage
> getPages() {
451 public void setPages(List
<AdminPage
> pages
) {
456 public static class AdminPage
{
460 public String
getName() {
464 public void setName(String name
) {
468 public String
getUrl() {
472 public void setUrl(String url
) {
477 public static class AsyncSessionPersistence
{
478 private boolean enabled
= false;
479 private String queue_name
;
481 public String
getEnabled() {
485 public void setEnabled(String enabled
) {
486 this.enabled
= YamlUtils
.parseBoolean(enabled
);
489 public String
getQueue_name() {
490 return this.queue_name
;
493 public void setQueue_name(String queue_name
) {
494 this.queue_name
= queue_name
;
498 public static class ErrorHandler
{
500 private String errorCode
;
502 public String
getFile() {
506 public void setFile(String file
) {
510 public String
getError_code() {
514 public void setError_code(String errorCode
) {
515 this.errorCode
= errorCode
;
520 * AutomaticScaling bean.
522 public static class AutomaticScaling
{
523 private String minPendingLatency
;
524 private String maxPendingLatency
;
525 private String minIdleInstances
;
526 private String maxIdleInstances
;
527 private String maxConcurrentRequests
;
529 public String
getMin_pending_latency() {
530 return minPendingLatency
;
533 public void setMin_pending_latency(String minPendingLatency
) {
534 this.minPendingLatency
= minPendingLatency
;
537 public String
getMax_pending_latency() {
538 return maxPendingLatency
;
541 public void setMax_pending_latency(String maxPendingLatency
) {
542 this.maxPendingLatency
= maxPendingLatency
;
545 public String
getMin_idle_instances() {
546 return minIdleInstances
;
549 public void setMin_idle_instances(String minIdleInstances
) {
550 this.minIdleInstances
= minIdleInstances
;
553 public String
getMax_idle_instances() {
554 return maxIdleInstances
;
557 public void setMax_idle_instances(String maxIdleInstances
) {
558 this.maxIdleInstances
= maxIdleInstances
;
561 public String
getMax_concurrent_requests() {
562 return maxConcurrentRequests
;
565 public void setMax_concurrent_requests(String maxConcurrentRequests
) {
566 this.maxConcurrentRequests
= maxConcurrentRequests
;
571 * ManualScaling bean.
573 public static class ManualScaling
{
574 private String instances
;
576 public String
getInstances() {
580 public void setInstances(String instances
) {
581 this.instances
= instances
;
588 public static class BasicScaling
{
589 private String maxInstances
;
590 private String idleTimeout
;
592 public String
getMax_instances() {
596 public void setMax_instances(String maxInstances
) {
597 this.maxInstances
= maxInstances
;
599 public String
getIdle_timeout() {
603 public void setIdle_timeout(String idleTimeout
) {
604 this.idleTimeout
= idleTimeout
;
608 private String application
;
609 private String version
;
610 private String source_language
;
611 private String module
;
612 private String instanceClass
;
613 private AutomaticScaling automatic_scaling
;
614 private ManualScaling manual_scaling
;
615 private BasicScaling basic_scaling
;
616 private String runtime
;
617 private List
<Handler
> handlers
;
618 private String public_root
;
619 private List
<StaticFile
> static_files
;
620 private List
<ResourceFile
> resource_files
;
621 private boolean ssl_enabled
= true;
622 private boolean precompilation_enabled
= true;
623 private boolean sessions_enabled
= false;
624 private AsyncSessionPersistence async_session_persistence
;
625 private boolean threadsafe
= false;
626 private String auto_id_policy
;
627 private boolean threadsafeWasSet
= false;
628 private boolean codeLock
= false;
629 private Map
<String
, String
> system_properties
;
630 private Map
<String
, String
> env_variables
;
631 private Map
<String
, String
> context_params
;
632 private List
<String
> welcome_files
;
633 private List
<String
> listeners
;
634 private List
<String
> inbound_services
;
635 private AdminConsole admin_console
;
636 private List
<ErrorHandler
> error_handlers
;
637 private ApiConfig api_config
;
638 private Pagespeed pagespeed
;
639 private String web_xml
;
641 private static final String REQUIRED_FIELD
= "Missing required element '%s'.";
643 public String
getApplication() {
644 if (application
== null) {
645 throw new AppEngineConfigException(String
.format(REQUIRED_FIELD
, "application"));
650 public void setApplication(String application
) {
651 this.application
= application
;
654 public String
getVersion() {
658 public void setVersion(String version
) {
659 this.version
= version
;
662 public void setSource_language(String sourceLanguage
) {
663 this.source_language
= sourceLanguage
;
666 public String
getSource_language() {
667 return source_language
;
670 public String
getModule() {
674 public void setModule(String module
) {
675 this.module
= module
;
678 public String
getInstance_class() {
679 return instanceClass
;
682 public void setInstance_class(String instanceClass
) {
683 this.instanceClass
= instanceClass
;
686 public AutomaticScaling
getAutomatic_scaling() {
687 return automatic_scaling
;
690 public void setAutomatic_scaling(AutomaticScaling automaticScaling
) {
691 this.automatic_scaling
= automaticScaling
;
694 public ManualScaling
getManual_scaling() {
695 return manual_scaling
;
698 public void setManual_scaling(ManualScaling manualScaling
) {
699 this.manual_scaling
= manualScaling
;
702 public BasicScaling
getBasic_scaling() {
703 return basic_scaling
;
706 public void setBasic_scaling(BasicScaling basicScaling
) {
707 this.basic_scaling
= basicScaling
;
710 public String
getRuntime() {
714 public void setRuntime(String runtime
) {
715 this.runtime
= runtime
;
718 public List
<Handler
> getHandlers() {
722 public void setHandlers(List
<Handler
> handlers
) {
723 this.handlers
= handlers
;
724 if (this.api_config
!= null) {
725 this.api_config
.setHandlers(handlers
);
729 public String
getPublic_root() {
733 public void setPublic_root(String public_root
) {
734 this.public_root
= public_root
;
737 public List
<StaticFile
> getStatic_files() {
741 public void setStatic_files(List
<StaticFile
> static_files
) {
742 this.static_files
= static_files
;
745 public List
<ResourceFile
> getResource_files() {
746 return resource_files
;
749 public void setResource_files(List
<ResourceFile
> resource_files
) {
750 this.resource_files
= resource_files
;
753 public String
getSsl_enabled() {
754 return "" + ssl_enabled
;
757 public void setSsl_enabled(String ssl_enabled
) {
758 this.ssl_enabled
= YamlUtils
.parseBoolean(ssl_enabled
);
761 public boolean isSslEnabled() {
765 public String
getPrecompilation_enabled() {
766 return "" + precompilation_enabled
;
769 public boolean isPrecompilationEnabled() {
770 return precompilation_enabled
;
773 public void setPrecompilation_enabled(String precompilation_enabled
) {
774 this.precompilation_enabled
= YamlUtils
.parseBoolean(precompilation_enabled
);
777 public String
getSessions_enabled() {
778 return "" + sessions_enabled
;
781 public boolean isSessionsEnabled() {
782 return sessions_enabled
;
785 public void setSessions_enabled(String sessions_enabled
) {
786 this.sessions_enabled
= YamlUtils
.parseBoolean(sessions_enabled
);
789 public AsyncSessionPersistence
getAsync_session_persistence() {
790 return async_session_persistence
;
793 public void setAsync_session_persistence(AsyncSessionPersistence async_session_persistence
) {
794 this.async_session_persistence
= async_session_persistence
;
797 public String
getThreadsafe() {
798 return "" + threadsafe
;
801 public boolean isThreadsafeSet() {
802 return threadsafeWasSet
;
805 public void setThreadsafe(String threadsafe
) {
806 this.threadsafe
= YamlUtils
.parseBoolean(threadsafe
);
807 threadsafeWasSet
= true;
810 public String
getAuto_id_policy() {
811 return auto_id_policy
;
814 public void setAuto_id_policy(String policy
) {
815 auto_id_policy
= policy
;
818 public String
getCode_lock() {
819 return "" + codeLock
;
822 public void setCode_lock(String codeLock
) {
823 this.codeLock
= YamlUtils
.parseBoolean(codeLock
);
826 public Map
<String
, String
> getSystem_properties() {
827 return system_properties
;
830 public void setSystem_properties(Map
<String
, String
> system_properties
) {
831 this.system_properties
= system_properties
;
834 public Map
<String
, String
> getEnv_variables() {
835 return env_variables
;
838 public void setEnv_variables(Map
<String
, String
> env_variables
) {
839 this.env_variables
= env_variables
;
842 public List
<String
> getWelcome_files() {
843 return welcome_files
;
846 public void setWelcome_files(List
<String
> welcome_files
) {
847 this.welcome_files
= welcome_files
;
850 public Map
<String
, String
> getContext_params() {
851 return context_params
;
854 public void setContext_params(Map
<String
, String
> context_params
) {
855 this.context_params
= context_params
;
858 public List
<String
> getListeners() {
862 public void setListeners(List
<String
> listeners
) {
863 this.listeners
= listeners
;
866 public String
getWeb_xml() {
870 public void setWeb_xml(String web_xml
) {
871 this.web_xml
= web_xml
;
874 public List
<String
> getInbound_services() {
875 return inbound_services
;
878 public void setInbound_services(List
<String
> inbound_services
) {
879 this.inbound_services
= inbound_services
;
882 public AdminConsole
getAdmin_console() {
883 return admin_console
;
886 public void setAdmin_console(AdminConsole admin_console
) {
887 this.admin_console
= admin_console
;
890 public List
<ErrorHandler
> getError_handlers() {
891 return error_handlers
;
894 public void setError_handlers(List
<ErrorHandler
> error_handlers
) {
895 this.error_handlers
= error_handlers
;
898 public ApiConfig
getApi_config() {
902 public void setApi_config(ApiConfig api_config
) {
903 this.api_config
= api_config
;
904 if (handlers
!= null) {
905 this.api_config
.setHandlers(handlers
);
909 public Pagespeed
getPagespeed() {
913 public void setPagespeed(Pagespeed pagespeed
) {
914 this.pagespeed
= pagespeed
;
917 public AppYaml
applyPlugins() {
919 for (Plugin plugin
: PluginLoader
.loadPlugins(Plugin
.class)) {
920 AppYaml modified
= plugin
.process(yaml
);
921 if (modified
!= null) {
929 * Represents an api-config: top level app.yaml stanza
930 * This is a singleton specifying url: and servlet: for the api config server.
932 public static class ApiConfig
{
934 private String servlet
;
935 private List
<Handler
> handlers
;
937 public void setHandlers(List
<Handler
> handlers
) {
938 this.handlers
= handlers
;
941 public String
getUrl() {
945 public void setUrl(String url
) {
946 YamlUtils
.validateUrl(url
);
950 public String
getServlet() {
954 public void setServlet(String servlet
) {
955 this.servlet
= servlet
;
958 private void generateXml(XmlWriter xml
) {
959 xml
.startElement("api-config", "servlet-class", getServlet(), "url-pattern", getUrl());
960 if (handlers
!= null) {
961 for (Handler handler
: handlers
) {
962 handler
.generateEndpointServletMappingId(xml
);
965 xml
.endElement("api-config");
970 * Represents a <pagespeed> element. This is used to specify configuration for the Page
971 * Speed Service, which can be used to automatically optimize the loading speed of app engine
974 public static class Pagespeed
{
975 private List
<String
> urlBlacklist
;
976 private List
<String
> domainsToRewrite
;
977 private List
<String
> enabledRewriters
;
978 private List
<String
> disabledRewriters
;
980 public void setUrl_blacklist(List
<String
> urls
) {
984 public List
<String
> getUrl_blacklist() {
988 public void setDomains_to_rewrite(List
<String
> domains
) {
989 domainsToRewrite
= domains
;
992 public List
<String
> getDomains_to_rewrite() {
993 return domainsToRewrite
;
996 public void setEnabled_rewriters(List
<String
> rewriters
) {
997 enabledRewriters
= rewriters
;
1000 public List
<String
> getEnabled_rewriters() {
1001 return enabledRewriters
;
1004 public void setDisabled_rewriters(List
<String
> rewriters
) {
1005 disabledRewriters
= rewriters
;
1008 public List
<String
> getDisabled_rewriters() {
1009 return disabledRewriters
;
1012 private void generateXml(XmlWriter xml
) {
1014 xml
.startElement("pagespeed");
1015 appendElements(xml
, "url-blacklist", urlBlacklist
);
1016 appendElements(xml
, "domain-to-rewrite", domainsToRewrite
);
1017 appendElements(xml
, "enabled-rewriter", enabledRewriters
);
1018 appendElements(xml
, "disabled-rewriter", disabledRewriters
);
1019 xml
.endElement("pagespeed");
1023 private void appendElements(XmlWriter xml
, String name
, List
<String
> l
) {
1025 for (String elt
: l
) {
1026 xml
.simpleElement(name
, elt
);
1031 private boolean isEmpty() {
1032 return !hasElements(urlBlacklist
) && !hasElements(domainsToRewrite
)
1033 && !hasElements(enabledRewriters
) && !hasElements(disabledRewriters
);
1036 private boolean hasElements(List
<?
> coll
) {
1037 return coll
!= null && !coll
.isEmpty();
1041 private class XmlWriter
{
1042 private static final String XML_HEADER
= "<!-- Generated from app.yaml. Do not edit. -->";
1043 private final PrintWriter writer
;
1044 private int indent
= 0;
1045 private int apiEndpointId
= 0;
1047 public XmlWriter(Writer w
) {
1048 writer
= new PrintWriter(w
);
1049 writer
.println(XML_HEADER
);
1052 public void startElement(String name
, String
... attributes
) {
1053 startElement(name
, false, attributes
);
1057 public void startElement(String name
, boolean empty
, String
... attributes
) {
1061 for (int i
= 0; i
< attributes
.length
; i
+= 2) {
1062 String attributeName
= attributes
[i
];
1063 String value
= attributes
[i
+ 1];
1064 if (value
!= null) {
1066 writer
.print(attributeName
);
1068 writer
.print(escapeAttribute(value
));
1073 writer
.println("/>");
1080 public void endElement(String name
) {
1081 endElement(name
, true);
1084 public void endElement(String name
, boolean needIndent
) {
1091 writer
.println(">");
1094 public void emptyElement(String name
, String
... attributes
) {
1095 startElement(name
, true, attributes
);
1098 public void simpleElement(String name
, String value
, String
... attributes
) {
1099 startElement(name
, false, attributes
);
1100 writer
.print(escapeContent(value
));
1101 endElement(name
, false);
1104 public void writeUnescaped(String xmlContent
) {
1105 writer
.println(xmlContent
);
1108 private void indent() {
1109 for (int i
= 0; i
< indent
; i
++) {
1114 private String
escapeContent(String value
) {
1115 if (value
== null) {
1118 return XmlEscapers
.xmlContentEscaper().escape(value
);
1121 private String
escapeAttribute(String value
) {
1122 if (value
== null) {
1125 return XmlEscapers
.xmlAttributeEscaper().escape(value
);
1128 private String
nextApiEndpointId() {
1129 return String
.format("endpoint-%1$d", ++apiEndpointId
);
1133 private void addOptionalElement(XmlWriter xml
, String name
, String value
) {
1134 if (value
!= null) {
1135 xml
.simpleElement(name
, value
);
1139 public void generateAppEngineWebXml(Writer writer
) {
1140 XmlWriter xml
= new XmlWriter(writer
);
1141 xml
.startElement("appengine-web-app", "xmlns", "http://appengine.google.com/ns/1.0");
1142 xml
.simpleElement("application", getApplication());
1143 addOptionalElement(xml
, "version", getVersion());
1144 addOptionalElement(xml
, "source-language", getSource_language());
1145 addOptionalElement(xml
, "module", getModule());
1146 addOptionalElement(xml
, "instance-class", getInstance_class());
1147 addOptionalElement(xml
, "public-root", public_root
);
1148 addOptionalElement(xml
, "auto-id-policy", getAuto_id_policy());
1149 if (automatic_scaling
!= null) {
1150 xml
.startElement("automatic-scaling");
1151 addOptionalElement(xml
, "min-pending-latency", automatic_scaling
.getMin_pending_latency());
1152 addOptionalElement(xml
, "max-pending-latency", automatic_scaling
.getMax_pending_latency());
1153 addOptionalElement(xml
, "min-idle-instances", automatic_scaling
.getMin_idle_instances());
1154 addOptionalElement(xml
, "max-idle-instances", automatic_scaling
.getMax_idle_instances());
1155 addOptionalElement(xml
, "max-concurrent-requests",
1156 automatic_scaling
.getMax_concurrent_requests());
1157 xml
.endElement("automatic-scaling");
1159 if (manual_scaling
!= null) {
1160 xml
.startElement("manual-scaling");
1161 xml
.simpleElement("instances", manual_scaling
.getInstances());
1162 xml
.endElement("manual-scaling");
1164 if (basic_scaling
!= null) {
1165 xml
.startElement("basic-scaling");
1166 xml
.simpleElement("max-instances", basic_scaling
.getMax_instances());
1167 addOptionalElement(xml
, "idle-timeout", basic_scaling
.getIdle_timeout());
1168 xml
.endElement("basic-scaling");
1170 xml
.startElement("static-files");
1171 if (static_files
!= null) {
1172 for (StaticFile file
: static_files
) {
1174 if (file
.getInclude() != null) {
1175 generateInclude(file
, xml
);
1177 xml
.emptyElement("exclude", "path", file
.getExclude());
1181 xml
.endElement("static-files");
1182 xml
.startElement("resource-files");
1183 if (resource_files
!= null) {
1184 for (ResourceFile file
: resource_files
) {
1186 if (file
.getInclude() != null) {
1188 path
= file
.getInclude();
1191 path
= file
.getExclude();
1193 xml
.emptyElement(name
, "path", path
);
1196 xml
.endElement("resource-files");
1197 xml
.simpleElement("ssl-enabled", getSsl_enabled());
1198 xml
.simpleElement("precompilation-enabled", getPrecompilation_enabled());
1199 if (isThreadsafeSet()) {
1200 xml
.simpleElement("threadsafe", getThreadsafe());
1202 xml
.simpleElement("code-lock", getCode_lock());
1203 xml
.simpleElement("sessions-enabled", getSessions_enabled());
1204 if (async_session_persistence
!= null) {
1205 xml
.simpleElement("async-session-persistence", null,
1206 "enabled", getAsync_session_persistence().getEnabled(),
1207 "queue-name", getAsync_session_persistence().getQueue_name());
1209 if (system_properties
!= null) {
1210 xml
.startElement("system-properties");
1211 for (Map
.Entry
<String
, String
> entry
: system_properties
.entrySet()) {
1212 xml
.emptyElement("property", "name", entry
.getKey(), "value", entry
.getValue());
1214 xml
.endElement("system-properties");
1216 if (env_variables
!= null) {
1217 xml
.startElement("env-variables");
1218 for (Map
.Entry
<String
, String
> entry
: env_variables
.entrySet()) {
1219 xml
.emptyElement("env-var", "name", entry
.getKey(), "value", entry
.getValue());
1221 xml
.endElement("env-variables");
1223 boolean warmupService
= false;
1224 if (inbound_services
!= null) {
1225 xml
.startElement("inbound-services");
1226 for (String service
: inbound_services
) {
1227 if (AppEngineWebXml
.WARMUP_SERVICE
.equals(service
)) {
1228 warmupService
= true;
1230 xml
.simpleElement("service", service
);
1233 xml
.endElement("inbound-services");
1235 xml
.simpleElement("warmup-requests-enabled", Boolean
.toString(warmupService
));
1236 if (admin_console
!= null && admin_console
.getPages() != null) {
1237 xml
.startElement("admin-console");
1238 for (AdminPage page
: admin_console
.getPages()) {
1239 xml
.emptyElement("page", "name", page
.getName(), "url", page
.getUrl());
1241 xml
.endElement("admin-console");
1243 if (error_handlers
!= null) {
1244 xml
.startElement("static-error-handlers");
1245 for (ErrorHandler handler
: error_handlers
) {
1246 xml
.emptyElement("handler",
1247 "file", handler
.getFile(),
1248 "error-code", handler
.getError_code());
1250 xml
.endElement("static-error-handlers");
1252 if (api_config
!= null) {
1253 api_config
.generateXml(xml
);
1255 if (pagespeed
!= null) {
1256 pagespeed
.generateXml(xml
);
1258 xml
.endElement("appengine-web-app");
1262 * Generates the {@code servlet}, {@code servlet-mapping}, {@code filter}, and
1263 * {@code filter-mapping} elements of web.xml corresponding to the {@link #handlers} list. There
1264 * may be multiple {@link Handler handlers} corresponding to the same servlet or filter, because a
1265 * single handler can only specify one URL pattern and the user may wish to map several URL
1266 * patterns to the same servlet or filter. In this case we want to have multiple
1267 * {@code servlet-mapping} or {@code filter-mapping} elements but only a single {@code servlet} or
1268 * {@code filter} element.
1270 private void generateHandlerXml(XmlWriter xmlWriter
) {
1271 if (handlers
== null) {
1274 Map
<String
, Handler
> servletsByName
= newLinkedHashMapWithExpectedSize(handlers
.size());
1275 Map
<String
, Handler
> filtersByName
= newLinkedHashMapWithExpectedSize(handlers
.size());
1276 for (Handler handler
: handlers
) {
1277 String name
= handler
.getName();
1279 Handler
.Type type
= handler
.getType();
1280 boolean isServlet
= (type
== Handler
.Type
.SERVLET
|| type
== Handler
.Type
.JSP
);
1281 boolean isFilter
= (type
== Handler
.Type
.FILTER
);
1282 Handler existing
= (isServlet ? servletsByName
.get(name
) : filtersByName
.get(name
));
1283 if (existing
!= null) {
1284 existing
.mergeDefinitions(handler
);
1287 servletsByName
.put(name
, handler
);
1290 filtersByName
.put(name
, handler
);
1295 for (Handler handler
: servletsByName
.values()) {
1296 handler
.generateDefinitionXml(xmlWriter
);
1298 for (Handler handler
: filtersByName
.values()) {
1299 handler
.generateDefinitionXml(xmlWriter
);
1301 for (Handler handler
: handlers
) {
1302 handler
.generateMappingXml(xmlWriter
);
1306 public void generateWebXml(Writer writer
) {
1307 XmlWriter xml
= new XmlWriter(writer
);
1308 xml
.startElement("web-app", "version", "2.5",
1309 "xmlns", "http://java.sun.com/xml/ns/javaee",
1310 "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance",
1311 "xsi:schemaLocation",
1312 "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
1314 generateHandlerXml(xml
);
1315 if (context_params
!= null) {
1316 for (Map
.Entry
<String
, String
> entry
: context_params
.entrySet()) {
1317 xml
.startElement("context-param");
1318 xml
.simpleElement("param-name", entry
.getKey());
1319 xml
.simpleElement("param-value", entry
.getValue());
1320 xml
.endElement("context-param");
1323 if (welcome_files
!= null) {
1324 xml
.startElement("welcome-file-list");
1325 for (String file
: welcome_files
) {
1326 xml
.simpleElement("welcome-file", file
);
1328 xml
.endElement("welcome-file-list");
1330 if (listeners
!= null) {
1331 for (String listener
: listeners
) {
1332 xml
.startElement("listener");
1333 xml
.simpleElement("listener-class", listener
);
1334 xml
.endElement("listener");
1337 if (web_xml
!= null) {
1338 xml
.writeUnescaped(web_xml
);
1340 xml
.endElement("web-app");
1343 public static AppYaml
parse(Reader reader
) {
1344 YamlReader yaml
= new YamlReader(reader
);
1345 prepareParser(yaml
.getConfig());
1347 AppYaml appYaml
= yaml
.read(AppYaml
.class);
1348 if (appYaml
== null) {
1349 throw new YamlException("Unable to parse yaml file");
1351 return appYaml
.applyPlugins();
1352 } catch (YamlException e
) {
1353 Throwable innerException
= e
.getCause();
1355 while (innerException
!= null) {
1356 if (innerException
instanceof AppEngineConfigException
) {
1357 throw (AppEngineConfigException
) innerException
;
1359 innerException
= innerException
.getCause();
1362 throw new AppEngineConfigException(e
.getMessage(), e
);
1366 public static AppYaml
parse(String yaml
) {
1367 return parse(new StringReader(yaml
));
1370 public static void prepareParser(YamlConfig config
) {
1371 config
.setPropertyElementType(AppYaml
.class, "handlers", Handler
.class);
1372 config
.setPropertyElementType(AppYaml
.class, "static_files", StaticFile
.class);
1373 config
.setPropertyElementType(AppYaml
.class, "resource_files", ResourceFile
.class);
1374 config
.setPropertyElementType(AppYaml
.class, "system_properties", String
.class);
1375 config
.setPropertyElementType(AppYaml
.class, "context_params", String
.class);
1376 config
.setPropertyElementType(AppYaml
.class, "env_variables", String
.class);
1377 config
.setPropertyElementType(AppYaml
.class, "welcome_files", String
.class);
1378 config
.setPropertyElementType(AppYaml
.class, "listeners", String
.class);
1379 config
.setPropertyElementType(AppYaml
.class, "inbound_services", String
.class);
1380 config
.setPropertyElementType(Handler
.class, "init_params", String
.class);
1381 config
.setPropertyElementType(AdminConsole
.class, "pages", AdminPage
.class);
1382 config
.setPropertyElementType(AppYaml
.class, "error_handlers", ErrorHandler
.class);
1383 config
.setPropertyElementType(Pagespeed
.class, "url_blacklist", String
.class);
1384 config
.setPropertyElementType(Pagespeed
.class, "domains_to_rewrite", String
.class);
1385 config
.setPropertyElementType(Pagespeed
.class, "enabled_rewriters", String
.class);
1386 config
.setPropertyElementType(Pagespeed
.class, "disabled_rewriters", String
.class);
1389 private void generateInclude(StaticFile include
, XmlWriter xml
) {
1390 String path
= include
.getInclude();
1391 Map
<String
, String
> httpHeaders
= include
.getHttp_headers();
1392 if (httpHeaders
== null || httpHeaders
.isEmpty()) {
1393 xml
.emptyElement("include",
1394 "path", include
.getInclude(),
1395 "expiration", include
.getExpiration());
1397 xml
.startElement("include",
1399 "path", include
.getInclude(),
1400 "expiration", include
.getExpiration());
1401 for (Map
.Entry
<String
, String
> entry
: httpHeaders
.entrySet()) {
1402 xml
.emptyElement("http-header",
1403 "name", entry
.getKey(),
1404 "value", entry
.getValue());
1406 xml
.endElement("include");