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

com.google.appengine.tools.development.AbstractContainerService Maven / Gradle / Ivy

Go to download

SDK for dev_appserver (local development) with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.31
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.appengine.tools.development;

import static com.google.appengine.tools.development.LocalEnvironment.DEFAULT_VERSION_HOSTNAME;

import com.google.appengine.api.backends.BackendService;
import com.google.appengine.tools.development.ApplicationConfigurationManager.ModuleConfigurationHandle;
import com.google.appengine.tools.info.AppengineSdk;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.apphosting.utils.config.AppEngineWebXml;
import com.google.apphosting.utils.config.ClassPathBuilder;
import com.google.apphosting.utils.config.WebModule;
import com.google.apphosting.utils.config.WebXml;
import com.google.appengine.repackaged.com.google.common.base.Joiner;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.Permissions;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Common implementation for the {@link ContainerService} interface.
 *
 * 

There should be no reference to any third-party servlet container from here. * */ public abstract class AbstractContainerService implements ContainerService { private static final Logger log = Logger.getLogger(AbstractContainerService.class.getName()); protected static final String AH_URL_RELOAD = "/_ah/reloadwebapp"; private static final String USER_CODE_CLASSPATH_MANAGER_PROP = "devappserver.userCodeClasspathManager"; private static final String USER_CODE_CLASSPATH = USER_CODE_CLASSPATH_MANAGER_PROP + ".classpath"; private static final String USER_CODE_REQUIRES_WEB_INF = USER_CODE_CLASSPATH_MANAGER_PROP + ".requiresWebInf"; public static final String PORT_MAPPING_PROVIDER_PROP = "devappserver.portMappingProvider"; // Begin members that are set via configure() protected ModuleConfigurationHandle moduleConfigurationHandle; protected String devAppServerVersion; protected File appDir; protected File externalResourceDir; /** * The location of web.xml. If not provided, defaults to * {@code /WEB-INF/web.xml} */ protected File webXmlLocation; /** * The hostname on which the module instance is listening for http requests. */ protected String hostName; /** * The network address on which the module instance is listening for http requests. */ protected String address; /** * The port on which the module instance is listening for http requests. */ protected int port; /** * The 0 based index for this instance or {@link LocalEnvironment#MAIN_INSTANCE}. */ protected int instance; /** * A reference to the parent DevAppServer that configured this container. */ protected DevAppServer devAppServer; // End members that are set via configure() protected AppEngineWebXml appEngineWebXml; protected WebXml webXml; // The backend name if this container is for a back end and otherwise null. protected String backendName; // The backend instance id if this container is for a back end instance and // otherwise null. protected int backendInstance; // Provider for the port mapping required by backend service. Note we use a provider // because this is passed to us in configure which is called before the full port // mapping is available. The advantage to this approach is that it avoids changing // the public ContainerService interface and hence avoids exposing our management // of port mappings to users. protected PortMappingProvider portMappingProvider = new PortMappingProvider() { @Override public Map getPortMapping() { return Collections.emptyMap(); } }; /** * Latch that will open once the module instance is fully initialized. TODO: This is used by some * services but only for the default instance of the default module. Investigate. Does module * start/stop cause issues? There is some issue with tasks during Servlet initialization. */ private CountDownLatch moduleInitLatch; /** * Not initialized until {@link #startup()} has been called. */ protected ApiProxy.Delegate apiProxyDelegate; protected UserCodeClasspathManager userCodeClasspathManager; protected ModulesFilterHelper modulesFilterHelper; @Override public final LocalServerEnvironment configure(String devAppServerVersion, final String address, int port, final ModuleConfigurationHandle moduleConfigurationHandle, File externalResourceDir, Map containerConfigProperties, int instance, DevAppServer devAppServer) { this.devAppServerVersion = devAppServerVersion; this.moduleConfigurationHandle = moduleConfigurationHandle; extractFieldsFromWebModule(moduleConfigurationHandle.getModule()); this.externalResourceDir = externalResourceDir; this.address = address; this.port = port; this.moduleInitLatch = new CountDownLatch(1); this.hostName = "localhost"; this.devAppServer = devAppServer; if ("0.0.0.0".equals(address)) { try { InetAddress localhost = InetAddress.getLocalHost(); this.hostName = localhost.getHostName(); } catch (UnknownHostException ex) { log.log(Level.WARNING, "Unable to determine hostname - defaulting to localhost."); } } this.userCodeClasspathManager = newUserCodeClasspathProvider(containerConfigProperties); this.modulesFilterHelper = (ModulesFilterHelper) containerConfigProperties.get(DevAppServerImpl.MODULES_FILTER_HELPER_PROPERTY); this.backendName = (String) containerConfigProperties.get(BackendService.BACKEND_ID_ENV_ATTRIBUTE); Object rawBackendInstance = containerConfigProperties.get(BackendService.INSTANCE_ID_ENV_ATTRIBUTE); this.backendInstance = rawBackendInstance == null ? -1 : ((Integer) rawBackendInstance).intValue(); PortMappingProvider callersPortMappingProvider = (PortMappingProvider) containerConfigProperties.get(PORT_MAPPING_PROVIDER_PROP); if (callersPortMappingProvider == null) { log.warning("Null value for containerConfigProperties.get(" + PORT_MAPPING_PROVIDER_PROP + ")"); } else { this.portMappingProvider = callersPortMappingProvider; } this.instance = instance; return new LocalServerEnvironment() { @Override public File getAppDir() { return moduleConfigurationHandle.getModule().getApplicationDirectory(); } @Override public String getAddress() { return address; } @Override public String getHostName() { return hostName; } @Override public int getPort() { // It's important that we return the value of the member rather than // the value of the param because the param value may not reflect the // actual port to which the module instance is bound if the module // instance is selecting its own port. The member is guaranteed to // contain the actual port after startup() has been called. return AbstractContainerService.this.port; } @Override public void waitForServerToStart() throws InterruptedException { moduleInitLatch.await(); } @Override public boolean simulateProductionLatencies() { return false; } @Override public boolean enforceApiDeadlines() { return !Boolean.getBoolean("com.google.appengine.disable_api_deadlines"); } }; } @Override public void setApiProxyDelegate(ApiProxy.Delegate apiProxyDelegate) { this.apiProxyDelegate = apiProxyDelegate; } /** * @param webModule */ protected void extractFieldsFromWebModule(WebModule webModule) { this.appDir = webModule.getApplicationDirectory(); this.webXml = webModule.getWebXml(); this.webXmlLocation = webModule.getWebXmlFile(); this.appEngineWebXml = webModule.getAppEngineWebXml(); } /** * Constructs a {@link UserCodeClasspathManager} from the given properties. */ private static UserCodeClasspathManager newUserCodeClasspathProvider( Map containerConfigProperties) { // TODO: Support a mode where we combine this classpath with the // classpath generated from the war. if (containerConfigProperties.containsKey(USER_CODE_CLASSPATH_MANAGER_PROP)) { // If this key exists then the caller wants to customize the classpath // manager. @SuppressWarnings("unchecked") final Map userCodeClasspathManagerProps = (Map) containerConfigProperties.get(USER_CODE_CLASSPATH_MANAGER_PROP); return new UserCodeClasspathManager() { @SuppressWarnings("unchecked") @Override public Collection getUserCodeClasspath(File root) { return (Collection) userCodeClasspathManagerProps.get(USER_CODE_CLASSPATH); } @Override public boolean requiresWebInf() { return (Boolean) userCodeClasspathManagerProps.get(USER_CODE_REQUIRES_WEB_INF); } }; } // No customization, just use the default implementation. return new WebAppUserCodeClasspathManager(); } @Override public final void createConnection() throws Exception { connectContainer(); } @Override public final void startup() throws Exception { // InitContext installs an Environment for initializing the // container. We preserve and restore the caller's // Environment in case this is called from an HTTP request. Environment prevEnvironment = ApiProxy.getCurrentEnvironment(); try { initContext(); if (appEngineWebXml == null) { throw new IllegalStateException("initContext failed to initialize appEngineWebXml."); } startContainer(); startHotDeployScanner(); moduleInitLatch.countDown(); } catch (Exception e) { throw e; } finally { ApiProxy.setEnvironmentForCurrentThread(prevEnvironment); } } @Override public final void shutdown() throws Exception { stopHotDeployScanner(); stopContainer(); // TODO: shutdown is generally called for application level shutdown. // The exception is AbstractBackendServers.stopBackend which // stops a single back end. In that case clearing the system // properties for all modules seems wrong. moduleConfigurationHandle.restoreSystemProperties(); } @Override public Map getServiceProperties() { return ImmutableMap.of("appengine.dev.inbound-services", Joiner.on(",").useForNull("null").join(appEngineWebXml.getInboundServices())); } // the actual "API" for a concrete implementation /** * Set up the webapp context in a container specific way. *

Note that {@link #initContext()} is required to call * {@link #installLocalInitializationEnvironment()} for the service to be correctly * initialized. * * @return the effective webapp directory. */ protected abstract File initContext() throws IOException; /** * Creates the servlet container's network connections. */ protected abstract void connectContainer() throws Exception; /** * Start up the servlet container runtime. */ protected abstract void startContainer() throws Exception; /** * Stop the servlet container runtime. */ protected abstract void stopContainer() throws Exception; /** Start up the hot-deployment scanner. */ // TODO: we may want to make this configurable. protected abstract void startHotDeployScanner() throws Exception; /** * Stop the hot-deployment scanner. */ protected abstract void stopHotDeployScanner() throws Exception; /** * Re-deploy the current webapp context in a container specific way, * while taking into account possible appengine-web.xml change too, * without restarting the module instance. */ protected abstract void reloadWebApp() throws Exception; @Override public String getAddress() { return address; } @Override public AppEngineWebXml getAppEngineWebXmlConfig(){ return appEngineWebXml; } @Override public int getPort() { return port; } @Override public String getHostName() { return hostName; } protected Permissions getUserPermissions() { return appEngineWebXml.getUserPermissions(); } // common utils /** * Installs a {@link LocalInitializationEnvironment} with * {@link ApiProxy#setEnvironmentForCurrentThread}. *

* Filters and servlets get initialized when we call server.start(). If any of * those filters and servlets need access to the current execution environment * they'll call ApiProxy.getCurrentEnvironment(). We set a special initialization * environment so that there is an environment available when this happens. *

* This depends on port which may not be set to its final value until {@link #connectContainer()} * is called. */ protected void installLocalInitializationEnvironment() { installLocalInitializationEnvironment(appEngineWebXml, instance, port, devAppServer.getPort(), backendName, backendInstance, portMappingProvider.getPortMapping()); } /** Returns {@code true} if appengine-web.xml {@code } is true. */ protected boolean isSessionsEnabled() { return appEngineWebXml.getSessionsEnabled(); } /** * Gets all of the URLs that should be added to the classpath for an * application located at {@code root}. */ protected URL[] getClassPathForApp(File root) { // N.B.: Do not use File.toURI().toURL() here, as that // will cause the file to be URL quoted (e.g. spaces replaced with // %20's). URLClassLoader seems to cope with this okay, but // Jasper's JSP compiler uses its own classpath to populate the // -cp argument for the javac command it launches, and -cp doesn't // understand URL-encoded classpaths. File.toURL() is deprecated, // but it does not URL-encode the returned file. ClassPathBuilder classPathBuilder = new ClassPathBuilder(appEngineWebXml.getClassLoaderConfig()); classPathBuilder.addUrls(userCodeClasspathManager.getUserCodeClasspath(root)); classPathBuilder.addUrls(AppengineSdk.getSdk().getUserJspLibs()); return getUrls(classPathBuilder); } // Returns the urls from classPathBuilder and logs a message if needed. private static URL[] getUrls(ClassPathBuilder classPathBuilder) { URL[] urls = classPathBuilder.getUrls(); String message = classPathBuilder.getLogMessage(); if (!message.isEmpty()) { log.warning(message); } return urls; } /** * Sets up an {@link com.google.apphosting.api.ApiProxy.Environment} for container * initialization. */ public static void installLocalInitializationEnvironment(AppEngineWebXml appEngineWebXml, int instance, int port, int defaultModuleMainPort, String backendName, int backendInstance, Map portMapping) { Environment environment = new LocalInitializationEnvironment( appEngineWebXml.getAppId(), WebModule.getModuleName(appEngineWebXml), appEngineWebXml.getMajorVersionId(), instance, port); environment.getAttributes().put(DEFAULT_VERSION_HOSTNAME, "localhost:" + defaultModuleMainPort); ApiProxy.setEnvironmentForCurrentThread(environment); DevAppServerModulesCommon.injectBackendServiceCurrentApiInfo( backendName, backendInstance, portMapping); } /** * A fake {@link LocalEnvironment} implementation that is used during the * initialization of the Development AppServer. */ public static class LocalInitializationEnvironment extends LocalEnvironment { public LocalInitializationEnvironment(String appId, String moduleName, String majorVersionId, int instance, int port) { super(appId, moduleName, majorVersionId, instance, port, null); } @Override public String getEmail() { // No user return null; } @Override public boolean isLoggedIn() { // No user return false; } @Override public boolean isAdmin() { // No user return false; } } /** * Provider for the 'portMapping'. *

* The provided map contains an entry for every backend instance. * For the main instance the key is the backend name and the value is * the hostname:port for sending http requests to the instance (i.e. * bob->127.0.0.1:1234). For other instances the key is * instance-id.hostname and the value is again the hostname:port for * sending http requests to the instance (i.e. 2.bob->127.0.0.1:1234). */ public interface PortMappingProvider { Map getPortMapping(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy