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
.getSourceLanguage() != null) {
129 builder
.append("source_language: '" + appEngineWebXml
.getSourceLanguage() + "'\n");
132 if (appEngineWebXml
.getMajorVersionId() != null) {
133 builder
.append("version: '" + appEngineWebXml
.getMajorVersionId() + "'\n");
136 if (appEngineWebXml
.getModule() != null) {
137 builder
.append("module: '" + appEngineWebXml
.getModule() + "'\n");
140 if (appEngineWebXml
.getInstanceClass() != null) {
141 builder
.append("instance_class: " + appEngineWebXml
.getInstanceClass() + "\n");
144 if (!appEngineWebXml
.getAutomaticScaling().isEmpty()) {
145 builder
.append("automatic_scaling:\n");
146 AppEngineWebXml
.AutomaticScaling settings
= appEngineWebXml
.getAutomaticScaling();
147 appendIfNotNull(builder
, " min_pending_latency: ", settings
.getMinPendingLatency());
148 appendIfNotNull(builder
, " max_pending_latency: ", settings
.getMaxPendingLatency());
149 appendIfNotNull(builder
, " min_idle_instances: ", settings
.getMinIdleInstances());
150 appendIfNotNull(builder
, " max_idle_instances: ", settings
.getMaxIdleInstances());
151 appendIfNotNull(builder
, " max_concurrent_requests: ", settings
.getMaxConcurrentRequests());
152 appendIfNotNull(builder
, " min_num_instances: ", settings
.getMinNumInstances());
153 appendIfNotNull(builder
, " max_num_instances: ", settings
.getMaxNumInstances());
154 appendIfNotNull(builder
, " cool_down_period_sec: ", settings
.getCoolDownPeriodSec());
155 CpuUtilization cpuUtil
= settings
.getCpuUtilization();
157 && (cpuUtil
.getTargetUtilization() != null
158 || cpuUtil
.getAggregationWindowLengthSec() != null)) {
159 builder
.append(" cpu_utilization:\n");
160 appendIfNotNull(builder
, " target_utilization: ", cpuUtil
.getTargetUtilization());
162 builder
, " aggregation_window_length_sec: ",
163 cpuUtil
.getAggregationWindowLengthSec());
167 if (!appEngineWebXml
.getManualScaling().isEmpty()) {
168 builder
.append("manual_scaling:\n");
169 AppEngineWebXml
.ManualScaling settings
= appEngineWebXml
.getManualScaling();
170 builder
.append(" instances: " + settings
.getInstances() + "\n");
173 if (!appEngineWebXml
.getBasicScaling().isEmpty()) {
174 builder
.append("basic_scaling:\n");
175 AppEngineWebXml
.BasicScaling settings
= appEngineWebXml
.getBasicScaling();
176 builder
.append(" max_instances: " + settings
.getMaxInstances() + "\n");
177 appendIfNotNull(builder
, " idle_timeout: ", settings
.getIdleTimeout());
180 Collection
<String
> services
= appEngineWebXml
.getInboundServices();
181 if (!services
.isEmpty()) {
182 builder
.append("inbound_services:\n");
183 for (String service
: services
) {
184 builder
.append("- " + service
+ "\n");
188 if (appEngineWebXml
.getPrecompilationEnabled()) {
189 builder
.append("derived_file_type:\n");
190 builder
.append("- java_precompiled\n");
193 if (appEngineWebXml
.getThreadsafe()) {
194 builder
.append("threadsafe: True\n");
197 if (appEngineWebXml
.getAutoIdPolicy() != null) {
198 builder
.append("auto_id_policy: " + appEngineWebXml
.getAutoIdPolicy() + "\n");
200 builder
.append("auto_id_policy: default\n");
203 if (appEngineWebXml
.getCodeLock()) {
204 builder
.append("code_lock: True\n");
207 List
<AdminConsolePage
> adminConsolePages
= appEngineWebXml
.getAdminConsolePages();
208 if (!adminConsolePages
.isEmpty()) {
209 builder
.append("admin_console:\n");
210 builder
.append(" pages:\n");
211 for (AdminConsolePage page
: adminConsolePages
) {
212 builder
.append(" - name: " + page
.getName() + "\n");
213 builder
.append(" url: " + page
.getUrl() + "\n");
217 List
<ErrorHandler
> errorHandlers
= appEngineWebXml
.getErrorHandlers();
218 if (!errorHandlers
.isEmpty()) {
219 builder
.append("error_handlers:\n");
220 for (ErrorHandler handler
: errorHandlers
) {
221 String fileName
= handler
.getFile();
222 if (!fileName
.startsWith("/")) {
223 fileName
= "/" + fileName
;
225 if (!staticFiles
.contains("__static__" + fileName
)) {
226 throw new AppEngineConfigException("No static file found for error handler: "
227 + fileName
+ ", out of " + staticFiles
);
229 builder
.append("- file: __static__" + fileName
+ "\n");
230 if (handler
.getErrorCode() != null) {
231 builder
.append(" error_code: " + handler
.getErrorCode() + "\n");
233 String mimeType
= webXml
.getMimeTypeForPath(handler
.getFile());
234 if (mimeType
!= null) {
235 builder
.append(" mime_type: " + mimeType
+ "\n");
240 if (backendsXml
!= null) {
241 builder
.append(backendsXml
.toYaml());
244 ApiConfig apiConfig
= appEngineWebXml
.getApiConfig();
245 if (apiConfig
!= null) {
246 builder
.append("api_config:\n");
247 builder
.append(" url: " + apiConfig
.getUrl() + "\n");
248 builder
.append(" script: unused\n");
251 if (appEngineWebXml
.getPagespeed() != null) {
252 builder
.append("pagespeed:\n");
253 appendPagespeed(appEngineWebXml
.getPagespeed(), builder
, 2);
256 if (appEngineWebXml
.getUseVm()) {
257 appendEnvVariables(appEngineWebXml
.getEnvironmentVariables(), builder
);
258 appendBetaSettings(appEngineWebXml
.getBetaSettings(), builder
);
259 appendHealthCheck(appEngineWebXml
.getHealthCheck(), builder
);
260 appendResources(appEngineWebXml
.getResources(), builder
);
261 appendNetwork(appEngineWebXml
.getNetwork(), builder
);
266 * Appends the given environment variables as YAML to the given StringBuilder.
268 * @param envVariables The env variables map to append as YAML.
269 * @param builder The StringBuilder to append to.
271 private void appendEnvVariables(Map
<String
, String
> envVariables
, StringBuilder builder
) {
272 if (envVariables
.size() > 0) {
273 builder
.append("env_variables:\n");
274 for (Map
.Entry
<String
, String
> envVariable
: envVariables
.entrySet()) {
275 String k
= envVariable
.getKey();
276 String v
= envVariable
.getValue();
277 builder
.append(" ").append(yamlQuote(k
)).append(": ").append(yamlQuote(v
)).append("\n");
283 * Appends the given Beta Settings as YAML to the given StringBuilder.
285 * @param betaSettings The beta settings map to append as YAML.
286 * @param builder The StringBuilder to append to.
288 private void appendBetaSettings(Map
<String
, String
> betaSettings
, StringBuilder builder
) {
289 builder
.append("beta_settings:\n");
290 if (betaSettings
== null || !betaSettings
.containsKey("image")) {
291 if (sdkVersion
!= null && sdkVersion
.getRelease() != null) {
292 builder
.append(" 'image': " + yamlQuote(sdkVersion
.getRelease()) + "\n");
295 if (betaSettings
!= null) {
296 for (Map
.Entry
<String
, String
> setting
: betaSettings
.entrySet()) {
298 " " + yamlQuote(setting
.getKey()) + ": " + yamlQuote(setting
.getValue()) + "\n");
303 private void appendHealthCheck(HealthCheck healthCheck
, StringBuilder builder
) {
304 builder
.append("health_check:\n");
305 if (healthCheck
.getEnableHealthCheck()) {
306 builder
.append(" enable_health_check: True\n");
308 builder
.append(" enable_health_check: False\n");
311 appendIfNotNull(builder
, " check_interval_sec: ", healthCheck
.getCheckIntervalSec());
312 appendIfNotNull(builder
, " timeout_sec: ", healthCheck
.getTimeoutSec());
313 appendIfNotNull(builder
, " unhealthy_threshold: ", healthCheck
.getUnhealthyThreshold());
314 appendIfNotNull(builder
, " healthy_threshold: ", healthCheck
.getHealthyThreshold());
315 appendIfNotNull(builder
, " restart_threshold: ", healthCheck
.getRestartThreshold());
316 appendIfNotNull(builder
, " host: ", healthCheck
.getHost());
319 private void appendResources(Resources resources
, StringBuilder builder
) {
320 if (!resources
.isEmpty()) {
321 builder
.append("resources:\n");
322 appendIfNotZero(builder
, " cpu: ", resources
.getCpu());
323 appendIfNotZero(builder
, " memory_gb: ", resources
.getMemoryGb());
324 appendIfNotZero(builder
, " disk_size_gb: ", resources
.getDiskSizeGb());
328 private void appendNetwork(Network network
, StringBuilder builder
) {
329 if (!network
.isEmpty()) {
330 builder
.append("network:\n");
331 appendIfNotNull(builder
, " instance_tag: ", network
.getInstanceTag());
332 if (!network
.getForwardedPorts().isEmpty()) {
333 builder
.append(" forwarded_ports:\n");
334 for (String forwardedPort
: network
.getForwardedPorts()) {
335 builder
.append(" - " + forwardedPort
+ "\n");
338 appendIfNotNull(builder
, " name: ", network
.getName());
343 * Append the given Pagespeed node as YAML to the given StringBuilder.
344 * @param pagespeed The Pagespeed instance to append as YAML.
345 * @param builder The StringBuilder to append to.
346 * @param indent The number of spaces to indent the pagespeed YAML.
348 public static void appendPagespeed(Pagespeed pagespeed
, StringBuilder builder
, int indent
) {
349 if (pagespeed
!= null && !pagespeed
.isEmpty()) {
350 Map
<String
, List
<String
>> config
= Maps
.newTreeMap();
351 putListInMapIfNotEmpty(config
, "url_blacklist", pagespeed
.getUrlBlacklist());
352 putListInMapIfNotEmpty(config
, "domains_to_rewrite", pagespeed
.getDomainsToRewrite());
353 putListInMapIfNotEmpty(config
, "enabled_rewriters", pagespeed
.getEnabledRewriters());
354 putListInMapIfNotEmpty(config
, "disabled_rewriters", pagespeed
.getDisabledRewriters());
355 appendObjectAsYaml(builder
, config
, indent
);
360 * Adds the list to the given map, using the specified name, if the list is non-null and not
363 private static void putListInMapIfNotEmpty(Map
<String
, List
<String
>> map
, String name
,
364 List
<String
> values
) {
365 if (values
!= null && !values
.isEmpty()) {
366 map
.put(name
, values
);
371 * Appends the given collection to the StringBuilder as YAML, indenting each emitted line by
374 private static void appendObjectAsYaml(
375 StringBuilder builder
, Object collection
, int numIndentSpaces
) {
376 StringBuilder prefixBuilder
= new StringBuilder();
377 for (int i
= 0; i
< numIndentSpaces
; ++i
) {
378 prefixBuilder
.append(' ');
380 final String indentPrefix
= prefixBuilder
.toString();
382 StringWriter stringWriter
= new StringWriter();
383 YamlConfig yamlConfig
= new YamlConfig();
384 yamlConfig
.writeConfig
.setIndentSize(2);
385 yamlConfig
.writeConfig
.setWriteRootTags(false);
387 YamlWriter writer
= new YamlWriter(stringWriter
, yamlConfig
);
389 writer
.write(collection
);
391 } catch (YamlException e
) {
392 throw new AppEngineConfigException("Unable to generate YAML.", e
);
395 for (String line
: stringWriter
.toString().split("\n")) {
396 builder
.append(indentPrefix
);
397 builder
.append(line
);
398 builder
.append("\n");
403 * Surrounds the provided string with single quotes, escaping any single
404 * quotes in the string by replacing them with ''.
406 private String
yamlQuote(String str
) {
407 return "'" + str
.replace("'", "''") + "'";
410 private void translateApiVersion(StringBuilder builder
) {
411 if (apiVersion
== null) {
412 builder
.append("api_version: '" + NO_API_VERSION
+ "'\n");
414 builder
.append("api_version: '" + apiVersion
+ "'\n");
418 private void translateWebXml(StringBuilder builder
) {
419 builder
.append("handlers:\n");
421 AbstractHandlerGenerator staticGenerator
= null;
422 if (staticFiles
.isEmpty()) {
423 staticGenerator
= new EmptyHandlerGenerator();
425 staticGenerator
= new StaticHandlerGenerator(appEngineWebXml
.getPublicRoot());
428 DynamicHandlerGenerator dynamicGenerator
=
429 new DynamicHandlerGenerator(webXml
.getFallThroughToRuntime());
430 if (staticGenerator
.size() + dynamicGenerator
.size() > MAX_HANDLERS
) {
431 dynamicGenerator
= new DynamicHandlerGenerator(true);
434 staticGenerator
.translate(builder
);
435 dynamicGenerator
.translate(builder
);
438 class StaticHandlerGenerator
extends AbstractHandlerGenerator
{
439 private final String root
;
441 public StaticHandlerGenerator(String root
) {
446 protected Map
<String
, Object
> getWelcomeProperties() {
447 List
<String
> staticWelcomeFiles
= new ArrayList
<String
>();
448 for (String welcomeFile
: webXml
.getWelcomeFiles()) {
449 for (String staticFile
: staticFiles
) {
450 if (staticFile
.endsWith("/" + welcomeFile
)) {
451 staticWelcomeFiles
.add(welcomeFile
);
456 return Collections
.<String
,Object
>singletonMap(WELCOME_FILES
, staticWelcomeFiles
);
460 protected void addPatterns(GlobIntersector intersector
) {
461 List
<AppEngineWebXml
.StaticFileInclude
> includes
= appEngineWebXml
.getStaticFileIncludes();
462 if (includes
.isEmpty()) {
463 intersector
.addGlob(GlobFactory
.createGlob("/*", STATIC_PROPERTY
, true));
465 for (AppEngineWebXml
.StaticFileInclude include
: includes
) {
466 String pattern
= include
.getPattern().replaceAll("\\*\\*", "*");
467 if (!pattern
.startsWith("/")) {
468 pattern
= "/" + pattern
;
470 Map
<String
, Object
> props
= new HashMap
<String
, Object
>();
471 props
.put(STATIC_PROPERTY
, true);
472 if (include
.getExpiration() != null) {
473 props
.put(EXPIRATION_PROPERTY
, include
.getExpiration());
475 if (include
.getHttpHeaders() != null) {
476 props
.put(HTTP_HEADERS_PROPERTY
, include
.getHttpHeaders());
479 intersector
.addGlob(GlobFactory
.createGlob(pattern
, props
));
485 public void translateGlob(StringBuilder builder
, Glob glob
) {
486 String regex
= glob
.getRegularExpression().pattern();
487 if (!root
.isEmpty()) {
488 if (regex
.startsWith(root
)){
489 regex
= regex
.substring(root
.length(), regex
.length());
492 @SuppressWarnings("unchecked")
493 List
<String
> welcomeFiles
=
494 (List
<String
>) glob
.getProperty(WELCOME_FILES
, RESOLVER
);
495 if (welcomeFiles
!= null) {
496 for (String welcomeFile
: welcomeFiles
) {
497 builder
.append("- url: (" + regex
+ ")\n");
498 builder
.append(" static_files: __static__" + root
+ "\\1" + welcomeFile
+ "\n");
499 builder
.append(" upload: __NOT_USED__\n");
500 builder
.append(" require_matching_file: True\n");
501 translateHandlerOptions(builder
, glob
);
502 translateAdditionalStaticOptions(builder
, glob
);
505 Boolean isStatic
= (Boolean
) glob
.getProperty(STATIC_PROPERTY
, RESOLVER
);
506 if (isStatic
!= null && isStatic
.booleanValue()) {
507 builder
.append("- url: (" + regex
+ ")\n");
508 builder
.append(" static_files: __static__" + root
+ "\\1\n");
509 builder
.append(" upload: __NOT_USED__\n");
510 builder
.append(" require_matching_file: True\n");
511 translateHandlerOptions(builder
, glob
);
512 translateAdditionalStaticOptions(builder
, glob
);
517 private void translateAdditionalStaticOptions(StringBuilder builder
, Glob glob
)
518 throws AppEngineConfigException
{
519 String expiration
= (String
) glob
.getProperty(EXPIRATION_PROPERTY
, RESOLVER
);
520 if (expiration
!= null) {
521 builder
.append(" expiration: " + expiration
+ "\n");
524 @SuppressWarnings("unchecked")
525 Map
<String
, String
> httpHeaders
=
526 (Map
<String
, String
>) glob
.getProperty(HTTP_HEADERS_PROPERTY
, RESOLVER
);
527 if (httpHeaders
!= null && !httpHeaders
.isEmpty()) {
528 builder
.append(" http_headers:\n");
529 appendObjectAsYaml(builder
, httpHeaders
, 4);
535 * According to the example In section 12.2.2 of Servlet Spec 3.0 , /baz/* should also match /baz,
536 * so add an additional glob for that.
538 private static void extendMeaningOfTrailingStar(
539 GlobIntersector intersector
, String pattern
, String property
, Object value
) {
540 if (pattern
.endsWith("/*") && pattern
.length() > 2) {
542 GlobFactory
.createGlob(pattern
.substring(0, pattern
.length() - 2), property
, value
));
546 class DynamicHandlerGenerator
extends AbstractHandlerGenerator
{
547 private final List
<String
> patterns
;
548 private boolean fallthrough
;
549 private boolean hasJsps
;
551 DynamicHandlerGenerator(boolean alwaysFallthrough
) {
552 fallthrough
= alwaysFallthrough
;
553 patterns
= new ArrayList
<String
>();
554 for (String servletPattern
: webXml
.getServletPatterns()) {
555 if (servletPattern
.equals("/") || servletPattern
.equals("/*")) {
557 } else if (servletPattern
.equals(API_ENDPOINT_REGEX
)) {
558 hasApiEndpoint
= true;
559 } else if (servletPattern
.endsWith(".jsp")) {
562 patterns
.add(servletPattern
);
568 protected Map
<String
, Object
> getWelcomeProperties() {
572 return Collections
.<String
,Object
>singletonMap(DYNAMIC_PROPERTY
, true);
577 protected void addPatterns(GlobIntersector intersector
) {
579 intersector
.addGlob(GlobFactory
.createGlob(
581 DYNAMIC_PROPERTY
, true));
583 for (String servletPattern
: patterns
) {
584 intersector
.addGlob(GlobFactory
.createGlob(
586 DYNAMIC_PROPERTY
, true));
587 extendMeaningOfTrailingStar(intersector
, servletPattern
, DYNAMIC_PROPERTY
, true);
590 intersector
.addGlob(GlobFactory
.createGlob(
592 DYNAMIC_PROPERTY
, true));
593 } else if (appEngineWebXml
.getUseVm()) {
594 intersector
.addGlob(GlobFactory
.createGlob(
596 DYNAMIC_PROPERTY
, true));
598 intersector
.addGlob(GlobFactory
.createGlob(
600 DYNAMIC_PROPERTY
, true));
605 public void translateGlob(StringBuilder builder
, Glob glob
) {
606 String regex
= glob
.getRegularExpression().pattern();
608 Boolean isDynamic
= (Boolean
) glob
.getProperty(DYNAMIC_PROPERTY
, RESOLVER
);
609 if (isDynamic
!= null && isDynamic
.booleanValue()) {
610 builder
.append("- url: " + regex
+ "\n");
611 builder
.append(" script: unused\n");
612 translateHandlerOptions(builder
, glob
);
618 * An {@code AbstractHandlerGenerator} that returns no globs.
620 class EmptyHandlerGenerator
extends AbstractHandlerGenerator
{
622 protected void addPatterns(GlobIntersector intersector
) {
626 protected void translateGlob(StringBuilder builder
, Glob glob
) {
630 protected Map
<String
, Object
> getWelcomeProperties() {
631 return Collections
.emptyMap();
635 abstract class AbstractHandlerGenerator
{
636 private List
<Glob
> globs
= null;
637 protected boolean hasApiEndpoint
;
640 return getGlobPatterns().size();
643 public void translate(StringBuilder builder
) {
644 for (Glob glob
: getGlobPatterns()) {
645 translateGlob(builder
, glob
);
649 abstract protected void addPatterns(GlobIntersector intersector
);
650 abstract protected void translateGlob(StringBuilder builder
, Glob glob
);
653 * @returns a map of welcome properties to apply to the welcome
654 * file entries, or {@code null} if no welcome file entries are
657 abstract protected Map
<String
, Object
> getWelcomeProperties();
659 protected List
<Glob
> getGlobPatterns() {
661 GlobIntersector intersector
= new GlobIntersector();
662 addPatterns(intersector
);
663 addSecurityConstraints(intersector
);
664 addWelcomeFiles(intersector
);
666 globs
= intersector
.getIntersection();
667 removeNearDuplicates(globs
);
668 if (hasApiEndpoint
) {
669 globs
.add(GlobFactory
.createGlob(API_ENDPOINT_REGEX
, DYNAMIC_PROPERTY
, true));
675 protected void addWelcomeFiles(GlobIntersector intersector
) {
676 Map
<String
, Object
> welcomeProperties
= getWelcomeProperties();
677 if (welcomeProperties
!= null) {
678 intersector
.addGlob(GlobFactory
.createGlob("/", welcomeProperties
));
679 intersector
.addGlob(GlobFactory
.createGlob("/*/", welcomeProperties
));
683 protected void addSecurityConstraints(GlobIntersector intersector
) {
684 for (SecurityConstraint constraint
: webXml
.getSecurityConstraints()) {
685 for (String pattern
: constraint
.getUrlPatterns()) {
686 intersector
.addGlob(GlobFactory
.createGlob(
688 TRANSPORT_GUARANTEE_PROPERTY
,
689 constraint
.getTransportGuarantee()));
690 extendMeaningOfTrailingStar(intersector
, pattern
, TRANSPORT_GUARANTEE_PROPERTY
,
691 constraint
.getTransportGuarantee());
692 intersector
.addGlob(GlobFactory
.createGlob(
694 REQUIRED_ROLE_PROPERTY
,
695 constraint
.getRequiredRole()));
696 extendMeaningOfTrailingStar(
697 intersector
, pattern
, REQUIRED_ROLE_PROPERTY
, constraint
.getRequiredRole());
702 protected void translateHandlerOptions(StringBuilder builder
, Glob glob
) {
703 SecurityConstraint
.RequiredRole requiredRole
=
704 (SecurityConstraint
.RequiredRole
) glob
.getProperty(REQUIRED_ROLE_PROPERTY
, RESOLVER
);
705 if (requiredRole
== null) {
706 requiredRole
= SecurityConstraint
.RequiredRole
.NONE
;
708 switch (requiredRole
) {
710 builder
.append(" login: optional\n");
713 builder
.append(" login: required\n");
716 builder
.append(" login: admin\n");
720 SecurityConstraint
.TransportGuarantee transportGuarantee
=
721 (SecurityConstraint
.TransportGuarantee
) glob
.getProperty(TRANSPORT_GUARANTEE_PROPERTY
,
723 if (transportGuarantee
== null) {
724 transportGuarantee
= SecurityConstraint
.TransportGuarantee
.NONE
;
726 switch (transportGuarantee
) {
728 if (appEngineWebXml
.getSslEnabled()) {
729 builder
.append(" secure: optional\n");
731 builder
.append(" secure: never\n");
736 if (!appEngineWebXml
.getSslEnabled()) {
737 throw new AppEngineConfigException(
738 "SSL must be enabled in appengine-web.xml to use transport-guarantee");
740 builder
.append(" secure: always\n");
744 String pattern
= glob
.getRegularExpression().pattern();
745 String id
= webXml
.getHandlerIdForPattern(pattern
);
747 if (appEngineWebXml
.isApiEndpoint(id
)) {
748 builder
.append(" api_endpoint: True\n");
753 private void removeNearDuplicates(List
<Glob
> globs
) {
754 for (int i
= 0; i
< globs
.size(); i
++) {
755 Glob topGlob
= globs
.get(i
);
756 for (int j
= i
+ 1; j
< globs
.size(); j
++) {
757 Glob bottomGlob
= globs
.get(j
);
758 if (bottomGlob
.matchesAll(topGlob
)) {
759 if (propertiesMatch(topGlob
, bottomGlob
)) {
769 private boolean propertiesMatch(Glob glob1
, Glob glob2
) {
770 for (String property
: PROPERTIES
) {
771 Object value1
= glob1
.getProperty(property
, RESOLVER
);
772 Object value2
= glob2
.getProperty(property
, RESOLVER
);
773 if (value1
!= value2
&& (value1
== null || !value1
.equals(value2
))) {