1.9.30 sync.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppYamlTranslator.java
blob3df92fcae5e138be97d06732862f3e9ea6dbcc09
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;
36 import java.util.Map;
37 import java.util.Set;
39 /**
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[] {
60 DYNAMIC_PROPERTY,
61 STATIC_PROPERTY,
62 WELCOME_FILES,
63 TRANSPORT_GUARANTEE_PROPERTY,
64 REQUIRED_ROLE_PROPERTY,
65 EXPIRATION_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,
79 WebXml webXml,
80 BackendsXml backendsXml,
81 String apiVersion,
82 Set<String> staticFiles,
83 ApiConfig apiConfig,
84 String runtime,
85 Version sdkVersion) {
86 this.appEngineWebXml = appEngineWebXml;
87 this.webXml = webXml;
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) {
104 if (value != null) {
105 builder.append(tag);
106 builder.append(value);
107 builder.append("\n");
111 private void appendIfNotZero(StringBuilder builder, String tag, double value) {
112 if (value != 0) {
113 builder.append(tag);
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();
163 if (cpuUtil != null
164 && (cpuUtil.getTargetUtilization() != null
165 || cpuUtil.getAggregationWindowLengthSec() != null)) {
166 builder.append(" cpu_utilization:\n");
167 appendIfNotNull(builder, " target_utilization: ", cpuUtil.getTargetUtilization());
168 appendIfNotNull(
169 builder, " aggregation_window_length_sec: ",
170 cpuUtil.getAggregationWindowLengthSec());
173 appendIfNotNull(
174 builder, " target_network_sent_bytes_per_sec: ",
175 settings.getTargetNetworkSentBytesPerSec());
176 appendIfNotNull(
177 builder, " target_network_sent_packets_per_sec: ",
178 settings.getTargetNetworkSentPacketsPerSec());
179 appendIfNotNull(
180 builder, " target_network_received_bytes_per_sec: ",
181 settings.getTargetNetworkReceivedBytesPerSec());
182 appendIfNotNull(
183 builder, " target_network_received_packets_per_sec: ",
184 settings.getTargetNetworkReceivedPacketsPerSec());
185 appendIfNotNull(
186 builder, " target_disk_write_bytes_per_sec: ",
187 settings.getTargetDiskWriteBytesPerSec());
188 appendIfNotNull(
189 builder, " target_disk_write_ops_per_sec: ",
190 settings.getTargetDiskWriteOpsPerSec());
191 appendIfNotNull(
192 builder, " target_disk_read_bytes_per_sec: ",
193 settings.getTargetDiskReadBytesPerSec());
194 appendIfNotNull(
195 builder, " target_disk_read_ops_per_sec: ",
196 settings.getTargetDiskReadOpsPerSec());
197 appendIfNotNull(
198 builder, " target_request_count_per_sec: ",
199 settings.getTargetRequestCountPerSec());
200 appendIfNotNull(
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");
237 } else {
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()) {
330 builder.append(
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");
340 } else {
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
394 * empty.
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
405 * numIndentSpaces.
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);
421 try {
422 writer.write(collection);
423 writer.close();
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");
446 } else {
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();
457 } else {
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) {
475 this.root = root;
478 @Override
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);
485 break;
489 return Collections.<String,Object>singletonMap(WELCOME_FILES, staticWelcomeFiles);
492 @Override
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));
497 } else {
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));
517 @Override
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);
537 } else {
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) {
574 intersector.addGlob(
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("/*")) {
589 fallthrough = true;
590 } else if (servletPattern.equals(API_ENDPOINT_REGEX)) {
591 hasApiEndpoint = true;
592 } else if (servletPattern.endsWith(".jsp")) {
593 hasJsps = true;
594 } else {
595 patterns.add(servletPattern);
600 @Override
601 protected Map<String, Object> getWelcomeProperties() {
602 if (fallthrough) {
603 return null;
604 } else {
605 return Collections.<String,Object>singletonMap(DYNAMIC_PROPERTY, true);
609 @Override
610 protected void addPatterns(GlobIntersector intersector) {
611 if (fallthrough) {
612 intersector.addGlob(GlobFactory.createGlob(
613 "/*",
614 DYNAMIC_PROPERTY, true));
615 } else {
616 for (String servletPattern : patterns) {
617 intersector.addGlob(GlobFactory.createGlob(
618 servletPattern,
619 DYNAMIC_PROPERTY, true));
620 extendMeaningOfTrailingStar(intersector, servletPattern, DYNAMIC_PROPERTY, true);
622 if (hasJsps) {
623 intersector.addGlob(GlobFactory.createGlob(
624 "*.jsp",
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(
630 "/_ah/*",
631 DYNAMIC_PROPERTY, true));
635 @Override
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 {
652 @Override
653 protected void addPatterns(GlobIntersector intersector) {
656 @Override
657 protected void translateGlob(StringBuilder builder, Glob glob) {
660 @Override
661 protected Map<String, Object> getWelcomeProperties() {
662 return Collections.emptyMap();
666 abstract class AbstractHandlerGenerator {
667 private List<Glob> globs = null;
668 protected boolean hasApiEndpoint;
670 public int size() {
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
686 * necessary.
688 abstract protected Map<String, Object> getWelcomeProperties();
690 protected List<Glob> getGlobPatterns() {
691 if (globs == null) {
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));
703 return globs;
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(
718 pattern,
719 TRANSPORT_GUARANTEE_PROPERTY,
720 constraint.getTransportGuarantee()));
721 extendMeaningOfTrailingStar(intersector, pattern, TRANSPORT_GUARANTEE_PROPERTY,
722 constraint.getTransportGuarantee());
723 intersector.addGlob(GlobFactory.createGlob(
724 pattern,
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) {
740 case NONE:
741 builder.append(" login: optional\n");
742 break;
743 case ANY_USER:
744 builder.append(" login: required\n");
745 break;
746 case ADMIN:
747 builder.append(" login: admin\n");
748 break;
751 SecurityConstraint.TransportGuarantee transportGuarantee =
752 (SecurityConstraint.TransportGuarantee) glob.getProperty(TRANSPORT_GUARANTEE_PROPERTY,
753 RESOLVER);
754 if (transportGuarantee == null) {
755 transportGuarantee = SecurityConstraint.TransportGuarantee.NONE;
757 switch (transportGuarantee) {
758 case NONE:
759 if (appEngineWebXml.getSslEnabled()) {
760 builder.append(" secure: optional\n");
761 } else {
762 builder.append(" secure: never\n");
764 break;
765 case INTEGRAL:
766 case CONFIDENTIAL:
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");
772 break;
775 String pattern = glob.getRegularExpression().pattern();
776 String id = webXml.getHandlerIdForPattern(pattern);
777 if (id != null) {
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)) {
791 globs.remove(i);
792 i--;
794 break;
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))) {
805 return false;
808 return true;