diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 9c223a3b2d6..48da9418c24 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -43,6 +43,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-freemarker spring-boot-sample-web-groovy-templates 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 00000000000..e06cdddf3cf --- /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 00000000000..6ab7e320f38 --- /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 00000000000..03d439caa3c --- /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 00000000000..f95f1d3c014 --- /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 00000000000..3196dac625d --- /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 00000000000..09d557e12bc --- /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-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java index 95936845e02..569594e6c1f 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java @@ -51,6 +51,7 @@ class JarURLConnection extends java.net.JarURLConnection { protected JarURLConnection(URL url, JarFile jarFile) throws MalformedURLException { super(new URL(buildRootUrl(jarFile))); + this.url = url; this.jarFile = jarFile; String spec = url.getFile(); @@ -59,27 +60,19 @@ class JarURLConnection extends java.net.JarURLConnection { throw new MalformedURLException("no " + SEPARATOR + " found in url spec:" + spec); } - /* - * The superclass constructor creates a jarFileUrl which is equal to the root URL - * of the containing archive (therefore not unique if we are connecting to - * multiple nested jars in the same archive). Therefore we need to make something - * sensible for #getJarFileURL(). - */ - if (separator + SEPARATOR.length() != spec.length()) { + if (separator + 2 != spec.length()) { this.jarEntryName = decode(spec.substring(separator + 2)); - this.jarFileUrl = new URL("jar:" + spec.substring(0, separator) + SEPARATOR - + this.jarEntryName); + } + + String container = spec.substring(0, separator); + if (container.indexOf(SEPARATOR) == -1) { + this.jarFileUrl = new URL(container); } else { - this.jarFileUrl = new URL("jar:" + spec.substring(0, separator)); + this.jarFileUrl = new URL("jar:" + container); } } - @Override - public URL getJarFileURL() { - return this.jarFileUrl; - } - @Override public void connect() throws IOException { if (this.jarEntryName != null) { @@ -108,12 +101,22 @@ class JarURLConnection extends java.net.JarURLConnection { return this.jarFile; } + @Override + public URL getJarFileURL() { + return this.jarFileUrl; + } + @Override public JarEntry getJarEntry() throws IOException { connect(); return (this.jarEntryData == null ? null : this.jarEntryData.asJarEntry()); } + @Override + public String getEntryName() { + return this.jarEntryName; + } + @Override public InputStream getInputStream() throws IOException { connect(); diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java index ea4ecfba5d8..1cdf36c0661 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java @@ -183,7 +183,7 @@ public class JarFileTests { assertThat(jarURLConnection.getContentLength(), greaterThan(1)); assertThat(jarURLConnection.getContent(), sameInstance((Object) this.jarFile)); assertThat(jarURLConnection.getContentType(), equalTo("x-java/jar")); - assertThat(jarURLConnection.getJarFileURL().toString(), equalTo("jar:file:" + assertThat(jarURLConnection.getJarFileURL().toString(), equalTo("file:" + this.rootJarFile)); } @@ -296,6 +296,27 @@ public class JarFileTests { InputStream inputStream = url.openStream(); assertThat(inputStream, notNullValue()); assertThat(inputStream.read(), equalTo(3)); + JarURLConnection connection = (JarURLConnection) url.openConnection(); + assertThat(connection.getURL().toString(), equalTo(spec)); + assertThat(connection.getJarFileURL().toString(), equalTo("jar:file:" + + this.rootJarFile.getPath() + "!/nested.jar")); + assertThat(connection.getEntryName(), equalTo("3.dat")); + } + + @Test + public void createNonNestedUrlFromString() throws Exception { + JarFile.registerUrlProtocolHandler(); + String spec = "jar:file:" + this.rootJarFile.getPath() + "!/2.dat"; + URL url = new URL(spec); + assertThat(url.toString(), equalTo(spec)); + InputStream inputStream = url.openStream(); + assertThat(inputStream, notNullValue()); + assertThat(inputStream.read(), equalTo(2)); + JarURLConnection connection = (JarURLConnection) url.openConnection(); + assertThat(connection.getURL().toString(), equalTo(spec)); + assertThat(connection.getJarFileURL().toString(), equalTo("file:" + + this.rootJarFile.getPath())); + assertThat(connection.getEntryName(), equalTo("2.dat")); } @Test 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 ad083a9fc4c..454ee0e787c 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 6a7e107c782..00000000000 --- 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 2fd2463c80b..089175553b5 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); }