Skip to content

Commit

Permalink
Upgrade to Servlet 3.1, Tomcat 8 and Jetty 9
Browse files Browse the repository at this point in the history
Upgrade to latest versions of Tomcat and Jetty and to the latest Servlet
API whilst will remaining compatible with Tomcat 7 and Jetty 8.

Fixes spring-projectsgh-1832, spring-projectsgh-369
  • Loading branch information
philwebb committed Nov 6, 2014
1 parent 004904a commit b6bacd5
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.request.RequestAttributes;
Expand Down Expand Up @@ -121,10 +122,10 @@ private void addErrorDetails(Map<String, Object> errorAttributes,
}
}
Object message = getAttribute(requestAttributes, "javax.servlet.error.message");
if ((message != null || errorAttributes.get("message") == null)
if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
&& !(error instanceof BindingResult)) {
errorAttributes.put("message", message == null ? "No message available"
: message);
errorAttributes.put("message",
StringUtils.isEmpty(message) ? "No message available" : message);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ public class BasicErrorControllerIntegrationTests {
public void testErrorForMachineClient() throws Exception {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.port, Map.class);
assertThat(entity.getBody().toString(), endsWith("status=500, "
+ "error=Internal Server Error, "
+ "exception=java.lang.IllegalStateException, "
+ "message=Server Error, " + "path=/}"));
String body = entity.getBody().toString();
assertThat(body, endsWith("status=500, " + "error=Internal Server Error, "
+ "exception=java.lang.IllegalStateException, " + "message=Expected!, "
+ "path=/}"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ public void testDirectAccessForBrowserClient() throws Exception {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EmbeddedServletContainerAutoConfiguration.class,
@Import({ EmbeddedServletContainerAutoConfiguration.EmbeddedTomcat.class,
EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
Expand Down Expand Up @@ -94,6 +96,7 @@ public String jar(String... args) throws Exception {

private <T extends OptionParsingCommand> Future<T> submitCommand(final T command,
String... args) {
clearUrlHandler();
final String[] sources = getSources(args);
return Executors.newSingleThreadExecutor().submit(new Callable<T>() {
@Override
Expand All @@ -112,6 +115,21 @@ public T call() throws Exception {
});
}

/**
* The TomcatURLStreamHandlerFactory fails if the factory is already set, use
* reflection to reset it.
*/
private void clearUrlHandler() {
try {
Field field = URL.class.getDeclaredField("factory");
field.setAccessible(true);
field.set(null, null);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

protected String[] getSources(String... args) {
final String[] sources = new String[args.length];
for (int i = 0; i < args.length; i++) {
Expand Down
6 changes: 3 additions & 3 deletions spring-boot-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
<javax-cache.version>1.0.0</javax-cache.version>
<javax-mail.version>1.5.2</javax-mail.version>
<jedis.version>2.4.2</jedis.version>
<jetty.version>8.1.15.v20140411</jetty.version>
<jetty.version>9.2.4.v20141103</jetty.version>
<jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
<jaxen.version>1.1.6</jaxen.version>
<jdom2.version>2.0.5</jdom2.version>
Expand All @@ -101,7 +101,7 @@
<mysql.version>5.1.32</mysql.version>
<reactor.version>1.1.5.RELEASE</reactor.version>
<reactor-spring.version>1.1.3.RELEASE</reactor-spring.version>
<servlet-api.version>3.0.1</servlet-api.version>
<servlet-api.version>3.1.0</servlet-api.version>
<slf4j.version>1.7.7</slf4j.version>
<snakeyaml.version>1.13</snakeyaml.version>
<solr.version>4.7.2</solr.version>
Expand All @@ -127,7 +127,7 @@
<thymeleaf-extras-springsecurity3.version>2.1.1.RELEASE</thymeleaf-extras-springsecurity3.version>
<thymeleaf-layout-dialect.version>1.2.5</thymeleaf-layout-dialect.version>
<thymeleaf-extras-data-attribute.version>1.3</thymeleaf-extras-data-attribute.version>
<tomcat.version>7.0.56</tomcat.version>
<tomcat.version>8.0.14</tomcat.version>
<velocity.version>1.7</velocity.version>
<velocity-tools.version>2.0</velocity-tools.version>
<wsdl4j.version>1.6.3</wsdl4j.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,11 @@ public class WarPackagingTests {

private static final Set<String> JETTY_EXPECTED_IN_WEB_INF_LIB_PROVIDED = new HashSet<String>(
Arrays.asList("spring-boot-starter-jetty-", "jetty-util-", "jetty-xml-",
"javax.servlet-", "jetty-continuation-", "jetty-io-", "jetty-http-",
"jetty-schemas-", "javax.servlet-", "jetty-io-", "jetty-http-",
"jetty-server-", "jetty-security-", "jetty-servlet-",
"jetty-webapp-", "javax.servlet.jsp-",
"org.apache.jasper.glassfish-", "javax.servlet.jsp.jstl-",
"org.apache.taglibs.standard.glassfish-", "javax.el-", "com.sun.el-",
"org.eclipse.jdt.core-", "jetty-jsp-"));
"jetty-webapp-", "javax.servlet.jsp-2", "javax.servlet.jsp-api-",
"javax.servlet.jsp.jstl-1.2.2", "javax.servlet.jsp.jstl-1.2.0",
"javax.el-", "org.eclipse.jdt.core-", "jetty-jsp-"));

private static final String BOOT_VERSION = ManagedDependencies.get()
.find("spring-boot").getVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
import java.util.Collection;
import java.util.List;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.ssl.SslSocketConnector;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
Expand Down Expand Up @@ -117,10 +120,10 @@ public EmbeddedServletContainer getEmbeddedServletContainer(
if (getSsl() != null) {
SslContextFactory sslContextFactory = new SslContextFactory();
configureSsl(sslContextFactory, getSsl());

SslSocketConnector sslConnector = new SslSocketConnector(sslContextFactory);
sslConnector.setPort(port);
server.setConnectors(new Connector[] { sslConnector });
ServerConnector connector = getSslServerConnectorFactory().getConnector(
server, sslContextFactory);
connector.setPort(port);
server.setConnectors(new Connector[] { connector });
}

for (JettyServerCustomizer customizer : getServerCustomizers()) {
Expand All @@ -130,6 +133,13 @@ public EmbeddedServletContainer getEmbeddedServletContainer(
return getJettyEmbeddedServletContainer(server);
}

private SslServerConnectorFactory getSslServerConnectorFactory() {
if (ClassUtils.isPresent("org.eclipse.jetty.server.ssl.SslSocketConnector", null)) {
return new Jetty8SslServerConnectorFactory();
}
return new Jetty9SslServerConnectorFactory();
}

/**
* Configure the SSL connection.
* @param factory the Jetty {@link SslContextFactory}.
Expand Down Expand Up @@ -246,7 +256,7 @@ private void configureDocumentRoot(WebAppContext handler) {
handler.setBaseResource(resource);
}
else {
handler.setBaseResource(Resource.newResource(root));
handler.setBaseResource(Resource.newResource(root.getCanonicalFile()));
}
}
catch (Exception ex) {
Expand Down Expand Up @@ -458,4 +468,50 @@ private void addJettyErrorPages(ErrorHandler errorHandler,
}
}

/**
* Factory to create the SSL {@link ServerConnector}.
*/
private static interface SslServerConnectorFactory {

ServerConnector getConnector(Server server, SslContextFactory sslContextFactory);

}

/**
* {@link SslServerConnectorFactory} for Jetty 9.
*/
private static class Jetty9SslServerConnectorFactory implements
SslServerConnectorFactory {

@Override
public ServerConnector getConnector(Server server,
SslContextFactory sslContextFactory) {
return new ServerConnector(server, new SslConnectionFactory(
sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory());
}
}

/**
* {@link SslServerConnectorFactory} for Jetty 8.
*/
private static class Jetty8SslServerConnectorFactory implements
SslServerConnectorFactory {

@Override
public ServerConnector getConnector(Server server,
SslContextFactory sslContextFactory) {
try {
Class<?> connectorClass = Class
.forName("org.eclipse.jetty.server.ssl.SslSocketConnector");
return (ServerConnector) connectorClass.getConstructor(
SslContextFactory.class).newInstance(sslContextFactory);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.springframework.boot.context.embedded.tomcat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashSet;
Expand All @@ -27,6 +26,7 @@

import org.apache.tomcat.JarScanner;
import org.apache.tomcat.JarScannerCallback;
import org.apache.tomcat.util.scan.StandardJarScanFilter;
import org.apache.tomcat.util.scan.StandardJarScanner;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
Expand All @@ -45,8 +45,6 @@ class SkipPatternJarScanner extends StandardJarScanner {

private static final String JAR_SCAN_FILTER_CLASS = "org.apache.tomcat.JarScanFilter";

private static final String STANDARD_JAR_SCAN_FILTER_CLASS = "org.apache.tomcat.util.scan.StandardJarScanFilter";

private final JarScanner jarScanner;

private final SkipPattern pattern;
Expand All @@ -60,34 +58,24 @@ class SkipPatternJarScanner extends StandardJarScanner {

private void setPatternToTomcat8SkipFilter(SkipPattern pattern) {
if (ClassUtils.isPresent(JAR_SCAN_FILTER_CLASS, null)) {
try {
Class<?> filterClass = Class.forName(JAR_SCAN_FILTER_CLASS);
Method setJarScanner = ReflectionUtils.findMethod(
StandardJarScanner.class, "setJarScanFilter", filterClass);
setJarScanner.invoke(this, createStandardJarScanFilter(pattern));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
new Tomcat8TldSkipSetter(this).setSkipPattern(pattern);
}
}

private Object createStandardJarScanFilter(SkipPattern pattern)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException, InvocationTargetException {
Class<?> filterClass = Class.forName(STANDARD_JAR_SCAN_FILTER_CLASS);
Method setTldSkipMethod = ReflectionUtils.findMethod(filterClass, "setTldSkip",
String.class);
Object scanner = filterClass.newInstance();
setTldSkipMethod.invoke(scanner, pattern.asCommaDelimitedString());
return scanner;
}

@Override
// For Tomcat 7 compatibility
public void scan(ServletContext context, ClassLoader classloader,
JarScannerCallback callback, Set<String> jarsToSkip) {
this.jarScanner.scan(context, classloader, callback,
(jarsToSkip == null ? this.pattern.asSet() : jarsToSkip));
Method scanMethod = ReflectionUtils.findMethod(this.jarScanner.getClass(),
"scan", ServletContext.class, ClassLoader.class,
JarScannerCallback.class, Set.class);
Assert.notNull(scanMethod, "Unable to find scan method");
try {
scanMethod.invoke(this.jarScanner, context, classloader, callback,
(jarsToSkip == null ? this.pattern.asSet() : jarsToSkip));
}
catch (Exception ex) {
throw new IllegalStateException("Tomcat 7 reflection failed", ex);
}
}

/**
Expand All @@ -101,6 +89,28 @@ public static void apply(TomcatEmbeddedContext context, String pattern) {
context.setJarScanner(scanner);
}

/**
* Tomcat 8 specific logic to setup the scanner.
*/
private static class Tomcat8TldSkipSetter {

private final StandardJarScanner jarScanner;

public Tomcat8TldSkipSetter(StandardJarScanner jarScanner) {
this.jarScanner = jarScanner;
}

public void setSkipPattern(SkipPattern pattern) {
StandardJarScanFilter filter = new StandardJarScanFilter();
filter.setTldSkip(pattern.asCommaDelimitedString());
this.jarScanner.setJarScanFilter(filter);
}

}

/**
* Skip patterns used by Spring Boot
*/
private static class SkipPattern {

private Set<String> patterns = new LinkedHashSet<String>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package org.springframework.boot.context.embedded.tomcat;

import java.lang.reflect.Method;

import org.apache.catalina.Container;
import org.apache.catalina.core.StandardContext;
import org.springframework.util.ClassUtils;
Expand All @@ -33,9 +31,19 @@ class TomcatEmbeddedContext extends StandardContext {

private ServletContextInitializerLifecycleListener starter;

private final boolean overrideLoadOnStart;

public TomcatEmbeddedContext() {
this.overrideLoadOnStart = ReflectionUtils.findMethod(StandardContext.class,
"loadOnStartup", Container[].class).getReturnType() == boolean.class;
}

@Override
public boolean loadOnStartup(Container[] children) {
return true;
if (this.overrideLoadOnStart) {
return true;
}
return super.loadOnStartup(children);
}

public void deferredLoadOnStartup() {
Expand All @@ -49,12 +57,13 @@ public void deferredLoadOnStartup() {
if (classLoader != null) {
existingLoader = ClassUtils.overrideThreadContextClassLoader(classLoader);
}
if (ClassUtils.isPresent("org.apache.catalina.deploy.ErrorPage", null)) {

if (this.overrideLoadOnStart) {
// Earlier versions of Tomcat used a version that returned void. If that
// version is used our overridden loadOnStart method won't have been called
// and the original will have already run.
super.loadOnStartup(findChildren());
}
else {
callSuper(this, "loadOnStartup", findChildren(), Container[].class);
}
if (existingLoader != null) {
ClassUtils.overrideThreadContextClassLoader(existingLoader);
}
Expand All @@ -68,10 +77,4 @@ public ServletContextInitializerLifecycleListener getStarter() {
return this.starter;
}

private void callSuper(Object target, String name, Object value, Class<?> type) {
Method method = ReflectionUtils.findMethod(target.getClass().getSuperclass(),
name, type);
ReflectionUtils.invokeMethod(method, target, value);
}

}
Loading

0 comments on commit b6bacd5

Please sign in to comment.