From b4542f722f9ed19ba63de9222427ba716df9a710 Mon Sep 17 00:00:00 2001 From: Sergey Shcherbakov Date: Wed, 14 Aug 2013 20:49:52 +0200 Subject: [PATCH] Support for embedded Tomcat 8 container parallel to the current Tomcat 7 --- pom.xml | 1 + .../spring-boot-tomcat8/pom.xml | 47 +++ ...etContextInitializerLifecycleListener.java | 63 +++ .../tomcat8/TomcatContextCustomizer.java | 31 ++ .../TomcatEmbeddedServletContainer.java | 112 +++++ ...TomcatEmbeddedServletContainerFactory.java | 383 ++++++++++++++++++ .../TomcatEmbeddedWebappClassLoader.java | 115 ++++++ .../embedded/tomcat8/package-info.java | 21 + 8 files changed, 773 insertions(+) create mode 100644 spring-boot-containers/spring-boot-tomcat8/pom.xml create mode 100644 spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/ServletContextInitializerLifecycleListener.java create mode 100644 spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatContextCustomizer.java create mode 100644 spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainer.java create mode 100644 spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainerFactory.java create mode 100644 spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedWebappClassLoader.java create mode 100644 spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/package-info.java diff --git a/pom.xml b/pom.xml index 96c26b71387..81994113773 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ spring-boot-starters spring-boot-cli spring-boot-integration-tests + spring-boot-containers/spring-boot-tomcat8 diff --git a/spring-boot-containers/spring-boot-tomcat8/pom.xml b/spring-boot-containers/spring-boot-tomcat8/pom.xml new file mode 100644 index 00000000000..e386511fc39 --- /dev/null +++ b/spring-boot-containers/spring-boot-tomcat8/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-parent + 0.5.0.BUILD-SNAPSHOT + ../../spring-boot-parent + + spring-boot-tomcat8 + jar + + ${basedir}/../.. + 8.0.0-RC1 + + + + + ${project.groupId} + spring-boot + ${project.version} + + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat8.version} + true + + + + + + + maven-jar-plugin + + + + test-jar + + + + + + + diff --git a/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/ServletContextInitializerLifecycleListener.java b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/ServletContextInitializerLifecycleListener.java new file mode 100644 index 00000000000..72c2f8d9410 --- /dev/null +++ b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/ServletContextInitializerLifecycleListener.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 + * + * http://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 org.springframework.boot.context.embedded.tomcat8; + +import javax.servlet.ServletException; + +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.core.StandardContext; +import org.springframework.boot.context.embedded.ServletContextInitializer; +import org.springframework.util.Assert; + +/** + * Tomcat {@link LifecycleListener} that calls {@link ServletContextInitializer}s. + * + * @author Phillip Webb + */ +public class ServletContextInitializerLifecycleListener implements LifecycleListener { + + private ServletContextInitializer[] initializers; + + /** + * Create a new {@link ServletContextInitializerLifecycleListener} instance with the + * specified initializers. + * @param initializers the initializers to call + */ + public ServletContextInitializerLifecycleListener( + ServletContextInitializer... initializers) { + this.initializers = initializers; + } + + @Override + public void lifecycleEvent(LifecycleEvent event) { + if (Lifecycle.CONFIGURE_START_EVENT.equals(event.getType())) { + Assert.isInstanceOf(StandardContext.class, event.getSource()); + StandardContext standardContext = (StandardContext) event.getSource(); + for (ServletContextInitializer initializer : this.initializers) { + try { + initializer.onStartup(standardContext.getServletContext()); + } + catch (ServletException ex) { + throw new IllegalStateException(ex); + } + } + } + } + +} diff --git a/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatContextCustomizer.java b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatContextCustomizer.java new file mode 100644 index 00000000000..f7a7ebc3939 --- /dev/null +++ b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatContextCustomizer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * 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 + * + * http://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 org.springframework.boot.context.embedded.tomcat8; + +import org.apache.catalina.Context; + +/** + * @author Dave Syer + */ +public interface TomcatContextCustomizer { + + /** + * @param context the context to customize + */ + void customize(Context context); + +} diff --git a/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainer.java b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainer.java new file mode 100644 index 00000000000..6b8b5a5052e --- /dev/null +++ b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainer.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 + * + * http://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 org.springframework.boot.context.embedded.tomcat8; + +import org.apache.catalina.LifecycleException; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.startup.Tomcat; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerException; +import org.springframework.util.Assert; + +/** + * {@link EmbeddedServletContainer} that can be used to control an embedded Tomcat server. + * Usually this class should be created using the + * {@link TomcatEmbeddedServletContainerFactory} and not directly. + * + * @author Phillip Webb + * @author Dave Syer + * @see TomcatEmbeddedServletContainerFactory + */ +public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer { + + private final Log logger = LogFactory.getLog(TomcatEmbeddedServletContainer.class); + + private static int containerCounter = 0; + + private final Tomcat tomcat; + + /** + * Create a new {@link TomcatEmbeddedServletContainer} instance. + * @param tomcat the underlying Tomcat server + */ + public TomcatEmbeddedServletContainer(Tomcat tomcat) { + Assert.notNull(tomcat, "Tomcat Server must not be null"); + this.tomcat = tomcat; + initialize(); + } + + private synchronized void initialize() throws EmbeddedServletContainerException { + try { + this.tomcat.start(); + // Unlike Jetty, all Tomcat threads are daemon threads. We create a + // blocking non-daemon to stop immediate shutdown + Thread awaitThread = new Thread("container-" + (containerCounter++)) { + @Override + public void run() { + TomcatEmbeddedServletContainer.this.tomcat.getServer().await(); + }; + }; + awaitThread.setDaemon(false); + awaitThread.start(); + } + catch (Exception ex) { + throw new EmbeddedServletContainerException( + "Unable to start embdedded Tomcat", ex); + } + } + + @Override + public void start() throws EmbeddedServletContainerException { + Connector connector = this.tomcat.getConnector(); + if (connector != null) { + try { + connector.getProtocolHandler().resume(); + } + catch (Exception e) { + this.logger.error("Cannot start connector: ", e); + } + } + } + + @Override + public synchronized void stop() throws EmbeddedServletContainerException { + try { + try { + this.tomcat.stop(); + } + catch (LifecycleException ex) { + // swallow and continue + } + this.tomcat.destroy(); + } + catch (Exception ex) { + throw new EmbeddedServletContainerException( + "Unable to stop embdedded Tomcat", ex); + } + } + + /** + * Returns access to the underlying Tomcat server. + */ + public Tomcat getTomcat() { + return this.tomcat; + } + +} diff --git a/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainerFactory.java b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainerFactory.java new file mode 100644 index 00000000000..3bb0bb0a04c --- /dev/null +++ b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedServletContainerFactory.java @@ -0,0 +1,383 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 + * + * http://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 org.springframework.boot.context.embedded.tomcat8; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.apache.catalina.Context; +import org.apache.catalina.Host; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.Valve; +import org.apache.catalina.Wrapper; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.loader.WebappLoader; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.Tomcat.FixContextListener; +import org.apache.coyote.AbstractProtocol; +import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerException; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.ErrorPage; +import org.springframework.boot.context.embedded.MimeMappings; +import org.springframework.boot.context.embedded.ServletContextInitializer; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * {@link EmbeddedServletContainerFactory} that can be used to create + * {@link TomcatEmbeddedServletContainer}s. Can be initialized using Spring's + * {@link ServletContextInitializer}s or Tomcat {@link LifecycleListener}s. + * + *

