1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.admin
;
5 import com
.google
.appengine
.tools
.info
.Version
;
6 import com
.google
.apphosting
.utils
.config
.AppEngineConfigException
;
7 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
;
8 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.AdminConsolePage
;
9 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.ApiConfig
;
10 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.CpuUtilization
;
11 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.ErrorHandler
;
12 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.Pagespeed
;
13 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.HealthCheck
;
14 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.Resources
;
15 import com
.google
.apphosting
.utils
.config
.AppEngineWebXml
.Network
;
16 import com
.google
.apphosting
.utils
.config
.BackendsXml
;
17 import com
.google
.apphosting
.utils
.config
.WebXml
;
18 import com
.google
.apphosting
.utils
.config
.WebXml
.SecurityConstraint
;
19 import com
.google
.apphosting
.utils
.glob
.ConflictResolver
;
20 import com
.google
.apphosting
.utils
.glob
.Glob
;
21 import com
.google
.apphosting
.utils
.glob
.GlobFactory
;
22 import com
.google
.apphosting
.utils
.glob
.GlobIntersector
;
23 import com
.google
.apphosting
.utils
.glob
.LongestPatternConflictResolver
;
24 import com
.google
.common
.collect
.Maps
;
26 import net
.sourceforge
.yamlbeans
.YamlConfig
;
27 import net
.sourceforge
.yamlbeans
.YamlException
;
28 import net
.sourceforge
.yamlbeans
.YamlWriter
;
30 import java
.io
.StringWriter
;
31 import java
.util
.ArrayList
;
32 import java
.util
.Collection
;
33 import java
.util
.Collections
;
34 import java
.util
.HashMap
;
35 import java
.util
.List
;
40 * Generates {@code app.yaml} files suitable for uploading as part of
41 * a Google App Engine application.
44 public class AppYamlTranslator
{
45 private static final String NO_API_VERSION
= "none";
47 private static final ConflictResolver RESOLVER
=
48 new LongestPatternConflictResolver();
50 private static final String DYNAMIC_PROPERTY
= "dynamic";
51 private static final String STATIC_PROPERTY
= "static";
52 private static final String WELCOME_FILES
= "welcome";
53 private static final String TRANSPORT_GUARANTEE_PROPERTY
= "transportGuarantee";
54 private static final String REQUIRED_ROLE_PROPERTY
= "requiredRole";
55 private static final String EXPIRATION_PROPERTY
= "expiration";
56 private static final String HTTP_HEADERS_PROPERTY
= "http_headers";
57 private static final String API_ENDPOINT_REGEX
= "/_ah/spi/*";
59 private static final String
[] PROPERTIES
= new String
[] {
63 TRANSPORT_GUARANTEE_PROPERTY
,
64 REQUIRED_ROLE_PROPERTY
,
68 private static final int MAX_HANDLERS
= 100;
70 private final AppEngineWebXml appEngineWebXml
;
71 private final WebXml webXml
;
72 private final BackendsXml backendsXml
;
73 private final String apiVersion
;
74 private final Set
<String
> staticFiles
;
75 private final String runtime
;
76 private final Version sdkVersion
;
78 public AppYamlTranslator(AppEngineWebXml appEngineWebXml
,
80 BackendsXml backendsXml
,
82 Set
<String
> staticFiles
,
86 this.appEngineWebXml
= appEngineWebXml
;
88 this.backendsXml
= backendsXml
;
89 this.apiVersion
= apiVersion
;
90 this.staticFiles
= staticFiles
;
91 this.runtime
= runtime
;
92 this.sdkVersion
= sdkVersion
;
95 public String
getYaml() {
96 StringBuilder builder
= new StringBuilder();
97 translateAppEngineWebXml(builder
);
98 translateApiVersion(builder
);
99 translateWebXml(builder
);
100 return builder
.toString();
103 private void appendIfNotNull(StringBuilder builder
, String tag
, Object value
) {
106 builder
.append(value
);
107 builder
.append("\n");
111 private void appendIfNotZero(StringBuilder builder
, String tag
, double value
) {
114 builder
.append(value
);
115 builder
.append("\n");
119 private void translateAppEngineWebXml(StringBuilder builder
) {
120 if (appEngineWebXml
.getAppId() != null) {
121 builder
.append("application: '" + appEngineWebXml
.getAppId() + "'\n");
123 builder
.append("runtime: " + runtime
+ "\n");
124 if (appEngineWebXml
.getUseVm()) {
125 builder
.append("vm: True\n");
128 if (!appEngineWebXml
.getEnv().equals("1")) {
129 builder
.append("env: " + appEngineWebXml
.getEnv() + "\n");
132 if (appEngineWebXml
.getSourceLanguage() != null) {
133 builder
.append("source_language: '" + appEngineWebXml
.getSourceLanguage() + "'\n");
136 if (appEngineWebXml
.getMajorVersionId() != null) {
137 builder
.append("version: '" + appEngineWebXml
.getMajorVersionId() + "'\n");
140 if (appEngineWebXml
.getModule() != null) {
141 builder
.append("module: '" + appEngineWebXml
.getModule() + "'\n");
144 if (appEngineWebXml
.getInstanceClass() != null) {
145 builder
.append("instance_class: " + appEngineWebXml
.getInstanceClass() + "\n");
148 if (!appEngineWebXml
.getAutomaticScaling().isEmpty()) {
149 builder
.append("automatic_scaling:\n");
150 AppEngineWebXml
.AutomaticScaling settings
= appEngineWebXml
.getAutomaticScaling();
151 appendIfNotNull(builder
, " min_pending_latency: ", settings
.getMinPendingLatency());
152 appendIfNotNull(builder
, " max_pending_latency: ", settings
.getMaxPendingLatency());
153 appendIfNotNull(builder
, " min_idle_instances: ", settings
.getMinIdleInstances());
154 appendIfNotNull(builder
, " max_idle_instances: ", settings
.getMaxIdleInstances());
155 appendIfNotNull(builder
, " max_concurrent_requests: ", settings
.getMaxConcurrentRequests());
156 appendIfNotNull(builder
, " min_num_instances: ", settings
.getMinNumInstances());
157 appendIfNotNull(builder
, " max_num_instances: ", settings
.getMaxNumInstances());
158 appendIfNotNull(builder
, " cool_down_period_sec: ", settings
.getCoolDownPeriodSec());
160 CpuUtilization cpuUtil
= settings
.getCpuUtilization();
162 && (cpuUtil
.getTargetUtilization() != null
163 || cpuUtil
.getAggregationWindowLengthSec() != null)) {
164 builder
.append(" cpu_utilization:\n");
165 appendIfNotNull(builder
, " target_utilization: ", cpuUtil
.getTargetUtilization());
167 builder
, " aggregation_window_length_sec: ",
168 cpuUtil
.getAggregationWindowLengthSec());
172 builder
, " target_network_sent_bytes_per_sec: ",
173 settings
.getTargetNetworkSentBytesPerSec());
175 builder
, " target_network_sent_packets_per_sec: ",
176 settings
.getTargetNetworkSentPacketsPerSec());
178 builder
, " target_network_received_bytes_per_sec: ",
179 settings
.getTargetNetworkReceivedBytesPerSec());
181 builder
, " target_network_received_packets_per_sec: ",
182 settings
.getTargetNetworkReceivedPacketsPerSec());
184 builder
, " target_disk_write_bytes_per_sec: ",
185 settings
.getTargetDiskWriteBytesPerSec());
187 builder
, " target_disk_write_ops_per_sec: ",
188 settings
.getTargetDiskWriteOpsPerSec());
190 builder
, " target_disk_read_bytes_per_sec: ",
191 settings
.getTargetDiskReadBytesPerSec());
193 builder
, " target_disk_read_ops_per_sec: ",
194 settings
.getTargetDiskReadOpsPerSec());
196 builder
, " target_request_count_per_sec: ",
197 settings
.getTargetRequestCountPerSec());
199 builder
, " target_concurrent_requests: ",
200 settings
.getTargetConcurrentRequests());
203 if (!appEngineWebXml
.getManualScaling().isEmpty()) {
204 builder
.append("manual_scaling:\n");
205 AppEngineWebXml
.ManualScaling settings
= appEngineWebXml
.getManualScaling();
206 builder
.append(" instances: " + settings
.getInstances() + "\n");
209 if (!appEngineWebXml
.getBasicScaling().isEmpty()) {
210 builder
.append("basic_scaling:\n");
211 AppEngineWebXml
.BasicScaling settings
= appEngineWebXml
.getBasicScaling();
212 builder
.append(" max_instances: " + settings
.getMaxInstances() + "\n");
213 appendIfNotNull(builder
, " idle_timeout: ", settings
.getIdleTimeout());
216 Collection
<String
> services
= appEngineWebXml
.getInboundServices();
217 if (!services
.isEmpty()) {
218 builder
.append("inbound_services:\n");
219 for (String service
: services
) {
220 builder
.append("- " + service
+ "\n");
224 if (appEngineWebXml
.getPrecompilationEnabled()) {
225 builder
.append("derived_file_type:\n");
226 builder
.append("- java_precompiled\n");
229 if (appEngineWebXml
.getThreadsafe()) {
230 builder
.append("threadsafe: True\n");
233 if (appEngineWebXml
.getAutoIdPolicy() != null) {
234 builder
.append("auto_id_policy: " + appEngineWebXml
.getAutoIdPolicy() + "\n");
236 builder
.append("auto_id_policy: default\n");
239 if (appEngineWebXml
.getCodeLock()) {
240 builder
.append("code_lock: True\n");
243 List
<AdminConsolePage
> adminConsolePages
= appEngineWebXml
.getAdminConsolePages();
244 if (!adminConsolePages
.isEmpty()) {
245 builder
.append("admin_console:\n");
246 builder
.append(" pages:\n");
247 for (AdminConsolePage page
: adminConsolePages
) {
248 builder
.append(" - name: " + page
.getName() + "\n");
249 builder
.append(" url: " + page
.getUrl() + "\n");
253 List
<ErrorHandler
> errorHandlers
= appEngineWebXml
.getErrorHandlers();
254 if (!errorHandlers
.isEmpty()) {
255 builder
.append("error_handlers:\n");
256 for (ErrorHandler handler
: errorHandlers
) {
257 String fileName
= handler
.getFile();
258 if (!fileName
.startsWith("/")) {
259 fileName
= "/" + fileName
;
261 if (!staticFiles
.contains("__static__" + fileName
)) {
262 throw new AppEngineConfigException("No static file found for error handler: "
263 + fileName
+ ", out of " + staticFiles
);
265 builder
.append("- file: __static__" + fileName
+ "\n");
266 if (handler
.getErrorCode() != null) {
267 builder
.append(" error_code: " + handler
.getErrorCode() + "\n");
269 String mimeType
= webXml
.getMimeTypeForPath(handler
.getFile());
270 if (mimeType
!= null) {
271 builder
.append(" mime_type: " + mimeType
+ "\n");
276 if (backendsXml
!= null) {
277 builder
.append(backendsXml
.toYaml());
280 ApiConfig apiConfig
= appEngineWebXml
.getApiConfig();
281 if (apiConfig
!= null) {
282 builder
.append("api_config:\n");
283 builder
.append(" url: " + apiConfig
.getUrl() + "\n");
284 builder
.append(" script: unused\n");
287 if (appEngineWebXml
.getPagespeed() != null) {
288 builder
.append("pagespeed:\n");
289 appendPagespeed(appEngineWebXml
.getPagespeed(), builder
, 2);
292 if (appEngineWebXml
.getUseVm() || appEngineWebXml
.getEnv().equals("2")) {
293 appendEnvVariables(appEngineWebXml
.getEnvironmentVariables(), builder
);
294 appendBetaSettings(appEngineWebXml
.getBetaSettings(), builder
);
295 appendHealthCheck(appEngineWebXml
.getHealthCheck(), builder
);
296 appendResources(appEngineWebXml
.getResources(), builder
);
297 appendNetwork(appEngineWebXml
.getNetwork(), builder
);
302 * Appends the given environment variables as YAML to the given StringBuilder.
304 * @param envVariables The env variables map to append as YAML.
305 * @param builder The StringBuilder to append to.
307 private void appendEnvVariables(Map
<String
, String
> envVariables
, StringBuilder builder
) {
308 if (envVariables
.size() > 0) {
309 builder
.append("env_variables:\n");
310 for (Map
.Entry
<String
, String
> envVariable
: envVariables
.entrySet()) {
311 String k
= envVariable
.getKey();
312 String v
= envVariable
.getValue();
313 builder
.append(" ").append(yamlQuote(k
)).append(": ").append(yamlQuote(v
)).append("\n");
319 * Appends the given Beta Settings as YAML to the given StringBuilder.
321 * @param betaSettings The beta settings map to append as YAML.
322 * @param builder The StringBuilder to append to.
324 private void appendBetaSettings(Map
<String
, String
> betaSettings
, StringBuilder builder
) {
325 if (betaSettings
!= null && !betaSettings
.isEmpty()) {
326 builder
.append("beta_settings:\n");
327 for (Map
.Entry
<String
, String
> setting
: betaSettings
.entrySet()) {
329 " " + yamlQuote(setting
.getKey()) + ": " + yamlQuote(setting
.getValue()) + "\n");
334 private void appendHealthCheck(HealthCheck healthCheck
, StringBuilder builder
) {
335 builder
.append("health_check:\n");
336 if (healthCheck
.getEnableHealthCheck()) {
337 builder
.append(" enable_health_check: True\n");
339 builder
.append(" enable_health_check: False\n");
342 appendIfNotNull(builder
, " check_interval_sec: ", healthCheck
.getCheckIntervalSec());
343 appendIfNotNull(builder
, " timeout_sec: ", healthCheck
.getTimeoutSec());
344 appendIfNotNull(builder
, " unhealthy_threshold: ", healthCheck
.getUnhealthyThreshold());
345 appendIfNotNull(builder
, " healthy_threshold: ", healthCheck
.getHealthyThreshold());
346 appendIfNotNull(builder
, " restart_threshold: ", healthCheck
.getRestartThreshold());
347 appendIfNotNull(builder
, " host: ", healthCheck
.getHost());
350 private void appendResources(Resources resources
, StringBuilder builder
) {
351 if (!resources
.isEmpty()) {
352 builder
.append("resources:\n");
353 appendIfNotZero(builder
, " cpu: ", resources
.getCpu());
354 appendIfNotZero(builder
, " memory_gb: ", resources
.getMemoryGb());
355 appendIfNotZero(builder
, " disk_size_gb: ", resources
.getDiskSizeGb());
359 private void appendNetwork(Network network
, StringBuilder builder
) {
360 if (!network
.isEmpty()) {
361 builder
.append("network:\n");
362 appendIfNotNull(builder
, " instance_tag: ", network
.getInstanceTag());
363 if (!network
.getForwardedPorts().isEmpty()) {
364 builder
.append(" forwarded_ports:\n");
365 for (String forwardedPort
: network
.getForwardedPorts()) {
366 builder
.append(" - " + forwardedPort
+ "\n");
369 appendIfNotNull(builder
, " name: ", network
.getName());
374 * Append the given Pagespeed node as YAML to the given StringBuilder.
375 * @param pagespeed The Pagespeed instance to append as YAML.
376 * @param builder The StringBuilder to append to.
377 * @param indent The number of spaces to indent the pagespeed YAML.
379 public static void appendPagespeed(Pagespeed pagespeed
, StringBuilder builder
, int indent
) {
380 if (pagespeed
!= null && !pagespeed
.isEmpty()) {
381 Map
<String
, List
<String
>> config
= Maps
.newTreeMap();
382 putListInMapIfNotEmpty(config
, "url_blacklist", pagespeed
.getUrlBlacklist());
383 putListInMapIfNotEmpty(config
, "domains_to_rewrite", pagespeed
.getDomainsToRewrite());
384 putListInMapIfNotEmpty(config
, "enabled_rewriters", pagespeed
.getEnabledRewriters());
385 putListInMapIfNotEmpty(config
, "disabled_rewriters", pagespeed
.getDisabledRewriters());
386 appendObjectAsYaml(builder
, config
, indent
);
391 * Adds the list to the given map, using the specified name, if the list is non-null and not
394 private static void putListInMapIfNotEmpty(Map
<String
, List
<String
>> map
, String name
,
395 List
<String
> values
) {
396 if (values
!= null && !values
.isEmpty()) {
397 map
.put(name
, values
);
402 * Appends the given collection to the StringBuilder as YAML, indenting each emitted line by
405 private static void appendObjectAsYaml(
406 StringBuilder builder
, Object collection
, int numIndentSpaces
) {
407 StringBuilder prefixBuilder
= new StringBuilder();
408 for (int i
= 0; i
< numIndentSpaces
; ++i
) {
409 prefixBuilder
.append(' ');
411 final String indentPrefix
= prefixBuilder
.toString();
413 StringWriter stringWriter
= new StringWriter();
414 YamlConfig yamlConfig
= new YamlConfig();
415 yamlConfig
.writeConfig
.setIndentSize(2);
416 yamlConfig
.writeConfig
.setWriteRootTags(false);
418 YamlWriter writer
= new YamlWriter(stringWriter
, yamlConfig
);
420 writer
.write(collection
);
422 } catch (YamlException e
) {
423 throw new AppEngineConfigException("Unable to generate YAML.", e
);
426 for (String line
: stringWriter
.toString().split("\n")) {
427 builder
.append(indentPrefix
);
428 builder
.append(line
);
429 builder
.append("\n");
434 * Surrounds the provided string with single quotes, escaping any single
435 * quotes in the string by replacing them with ''.
437 private String
yamlQuote(String str
) {
438 return "'" + str
.replace("'", "''") + "'";
441 private void translateApiVersion(StringBuilder builder
) {
442 if (apiVersion
== null) {
443 builder
.append("api_version: '" + NO_API_VERSION
+ "'\n");
445 builder
.append("api_version: '" + apiVersion
+ "'\n");
449 private void translateWebXml(StringBuilder builder
) {
450 builder
.append("handlers:\n");
452 AbstractHandlerGenerator staticGenerator
= null;
453 if (staticFiles
.isEmpty()) {
454 staticGenerator
= new EmptyHandlerGenerator();
456 staticGenerator
= new StaticHandlerGenerator(appEngineWebXml
.getPublicRoot());
459 DynamicHandlerGenerator dynamicGenerator
=
460 new DynamicHandlerGenerator(webXml
.getFallThroughToRuntime());
461 if (staticGenerator
.size() + dynamicGenerator
.size() > MAX_HANDLERS
) {
462 dynamicGenerator
= new DynamicHandlerGenerator(true);
465 staticGenerator
.translate(builder
);
466 dynamicGenerator
.translate(builder
);
469 class StaticHandlerGenerator
extends AbstractHandlerGenerator
{
470 private final String root
;
472 public StaticHandlerGenerator(String root
) {
477 protected Map
<String
, Object
> getWelcomeProperties() {
478 List
<String
> staticWelcomeFiles
= new ArrayList
<String
>();
479 for (String welcomeFile
: webXml
.getWelcomeFiles()) {
480 for (String staticFile
: staticFiles
) {
481 if (staticFile
.endsWith("/" + welcomeFile
)) {
482 staticWelcomeFiles
.add(welcomeFile
);
487 return Collections
.<String
,Object
>singletonMap(WELCOME_FILES
, staticWelcomeFiles
);
491 protected void addPatterns(GlobIntersector intersector
) {
492 List
<AppEngineWebXml
.StaticFileInclude
> includes
= appEngineWebXml
.getStaticFileIncludes();
493 if (includes
.isEmpty()) {
494 intersector
.addGlob(GlobFactory
.createGlob("/*", STATIC_PROPERTY
, true));
496 for (AppEngineWebXml
.StaticFileInclude include
: includes
) {
497 String pattern
= include
.getPattern().replaceAll("\\*\\*", "*");
498 if (!pattern
.startsWith("/")) {
499 pattern
= "/" + pattern
;
501 Map
<String
, Object
> props
= new HashMap
<String
, Object
>();
502 props
.put(STATIC_PROPERTY
, true);
503 if (include
.getExpiration() != null) {
504 props
.put(EXPIRATION_PROPERTY
, include
.getExpiration());
506 if (include
.getHttpHeaders() != null) {
507 props
.put(HTTP_HEADERS_PROPERTY
, include
.getHttpHeaders());
510 intersector
.addGlob(GlobFactory
.createGlob(pattern
, props
));
516 public void translateGlob(StringBuilder builder
, Glob glob
) {
517 String regex
= glob
.getRegularExpression().pattern();
518 if (!root
.isEmpty()) {
519 if (regex
.startsWith(root
)){
520 regex
= regex
.substring(root
.length(), regex
.length());
523 @SuppressWarnings("unchecked")
524 List
<String
> welcomeFiles
=
525 (List
<String
>) glob
.getProperty(WELCOME_FILES
, RESOLVER
);
526 if (welcomeFiles
!= null) {
527 for (String welcomeFile
: welcomeFiles
) {
528 builder
.append("- url: (" + regex
+ ")\n");
529 builder
.append(" static_files: __static__" + root
+ "\\1" + welcomeFile
+ "\n");
530 builder
.append(" upload: __NOT_USED__\n");
531 builder
.append(" require_matching_file: True\n");
532 translateHandlerOptions(builder
, glob
);
533 translateAdditionalStaticOptions(builder
, glob
);
536 Boolean isStatic
= (Boolean
) glob
.getProperty(STATIC_PROPERTY
, RESOLVER
);
537 if (isStatic
!= null && isStatic
.booleanValue()) {
538 builder
.append("- url: (" + regex
+ ")\n");
539 builder
.append(" static_files: __static__" + root
+ "\\1\n");
540 builder
.append(" upload: __NOT_USED__\n");
541 builder
.append(" require_matching_file: True\n");
542 translateHandlerOptions(builder
, glob
);
543 translateAdditionalStaticOptions(builder
, glob
);
548 private void translateAdditionalStaticOptions(StringBuilder builder
, Glob glob
)
549 throws AppEngineConfigException
{
550 String expiration
= (String
) glob
.getProperty(EXPIRATION_PROPERTY
, RESOLVER
);
551 if (expiration
!= null) {
552 builder
.append(" expiration: " + expiration
+ "\n");
555 @SuppressWarnings("unchecked")
556 Map
<String
, String
> httpHeaders
=
557 (Map
<String
, String
>) glob
.getProperty(HTTP_HEADERS_PROPERTY
, RESOLVER
);
558 if (httpHeaders
!= null && !httpHeaders
.isEmpty()) {
559 builder
.append(" http_headers:\n");
560 appendObjectAsYaml(builder
, httpHeaders
, 4);
566 * According to the example In section 12.2.2 of Servlet Spec 3.0 , /baz/* should also match /baz,
567 * so add an additional glob for that.
569 private static void extendMeaningOfTrailingStar(
570 GlobIntersector intersector
, String pattern
, String property
, Object value
) {
571 if (pattern
.endsWith("/*") && pattern
.length() > 2) {
573 GlobFactory
.createGlob(pattern
.substring(0, pattern
.length() - 2), property
, value
));
577 class DynamicHandlerGenerator
extends AbstractHandlerGenerator
{
578 private final List
<String
> patterns
;
579 private boolean fallthrough
;
580 private boolean hasJsps
;
582 DynamicHandlerGenerator(boolean alwaysFallthrough
) {
583 fallthrough
= alwaysFallthrough
;
584 patterns
= new ArrayList
<String
>();
585 for (String servletPattern
: webXml
.getServletPatterns()) {
586 if (servletPattern
.equals("/") || servletPattern
.equals("/*")) {
588 } else if (servletPattern
.equals(API_ENDPOINT_REGEX
)) {
589 hasApiEndpoint
= true;
590 } else if (servletPattern
.endsWith(".jsp")) {
593 patterns
.add(servletPattern
);
599 protected Map
<String
, Object
> getWelcomeProperties() {
603 return Collections
.<String
,Object
>singletonMap(DYNAMIC_PROPERTY
, true);
608 protected void addPatterns(GlobIntersector intersector
) {
610 intersector
.addGlob(GlobFactory
.createGlob(
612 DYNAMIC_PROPERTY
, true));
614 for (String servletPattern
: patterns
) {
615 intersector
.addGlob(GlobFactory
.createGlob(
617 DYNAMIC_PROPERTY
, true));
618 extendMeaningOfTrailingStar(intersector
, servletPattern
, DYNAMIC_PROPERTY
, true);
621 intersector
.addGlob(GlobFactory
.createGlob(
623 DYNAMIC_PROPERTY
, true));
624 } else if (appEngineWebXml
.getUseVm() || appEngineWebXml
.getEnv().equals("2")) {
625 intersector
.addGlob(GlobFactory
.createGlob("*.jsp", DYNAMIC_PROPERTY
, true));
627 intersector
.addGlob(GlobFactory
.createGlob(
629 DYNAMIC_PROPERTY
, true));
634 public void translateGlob(StringBuilder builder
, Glob glob
) {
635 String regex
= glob
.getRegularExpression().pattern();
637 Boolean isDynamic
= (Boolean
) glob
.getProperty(DYNAMIC_PROPERTY
, RESOLVER
);
638 if (isDynamic
!= null && isDynamic
.booleanValue()) {
639 builder
.append("- url: " + regex
+ "\n");
640 builder
.append(" script: unused\n");
641 translateHandlerOptions(builder
, glob
);
647 * An {@code AbstractHandlerGenerator} that returns no globs.
649 class EmptyHandlerGenerator
extends AbstractHandlerGenerator
{
651 protected void addPatterns(GlobIntersector intersector
) {
655 protected void translateGlob(StringBuilder builder
, Glob glob
) {
659 protected Map
<String
, Object
> getWelcomeProperties() {
660 return Collections
.emptyMap();
664 abstract class AbstractHandlerGenerator
{
665 private List
<Glob
> globs
= null;
666 protected boolean hasApiEndpoint
;
669 return getGlobPatterns().size();
672 public void translate(StringBuilder builder
) {
673 for (Glob glob
: getGlobPatterns()) {
674 translateGlob(builder
, glob
);
678 abstract protected void addPatterns(GlobIntersector intersector
);
679 abstract protected void translateGlob(StringBuilder builder
, Glob glob
);
682 * @returns a map of welcome properties to apply to the welcome
683 * file entries, or {@code null} if no welcome file entries are
686 abstract protected Map
<String
, Object
> getWelcomeProperties();
688 protected List
<Glob
> getGlobPatterns() {
690 GlobIntersector intersector
= new GlobIntersector();
691 addPatterns(intersector
);
692 addSecurityConstraints(intersector
);
693 addWelcomeFiles(intersector
);
695 globs
= intersector
.getIntersection();
696 removeNearDuplicates(globs
);
697 if (hasApiEndpoint
) {
698 globs
.add(GlobFactory
.createGlob(API_ENDPOINT_REGEX
, DYNAMIC_PROPERTY
, true));
704 protected void addWelcomeFiles(GlobIntersector intersector
) {
705 Map
<String
, Object
> welcomeProperties
= getWelcomeProperties();
706 if (welcomeProperties
!= null) {
707 intersector
.addGlob(GlobFactory
.createGlob("/", welcomeProperties
));
708 intersector
.addGlob(GlobFactory
.createGlob("/*/", welcomeProperties
));
712 protected void addSecurityConstraints(GlobIntersector intersector
) {
713 for (SecurityConstraint constraint
: webXml
.getSecurityConstraints()) {
714 for (String pattern
: constraint
.getUrlPatterns()) {
715 intersector
.addGlob(GlobFactory
.createGlob(
717 TRANSPORT_GUARANTEE_PROPERTY
,
718 constraint
.getTransportGuarantee()));
719 extendMeaningOfTrailingStar(intersector
, pattern
, TRANSPORT_GUARANTEE_PROPERTY
,
720 constraint
.getTransportGuarantee());
721 intersector
.addGlob(GlobFactory
.createGlob(
723 REQUIRED_ROLE_PROPERTY
,
724 constraint
.getRequiredRole()));
725 extendMeaningOfTrailingStar(
726 intersector
, pattern
, REQUIRED_ROLE_PROPERTY
, constraint
.getRequiredRole());
731 protected void translateHandlerOptions(StringBuilder builder
, Glob glob
) {
732 SecurityConstraint
.RequiredRole requiredRole
=
733 (SecurityConstraint
.RequiredRole
) glob
.getProperty(REQUIRED_ROLE_PROPERTY
, RESOLVER
);
734 if (requiredRole
== null) {
735 requiredRole
= SecurityConstraint
.RequiredRole
.NONE
;
737 switch (requiredRole
) {
739 builder
.append(" login: optional\n");
742 builder
.append(" login: required\n");
745 builder
.append(" login: admin\n");
749 SecurityConstraint
.TransportGuarantee transportGuarantee
=
750 (SecurityConstraint
.TransportGuarantee
) glob
.getProperty(TRANSPORT_GUARANTEE_PROPERTY
,
752 if (transportGuarantee
== null) {
753 transportGuarantee
= SecurityConstraint
.TransportGuarantee
.NONE
;
755 switch (transportGuarantee
) {
757 if (appEngineWebXml
.getSslEnabled()) {
758 builder
.append(" secure: optional\n");
760 builder
.append(" secure: never\n");
765 if (!appEngineWebXml
.getSslEnabled()) {
766 throw new AppEngineConfigException(
767 "SSL must be enabled in appengine-web.xml to use transport-guarantee");
769 builder
.append(" secure: always\n");
773 String pattern
= glob
.getRegularExpression().pattern();
774 String id
= webXml
.getHandlerIdForPattern(pattern
);
776 if (appEngineWebXml
.isApiEndpoint(id
)) {
777 builder
.append(" api_endpoint: True\n");
782 private void removeNearDuplicates(List
<Glob
> globs
) {
783 for (int i
= 0; i
< globs
.size(); i
++) {
784 Glob topGlob
= globs
.get(i
);
785 for (int j
= i
+ 1; j
< globs
.size(); j
++) {
786 Glob bottomGlob
= globs
.get(j
);
787 if (bottomGlob
.matchesAll(topGlob
)) {
788 if (propertiesMatch(topGlob
, bottomGlob
)) {
798 private boolean propertiesMatch(Glob glob1
, Glob glob2
) {
799 for (String property
: PROPERTIES
) {
800 Object value1
= glob1
.getProperty(property
, RESOLVER
);
801 Object value2
= glob2
.getProperty(property
, RESOLVER
);
802 if (value1
!= value2
&& (value1
== null || !value1
.equals(value2
))) {