From 0a33d0464dbd3a7aeb7d919122e71795ad30336b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 24 Mar 2015 18:02:04 -0700 Subject: [PATCH] Respect Boot locations in MockServletContext Update SpringApplicationContextLoader to use a MockServletContext subclass which also respects common Spring Boot resource locations. This allows resources in `/META-INF/resources`, `/resources`, `/public` and `/static` to be loaded in the same way as those in `/src/main/webapp`. SpringBootMockServletContext also returns an empty temporary folder for '/' when no resource locations can be found. This is primarily to prevent superfluous warnings from Liquibase. Fixes gh-2654 --- .../test/SpringApplicationContextLoader.java | 2 +- .../test/SpringBootMockServletContext.java | 110 ++++++++++++++++++ .../SpringBootMockServletContextTests.java | 102 ++++++++++++++++ .../META-INF/resources/inmetainfresources | 0 .../src/test/resources/public/inpublic | 0 .../src/test/resources/resources/inresources | 0 .../src/test/resources/static/instatic | 0 spring-boot/src/test/webapp/inwebapp | 0 8 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/test/SpringBootMockServletContext.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/test/SpringBootMockServletContextTests.java create mode 100644 spring-boot/src/test/resources/META-INF/resources/inmetainfresources create mode 100644 spring-boot/src/test/resources/public/inpublic create mode 100644 spring-boot/src/test/resources/resources/inresources create mode 100644 spring-boot/src/test/resources/static/instatic create mode 100644 spring-boot/src/test/webapp/inwebapp diff --git a/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java b/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java index a821c78142..0898bd65ff 100644 --- a/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java +++ b/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java @@ -260,7 +260,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { private void addMockServletContext( List> initializers, WebMergedContextConfiguration webConfiguration) { - MockServletContext servletContext = new MockServletContext( + SpringBootMockServletContext servletContext = new SpringBootMockServletContext( webConfiguration.getResourceBasePath()); initializers.add(0, new ServletContextApplicationContextInitializer( servletContext)); diff --git a/spring-boot/src/main/java/org/springframework/boot/test/SpringBootMockServletContext.java b/spring-boot/src/main/java/org/springframework/boot/test/SpringBootMockServletContext.java new file mode 100644 index 0000000000..522f6caf0b --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/test/SpringBootMockServletContext.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2015 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.test; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import org.springframework.core.io.FileSystemResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.mock.web.MockServletContext; + +/** + * {@link MockServletContext} implementation for Spring Boot. Respects well know Spring + * Boot resource locations and uses an empty folder for "/" if no locations can be found. + * + * @author Phillip Webb + */ +public class SpringBootMockServletContext extends MockServletContext { + + private static final String[] SPRING_BOOT_RESOURCE_LOCATIONS = new String[] { + "classpath:META-INF/resources", "classpath:resources", "classpath:static", + "classpath:public" }; + + private final ResourceLoader resourceLoader; + + private File emptyRootFolder; + + public SpringBootMockServletContext(String resourceBasePath) { + this(resourceBasePath, new FileSystemResourceLoader()); + } + + public SpringBootMockServletContext(String resourceBasePath, + ResourceLoader resourceLoader) { + super(resourceBasePath, resourceLoader); + this.resourceLoader = resourceLoader; + } + + @Override + protected String getResourceLocation(String path) { + if (!path.startsWith("/")) { + path = "/" + path; + } + String resourceLocation = getResourceBasePathLocation(path); + if (exists(resourceLocation)) { + return resourceLocation; + } + for (String prefix : SPRING_BOOT_RESOURCE_LOCATIONS) { + resourceLocation = prefix + path; + if (exists(resourceLocation)) { + return resourceLocation; + } + } + return super.getResourceLocation(path); + } + + protected final String getResourceBasePathLocation(String path) { + return super.getResourceLocation(path); + } + + private boolean exists(String resourceLocation) { + try { + Resource resource = this.resourceLoader.getResource(resourceLocation); + return resource.exists(); + } + catch (Exception ex) { + return false; + } + } + + @Override + public URL getResource(String path) throws MalformedURLException { + URL resource = super.getResource(path); + if (resource == null && "/".equals(path)) { + // Liquibase assumes that "/" always exists, if we don't have a folder + // use a temporary location. + try { + if (this.emptyRootFolder == null) { + synchronized (this) { + File tempFolder = File.createTempFile("spr", "servlet"); + tempFolder.delete(); + tempFolder.mkdirs(); + tempFolder.deleteOnExit(); + this.emptyRootFolder = tempFolder; + } + } + return this.emptyRootFolder.toURI().toURL(); + } + catch (IOException ex) { + } + } + return resource; + } +} diff --git a/spring-boot/src/test/java/org/springframework/boot/test/SpringBootMockServletContextTests.java b/spring-boot/src/test/java/org/springframework/boot/test/SpringBootMockServletContextTests.java new file mode 100644 index 0000000000..ea172f01ae --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/test/SpringBootMockServletContextTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2015 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.test; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.servlet.ServletContext; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.context.ServletContextAware; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link SpringBootMockServletContext}. + * + * @author Phillip Webb + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootMockServletContextTests.Config.class) +@WebAppConfiguration("src/test/webapp") +public class SpringBootMockServletContextTests implements ServletContextAware { + + private ServletContext servletContext; + + @Override + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + @Test + public void getResourceLocation() throws Exception { + testResource("/inwebapp", "src/test/webapp"); + testResource("/inmetainfresources", "/META-INF/resources"); + testResource("/inresources", "/resources"); + testResource("/instatic", "/static"); + testResource("/inpublic", "/public"); + } + + private void testResource(String path, String expectedLocation) + throws MalformedURLException { + URL resource = this.servletContext.getResource(path); + assertThat("Path " + path + " not found", resource, not(nullValue())); + assertThat("Path " + path + " not in " + expectedLocation, resource.getPath(), + containsString(expectedLocation)); + } + + // gh-2654 + @Test + public void getRootUrlExistsAndIsEmpty() throws Exception { + SpringBootMockServletContext context = new SpringBootMockServletContext( + "src/test/doesntexist") { + @Override + protected String getResourceLocation(String path) { + // Don't include the Spring Boot defaults for this test + return getResourceBasePathLocation(path); + }; + }; + URL resource = context.getResource("/"); + assertThat(resource, not(nullValue())); + File file = new File(resource.getPath()); + String[] contents = file.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return !(".".equals(name) || "..".equals(name)); + } + }); + assertThat(contents, not(nullValue())); + assertThat(contents.length, equalTo(0)); + } + + @Configuration + static class Config { + + } + +} diff --git a/spring-boot/src/test/resources/META-INF/resources/inmetainfresources b/spring-boot/src/test/resources/META-INF/resources/inmetainfresources new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot/src/test/resources/public/inpublic b/spring-boot/src/test/resources/public/inpublic new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot/src/test/resources/resources/inresources b/spring-boot/src/test/resources/resources/inresources new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot/src/test/resources/static/instatic b/spring-boot/src/test/resources/static/instatic new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot/src/test/webapp/inwebapp b/spring-boot/src/test/webapp/inwebapp new file mode 100644 index 0000000000..e69de29bb2