+ * Unless explicitly configured otherwise this factory will created containers that + * listens for HTTP requests on port 8080. + * + * @author Phillip Webb + * @author Dave Syer + * @see #setPort(int) + * @see #setContextLifecycleListeners(Collection) + * @see TomcatEmbeddedServletContainer + */ +public class TomcatEmbeddedServletContainerFactory extends + AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware { + + private static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol"; + + private File baseDirectory; + + private List contextValves = new ArrayList(); + + private List contextLifecycleListeners = new ArrayList(); + + private List tomcatContextCustomizers = new ArrayList(); + + private ResourceLoader resourceLoader; + + private String protocol = DEFAULT_PROTOCOL; + + /** + * Create a new {@link TomcatEmbeddedServletContainerFactory} instance. + */ + public TomcatEmbeddedServletContainerFactory() { + super(); + } + + /** + * Create a new {@link TomcatEmbeddedServletContainerFactory} that listens for + * requests using the specified port. + * @param port the port to listen on + */ + public TomcatEmbeddedServletContainerFactory(int port) { + super(port); + } + + /** + * Create a new {@link TomcatEmbeddedServletContainerFactory} with the specified + * context path and port. + * @param contextPath root the context path + * @param port the port to listen on + */ + public TomcatEmbeddedServletContainerFactory(String contextPath, int port) { + super(contextPath, port); + } + + @Override + public EmbeddedServletContainer getEmbeddedServletContainer( + ServletContextInitializer... initializers) { + + Connector connector; + Tomcat tomcat = new Tomcat(); + + if (getPort() == 0) { + return EmbeddedServletContainer.NONE; + } + File baseDir = (this.baseDirectory != null ? this.baseDirectory + : createTempDir("tomcat")); + tomcat.setBaseDir(baseDir.getAbsolutePath()); + connector = new Connector(this.protocol); + customizeConnector(connector); + tomcat.getService().addConnector(connector); + tomcat.setConnector(connector); + try { + // Allow the server to start so the ServletContext is available, but stop the + // connector to prevent requests from being handled before the Spring context + // is ready: + connector.getProtocolHandler().pause(); + } + catch (Exception e) { + this.logger.error("Cannot pause connector: ", e); + } + tomcat.getHost().setAutoDeploy(false); + tomcat.getEngine().setBackgroundProcessorDelay(-1); + + prepareContext(tomcat.getHost(), initializers); + return getTomcatEmbeddedServletContainer(tomcat); + } + + protected void prepareContext(Host host, ServletContextInitializer[] initializers) { + File docBase = getValidDocumentRoot(); + docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase")); + Context context = new StandardContext(); + context.setName(getContextPath()); + context.setPath(getContextPath()); + context.setDocBase(docBase.getAbsolutePath()); + context.addLifecycleListener(new FixContextListener()); + context.setParentClassLoader(this.resourceLoader != null ? this.resourceLoader + .getClassLoader() : ClassUtils.getDefaultClassLoader()); + WebappLoader loader = new WebappLoader(context.getParentClassLoader()); + loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); + context.setLoader(loader); + if (isRegisterDefaultServlet()) { + addDefaultServlet(context); + } + if (isRegisterJspServlet() + && ClassUtils.isPresent(getJspServletClassName(), getClass() + .getClassLoader())) { + addJspServlet(context); + } + ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); + configureContext(context, initializersToUse); + host.addChild(context); + postProcessContext(context); + } + + private void addDefaultServlet(Context context) { + Wrapper defaultServlet = context.createWrapper(); + defaultServlet.setName("default"); + defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet"); + defaultServlet.addInitParameter("debug", "0"); + defaultServlet.addInitParameter("listings", "false"); + defaultServlet.setLoadOnStartup(1); + // Otherwise the default location of a Spring DispatcherServlet cannot be set + defaultServlet.setOverridable(true); + context.addChild(defaultServlet); + context.addServletMapping("/", "default"); + } + + private void addJspServlet(Context context) { + Wrapper jspServlet = context.createWrapper(); + jspServlet.setName("jsp"); + jspServlet.setServletClass(getJspServletClassName()); + jspServlet.addInitParameter("fork", "false"); + jspServlet.setLoadOnStartup(3); + context.addChild(jspServlet); + context.addServletMapping("*.jsp", "jsp"); + context.addServletMapping("*.jspx", "jsp"); + } + + // Needs to be protected so it can be used by subclasses + protected void customizeConnector(Connector connector) { + connector.setPort(getPort()); + if (connector.getProtocolHandler() instanceof AbstractProtocol + && getAddress() != null) { + ((AbstractProtocol) connector.getProtocolHandler()).setAddress(getAddress()); + } + } + + /** + * Configure the Tomcat {@link Context}. + * @param context the Tomcat context + * @param initializers initializers to apply + */ + protected void configureContext(Context context, + ServletContextInitializer[] initializers) { + context.addLifecycleListener(new ServletContextInitializerLifecycleListener( + initializers)); + for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { + context.addLifecycleListener(lifecycleListener); + } + for (Valve valve : this.contextValves) { + context.getPipeline().addValve(valve); + } + for (ErrorPage errorPage : getErrorPages()) { + org.apache.tomcat.util.descriptor.web.ErrorPage tomcatPage = new org.apache.tomcat.util.descriptor.web.ErrorPage(); + tomcatPage.setLocation(errorPage.getPath()); + tomcatPage.setExceptionType(errorPage.getExceptionName()); + tomcatPage.setErrorCode(errorPage.getStatusCode()); + context.addErrorPage(tomcatPage); + } + for (MimeMappings.Mapping mapping : getMimeMappings()) { + context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); + } + context.setSessionTimeout(getSessionTimeout()); + for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) { + customizer.customize(context); + } + } + + /** + * Post process the Tomcat {@link Context} before it used with the Tomcat Server. + * Subclasses can override this method to apply additional processing to the + * {@link Context}. + * @param context the Tomcat {@link Context} + */ + protected void postProcessContext(Context context) { + } + + /** + * Factory method called to create the {@link TomcatEmbeddedServletContainer}. + * Subclasses can override this method to return a different + * {@link TomcatEmbeddedServletContainer} or apply additional processing to the Tomcat + * server. + * @param tomcat the Tomcat server. + * @return a new {@link TomcatEmbeddedServletContainer} instance + */ + protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer( + Tomcat tomcat) { + return new TomcatEmbeddedServletContainer(tomcat); + } + + private File createTempDir(String prefix) { + try { + File tempFolder = File.createTempFile(prefix + ".", "." + getPort()); + tempFolder.delete(); + tempFolder.mkdir(); + tempFolder.deleteOnExit(); + return tempFolder; + } + catch (IOException ex) { + throw new EmbeddedServletContainerException( + "Unable to create Tomcat tempdir", ex); + } + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + /** + * Set the Tomcat base directory. If not specified a temporary directory will be used. + * @param baseDirectory the tomcat base directory + */ + public void setBaseDirectory(File baseDirectory) { + this.baseDirectory = baseDirectory; + } + + /** + * The Tomcat protocol to use when create the {@link Connector}. + * @see Connector#Connector(String) + */ + public void setProtocol(String protocol) { + Assert.hasLength(protocol, "Protocol must not be empty"); + this.protocol = protocol; + } + + /** + * Set {@link Valve}s that should be applied to the Tomcat {@link Context}. Calling + * this method will replace any existing listeners. + * @param contextValves the valves to set + */ + public void setContextValves(Collection contextValves) { + Assert.notNull(contextValves, "Valves must not be null"); + this.contextValves = new ArrayList(contextValves); + } + + /** + * Returns a mutable collection of the {@link Valve}s that will be applied to the + * Tomcat {@link Context}. + * @return the contextValves the valves that will be applied + */ + public Collection getValves() { + return this.contextValves; + } + + /** + * Add {@link Valve}s that should be applied to the Tomcat {@link Context}. + * @param contextValves the valves to add + */ + public void addContextValves(Valve... contextValves) { + Assert.notNull(contextValves, "Valves must not be null"); + this.contextValves.addAll(Arrays.asList(contextValves)); + } + + /** + * Set {@link LifecycleListener}s that should be applied to the Tomcat {@link Context} + * . Calling this method will replace any existing listeners. + * @param contextLifecycleListeners the listeners to set + */ + public void setContextLifecycleListeners( + Collection contextLifecycleListeners) { + Assert.notNull(contextLifecycleListeners, + "ContextLifecycleListeners must not be null"); + this.contextLifecycleListeners = new ArrayList( + contextLifecycleListeners); + } + + /** + * Returns a mutable collection of the {@link LifecycleListener}s that will be applied + * to the Tomcat {@link Context} . + * @return the contextLifecycleListeners the listeners that will be applied + */ + public Collection getContextLifecycleListeners() { + return this.contextLifecycleListeners; + } + + /** + * Add {@link LifecycleListener}s that should be added to the Tomcat {@link Context}. + * @param contextLifecycleListeners the listeners to add + */ + public void addContextLifecycleListeners( + LifecycleListener... contextLifecycleListeners) { + Assert.notNull(contextLifecycleListeners, + "ContextLifecycleListeners must not be null"); + this.contextLifecycleListeners.addAll(Arrays.asList(contextLifecycleListeners)); + } + + /** + * Set {@link TomcatContextCustomizer}s that should be applied to the Tomcat + * {@link Context} . Calling this method will replace any existing customizers. + * @param tomcatContextCustomizers the customizers to set + */ + public void setTomcatContextCustomizers( + Collection tomcatContextCustomizers) { + Assert.notNull(this.contextLifecycleListeners, + "TomcatContextCustomizers must not be null"); + this.tomcatContextCustomizers = new ArrayList( + tomcatContextCustomizers); + } + + /** + * Returns a mutable collection of the {@link TomcatContextCustomizer}s that will be + * applied to the Tomcat {@link Context} . + * @return the tomcatContextCustomizers the listeners that will be applied + */ + public Collection getTomcatContextCustomizers() { + return this.tomcatContextCustomizers; + } + + /** + * Add {@link TomcatContextCustomizer}s that should be added to the Tomcat + * {@link Context}. + * @param tomcatContextCustomizers the customizers to add + */ + public void addContextCustomizers(TomcatContextCustomizer... tomcatContextCustomizers) { + Assert.notNull(this.tomcatContextCustomizers, + "TomcatContextCustomizer must not be null"); + this.tomcatContextCustomizers.addAll(Arrays.asList(tomcatContextCustomizers)); + } + +} diff --git a/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedWebappClassLoader.java b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedWebappClassLoader.java new file mode 100644 index 00000000000..e091bc7cb2d --- /dev/null +++ b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/TomcatEmbeddedWebappClassLoader.java @@ -0,0 +1,115 @@ +package org.springframework.boot.context.embedded.tomcat8; + +/* + * Copyright 2012-2013 the original author or authors. + * + * 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 + * + * http://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. + */ + +import org.apache.catalina.loader.WebappClassLoader; + +/** + * Extension of Tomcat's {@link WebappClassLoader} that does not consider the + * {@link ClassLoader#getSystemClassLoader() system classloader}. This is required to to + * ensure that any custom context classloader is always used (as is the case with some + * executable archives). + * + * @author Phillip Webb + */ +public class TomcatEmbeddedWebappClassLoader extends WebappClassLoader { + + public TomcatEmbeddedWebappClassLoader() { + super(); + } + + public TomcatEmbeddedWebappClassLoader(ClassLoader parent) { + super(parent); + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + + Class resultClass = null; + + // Check local class caches + resultClass = (resultClass == null ? findLoadedClass0(name) : resultClass); + resultClass = (resultClass == null ? findLoadedClass(name) : resultClass); + if (resultClass != null) { + return resolveIfNecessary(resultClass, resolve); + } + + // Check security + checkPackageAccess(name); + + // Perform the actual load + boolean delegateLoad = (this.delegate || filter(name)); + + if (delegateLoad) { + resultClass = (resultClass == null ? loadFromParent(name) : resultClass); + } + resultClass = (resultClass == null ? findClassIgnoringNotFound(name) + : resultClass); + if (!delegateLoad) { + resultClass = (resultClass == null ? loadFromParent(name) : resultClass); + } + + if (resultClass == null) { + throw new ClassNotFoundException(name); + } + + return resolveIfNecessary(resultClass, resolve); + } + + private Class resolveIfNecessary(Class resultClass, boolean resolve) { + if (resolve) { + resolveClass(resultClass); + } + return (resultClass); + } + + private Class loadFromParent(String name) { + if (this.parent == null) { + return null; + } + try { + return Class.forName(name, false, this.parent); + } + catch (ClassNotFoundException e) { + return null; + } + } + + private Class findClassIgnoringNotFound(String name) { + try { + return findClass(name); + } + catch (ClassNotFoundException e) { + return null; + } + } + + private void checkPackageAccess(String name) throws ClassNotFoundException { + if (this.securityManager != null && name.lastIndexOf('.') >= 0) { + try { + this.securityManager.checkPackageAccess(name.substring(0, + name.lastIndexOf('.'))); + } + catch (SecurityException se) { + throw new ClassNotFoundException("Security Violation, attempt to use " + + "Restricted Class: " + name, se); + } + } + } + +} diff --git a/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/package-info.java b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/package-info.java new file mode 100644 index 00000000000..f1a98a595f8 --- /dev/null +++ b/spring-boot-containers/spring-boot-tomcat8/src/main/java/org/springframework/boot/context/embedded/tomcat8/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 + * + * http://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. + */ + +/** + * Support for Tomcat {@link org.springframework.boot.context.embedded.EmbeddedServletContainer EmbeddedServletContainers}. + */ +package org.springframework.boot.context.embedded.tomcat8; +