From 329175b95645c1e2dfe83cd63135021bb98ea81e Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 28 May 2014 14:52:21 +0100 Subject: [PATCH] Fix Jasper on Tomcat 8 Refactor Jasper initialization code to call JasperInitializer during server start. Fixes gh-962 See gh-919 --- spring-boot-samples/pom.xml | 1 + .../spring-boot-sample-tomcat8-jsp/pom.xml | 65 +++++++++++++++++++ .../jsp/SampleTomcat8JspApplication.java | 40 ++++++++++++ .../java/sample/jsp/WelcomeController.java | 39 +++++++++++ .../src/main/resources/application.properties | 3 + .../src/main/webapp/WEB-INF/jsp/welcome.jsp | 18 +++++ .../jsp/SampleWebJspApplicationTests.java | 58 +++++++++++++++++ .../tomcat/CustomSkipPatternJarScanner.java | 11 +++- .../JasperInitializerLifecycleListener.java | 58 ----------------- ...TomcatEmbeddedServletContainerFactory.java | 15 ++++- 10 files changed, 246 insertions(+), 62 deletions(-) create mode 100644 spring-boot-samples/spring-boot-sample-tomcat8-jsp/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/SampleTomcat8JspApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/WelcomeController.java create mode 100644 spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/webapp/WEB-INF/jsp/welcome.jsp create mode 100644 spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/test/java/sample/jsp/SampleWebJspApplicationTests.java delete mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/JasperInitializerLifecycleListener.java diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 2157038440..16606ab78d 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -39,6 +39,7 @@ spring-boot-sample-simple spring-boot-sample-tomcat spring-boot-sample-tomcat-multi-connectors + spring-boot-sample-tomcat8-jsp spring-boot-sample-traditional spring-boot-sample-web-method-security spring-boot-sample-web-secure diff --git a/spring-boot-samples/spring-boot-sample-tomcat8-jsp/pom.xml b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/pom.xml new file mode 100644 index 0000000000..e06cdddf3c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.0.3.BUILD-SNAPSHOT + + spring-boot-sample-tomcat8-jsp + war + Spring Boot Tomcat 8 JSP Sample + Spring Boot Tomcat 8 JSP Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + / + 8.0.8 + 1.7 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + javax.servlet + jstl + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + diff --git a/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/SampleTomcat8JspApplication.java b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/SampleTomcat8JspApplication.java new file mode 100644 index 0000000000..6ab7e320f3 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/SampleTomcat8JspApplication.java @@ -0,0 +1,40 @@ +/* + * 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 sample.jsp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.web.SpringBootServletInitializer; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableAutoConfiguration +@ComponentScan +public class SampleTomcat8JspApplication extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(SampleTomcat8JspApplication.class); + } + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleTomcat8JspApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/WelcomeController.java b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/WelcomeController.java new file mode 100644 index 0000000000..03d439caa3 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/java/sample/jsp/WelcomeController.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2014 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 sample.jsp; + +import java.util.Date; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class WelcomeController { + + @Value("${application.message:Hello World}") + private String message = "Hello World"; + + @RequestMapping("/") + public String welcome(Map model) { + model.put("time", new Date()); + model.put("message", this.message); + return "welcome"; + } + +} diff --git a/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/resources/application.properties new file mode 100644 index 0000000000..f95f1d3c01 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.view.prefix: /WEB-INF/jsp/ +spring.view.suffix: .jsp +application.message: Hello Phil \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/webapp/WEB-INF/jsp/welcome.jsp b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/webapp/WEB-INF/jsp/welcome.jsp new file mode 100644 index 0000000000..3196dac625 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/main/webapp/WEB-INF/jsp/welcome.jsp @@ -0,0 +1,18 @@ + + +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + + + + + + + Spring URL: ${springUrl} at ${time} +
+ JSTL URL: ${url} +
+ Message: ${message} + + + diff --git a/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/test/java/sample/jsp/SampleWebJspApplicationTests.java b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/test/java/sample/jsp/SampleWebJspApplicationTests.java new file mode 100644 index 0000000000..09d557e12b --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-tomcat8-jsp/src/test/java/sample/jsp/SampleWebJspApplicationTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2014 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 sample.jsp; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Basic integration tests for JSP application. + * + * @author Phillip Webb + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SampleTomcat8JspApplication.class) +@WebAppConfiguration +@IntegrationTest("server.port:0") +@DirtiesContext +public class SampleWebJspApplicationTests { + + @Value("${local.server.port}") + private int port; + + @Test + public void testJspWithEl() throws Exception { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + this.port, String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertTrue("Wrong body:\n" + entity.getBody(), + entity.getBody().contains("/resources/text.txt")); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/CustomSkipPatternJarScanner.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/CustomSkipPatternJarScanner.java index ad083a9fc4..454ee0e787 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/CustomSkipPatternJarScanner.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/CustomSkipPatternJarScanner.java @@ -25,15 +25,18 @@ import javax.servlet.ServletContext; import org.apache.tomcat.JarScanner; import org.apache.tomcat.JarScannerCallback; +import org.apache.tomcat.util.scan.StandardJarScanner; import org.springframework.util.Assert; /** - * {@link JarScanner} decorator allowing alternative default jar pattern matching. + * {@link JarScanner} decorator allowing alternative default jar pattern matching. This + * class extends {@link StandardJarScanner} rather than implementing the + * {@link JarScanner} due to API changes introduced in Tomcat 8. * * @author Phillip Webb * @see #apply(TomcatEmbeddedContext, String) */ -class SkipPatternJarScanner implements JarScanner { +class SkipPatternJarScanner extends StandardJarScanner { private final JarScanner jarScanner; @@ -58,7 +61,9 @@ class SkipPatternJarScanner implements JarScanner { * @param pattern the jar skip pattern or {@code null} for defaults */ public static void apply(TomcatEmbeddedContext context, String pattern) { - context.setJarScanner(new SkipPatternJarScanner(context.getJarScanner(), pattern)); + SkipPatternJarScanner scanner = new SkipPatternJarScanner( + context.getJarScanner(), pattern); + context.setJarScanner(scanner); } private static class SkipPattern { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/JasperInitializerLifecycleListener.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/JasperInitializerLifecycleListener.java deleted file mode 100644 index 6a7e107c78..0000000000 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/JasperInitializerLifecycleListener.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.springframework.boot.context.embedded.tomcat; - -import javax.servlet.ServletContainerInitializer; -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.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * Tomcat {@link LifecycleListener} to initialize Jasper by calling the - * `JasperInitializer` used in Tomcat 8. - * - * @author Phillip Webb - */ -class JasperInitializerLifecycleListener implements LifecycleListener { - - private static final String JASPER_INITIALIZER_CLASS = "org.apache.jasper.servlet.JasperInitializer"; - - private ServletContainerInitializer initializer; - - public JasperInitializerLifecycleListener() { - this.initializer = getJasperInitializer(); - } - - @Override - public void lifecycleEvent(LifecycleEvent event) { - if (this.initializer != null - && Lifecycle.CONFIGURE_START_EVENT.equals(event.getType())) { - onStartup(event); - } - } - - private void onStartup(LifecycleEvent event) { - Assert.isInstanceOf(StandardContext.class, event.getSource()); - StandardContext standardContext = (StandardContext) event.getSource(); - try { - this.initializer.onStartup(null, standardContext.getServletContext()); - } - catch (ServletException ex) { - throw new IllegalStateException(ex); - } - } - - private ServletContainerInitializer getJasperInitializer() { - try { - Class jasperClass = ClassUtils.forName(JASPER_INITIALIZER_CLASS, null); - return (ServletContainerInitializer) jasperClass.newInstance(); - } - catch (Exception ex) { - return null; - } - } - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index 2fd2463c80..089175553b 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import org.apache.catalina.Context; @@ -167,6 +168,7 @@ public class TomcatEmbeddedServletContainerFactory extends && ClassUtils.isPresent(getJspServletClassName(), getClass() .getClassLoader())) { addJspServlet(context); + addJasperInitializer(context); context.addLifecycleListener(new StoreMergedWebXmlListener()); } ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); @@ -199,6 +201,18 @@ public class TomcatEmbeddedServletContainerFactory extends context.addServletMapping("*.jspx", "jsp"); } + private void addJasperInitializer(TomcatEmbeddedContext context) { + try { + ServletContainerInitializer initializer = (ServletContainerInitializer) ClassUtils + .forName("org.apache.jasper.servlet.JasperInitializer", null) + .newInstance(); + context.addServletContainerInitializer(initializer, null); + } + catch (Exception ex) { + // Probably not Tomcat 8 + } + } + // Needs to be protected so it can be used by subclasses protected void customizeConnector(Connector connector) { int port = (getPort() >= 0 ? getPort() : 0); @@ -230,7 +244,6 @@ public class TomcatEmbeddedServletContainerFactory extends ServletContextInitializer[] initializers) { context.addLifecycleListener(new ServletContextInitializerLifecycleListener( initializers)); - context.addLifecycleListener(new JasperInitializerLifecycleListener()); for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { context.addLifecycleListener(lifecycleListener); }