Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppYamlTranslator.java
blobf91a42ef5dbbd26fbf2b5fe1859bd71e16bac173
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.ErrorHandler;
11 import com.google.apphosting.utils.config.AppEngineWebXml.Pagespeed;
12 import com.google.apphosting.utils.config.AppEngineWebXml.VmHealthCheck;
13 import com.google.apphosting.utils.config.BackendsXml;
14 import com.google.apphosting.utils.config.WebXml;
15 import com.google.apphosting.utils.config.WebXml.SecurityConstraint;
16 import com.google.apphosting.utils.glob.ConflictResolver;
17 import com.google.apphosting.utils.glob.Glob;
18 import com.google.apphosting.utils.glob.GlobFactory;
19 import com.google.apphosting.utils.glob.GlobIntersector;
20 import com.google.apphosting.utils.glob.LongestPatternConflictResolver;
21 import com.google.common.collect.Maps;
23 import net.sourceforge.yamlbeans.YamlConfig;
24 import net.sourceforge.yamlbeans.YamlException;
25 import net.sourceforge.yamlbeans.YamlWriter;
27 import java.io.StringWriter;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
36 /**
37 * Generates {@code app.yaml} files suitable for uploading as part of
38 * a Google App Engine application.
41 public class AppYamlTranslator {
42 private static final String NO_API_VERSION = "none";
44 private static final ConflictResolver RESOLVER =
45 new LongestPatternConflictResolver();
47 private static final String DYNAMIC_PROPERTY = "dynamic";
48 private static final String STATIC_PROPERTY = "static";
49 private static final String WELCOME_FILES = "welcome";
50 private static final String TRANSPORT_GUARANTEE_PROPERTY = "transportGuarantee";
51 private static final String REQUIRED_ROLE_PROPERTY = "requiredRole";
52 private static final String EXPIRATION_PROPERTY = "expiration";
53 private static final String HTTP_HEADERS_PROPERTY = "http_headers";
54 private static final String API_ENDPOINT_REGEX = "/_ah/spi/*";
56 private static final String[] PROPERTIES = new String[] {
57 DYNAMIC_PROPERTY,
58 STATIC_PROPERTY,
59 WELCOME_FILES,
60 TRANSPORT_GUARANTEE_PROPERTY,
61 REQUIRED_ROLE_PROPERTY,
62 EXPIRATION_PROPERTY,
65 private static final int MAX_HANDLERS = 100;
67 private final AppEngineWebXml appEngineWebXml;
68 private final WebXml webXml;
69 private final BackendsXml backendsXml;
70 private final String apiVersion;
71 private final Set<String> staticFiles;
72 private final String runtime;
73 private final Version sdkVersion;
75 public AppYamlTranslator(AppEngineWebXml appEngineWebXml,
76 WebXml webXml,
77 BackendsXml backendsXml,
78 String apiVersion,
79 Set<String> staticFiles,
80 ApiConfig apiConfig,
81 String runtime,
82 Version sdkVersion) {
83 this.appEngineWebXml = appEngineWebXml;
84 this.webXml = webXml;
85 this.backendsXml = backendsXml;
86 this.apiVersion = apiVersion;
87 this.staticFiles = staticFiles;
88 this.runtime = runtime;
89 this.sdkVersion = sdkVersion;
92 public String getYaml() {
93 StringBuilder builder = new StringBuilder();
94 translateAppEngineWebXml(builder);
95 translateApiVersion(builder);
96 translateWebXml(builder);
97 return builder.toString();
100 private void appendIfNotNull(StringBuilder builder, String tag, Object value) {
101 if (value != null) {
102 builder.append(tag);
103 builder.append(value);
104 builder.append("\n");
108 private void translateAppEngineWebXml(StringBuilder builder) {
109 builder.append("application: '" + appEngineWebXml.getAppId() + "'\n");
110 builder.append("runtime: " + runtime + "\n");
111 if (appEngineWebXml.getUseVm()) {
112 builder.append("vm: True\n");
115 if (appEngineWebXml.getSourceLanguage() != null) {
116 builder.append("source_language: '" + appEngineWebXml.getSourceLanguage() + "'\n");
119 if (appEngineWebXml.getMajorVersionId() != null) {
120 builder.append("version: '" + appEngineWebXml.getMajorVersionId() + "'\n");
123 if (appEngineWebXml.getModule() != null) {
124 builder.append("module: '" + appEngineWebXml.getModule() + "'\n");
127 if (appEngineWebXml.getInstanceClass() != null) {
128 builder.append("instance_class: " + appEngineWebXml.getInstanceClass() + "\n");
131 if (!appEngineWebXml.getAutomaticScaling().isEmpty()) {
132 builder.append("automatic_scaling:\n");
133 AppEngineWebXml.AutomaticScaling settings = appEngineWebXml.getAutomaticScaling();
134 appendIfNotNull(builder, " min_pending_latency: ", settings.getMinPendingLatency());
135 appendIfNotNull(builder, " max_pending_latency: ", settings.getMaxPendingLatency());
136 appendIfNotNull(builder, " min_idle_instances: ", settings.getMinIdleInstances());
137 appendIfNotNull(builder, " max_idle_instances: ", settings.getMaxIdleInstances());
138 appendIfNotNull(builder, " max_concurrent_requests: ", settings.getMaxConcurrentRequests());
141 if (!appEngineWebXml.getManualScaling().isEmpty()) {
142 builder.append("manual_scaling:\n");
143 AppEngineWebXml.ManualScaling settings = appEngineWebXml.getManualScaling();
144 builder.append(" instances: " + settings.getInstances() + "\n");
147 if (!appEngineWebXml.getBasicScaling().isEmpty()) {
148 builder.append("basic_scaling:\n");
149 AppEngineWebXml.BasicScaling settings = appEngineWebXml.getBasicScaling();
150 builder.append(" max_instances: " + settings.getMaxInstances() + "\n");
151 appendIfNotNull(builder, " idle_timeout: ", settings.getIdleTimeout());
154 Collection<String> services = appEngineWebXml.getInboundServices();
155 if (!services.isEmpty()) {
156 builder.append("inbound_services:\n");
157 for (String service : services) {
158 builder.append("- " + service + "\n");
162 if (appEngineWebXml.getPrecompilationEnabled()) {
163 builder.append("derived_file_type:\n");
164 builder.append("- java_precompiled\n");
167 if (appEngineWebXml.getThreadsafe()) {
168 builder.append("threadsafe: True\n");
171 if (appEngineWebXml.getAutoIdPolicy() != null) {
172 builder.append("auto_id_policy: " + appEngineWebXml.getAutoIdPolicy() + "\n");
173 } else {
174 builder.append("auto_id_policy: default\n");
177 if (appEngineWebXml.getCodeLock()) {
178 builder.append("code_lock: True\n");
181 List<AdminConsolePage> adminConsolePages = appEngineWebXml.getAdminConsolePages();
182 if (!adminConsolePages.isEmpty()) {
183 builder.append("admin_console:\n");
184 builder.append(" pages:\n");
185 for (AdminConsolePage page : adminConsolePages) {
186 builder.append(" - name: " + page.getName() + "\n");
187 builder.append(" url: " + page.getUrl() + "\n");
191 List<ErrorHandler> errorHandlers = appEngineWebXml.getErrorHandlers();
192 if (!errorHandlers.isEmpty()) {
193 builder.append("error_handlers:\n");
194 for (ErrorHandler handler : errorHandlers) {
195 String fileName = handler.getFile();
196 if (!fileName.startsWith("/")) {
197 fileName = "/" + fileName;
199 if (!staticFiles.contains("__static__" + fileName)) {
200 throw new AppEngineConfigException("No static file found for error handler: "
201 + fileName + ", out of " + staticFiles);
203 builder.append("- file: __static__" + fileName + "\n");
204 if (handler.getErrorCode() != null) {
205 builder.append(" error_code: " + handler.getErrorCode() + "\n");
207 String mimeType = webXml.getMimeTypeForPath(handler.getFile());
208 if (mimeType != null) {
209 builder.append(" mime_type: " + mimeType + "\n");
214 if (backendsXml != null) {
215 builder.append(backendsXml.toYaml());
218 ApiConfig apiConfig = appEngineWebXml.getApiConfig();
219 if (apiConfig != null) {
220 builder.append("api_config:\n");
221 builder.append(" url: " + apiConfig.getUrl() + "\n");
222 builder.append(" script: unused\n");
225 if (appEngineWebXml.getPagespeed() != null) {
226 builder.append("pagespeed:\n");
227 appendPagespeed(appEngineWebXml.getPagespeed(), builder, 2);
230 if (appEngineWebXml.getUseVm()) {
231 appendEnvVariables(appEngineWebXml.getEnvironmentVariables(), builder);
232 appendVmSettings(appEngineWebXml.getVmSettings(), builder);
233 appendVmHealthCheck(appEngineWebXml.getVmHealthCheck(), builder);
238 * Appends the given VM Settings as YAML to the given StringBuilder.
240 * @param vmSettings The vm settings map to append as YAML.
241 * @param builder The StringBuilder to append to.
243 private void appendEnvVariables(Map<String, String> envVariables, StringBuilder builder) {
244 if (envVariables.size() > 0) {
245 builder.append("env_variables:\n");
246 for (Map.Entry<String, String> envVariable : envVariables.entrySet()) {
247 String k = envVariable.getKey();
248 String v = envVariable.getValue();
249 builder.append(" ").append(yamlQuote(k)).append(": ").append(yamlQuote(v)).append("\n");
255 * Appends the given VM Settings as YAML to the given StringBuilder.
257 * @param vmSettings The vm settings map to append as YAML.
258 * @param builder The StringBuilder to append to.
260 private void appendVmSettings(Map<String, String> vmSettings, StringBuilder builder) {
261 builder.append("vm_settings:\n");
262 if (vmSettings == null || !vmSettings.containsKey("image")) {
263 if (sdkVersion != null && sdkVersion.getRelease() != null) {
264 builder.append(" 'image': " + yamlQuote(sdkVersion.getRelease()) + "\n");
267 if (vmSettings != null) {
268 for (Map.Entry<String, String> setting : vmSettings.entrySet()) {
269 builder.append(
270 " " + yamlQuote(setting.getKey()) + ": " + yamlQuote(setting.getValue()) + "\n");
275 private void appendVmHealthCheck(VmHealthCheck vmHealthCheck, StringBuilder builder) {
276 builder.append("vm_health_check:\n");
277 if (vmHealthCheck.getEnableHealthCheck()) {
278 builder.append(" enable_health_check: True\n");
279 } else {
280 builder.append(" enable_health_check: False\n");
283 appendIfNotNull(builder, " check_interval_sec: ", vmHealthCheck.getCheckIntervalSec());
284 appendIfNotNull(builder, " timeout_sec: ", vmHealthCheck.getTimeoutSec());
285 appendIfNotNull(builder, " unhealthy_threshold: ", vmHealthCheck.getUnhealthyThreshold());
286 appendIfNotNull(builder, " healthy_threshold: ", vmHealthCheck.getHealthyThreshold());
287 appendIfNotNull(builder, " restart_threshold: ", vmHealthCheck.getRestartThreshold());
288 appendIfNotNull(builder, " host: ", vmHealthCheck.getHost());
292 * Append the given Pagespeed node as YAML to the given StringBuilder.
293 * @param pagespeed The Pagespeed instance to append as YAML.
294 * @param builder The StringBuilder to append to.
295 * @param indent The number of spaces to indent the pagespeed YAML.
297 public static void appendPagespeed(Pagespeed pagespeed, StringBuilder builder, int indent) {
298 if (pagespeed != null && !pagespeed.isEmpty()) {
299 Map<String, List<String>> config = Maps.newTreeMap();
300 putListInMapIfNotEmpty(config, "url_blacklist", pagespeed.getUrlBlacklist());
301 putListInMapIfNotEmpty(config, "domains_to_rewrite", pagespeed.getDomainsToRewrite());
302 putListInMapIfNotEmpty(config, "enabled_rewriters", pagespeed.getEnabledRewriters());
303 putListInMapIfNotEmpty(config, "disabled_rewriters", pagespeed.getDisabledRewriters());
304 appendObjectAsYaml(builder, config, indent);
309 * Adds the list to the given map, using the specified name, if the list is non-null and not
310 * empty.
312 private static void putListInMapIfNotEmpty(Map<String, List<String>> map, String name,
313 List<String> values) {
314 if (values != null && !values.isEmpty()) {
315 map.put(name, values);
320 * Appends the given collection to the StringBuilder as YAML, indenting each emitted line by
321 * numIndentSpaces.
323 private static void appendObjectAsYaml(
324 StringBuilder builder, Object collection, int numIndentSpaces) {
325 StringBuilder prefixBuilder = new StringBuilder();
326 for (int i = 0; i < numIndentSpaces; ++i) {
327 prefixBuilder.append(' ');
329 final String indentPrefix = prefixBuilder.toString();
331 StringWriter stringWriter = new StringWriter();
332 YamlConfig yamlConfig = new YamlConfig();
333 yamlConfig.writeConfig.setIndentSize(2);
334 yamlConfig.writeConfig.setWriteRootTags(false);
336 YamlWriter writer = new YamlWriter(stringWriter, yamlConfig);
337 try {
338 writer.write(collection);
339 writer.close();
340 } catch (YamlException e) {
341 throw new AppEngineConfigException("Unable to generate YAML.", e);
344 for (String line : stringWriter.toString().split("\n")) {
345 builder.append(indentPrefix);
346 builder.append(line);
347 builder.append("\n");
352 * Surrounds the provided string with single quotes, escaping any single
353 * quotes in the string by replacing them with ''.
355 private String yamlQuote(String str) {
356 return "'" + str.replace("'", "''") + "'";
359 private void translateApiVersion(StringBuilder builder) {
360 if (apiVersion == null) {
361 builder.append("api_version: '" + NO_API_VERSION + "'\n");
362 } else {
363 builder.append("api_version: '" + apiVersion + "'\n");
367 private void translateWebXml(StringBuilder builder) {
368 builder.append("handlers:\n");
370 AbstractHandlerGenerator staticGenerator = null;
371 if (staticFiles.isEmpty()) {
372 staticGenerator = new EmptyHandlerGenerator();
373 } else {
374 staticGenerator = new StaticHandlerGenerator(appEngineWebXml.getPublicRoot());
377 DynamicHandlerGenerator dynamicGenerator =
378 new DynamicHandlerGenerator(webXml.getFallThroughToRuntime());
379 if (staticGenerator.size() + dynamicGenerator.size() > MAX_HANDLERS) {
380 dynamicGenerator = new DynamicHandlerGenerator(true);
383 staticGenerator.translate(builder);
384 dynamicGenerator.translate(builder);
387 class StaticHandlerGenerator extends AbstractHandlerGenerator {
388 private final String root;
390 public StaticHandlerGenerator(String root) {
391 this.root = root;
394 @Override
395 protected Map<String, Object> getWelcomeProperties() {
396 List<String> staticWelcomeFiles = new ArrayList<String>();
397 for (String welcomeFile : webXml.getWelcomeFiles()) {
398 for (String staticFile : staticFiles) {
399 if (staticFile.endsWith("/" + welcomeFile)) {
400 staticWelcomeFiles.add(welcomeFile);
401 break;
405 return Collections.<String,Object>singletonMap(WELCOME_FILES, staticWelcomeFiles);
408 @Override
409 protected void addPatterns(GlobIntersector intersector) {
410 List<AppEngineWebXml.StaticFileInclude> includes = appEngineWebXml.getStaticFileIncludes();
411 if (includes.isEmpty()) {
412 intersector.addGlob(GlobFactory.createGlob("/*", STATIC_PROPERTY, true));
413 } else {
414 for (AppEngineWebXml.StaticFileInclude include : includes) {
415 String pattern = include.getPattern().replaceAll("\\*\\*", "*");
416 if (!pattern.startsWith("/")) {
417 pattern = "/" + pattern;
419 Map<String, Object> props = new HashMap<String, Object>();
420 props.put(STATIC_PROPERTY, true);
421 if (include.getExpiration() != null) {
422 props.put(EXPIRATION_PROPERTY, include.getExpiration());
424 if (include.getHttpHeaders() != null) {
425 props.put(HTTP_HEADERS_PROPERTY, include.getHttpHeaders());
428 intersector.addGlob(GlobFactory.createGlob(pattern, props));
433 @Override
434 public void translateGlob(StringBuilder builder, Glob glob) {
435 String regex = glob.getRegularExpression().pattern();
436 if (!root.isEmpty()) {
437 if (regex.startsWith(root)){
438 regex = regex.substring(root.length(), regex.length());
441 @SuppressWarnings("unchecked")
442 List<String> welcomeFiles =
443 (List<String>) glob.getProperty(WELCOME_FILES, RESOLVER);
444 if (welcomeFiles != null) {
445 for (String welcomeFile : welcomeFiles) {
446 builder.append("- url: (" + regex + ")\n");
447 builder.append(" static_files: __static__" + root + "\\1" + welcomeFile + "\n");
448 builder.append(" upload: __NOT_USED__\n");
449 builder.append(" require_matching_file: True\n");
450 translateHandlerOptions(builder, glob);
451 translateAdditionalStaticOptions(builder, glob);
453 } else {
454 Boolean isStatic = (Boolean) glob.getProperty(STATIC_PROPERTY, RESOLVER);
455 if (isStatic != null && isStatic.booleanValue()) {
456 builder.append("- url: (" + regex + ")\n");
457 builder.append(" static_files: __static__" + root + "\\1\n");
458 builder.append(" upload: __NOT_USED__\n");
459 builder.append(" require_matching_file: True\n");
460 translateHandlerOptions(builder, glob);
461 translateAdditionalStaticOptions(builder, glob);
466 private void translateAdditionalStaticOptions(StringBuilder builder, Glob glob)
467 throws AppEngineConfigException {
468 String expiration = (String) glob.getProperty(EXPIRATION_PROPERTY, RESOLVER);
469 if (expiration != null) {
470 builder.append(" expiration: " + expiration + "\n");
473 @SuppressWarnings("unchecked")
474 Map<String, String> httpHeaders =
475 (Map<String, String>) glob.getProperty(HTTP_HEADERS_PROPERTY, RESOLVER);
476 if (httpHeaders != null && !httpHeaders.isEmpty()) {
477 builder.append(" http_headers:\n");
478 appendObjectAsYaml(builder, httpHeaders, 4);
484 * According to the example In section 12.2.2 of Servlet Spec 3.0 , /baz/* should also match /baz,
485 * so add an additional glob for that.
487 private static void extendMeaningOfTrailingStar(
488 GlobIntersector intersector, String pattern, String property, Object value) {
489 if (pattern.endsWith("/*") && pattern.length() > 2) {
490 intersector.addGlob(
491 GlobFactory.createGlob(pattern.substring(0, pattern.length() - 2), property, value));
495 class DynamicHandlerGenerator extends AbstractHandlerGenerator {
496 private final List<String> patterns;
497 private boolean fallthrough;
498 private boolean hasJsps;
500 DynamicHandlerGenerator(boolean alwaysFallthrough) {
501 fallthrough = alwaysFallthrough;
502 patterns = new ArrayList<String>();
503 for (String servletPattern : webXml.getServletPatterns()) {
504 if (servletPattern.equals("/") || servletPattern.equals("/*")) {
505 fallthrough = true;
506 } else if (servletPattern.equals(API_ENDPOINT_REGEX)) {
507 hasApiEndpoint = true;
508 } else if (servletPattern.endsWith(".jsp")) {
509 hasJsps = true;
510 } else {
511 patterns.add(servletPattern);
516 @Override
517 protected Map<String, Object> getWelcomeProperties() {
518 if (fallthrough) {
519 return null;
520 } else {
521 return Collections.<String,Object>singletonMap(DYNAMIC_PROPERTY, true);
525 @Override
526 protected void addPatterns(GlobIntersector intersector) {
527 if (fallthrough) {
528 intersector.addGlob(GlobFactory.createGlob(
529 "/*",
530 DYNAMIC_PROPERTY, true));
531 } else {
532 for (String servletPattern : patterns) {
533 intersector.addGlob(GlobFactory.createGlob(
534 servletPattern,
535 DYNAMIC_PROPERTY, true));
536 extendMeaningOfTrailingStar(intersector, servletPattern, DYNAMIC_PROPERTY, true);
538 if (hasJsps) {
539 intersector.addGlob(GlobFactory.createGlob(
540 "*.jsp",
541 DYNAMIC_PROPERTY, true));
542 } else if (appEngineWebXml.getUseVm()) {
543 intersector.addGlob(GlobFactory.createGlob(
544 "*.jsp",
545 DYNAMIC_PROPERTY, true));
547 intersector.addGlob(GlobFactory.createGlob(
548 "/_ah/*",
549 DYNAMIC_PROPERTY, true));
553 @Override
554 public void translateGlob(StringBuilder builder, Glob glob) {
555 String regex = glob.getRegularExpression().pattern();
557 Boolean isDynamic = (Boolean) glob.getProperty(DYNAMIC_PROPERTY, RESOLVER);
558 if (isDynamic != null && isDynamic.booleanValue()) {
559 builder.append("- url: " + regex + "\n");
560 builder.append(" script: unused\n");
561 translateHandlerOptions(builder, glob);
567 * An {@code AbstractHandlerGenerator} that returns no globs.
569 class EmptyHandlerGenerator extends AbstractHandlerGenerator {
570 @Override
571 protected void addPatterns(GlobIntersector intersector) {
574 @Override
575 protected void translateGlob(StringBuilder builder, Glob glob) {
578 @Override
579 protected Map<String, Object> getWelcomeProperties() {
580 return Collections.emptyMap();
584 abstract class AbstractHandlerGenerator {
585 private List<Glob> globs = null;
586 protected boolean hasApiEndpoint;
588 public int size() {
589 return getGlobPatterns().size();
592 public void translate(StringBuilder builder) {
593 for (Glob glob : getGlobPatterns()) {
594 translateGlob(builder, glob);
598 abstract protected void addPatterns(GlobIntersector intersector);
599 abstract protected void translateGlob(StringBuilder builder, Glob glob);
602 * @returns a map of welcome properties to apply to the welcome
603 * file entries, or {@code null} if no welcome file entries are
604 * necessary.
606 abstract protected Map<String, Object> getWelcomeProperties();
608 protected List<Glob> getGlobPatterns() {
609 if (globs == null) {
610 GlobIntersector intersector = new GlobIntersector();
611 addPatterns(intersector);
612 addSecurityConstraints(intersector);
613 addWelcomeFiles(intersector);
615 globs = intersector.getIntersection();
616 removeNearDuplicates(globs);
617 if (hasApiEndpoint) {
618 globs.add(GlobFactory.createGlob(API_ENDPOINT_REGEX, DYNAMIC_PROPERTY, true));
621 return globs;
624 protected void addWelcomeFiles(GlobIntersector intersector) {
625 Map<String, Object> welcomeProperties = getWelcomeProperties();
626 if (welcomeProperties != null) {
627 intersector.addGlob(GlobFactory.createGlob("/", welcomeProperties));
628 intersector.addGlob(GlobFactory.createGlob("/*/", welcomeProperties));
632 protected void addSecurityConstraints(GlobIntersector intersector) {
633 for (SecurityConstraint constraint : webXml.getSecurityConstraints()) {
634 for (String pattern : constraint.getUrlPatterns()) {
635 intersector.addGlob(GlobFactory.createGlob(
636 pattern,
637 TRANSPORT_GUARANTEE_PROPERTY,
638 constraint.getTransportGuarantee()));
639 extendMeaningOfTrailingStar(intersector, pattern, TRANSPORT_GUARANTEE_PROPERTY,
640 constraint.getTransportGuarantee());
641 intersector.addGlob(GlobFactory.createGlob(
642 pattern,
643 REQUIRED_ROLE_PROPERTY,
644 constraint.getRequiredRole()));
645 extendMeaningOfTrailingStar(
646 intersector, pattern, REQUIRED_ROLE_PROPERTY, constraint.getRequiredRole());
651 protected void translateHandlerOptions(StringBuilder builder, Glob glob) {
652 SecurityConstraint.RequiredRole requiredRole =
653 (SecurityConstraint.RequiredRole) glob.getProperty(REQUIRED_ROLE_PROPERTY, RESOLVER);
654 if (requiredRole == null) {
655 requiredRole = SecurityConstraint.RequiredRole.NONE;
657 switch (requiredRole) {
658 case NONE:
659 builder.append(" login: optional\n");
660 break;
661 case ANY_USER:
662 builder.append(" login: required\n");
663 break;
664 case ADMIN:
665 builder.append(" login: admin\n");
666 break;
669 SecurityConstraint.TransportGuarantee transportGuarantee =
670 (SecurityConstraint.TransportGuarantee) glob.getProperty(TRANSPORT_GUARANTEE_PROPERTY,
671 RESOLVER);
672 if (transportGuarantee == null) {
673 transportGuarantee = SecurityConstraint.TransportGuarantee.NONE;
675 switch (transportGuarantee) {
676 case NONE:
677 if (appEngineWebXml.getSslEnabled()) {
678 builder.append(" secure: optional\n");
679 } else {
680 builder.append(" secure: never\n");
682 break;
683 case INTEGRAL:
684 case CONFIDENTIAL:
685 if (!appEngineWebXml.getSslEnabled()) {
686 throw new AppEngineConfigException(
687 "SSL must be enabled in appengine-web.xml to use transport-guarantee");
689 builder.append(" secure: always\n");
690 break;
693 String pattern = glob.getRegularExpression().pattern();
694 String id = webXml.getHandlerIdForPattern(pattern);
695 if (id != null) {
696 if (appEngineWebXml.isApiEndpoint(id)) {
697 builder.append(" api_endpoint: True\n");
702 private void removeNearDuplicates(List<Glob> globs) {
703 for (int i = 0; i < globs.size(); i++) {
704 Glob topGlob = globs.get(i);
705 for (int j = i + 1; j < globs.size(); j++) {
706 Glob bottomGlob = globs.get(j);
707 if (bottomGlob.matchesAll(topGlob)) {
708 if (propertiesMatch(topGlob, bottomGlob)) {
709 globs.remove(i);
710 i--;
712 break;
718 private boolean propertiesMatch(Glob glob1, Glob glob2) {
719 for (String property : PROPERTIES) {
720 Object value1 = glob1.getProperty(property, RESOLVER);
721 Object value2 = glob2.getProperty(property, RESOLVER);
722 if (value1 != value2 && (value1 == null || !value1.equals(value2))) {
723 return false;
726 return true;