All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.apphosting.utils.config.AppYaml Maven / Gradle / Ivy

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.apphosting.utils.config;

import com.esotericsoftware.yamlbeans.YamlException;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import com.google.common.flogger.GoogleLogger;
import com.google.common.xml.XmlEscapers;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * JavaBean representation of the Java app.yaml file.
 *
 * 

The methods of this class are mapped to YAML keys via method name reflection, which * is why some of them contain underscores. */ public class AppYaml { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); /** * Plugin service to modify app.yaml with runtime-specific defaults. */ public interface Plugin { AppYaml process(AppYaml yaml); } /** * A {@code Handler} element from app.yaml. Maps to {@code servlet}, {@code servlet-mapping}, * {@code filter}, and {@code filter-mapping} elements in web.xml * */ public static class Handler { /** Type of handler, deduced from the presence of various YAML elements. */ public enum Type {SERVLET, JSP, FILTER, NONE} private String url; private String jsp; private String servlet; private String filter; private LoginType login; private Security secure; private ImmutableSortedMap initParams; private String name; private boolean loadOnStartup; /** Login requirement type specified in app.yaml */ public enum LoginType { admin, required, optional } public enum Security { always, optional, never } private boolean apiEndpoint = false; private boolean requireMatchingFile = false; private int redirectHttpResponseCode; private String mimeType; private String upload; private String staticFiles; private String staticDir; private String expiration; private Pattern urlPattern; private ImmutableSortedMap httpHeaders; //////////////////////////////////////////////// // Plugin-Only Properties //////////////////////////////////////////////// // The following properties do not map directly to xml. // Instead they are consumed by runtime plugins. // A "script:" value in a handler is typical of Python's app.yaml // but it is not used by the Java SDK because "servlet:" is used instead. // This tag may be used by a runtime plugin to allow users to express // runtime-specific URL handling instructions. private String script; //////////////////////////////////////////////// // end: Plugin-Only Properties //////////////////////////////////////////////// private static final String MULTIPLE_HANDLERS = "Cannot set both %s and %s for the same url."; public String getUrl() { return url; } public void setUrl(String url) { YamlUtils.validateUrl(url); try { this.urlPattern = Pattern.compile(url); } catch (PatternSyntaxException e) { logger.atWarning().log( "Url is not a valid regex pattern: %s, exception: %s", url, e.getMessage()); } this.url = url; } public Pattern getRegularExpression() { return urlPattern; } public String getJsp() { return jsp; } public void setJsp(String jsp) { this.jsp = jsp; checkHandlers(); } public String getServlet() { return servlet; } public void setServlet(String servlet) { this.servlet = servlet; checkHandlers(); } public String getFilter() { return filter; } public void setFilter(String filter) { this.filter = filter; checkHandlers(); } public Type getType() { if (servlet != null) { return Type.SERVLET; } if (filter != null) { return Type.FILTER; } if (jsp != null) { return Type.JSP; } return Type.NONE; } public String getTarget() { if (servlet != null) { return servlet; } if (filter != null) { return filter; } if (jsp != null) { return jsp; } return null; } public void setScript(String script) { this.script = script; } public String getScript() { return this.script; } public LoginType getLogin() { return login; } public void setLogin(LoginType login) { this.login = login; } public Security getSecure() { return secure; } public void setSecure(Security secure) { if (secure == Security.never) { throw new AppEngineConfigException("Java does not support secure: never"); } this.secure = secure; } public Map getInit_params() { return initParams; } public void setInit_params(Map initParams) { if (initParams == null) { this.initParams = null; } else { this.initParams = ImmutableSortedMap.copyOf(initParams); } } public String getName() { return (name == null ? getTarget() : name); } public void setLoad_on_startup(boolean loadOnStartup) { this.loadOnStartup = loadOnStartup; } public boolean getLoad_on_startup() { return this.loadOnStartup; } public void setName(String name) { this.name = name; } public String getApi_endpoint() { return Boolean.toString(this.apiEndpoint); } public void setApi_endpoint(String apiEndpoint) { this.apiEndpoint = YamlUtils.parseBoolean(apiEndpoint); } public boolean isApiEndpoint() { return this.apiEndpoint; } public boolean isRequire_matching_file() { return requireMatchingFile; } public void setRequire_matching_file(boolean requireMatchingFile) { this.requireMatchingFile = requireMatchingFile; } public int getRedirect_http_response_code() { return redirectHttpResponseCode; } public void setRedirect_http_response_code(int redirectHttpResponseCode) { this.redirectHttpResponseCode = redirectHttpResponseCode; } public String getMime_type() { return mimeType; } public void setMime_type(String mimeType) { this.mimeType = mimeType; } public String getUpload() { return upload; } public void setUpload(String upload) { this.upload = upload; } public String getStatic_files() { return staticFiles; } public void setStatic_files(String staticFiles) { this.staticFiles = staticFiles; } public String getStatic_dir() { return staticDir; } public void setStatic_dir(String staticDir) { this.staticDir = staticDir; } public String getExpiration() { return expiration; } public void setExpiration(String expiration) { this.expiration = expiration; } public Map getHttp_headers() { return httpHeaders; } public void setHttp_headers(Map httpHeaders) { this.httpHeaders = ImmutableSortedMap.copyOf(httpHeaders); } private void checkHandlers() { if (jsp != null && servlet != null) { throw new AppEngineConfigException(String.format(MULTIPLE_HANDLERS, "jsp", "servlet")); } if (jsp != null && filter != null) { throw new AppEngineConfigException(String.format(MULTIPLE_HANDLERS, "jsp", "filter")); } if (filter != null && servlet != null) { throw new AppEngineConfigException(String.format(MULTIPLE_HANDLERS, "filter", "servlet")); } } /** * Generates the {@code servlet} or {@code filter} element of web.xml * corresponding to this handler. */ private void generateDefinitionXml(XmlWriter xml) { if (getServlet() != null || getJsp() != null) { generateServletDefinition(xml); } else if (getFilter() != null) { generateFilterDefinition(xml); } } private void generateMappingXml(XmlWriter xml) { if (getServlet() != null || getJsp() != null) { generateServletMapping(xml); } else if (getFilter() != null) { generateFilterMapping(xml); } generateSecurityConstraints(xml); } private void generateSecurityConstraints(XmlWriter xml) { if (secure == Security.always || login == LoginType.required || login == LoginType.admin) { xml.startElement("security-constraint"); xml.startElement("web-resource-collection"); xml.simpleElement("web-resource-name", "aname"); xml.simpleElement("url-pattern", getUrl()); xml.endElement("web-resource-collection"); if (login == LoginType.required) { securityConstraint(xml, "auth", "role-name", "*"); } else if (login == LoginType.admin) { securityConstraint(xml, "auth", "role-name", "admin"); } if (secure == Security.always) { securityConstraint(xml, "user-data", "transport-guarantee", "CONFIDENTIAL"); } xml.endElement("security-constraint"); } } private void securityConstraint(XmlWriter xml, String type, String name, String value) { type = type + "-constraint"; xml.startElement(type); xml.simpleElement(name, value); xml.endElement(type); } /** * Generates a {@code filter} element of web.xml corresponding to this handler. */ private void generateFilterDefinition(XmlWriter xml) { xml.startElement("filter"); xml.simpleElement("filter-name", getName()); xml.simpleElement("filter-class", getFilter()); generateInitParams(xml); xml.endElement("filter"); } /** * Generates a {@code filter-mapping} element of web.xml corresponding to this handler. */ private void generateFilterMapping(XmlWriter xml) { xml.startElement("filter-mapping"); xml.simpleElement("filter-name", getName()); xml.simpleElement("url-pattern", getUrl()); xml.endElement("filter-mapping"); } /** * Generates a {@code servlet} or {@code jsp-file} element of web.xml corresponding to this * handler. */ private void generateServletDefinition(XmlWriter xml) { xml.startElement("servlet"); xml.simpleElement("servlet-name", getName()); if (getJsp() == null) { xml.simpleElement("servlet-class", getServlet()); } else { xml.simpleElement("jsp-file", getJsp()); } generateInitParams(xml); // The servlet spec says that the value of load-on-startup should be // a non-negative integer and at container startup time servlets with // this tag are loaded in increasing order of this value. Here we // only support it like a boolean. It's either not present or it's 1. if (loadOnStartup) { xml.simpleElement("load-on-startup", "1"); } xml.endElement("servlet"); } /** * Merges another handler into this handler, assuming that the other handler * has the same name, type and target. This operation is intended to be * used for generating a Servlet or Filter *definition* as opposed to a * mapping, and therefore the urls of this handler and the other handler * are not involved in the merge operation. The load_on_startup values * of the two handlers will be OR'd and the init_params will be unioned. */ public void mergeDefinitions(Handler otherHandler) { Preconditions.checkArgument( this.getName().equals(otherHandler.getName()), "Cannot merge handler named %s with handler named %s", this.getName(), otherHandler.getName()); Preconditions.checkArgument( this.getType() == otherHandler.getType(), "Cannot merge handler of type %s with handler of type %s", this.getType(), otherHandler.getType()); Preconditions.checkArgument( this.getTarget().equals(otherHandler.getTarget()), "Cannot merge handler with target %s with handler with target %s", this.getTarget(), otherHandler.getTarget()); this.loadOnStartup = this.loadOnStartup || otherHandler.loadOnStartup; Map mergedInitParams = new LinkedHashMap<>(); if (this.initParams != null) { mergedInitParams.putAll(this.initParams); } if (otherHandler.initParams != null) { for (String key : otherHandler.initParams.keySet()) { String thisValue = mergedInitParams.get(key); String otherValue = otherHandler.initParams.get(key); if (thisValue == null) { mergedInitParams.put(key, otherValue); } else if (!thisValue.equals(otherValue)) { throw new IllegalArgumentException( "Cannot merge handlers with conflicting values for the init_param: " + key + " : " + thisValue + " vs " + otherValue); } } } if (!mergedInitParams.isEmpty()) { this.initParams = ImmutableSortedMap.copyOf(mergedInitParams); } } /** * Generates a {@code servlet-mapping} element of web.xml corresponding to this handler. */ private void generateServletMapping(XmlWriter xml) { if (isApiEndpoint()) { xml.startElement("servlet-mapping", "id", xml.nextApiEndpointId()); } else { xml.startElement("servlet-mapping"); } xml.simpleElement("servlet-name", getName()); xml.simpleElement("url-pattern", getUrl()); xml.endElement("servlet-mapping"); } private void generateInitParams(XmlWriter xml) { if (initParams != null) { initParams.forEach( (name, value) -> { xml.startElement("init-param"); xml.simpleElement("param-name", name); xml.simpleElement("param-value", value); xml.endElement("init-param"); } ); } } private void generateEndpointServletMappingId(XmlWriter xml) { if (isApiEndpoint()) { xml.simpleElement("endpoint-servlet-mapping-id", xml.nextApiEndpointId()); } } } public static class ResourceFile { private static final String EMPTY_MESSAGE = "Missing include or exclude."; private static final String BOTH_MESSAGE = "Cannot specify both include and exclude."; String include; String exclude; ImmutableSortedMap httpHeaders; public String getInclude() { if (exclude == null && include == null) { throw new AppEngineConfigException(EMPTY_MESSAGE); } return include; } public void setInclude(String include) { if (exclude != null) { throw new AppEngineConfigException(BOTH_MESSAGE); } this.include = include; } public String getExclude() { if (exclude == null && include == null) { throw new AppEngineConfigException(EMPTY_MESSAGE); } return exclude; } public void setExclude(String exclude) { if (include != null) { throw new AppEngineConfigException(BOTH_MESSAGE); } this.exclude = exclude; } // The reason for these wacky getter and setter method names is so that // yamlbeans will find these method. public Map getHttp_headers() { if (include == null) { throw new AppEngineConfigException("Missing include."); } return httpHeaders; } public void setHttp_headers(Map httpHeaders) { if (include == null) { throw new AppEngineConfigException("Missing include."); } this.httpHeaders = ImmutableSortedMap.copyOf(httpHeaders); } } public static class StaticFile extends ResourceFile { private static final String NO_INCLUDE = "Missing include."; private static final String INCLUDE_ONLY = "Expiration can only be specified with include."; private String expiration; public String getExpiration() { if (expiration != null && include == null) { throw new AppEngineConfigException(NO_INCLUDE); } return expiration; } public void setExpiration(String expiration) { if (exclude != null) { throw new AppEngineConfigException(INCLUDE_ONLY); } this.expiration = expiration; } @Override public void setExclude(String exclude) { if (expiration != null) { throw new AppEngineConfigException(INCLUDE_ONLY); } super.setExclude(exclude); } } public static class AdminConsole { private ImmutableList pages; public List getPages() { return pages; } public void setPages(List pages) { this.pages = ImmutableList.copyOf(pages); } } public static class AdminPage { private String name; private String url; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } public static class AsyncSessionPersistence { private boolean enabled = false; private String queueName; public String getEnabled() { return Boolean.toString(enabled); } public void setEnabled(String enabled) { this.enabled = YamlUtils.parseBoolean(enabled); } public String getQueue_name() { return this.queueName; } public void setQueue_name(String queueName) { this.queueName = queueName; } } public static class ErrorHandler { private String file; private String errorCode; public String getFile() { return file; } public void setFile(String file) { this.file = file; } public String getError_code() { return errorCode; } public void setError_code(String errorCode) { this.errorCode = errorCode; } } /** * AutomaticScaling bean. */ public static class AutomaticScaling { private String minPendingLatency; private String maxPendingLatency; private String minIdleInstances; private String maxIdleInstances; private String maxConcurrentRequests; // New Standard clone scheduler config: private String minInstances; private String maxInstances; private String targetCpuUtilization; private String targetThroughputUtilization; public String getMin_pending_latency() { return minPendingLatency; } public void setMin_pending_latency(String minPendingLatency) { this.minPendingLatency = minPendingLatency; } public String getMax_pending_latency() { return maxPendingLatency; } public void setMax_pending_latency(String maxPendingLatency) { this.maxPendingLatency = maxPendingLatency; } public String getMin_idle_instances() { return minIdleInstances; } public void setMin_idle_instances(String minIdleInstances) { this.minIdleInstances = minIdleInstances; } public String getMax_idle_instances() { return maxIdleInstances; } public void setMax_idle_instances(String maxIdleInstances) { this.maxIdleInstances = maxIdleInstances; } public String getMin_instances() { return minInstances; } public void setTarget_cpu_utilization(String targetCpuUtilization) { this.targetCpuUtilization = targetCpuUtilization; } public String getTarget_cpu_utilization() { return targetCpuUtilization; } public void setTarget_throughput_utilization(String targetThroughputUtilization) { this.targetThroughputUtilization = targetThroughputUtilization; } public String getTarget_throughput_utilization() { return targetThroughputUtilization; } public void setMin_instances(String minInstances) { this.minInstances = minInstances; } public String getMax_instances() { return maxInstances; } public void setMax_instances(String maxInstances) { this.maxInstances = maxInstances; } public String getMax_concurrent_requests() { return maxConcurrentRequests; } public void setMax_concurrent_requests(String maxConcurrentRequests) { this.maxConcurrentRequests = maxConcurrentRequests; } } /** * ManualScaling bean. */ public static class ManualScaling { private String instances; public String getInstances() { return instances; } public void setInstances(String instances) { this.instances = instances; } } /** * BasicScaling bean. */ public static class BasicScaling { private String maxInstances; private String idleTimeout; public String getMax_instances() { return maxInstances; } public void setMax_instances(String maxInstances) { this.maxInstances = maxInstances; } public String getIdle_timeout() { return idleTimeout; } public void setIdle_timeout(String idleTimeout) { this.idleTimeout = idleTimeout; } } private String application; private String version; private String service; private String instanceClass; private AutomaticScaling automaticScaling; private ManualScaling manualScaling; private BasicScaling basicScaling; private String runtime; private boolean appEngineApis; private ImmutableList handlers; private String publicRoot; private ImmutableList staticFiles; private ImmutableList resourceFiles; private boolean sslEnabled = true; private boolean precompilationEnabled = true; private boolean sessionsEnabled = false; private AsyncSessionPersistence asyncSessionPersistence; private boolean threadsafe = false; private String autoIdPolicy; private String apiVersion; private boolean threadsafeWasSet = false; private boolean codeLock = false; private ImmutableSortedMap systemProperties; private ImmutableSortedMap envVariables; private ImmutableSortedMap buildEnvVariables; private ImmutableSortedMap contextParams; private ImmutableSortedMap betaSettings; private ImmutableList welcomeFiles; private ImmutableList listeners; private ImmutableList inboundServices; private ImmutableList derivedFileType; private AdminConsole adminConsole; private ImmutableList errorHandlers; private ApiConfig apiConfig; private String webXml; private String runtimeChannel; private String env; private ImmutableSortedMap vpcAccessConnector; private String skipFiles; private String defaultExpiration; private String entrypoint; private static final String VALIDATION_VPC = "vpc_access_connect has to be like: " + "projects/[PROJECT_ID]/locations/[REGION]/connectors/[CONNECTOR_NAME]" + " but was '%s'."; public String getApplication() { if (application == null) { throw new AppEngineConfigException( String.format("Missing required element '%s'.", "application")); } return application; } public void setApplication(String application) { this.application = application; } public Map getVpc_access_connector() { return vpcAccessConnector; } public void setVpc_access_connector(Map vpcAccessConnectorMap) { String vpcAccessConnector = vpcAccessConnectorMap.get("name"); List parts = Splitter.on('/').splitToList(vpcAccessConnector); if (parts.size() != 6) { throw new AppEngineConfigException(String.format(VALIDATION_VPC, vpcAccessConnector)); } if (!parts.get(0).equals("projects") || !parts.get(2).equals("locations") || !parts.get(4).equals("connectors")) { throw new AppEngineConfigException(String.format(VALIDATION_VPC, vpcAccessConnector)); } this.vpcAccessConnector = ImmutableSortedMap.copyOf(vpcAccessConnectorMap); } public String getDefault_expiration() { return defaultExpiration; } public void setDefault_expiration(String defaultExpiration) { this.defaultExpiration = defaultExpiration; } public String getRuntime_channel() { return runtimeChannel; } public void setRuntime_channel(String runtimeChannel) { this.runtimeChannel = runtimeChannel; } public String getEnv() { return env; } public void setEnv(String env) { this.env = env; } public String getSkip_files() { return skipFiles; } public void setSkip_files(String skipFiles) { this.skipFiles = skipFiles; } public String getEntrypoint() { return entrypoint; } public void setEntrypoint(String application) { this.entrypoint = application; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getModule() { return service; } public void setModule(String module) { this.service = module; } public String getService() { return service; } public void setService(String service) { this.service = service; } public String getInstance_class() { return instanceClass; } public void setInstance_class(String instanceClass) { this.instanceClass = instanceClass; } public AutomaticScaling getAutomatic_scaling() { return automaticScaling; } public void setAutomatic_scaling(AutomaticScaling automaticScaling) { this.automaticScaling = automaticScaling; } public ManualScaling getManual_scaling() { return manualScaling; } public void setManual_scaling(ManualScaling manualScaling) { this.manualScaling = manualScaling; } public BasicScaling getBasic_scaling() { return basicScaling; } public void setBasic_scaling(BasicScaling basicScaling) { this.basicScaling = basicScaling; } public String getRuntime() { return runtime; } public void setRuntime(String runtime) { this.runtime = runtime; } public String getApp_engine_apis() { return Boolean.toString(appEngineApis); } public void setApp_engine_apis(String appEngineApis) { this.appEngineApis = YamlUtils.parseBoolean(appEngineApis); } public boolean isAppEngineApis() { return appEngineApis; } public List getHandlers() { return handlers; } public void setHandlers(List handlers) { this.handlers = ImmutableList.copyOf(handlers); if (this.apiConfig != null) { this.apiConfig.setHandlers(this.handlers); } } public String getPublic_root() { return publicRoot; } public void setPublic_root(String publicRoot) { this.publicRoot = publicRoot; } public List getStatic_files() { return staticFiles; } public void setStatic_files(List staticFiles) { this.staticFiles = ImmutableList.copyOf(staticFiles); } public List getResource_files() { return resourceFiles; } public void setResource_files(List resourceFiles) { this.resourceFiles = ImmutableList.copyOf(resourceFiles); } public String getSsl_enabled() { return Boolean.toString(sslEnabled); } public void setSsl_enabled(String sslEnabled) { this.sslEnabled = YamlUtils.parseBoolean(sslEnabled); } public boolean isSslEnabled() { return sslEnabled; } public String getPrecompilation_enabled() { return Boolean.toString(precompilationEnabled); } public boolean isPrecompilationEnabled() { return precompilationEnabled; } public void setPrecompilation_enabled(String precompilationEnabled) { this.precompilationEnabled = YamlUtils.parseBoolean(precompilationEnabled); } public String getSessions_enabled() { return Boolean.toString(sessionsEnabled); } public boolean isSessionsEnabled() { return sessionsEnabled; } public void setSessions_enabled(String sessionsEnabled) { this.sessionsEnabled = YamlUtils.parseBoolean(sessionsEnabled); } public AsyncSessionPersistence getAsync_session_persistence() { return asyncSessionPersistence; } public void setAsync_session_persistence(AsyncSessionPersistence asyncSessionPersistence) { this.asyncSessionPersistence = asyncSessionPersistence; } public String getThreadsafe() { return Boolean.toString(threadsafe); } public boolean isThreadsafeSet() { return threadsafeWasSet; } public void setThreadsafe(String threadsafe) { this.threadsafe = YamlUtils.parseBoolean(threadsafe); threadsafeWasSet = true; } public String getAuto_id_policy() { return autoIdPolicy; } public void setAuto_id_policy(String policy) { autoIdPolicy = policy; } public String getApi_version() { return apiVersion; } public void setApi_version(String version) { apiVersion = version; } public String getCode_lock() { return Boolean.toString(codeLock); } public void setCode_lock(String codeLock) { this.codeLock = YamlUtils.parseBoolean(codeLock); } public Map getSystem_properties() { return systemProperties; } public void setSystem_properties(Map systemProperties) { this.systemProperties = ImmutableSortedMap.copyOf(systemProperties); } public Map getEnv_variables() { return envVariables; } public void setEnv_variables(Map envVariables) { this.envVariables = ImmutableSortedMap.copyOf(envVariables); } public Map getBuild_env_variables() { return buildEnvVariables; } public void setBuild_env_variables(Map buildEnvVariables) { this.buildEnvVariables = ImmutableSortedMap.copyOf(buildEnvVariables); } public Map getBeta_settings() { return betaSettings; } public void setBeta_settings(Map betaSettings) { this.betaSettings = ImmutableSortedMap.copyOf(betaSettings); } public List getWelcome_files() { return welcomeFiles; } public void setWelcome_files(List welcomeFiles) { this.welcomeFiles = ImmutableList.copyOf(welcomeFiles); } public Map getContext_params() { return contextParams; } public void setContext_params(Map contextParams) { this.contextParams = ImmutableSortedMap.copyOf(contextParams); } public List getListeners() { return listeners; } public void setListeners(List listeners) { this.listeners = ImmutableList.copyOf(listeners); } public String getWeb_xml() { return webXml; } public void setWeb_xml(String webXml) { this.webXml = webXml; } public List getInbound_services() { return inboundServices; } public void setInbound_services(List inboundServices) { this.inboundServices = ImmutableList.copyOf(inboundServices); } public List getDerived_file_type() { return derivedFileType; } public void setDerived_file_type(List derivedFileType) { this.derivedFileType = ImmutableList.copyOf(derivedFileType); } public AdminConsole getAdmin_console() { return adminConsole; } public void setAdmin_console(AdminConsole adminConsole) { this.adminConsole = adminConsole; } public List getError_handlers() { return errorHandlers; } public void setError_handlers(List errorHandlers) { this.errorHandlers = ImmutableList.copyOf(errorHandlers); } public ApiConfig getApi_config() { return apiConfig; } public void setApi_config(ApiConfig apiConfig) { this.apiConfig = apiConfig; if (handlers != null) { this.apiConfig.setHandlers(handlers); } } public AppYaml applyPlugins() { AppYaml yaml = this; for (Plugin plugin : PluginLoader.loadPlugins(Plugin.class)) { AppYaml modified = plugin.process(yaml); if (modified != null) { yaml = modified; } } return yaml; } /** * Represents an api-config: top level app.yaml stanza * This is a singleton specifying url: and servlet: for the api config server. */ public static class ApiConfig { private String url; private String servlet; private ImmutableList handlers; public void setHandlers(List handlers) { this.handlers = ImmutableList.copyOf(handlers); } public String getUrl() { return url; } public void setUrl(String url) { YamlUtils.validateUrl(url); this.url = url; } public String getServlet() { return servlet; } public void setServlet(String servlet) { this.servlet = servlet; } private void generateXml(XmlWriter xml) { xml.startElement("api-config", "servlet-class", getServlet(), "url-pattern", getUrl()); if (handlers != null) { for (Handler handler : handlers) { handler.generateEndpointServletMappingId(xml); } } xml.endElement("api-config"); } } private static class XmlWriter { private static final String XML_HEADER = ""; private final PrintWriter writer; private int indent = 0; // This must generate unique ids within each web.xml that is written. private int apiEndpointId = 0; public XmlWriter(Writer w) { writer = new PrintWriter(w); writer.println(XML_HEADER); } public void startElement(String name, String... attributes) { startElement(name, false, attributes); writer.println(); } public void startElement(String name, boolean empty, String... attributes) { indent(); writer.print("<"); writer.print(name); for (int i = 0; i < attributes.length; i += 2) { String attributeName = attributes[i]; String value = attributes[i + 1]; if (value != null) { writer.printf(" %s='%s'", attributeName, escapeAttribute(value)); } } if (empty) { writer.println("/>"); } else { writer.print(">"); indent += 2; } } public void endElement(String name) { endElement(name, true); } public void endElement(String name, boolean needIndent) { indent -= 2; if (needIndent) { indent(); } writer.print(""); } public void emptyElement(String name, String... attributes) { startElement(name, true, attributes); } public void simpleElement(String name, String value, String... attributes) { startElement(name, false, attributes); writer.print(escapeContent(value)); endElement(name, false); } public void writeUnescaped(String xmlContent) { writer.println(xmlContent); } private void indent() { for (int i = 0; i < indent; i++) { writer.print(" "); } } private String escapeContent(String value) { if (value == null) { return null; } return XmlEscapers.xmlContentEscaper().escape(value); } private String escapeAttribute(String value) { if (value == null) { return null; } return XmlEscapers.xmlAttributeEscaper().escape(value); } private String nextApiEndpointId() { return String.format("endpoint-%1$d", ++apiEndpointId); } } private void addOptionalElement(XmlWriter xml, String name, String value) { if (value != null) { xml.simpleElement(name, value); } } public void generateAppEngineWebXml(Writer writer) { XmlWriter xml = new XmlWriter(writer); xml.startElement("appengine-web-app", "xmlns", "http://appengine.google.com/ns/1.0"); xml.simpleElement("application", getApplication()); addOptionalElement(xml, "version", getVersion()); addOptionalElement(xml, "runtime", getRuntime()); addOptionalElement(xml, "module", getModule()); addOptionalElement(xml, "instance-class", getInstance_class()); addOptionalElement(xml, "public-root", publicRoot); addOptionalElement(xml, "auto-id-policy", getAuto_id_policy()); if (automaticScaling != null) { xml.startElement("automatic-scaling"); addOptionalElement(xml, "min-pending-latency", automaticScaling.getMin_pending_latency()); addOptionalElement(xml, "max-pending-latency", automaticScaling.getMax_pending_latency()); addOptionalElement(xml, "min-idle-instances", automaticScaling.getMin_idle_instances()); addOptionalElement(xml, "max-idle-instances", automaticScaling.getMax_idle_instances()); addOptionalElement(xml, "min-instances", automaticScaling.getMin_instances()); addOptionalElement(xml, "max-instances", automaticScaling.getMax_instances()); addOptionalElement( xml, "target-cpu-utilization", automaticScaling.getTarget_cpu_utilization()); addOptionalElement( xml, "target-throughput-utilization", automaticScaling.getTarget_throughput_utilization()); addOptionalElement(xml, "max-concurrent-requests", automaticScaling.getMax_concurrent_requests()); xml.endElement("automatic-scaling"); } if (manualScaling != null) { xml.startElement("manual-scaling"); xml.simpleElement("instances", manualScaling.getInstances()); xml.endElement("manual-scaling"); } if (basicScaling != null) { xml.startElement("basic-scaling"); xml.simpleElement("max-instances", basicScaling.getMax_instances()); addOptionalElement(xml, "idle-timeout", basicScaling.getIdle_timeout()); xml.endElement("basic-scaling"); } xml.startElement("static-files"); if (staticFiles != null) { for (StaticFile file : staticFiles) { if (file.getInclude() != null) { generateInclude(file, xml); } else { xml.emptyElement("exclude", /* attributes: */ "path", file.getExclude()); } } } xml.endElement("static-files"); xml.startElement("resource-files"); if (resourceFiles != null) { for (ResourceFile file : resourceFiles) { String name; String path; if (file.getInclude() != null) { name = "include"; path = file.getInclude(); } else { name = "exclude"; path = file.getExclude(); } xml.emptyElement(name, "path", path); } } xml.endElement("resource-files"); xml.simpleElement("ssl-enabled", getSsl_enabled()); xml.simpleElement("precompilation-enabled", getPrecompilation_enabled()); if (isThreadsafeSet()) { xml.simpleElement("threadsafe", getThreadsafe()); } xml.simpleElement("code-lock", getCode_lock()); xml.simpleElement("sessions-enabled", getSessions_enabled()); if (asyncSessionPersistence != null) { xml.simpleElement("async-session-persistence", null, "enabled", getAsync_session_persistence().getEnabled(), "queue-name", getAsync_session_persistence().getQueue_name()); } if (systemProperties != null) { xml.startElement("system-properties"); systemProperties.forEach( (name, value) -> xml.emptyElement("property", "name", name, "value", value)); xml.endElement("system-properties"); } if (envVariables != null) { xml.startElement("env-variables"); envVariables.forEach( (name, value) -> xml.emptyElement("env-var", "name", name, "value", value)); xml.endElement("env-variables"); } if (buildEnvVariables != null) { xml.startElement("build-env-variables"); buildEnvVariables.forEach( (name, value) -> xml.emptyElement("build-env-var", "name", name, "value", value)); xml.endElement("build-env-variables"); } if (betaSettings != null) { xml.startElement("beta-settings"); betaSettings.forEach( (name, value) -> xml.emptyElement("beta-setting", "name", name, "value", value)); xml.endElement("beta-settings"); } boolean warmupService = false; if (inboundServices != null) { xml.startElement("inbound-services"); for (String service : inboundServices) { if (AppEngineWebXml.WARMUP_SERVICE.equals(service)) { warmupService = true; } else { xml.simpleElement("service", service); } } xml.endElement("inbound-services"); } xml.simpleElement("warmup-requests-enabled", Boolean.toString(warmupService)); if (adminConsole != null && adminConsole.getPages() != null) { xml.startElement("admin-console"); for (AdminPage page : adminConsole.getPages()) { xml.emptyElement("page", "name", page.getName(), "url", page.getUrl()); } xml.endElement("admin-console"); } if (errorHandlers != null) { xml.startElement("static-error-handlers"); for (ErrorHandler handler : errorHandlers) { xml.emptyElement("handler", "file", handler.getFile(), "error-code", handler.getError_code()); } xml.endElement("static-error-handlers"); } if (apiConfig != null) { apiConfig.generateXml(xml); } xml.endElement("appengine-web-app"); } /** * Generates the {@code servlet}, {@code servlet-mapping}, {@code filter}, and * {@code filter-mapping} elements of web.xml corresponding to the {@link #handlers} list. There * may be multiple {@link Handler handlers} corresponding to the same servlet or filter, because a * single handler can only specify one URL pattern and the user may wish to map several URL * patterns to the same servlet or filter. In this case we want to have multiple * {@code servlet-mapping} or {@code filter-mapping} elements but only a single {@code servlet} or * {@code filter} element. */ private void generateHandlerXml(XmlWriter xmlWriter) { if (handlers == null) { return; } // Merge handlers for the same servlet or the same filter Map servletsByName = Maps.newLinkedHashMapWithExpectedSize(handlers.size()); Map filtersByName = Maps.newLinkedHashMapWithExpectedSize(handlers.size()); for (Handler handler : handlers) { String name = handler.getName(); if (name != null) { Handler.Type type = handler.getType(); boolean isServlet = (type == Handler.Type.SERVLET || type == Handler.Type.JSP); boolean isFilter = (type == Handler.Type.FILTER); Handler existing = (isServlet ? servletsByName.get(name) : filtersByName.get(name)); if (existing != null) { existing.mergeDefinitions(handler); } else { if (isServlet) { servletsByName.put(name, handler); } if (isFilter) { filtersByName.put(name, handler); } } } } for (Handler handler : servletsByName.values()) { handler.generateDefinitionXml(xmlWriter); } for (Handler handler : filtersByName.values()) { handler.generateDefinitionXml(xmlWriter); } for (Handler handler : handlers) { handler.generateMappingXml(xmlWriter); } } public void generateWebXml(Writer writer) { XmlWriter xml = new XmlWriter(writer); xml.startElement("web-app", "version", "2.5", "xmlns", "http://java.sun.com/xml/ns/javaee", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" ); generateHandlerXml(xml); if (contextParams != null) { contextParams.forEach( (name, value) -> { xml.startElement("context-param"); xml.simpleElement("param-name", name); xml.simpleElement("param-value", value); xml.endElement("context-param"); } ); } if (welcomeFiles != null) { xml.startElement("welcome-file-list"); for (String file : welcomeFiles) { xml.simpleElement("welcome-file", file); } xml.endElement("welcome-file-list"); } if (listeners != null) { for (String listener : listeners) { xml.startElement("listener"); xml.simpleElement("listener-class", listener); xml.endElement("listener"); } } if (webXml != null) { xml.writeUnescaped(webXml); } xml.endElement("web-app"); } public static AppYaml parse(Reader reader) { try { AppYaml appYaml = YamlUtils.parse(reader, AppYaml.class); if (appYaml == null) { throw new YamlException("Unable to parse yaml file"); } return appYaml.applyPlugins(); } catch (YamlException e) { // If an inner exception is already an AppEngineConfigException then // just re-throw it without re-wrapping Throwable innerException = e.getCause(); while (innerException != null) { if (innerException instanceof AppEngineConfigException) { throw (AppEngineConfigException) innerException; } innerException = innerException.getCause(); } throw new AppEngineConfigException(e.getMessage(), e); } } public static AppYaml parse(String yaml) { return parse(new StringReader(yaml)); } private void generateInclude(StaticFile include, XmlWriter xml) { Map httpHeaders = include.getHttp_headers(); if (httpHeaders == null || httpHeaders.isEmpty()) { xml.emptyElement( "include", "path", include.getInclude(), "expiration", include.getExpiration()); } else { xml.startElement( "include", false, // not empty "path", include.getInclude(), "expiration", include.getExpiration()); httpHeaders.forEach( (name, value) -> xml.emptyElement("http-header", "name", name, "value", value)); xml.endElement("include"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy