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
.getService() != null) {
141 builder
.append("module: '" + appEngineWebXml
.getService() + "'\n");
142 } else if (appEngineWebXml
.getModule() != null) {
143 builder
.append("module: '" + appEngineWebXml
.getModule() + "'\n");
146 if (appEngineWebXml
.getInstanceClass() != null) {
147 builder
.append("instance_class: " + appEngineWebXml
.getInstanceClass() + "\n");
150 if (!appEngineWebXml
.getAutomaticScaling().isEmpty()) {
151 builder
.append("automatic_scaling:\n");
152 AppEngineWebXml
.AutomaticScaling settings
= appEngineWebXml
.getAutomaticScaling();
153 appendIfNotNull(builder
, " min_pending_latency: ", settings
.getMinPendingLatency());
154 appendIfNotNull(builder
, " max_pending_latency: ", settings
.getMaxPendingLatency());
155 appendIfNotNull(builder
, " min_idle_instances: ", settings
.getMinIdleInstances());
156 appendIfNotNull(builder
, " max_idle_instances: ", settings
.getMaxIdleInstances());
157 appendIfNotNull(builder
, " max_concurrent_requests: ", settings
.getMaxConcurrentRequests());
158 appendIfNotNull(builder
, " min_num_instances: ", settings
.getMinNumInstances());
159 appendIfNotNull(builder
, " max_num_instances: ", settings
.getMaxNumInstances());
160 appendIfNotNull(builder
, " cool_down_period_sec: ", settings
.getCoolDownPeriodSec());
162 CpuUtilization cpuUtil
= settings
.getCpuUtilization();
164 && (cpuUtil
.getTargetUtilization() != null
165 || cpuUtil
.getAggregationWindowLengthSec() != null)) {
166 builder
.append(" cpu_utilization:\n");
167 appendIfNotNull(builder
, " target_utilization: ", cpuUtil
.getTargetUtilization());
169 builder
, " aggregation_window_length_sec: ",
170 cpuUtil
.getAggregationWindowLengthSec());
174 builder
, " target_network_sent_bytes_per_sec: ",
175 settings
.getTargetNetworkSentBytesPerSec());
177 builder
, " target_network_sent_packets_per_sec: ",
178 settings
.getTargetNetworkSentPacketsPerSec());
180 builder
, " target_network_received_bytes_per_sec: ",
181 settings
.getTargetNetworkReceivedBytesPerSec());
183 builder
, " target_network_received_packets_per_sec: ",
184 settings
.getTargetNetworkReceivedPacketsPerSec());
186 builder
, " target_disk_write_bytes_per_sec: ",
187 settings
.getTargetDiskWriteBytesPerSec());
189 builder
, " target_disk_write_ops_per_sec: ",
190 settings
.getTargetDiskWriteOpsPerSec());
192 builder
, " target_disk_read_bytes_per_sec: ",
193 settings
.getTargetDiskReadBytesPerSec());
195 builder
, " target_disk_read_ops_per_sec: ",
196 settings
.getTargetDiskReadOpsPerSec());
198 builder
, " target_request_count_per_sec: ",
199 settings
.getTargetRequestCountPerSec());
201 builder
, " target_concurrent_requests: ",
202 settings
.getTargetConcurrentRequests());
205 if (!appEngineWebXml
.getManualScaling().isEmpty()) {
206 builder
.append("manual_scaling:\n");
207 AppEngineWebXml
.ManualScaling settings
= appEngineWebXml
.getManualScaling();
208 builder
.append(" instances: " + settings
.getInstances() + "\n");
211 if (!appEngineWebXml
.getBasicScaling().isEmpty()) {
212 builder
.append("basic_scaling:\n");
213 AppEngineWebXml
.BasicScaling settings
= appEngineWebXml
.getBasicScaling();
214 builder
.append(" max_instances: " + settings
.getMaxInstances() + "\n");
215 appendIfNotNull(builder
, " idle_timeout: ", settings
.getIdleTimeout());
218 Collection
<String
> services
= appEngineWebXml
.getInboundServices();
219 if (!services
.isEmpty()) {
220 builder
.append("inbound_services:\n");
221 for (String service
: services
) {
222 builder
.append("- " + service
+ "\n");
226 if (appEngineWebXml
.getPrecompilationEnabled()) {
227 builder
.append("derived_file_type:\n");
228 builder
.append("- java_precompiled\n");
231 if (appEngineWebXml
.getThreadsafe()) {
232 builder
.append("threadsafe: True\n");
235 if (appEngineWebXml
.getAutoIdPolicy() != null) {
236 builder
.append("auto_id_policy: " + appEngineWebXml
.getAutoIdPolicy() + "\n");
238 builder
.append("auto_id_policy: default\n");
241 if (appEngineWebXml
.getCodeLock()) {
242 builder
.append("code_lock: True\n");
245 List
<AdminConsolePage
> adminConsolePages
= appEngineWebXml
.getAdminConsolePages();
246 if (!adminConsolePages
.isEmpty()) {
247 builder
.append("admin_console:\n");
248 builder
.append(" pages:\n");
249 for (AdminConsolePage page
: adminConsolePages
) {
250 builder
.append(" - name: " + page
.getName() + "\n");
251 builder
.append(" url: " + page
.getUrl() + "\n");
255 List
<ErrorHandler
> errorHandlers
= appEngineWebXml
.getErrorHandlers();
256 if (!errorHandlers
.isEmpty()) {
257 builder
.append("error_handlers:\n");
258 for (ErrorHandler handler
: errorHandlers
) {
259 String fileName
= handler
.getFile();
260 if (!fileName
.startsWith("/")) {
261 fileName
= "/" + fileName
;
263 if (!staticFiles
.contains("__static__" + fileName
)) {
264 throw new AppEngineConfigException("No static file found for error handler: "
265 + fileName
+ ", out of " + staticFiles
);
267 builder
.append("- file: __static__" + fileName
+ "\n");
268 if (handler
.getErrorCode() != null) {
269 builder
.append(" error_code: " + handler
.getErrorCode() + "\n");
271 String mimeType
= webXml
.getMimeTypeForPath(handler
.getFile());
272 if (mimeType
!= null) {
273 builder
.append(" mime_type: " + mimeType
+ "\n");
278 if (backendsXml
!= null) {
279 builder
.append(backendsXml
.toYaml());
282 ApiConfig apiConfig
= appEngineWebXml
.getApiConfig();
283 if (apiConfig
!= null) {
284 builder
.append("api_config:\n");
285 builder
.append(" url: " + apiConfig
.getUrl() + "\n");
286 builder
.append(" script: unused\n");
289 if (appEngineWebXml
.getPagespeed() != null) {
290 builder
.append("pagespeed:\n");
291 appendPagespeed(appEngineWebXml
.getPagespeed(), builder
, 2);
294 if (appEngineWebXml
.getUseVm() || appEngineWebXml
.getEnv().equals("2")) {
295 appendEnvVariables(appEngineWebXml
.getEnvironmentVariables(), builder
);
296 appendBetaSettings(appEngineWebXml
.getBetaSettings(), builder
);
297 appendHealthCheck(appEngineWebXml
.getHealthCheck(), builder
);
298 appendResources(appEngineWebXml
.getResources(), builder
);
299 appendNetwork(appEngineWebXml
.getNetwork(), builder
);
304 * Appends the given environment variables as YAML to the given StringBuilder.
306 * @param envVariables The env variables map to append as YAML.
307 * @param builder The StringBuilder to append to.
309 private void appendEnvVariables(Map
<String
, String
> envVariables
, StringBuilder builder
) {
310 if (envVariables
.size() > 0) {
311 builder
.append("env_variables:\n");
312 for (Map
.Entry
<String
, String
> envVariable
: envVariables
.entrySet()) {
313 String k
= envVariable
.getKey();
314 String v
= envVariable
.getValue();
315 builder
.append(" ").append(yamlQuote(k
)).append(": ").append(yamlQuote(v
)).append("\n");
321 * Appends the given Beta Settings as YAML to the given StringBuilder.
323 * @param betaSettings The beta settings map to append as YAML.
324 * @param builder The StringBuilder to append to.
326 private void appendBetaSettings(Map
<String
, String
> betaSettings
, StringBuilder builder
) {
327 if (betaSettings
!= null && !betaSettings
.isEmpty()) {
328 builder
.append("beta_settings:\n");
329 for (Map
.Entry
<String
, String
> setting
: betaSettings
.entrySet()) {
331 " " + yamlQuote(setting
.getKey()) + ": " + yamlQuote(setting
.getValue()) + "\n");
336 private void appendHealthCheck(HealthCheck healthCheck
, StringBuilder builder
) {
337 builder
.append("health_check:\n");
338 if (healthCheck
.getEnableHealthCheck()) {
339 builder
.append(" enable_health_check: True\n");
341 builder
.append(" enable_health_check: False\n");
344 appendIfNotNull(builder
, " check_interval_sec: ", healthCheck
.getCheckIntervalSec());
345 appendIfNotNull(builder
, " timeout_sec: ", healthCheck
.getTimeoutSec());
346 appendIfNotNull(builder
, " unhealthy_threshold: ", healthCheck
.getUnhealthyThreshold());
347 appendIfNotNull(builder
, " healthy_threshold: ", healthCheck
.getHealthyThreshold());
348 appendIfNotNull(builder
, " restart_threshold: ", healthCheck
.getRestartThreshold());
349 appendIfNotNull(builder
, " host: ", healthCheck
.getHost());
352 private void appendResources(Resources resources
, StringBuilder builder
) {
353 if (!resources
.isEmpty()) {
354 builder
.append("resources:\n");
355 appendIfNotZero(builder
, " cpu: ", resources
.getCpu());
356 appendIfNotZero(builder
, " memory_gb: ", resources
.getMemoryGb());
357 appendIfNotZero(builder
, " disk_size_gb: ", resources
.getDiskSizeGb());
361 private void appendNetwork(Network network
, StringBuilder builder
) {
362 if (!network
.isEmpty()) {
363 builder
.append("network:\n");
364 appendIfNotNull(builder
, " instance_tag: ", network
.getInstanceTag());
365 if (!network
.getForwardedPorts().isEmpty()) {
366 builder
.append(" forwarded_ports:\n");
367 for (String forwardedPort
: network
.getForwardedPorts()) {
368 builder
.append(" - " + forwardedPort
+ "\n");
371 appendIfNotNull(builder
, " name: ", network
.getName());
376 * Append the given Pagespeed node as YAML to the given StringBuilder.
377 * @param pagespeed The Pagespeed instance to append as YAML.
378 * @param builder The StringBuilder to append to.
379 * @param indent The number of spaces to indent the pagespeed YAML.
381 public static void appendPagespeed(Pagespeed pagespeed
, StringBuilder builder
, int indent
) {
382 if (pagespeed
!= null && !pagespeed
.isEmpty()) {
383 Map
<String
, List
<String
>> config
= Maps
.newTreeMap();
384 putListInMapIfNotEmpty(config
, "url_blacklist", pagespeed
.getUrlBlacklist());
385 putListInMapIfNotEmpty(config
, "domains_to_rewrite", pagespeed
.getDomainsToRewrite());
386 putListInMapIfNotEmpty(config
, "enabled_rewriters", pagespeed
.getEnabledRewriters());
387 putListInMapIfNotEmpty(config
, "disabled_rewriters", pagespeed
.getDisabledRewriters());
388 appendObjectAsYaml(builder
, config
, indent
);
393 * Adds the list to the given map, using the specified name, if the list is non-null and not
396 private static void putListInMapIfNotEmpty(Map
<String
, List
<String
>> map
, String name
,
397 List
<String
> values
) {
398 if (values
!= null && !values
.isEmpty()) {
399 map
.put(name
, values
);
404 * Appends the given collection to the StringBuilder as YAML, indenting each emitted line by
407 private static void appendObjectAsYaml(
408 StringBuilder builder
, Object collection
, int numIndentSpaces
) {
409 StringBuilder prefixBuilder
= new StringBuilder();
410 for (int i
= 0; i
< numIndentSpaces
; ++i
) {
411 prefixBuilder
.append(' ');
413 final String indentPrefix
= prefixBuilder
.toString();
415 StringWriter stringWriter
= new StringWriter();
416 YamlConfig yamlConfig
= new YamlConfig();
417 yamlConfig
.writeConfig
.setIndentSize(2);
418 yamlConfig
.writeConfig
.setWriteRootTags(false);
420 YamlWriter writer
= new YamlWriter(stringWriter
, yamlConfig
);
422 writer
.write(collection
);
424 } catch (YamlException e
) {
425 throw new AppEngineConfigException("Unable to generate YAML.", e
);
428 for (String line
: stringWriter
.toString().split("\n")) {
429 builder
.append(indentPrefix
);
430 builder
.append(line
);
431 builder
.append("\n");
436 * Surrounds the provided string with single quotes, escaping any single
437 * quotes in the string by replacing them with ''.
439 private String
yamlQuote(String str
) {
440 return "'" + str
.replace("'", "''") + "'";
443 private void translateApiVersion(StringBuilder builder
) {
444 if (apiVersion
== null) {
445 builder
.append("api_version: '" + NO_API_VERSION
+ "'\n");
447 builder
.append("api_version: '" + apiVersion
+ "'\n");
451 private void translateWebXml(StringBuilder builder
) {
452 builder
.append("handlers:\n");
454 AbstractHandlerGenerator staticGenerator
= null;
455 if (staticFiles
.isEmpty()) {
456 staticGenerator
= new EmptyHandlerGenerator();
458 staticGenerator
= new StaticHandlerGenerator(appEngineWebXml
.getPublicRoot());
461 DynamicHandlerGenerator dynamicGenerator
=
462 new DynamicHandlerGenerator(webXml
.getFallThroughToRuntime());
463 if (staticGenerator
.size() + dynamicGenerator
.size() > MAX_HANDLERS
) {
464 dynamicGenerator
= new DynamicHandlerGenerator(true);
467 staticGenerator
.translate(builder
);
468 dynamicGenerator
.translate(builder
);
471 class StaticHandlerGenerator
extends AbstractHandlerGenerator
{
472 private final String root
;
474 public StaticHandlerGenerator(String root
) {
479 protected Map
<String
, Object
> getWelcomeProperties() {
480 List
<String
> staticWelcomeFiles
= new ArrayList
<String
>();
481 for (String welcomeFile
: webXml
.getWelcomeFiles()) {
482 for (String staticFile
: staticFiles
) {
483 if (staticFile
.endsWith("/" + welcomeFile
)) {
484 staticWelcomeFiles
.add(welcomeFile
);
489 return Collections
.<String
,Object
>singletonMap(WELCOME_FILES
, staticWelcomeFiles
);
493 protected void addPatterns(GlobIntersector intersector
) {
494 List
<AppEngineWebXml
.StaticFileInclude
> includes
= appEngineWebXml
.getStaticFileIncludes();
495 if (includes
.isEmpty()) {
496 intersector
.addGlob(GlobFactory
.createGlob("/*", STATIC_PROPERTY
, true));
498 for (AppEngineWebXml
.StaticFileInclude include
: includes
) {
499 String pattern
= include
.getPattern().replaceAll("\\*\\*", "*");
500 if (!pattern
.startsWith("/")) {
501 pattern
= "/" + pattern
;
503 Map
<String
, Object
> props
= new HashMap
<String
, Object
>();
504 props
.put(STATIC_PROPERTY
, true);
505 if (include
.getExpiration() != null) {
506 props
.put(EXPIRATION_PROPERTY
, include
.getExpiration());
508 if (include
.getHttpHeaders() != null) {
509 props
.put(HTTP_HEADERS_PROPERTY
, include
.getHttpHeaders());
512 intersector
.addGlob(GlobFactory
.createGlob(pattern
, props
));
518 public void translateGlob(StringBuilder builder
, Glob glob
) {
519 String regex
= glob
.getRegularExpression().pattern();
520 if (!root
.isEmpty()) {
521 if (regex
.startsWith(root
)){
522 regex
= regex
.substring(root
.length(), regex
.length());
525 @SuppressWarnings("unchecked")
526 List
<String
> welcomeFiles
=
527 (List
<String
>) glob
.getProperty(WELCOME_FILES
, RESOLVER
);
528 if (welcomeFiles
!= null) {
529 for (String welcomeFile
: welcomeFiles
) {
530 builder
.append("- url: (" + regex
+ ")\n");
531 builder
.append(" static_files: __static__" + root
+ "\\1" + welcomeFile
+ "\n");
532 builder
.append(" upload: __NOT_USED__\n");
533 builder
.append(" require_matching_file: True\n");
534 translateHandlerOptions(builder
, glob
);
535 translateAdditionalStaticOptions(builder
, glob
);
538 Boolean isStatic
= (Boolean
) glob
.getProperty(STATIC_PROPERTY
, RESOLVER
);
539 if (isStatic
!= null && isStatic
.booleanValue()) {
540 builder
.append("- url: (" + regex
+ ")\n");
541 builder
.append(" static_files: __static__" + root
+ "\\1\n");
542 builder
.append(" upload: __NOT_USED__\n");
543 builder
.append(" require_matching_file: True\n");
544 translateHandlerOptions(builder
, glob
);
545 translateAdditionalStaticOptions(builder
, glob
);
550 private void translateAdditionalStaticOptions(StringBuilder builder
, Glob glob
)
551 throws AppEngineConfigException
{
552 String expiration
= (String
) glob
.getProperty(EXPIRATION_PROPERTY
, RESOLVER
);
553 if (expiration
!= null) {
554 builder
.append(" expiration: " + expiration
+ "\n");
557 @SuppressWarnings("unchecked")
558 Map
<String
, String
> httpHeaders
=
559 (Map
<String
, String
>) glob
.getProperty(HTTP_HEADERS_PROPERTY
, RESOLVER
);
560 if (httpHeaders
!= null && !httpHeaders
.isEmpty()) {
561 builder
.append(" http_headers:\n");
562 appendObjectAsYaml(builder
, httpHeaders
, 4);
568 * According to the example In section 12.2.2 of Servlet Spec 3.0 , /baz/* should also match /baz,
569 * so add an additional glob for that.
571 private static void extendMeaningOfTrailingStar(
572 GlobIntersector intersector
, String pattern
, String property
, Object value
) {
573 if (pattern
.endsWith("/*") && pattern
.length() > 2) {
575 GlobFactory
.createGlob(pattern
.substring(0, pattern
.length() - 2), property
, value
));
579 class DynamicHandlerGenerator
extends AbstractHandlerGenerator
{
580 private final List
<String
> patterns
;
581 private boolean fallthrough
;
582 private boolean hasJsps
;
584 DynamicHandlerGenerator(boolean alwaysFallthrough
) {
585 fallthrough
= alwaysFallthrough
;
586 patterns
= new ArrayList
<String
>();
587 for (String servletPattern
: webXml
.getServletPatterns()) {
588 if (servletPattern
.equals("/") || servletPattern
.equals("/*")) {
590 } else if (servletPattern
.equals(API_ENDPOINT_REGEX
)) {
591 hasApiEndpoint
= true;
592 } else if (servletPattern
.endsWith(".jsp")) {
595 patterns
.add(servletPattern
);
601 protected Map
<String
, Object
> getWelcomeProperties() {
605 return Collections
.<String
,Object
>singletonMap(DYNAMIC_PROPERTY
, true);
610 protected void addPatterns(GlobIntersector intersector
) {
612 intersector
.addGlob(GlobFactory
.createGlob(
614 DYNAMIC_PROPERTY
, true));
616 for (String servletPattern
: patterns
) {
617 intersector
.addGlob(GlobFactory
.createGlob(
619 DYNAMIC_PROPERTY
, true));
620 extendMeaningOfTrailingStar(intersector
, servletPattern
, DYNAMIC_PROPERTY
, true);
623 intersector
.addGlob(GlobFactory
.createGlob(
625 DYNAMIC_PROPERTY
, true));
626 } else if (appEngineWebXml
.getUseVm() || appEngineWebXml
.getEnv().equals("2")) {
627 intersector
.addGlob(GlobFactory
.createGlob("*.jsp", DYNAMIC_PROPERTY
, true));
629 intersector
.addGlob(GlobFactory
.createGlob(
631 DYNAMIC_PROPERTY
, true));
636 public void translateGlob(StringBuilder builder
, Glob glob
) {
637 String regex
= glob
.getRegularExpression().pattern();
639 Boolean isDynamic
= (Boolean
) glob
.getProperty(DYNAMIC_PROPERTY
, RESOLVER
);
640 if (isDynamic
!= null && isDynamic
.booleanValue()) {
641 builder
.append("- url: " + regex
+ "\n");
642 builder
.append(" script: unused\n");
643 translateHandlerOptions(builder
, glob
);
649 * An {@code AbstractHandlerGenerator} that returns no globs.
651 class EmptyHandlerGenerator
extends AbstractHandlerGenerator
{
653 protected void addPatterns(GlobIntersector intersector
) {
657 protected void translateGlob(StringBuilder builder
, Glob glob
) {
661 protected Map
<String
, Object
> getWelcomeProperties() {
662 return Collections
.emptyMap();
666 abstract class AbstractHandlerGenerator
{
667 private List
<Glob
> globs
= null;
668 protected boolean hasApiEndpoint
;
671 return getGlobPatterns().size();
674 public void translate(StringBuilder builder
) {
675 for (Glob glob
: getGlobPatterns()) {
676 translateGlob(builder
, glob
);
680 abstract protected void addPatterns(GlobIntersector intersector
);
681 abstract protected void translateGlob(StringBuilder builder
, Glob glob
);
684 * @returns a map of welcome properties to apply to the welcome
685 * file entries, or {@code null} if no welcome file entries are
688 abstract protected Map
<String
, Object
> getWelcomeProperties();
690 protected List
<Glob
> getGlobPatterns() {
692 GlobIntersector intersector
= new GlobIntersector();
693 addPatterns(intersector
);
694 addSecurityConstraints(intersector
);
695 addWelcomeFiles(intersector
);
697 globs
= intersector
.getIntersection();
698 removeNearDuplicates(globs
);
699 if (hasApiEndpoint
) {
700 globs
.add(GlobFactory
.createGlob(API_ENDPOINT_REGEX
, DYNAMIC_PROPERTY
, true));
706 protected void addWelcomeFiles(GlobIntersector intersector
) {
707 Map
<String
, Object
> welcomeProperties
= getWelcomeProperties();
708 if (welcomeProperties
!= null) {
709 intersector
.addGlob(GlobFactory
.createGlob("/", welcomeProperties
));
710 intersector
.addGlob(GlobFactory
.createGlob("/*/", welcomeProperties
));
714 protected void addSecurityConstraints(GlobIntersector intersector
) {
715 for (SecurityConstraint constraint
: webXml
.getSecurityConstraints()) {
716 for (String pattern
: constraint
.getUrlPatterns()) {
717 intersector
.addGlob(GlobFactory
.createGlob(
719 TRANSPORT_GUARANTEE_PROPERTY
,
720 constraint
.getTransportGuarantee()));
721 extendMeaningOfTrailingStar(intersector
, pattern
, TRANSPORT_GUARANTEE_PROPERTY
,
722 constraint
.getTransportGuarantee());
723 intersector
.addGlob(GlobFactory
.createGlob(
725 REQUIRED_ROLE_PROPERTY
,
726 constraint
.getRequiredRole()));
727 extendMeaningOfTrailingStar(
728 intersector
, pattern
, REQUIRED_ROLE_PROPERTY
, constraint
.getRequiredRole());
733 protected void translateHandlerOptions(StringBuilder builder
, Glob glob
) {
734 SecurityConstraint
.RequiredRole requiredRole
=
735 (SecurityConstraint
.RequiredRole
) glob
.getProperty(REQUIRED_ROLE_PROPERTY
, RESOLVER
);
736 if (requiredRole
== null) {
737 requiredRole
= SecurityConstraint
.RequiredRole
.NONE
;
739 switch (requiredRole
) {
741 builder
.append(" login: optional\n");
744 builder
.append(" login: required\n");
747 builder
.append(" login: admin\n");
751 SecurityConstraint
.TransportGuarantee transportGuarantee
=
752 (SecurityConstraint
.TransportGuarantee
) glob
.getProperty(TRANSPORT_GUARANTEE_PROPERTY
,
754 if (transportGuarantee
== null) {
755 transportGuarantee
= SecurityConstraint
.TransportGuarantee
.NONE
;
757 switch (transportGuarantee
) {
759 if (appEngineWebXml
.getSslEnabled()) {
760 builder
.append(" secure: optional\n");
762 builder
.append(" secure: never\n");
767 if (!appEngineWebXml
.getSslEnabled()) {
768 throw new AppEngineConfigException(
769 "SSL must be enabled in appengine-web.xml to use transport-guarantee");
771 builder
.append(" secure: always\n");
775 String pattern
= glob
.getRegularExpression().pattern();
776 String id
= webXml
.getHandlerIdForPattern(pattern
);
778 if (appEngineWebXml
.isApiEndpoint(id
)) {
779 builder
.append(" api_endpoint: True\n");
784 private void removeNearDuplicates(List
<Glob
> globs
) {
785 for (int i
= 0; i
< globs
.size(); i
++) {
786 Glob topGlob
= globs
.get(i
);
787 for (int j
= i
+ 1; j
< globs
.size(); j
++) {
788 Glob bottomGlob
= globs
.get(j
);
789 if (bottomGlob
.matchesAll(topGlob
)) {
790 if (propertiesMatch(topGlob
, bottomGlob
)) {
800 private boolean propertiesMatch(Glob glob1
, Glob glob2
) {
801 for (String property
: PROPERTIES
) {
802 Object value1
= glob1
.getProperty(property
, RESOLVER
);
803 Object value2
= glob2
.getProperty(property
, RESOLVER
);
804 if (value1
!= value2
&& (value1
== null || !value1
.equals(value2
))) {