Fix static resource handling when run in IDE or using Maven or Gradle
The changes made for gh-8299 attempted to make static resource handling consistent across Jetty, Tomcat, and Undertow. They did so for application's launched using JarLauncher or WarLauncher but did not consider application's launched in an IDE or using spring-boot:run in Maven or bootRun in Gradle. Running in an IDE or via Maven or Gradle introduces two new resource locations: - Jars on the classpath with file protocol URLs (they are always jar protocol URLs when using either launcher) - Directories on the classpath from a project that is depended upon and contains resources in META-INF/resources This commit updates the factories for all three containers to handle these new resources locations. The integration tests have also been updated.pull/8582/head
parent
67068fc83d
commit
a2cf0455fd
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AbstractApplicationLauncher} that launches a Spring Boot application with a
|
||||
* classpath similar to that used when run with Maven or Gradle.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class BootRunApplicationLauncher extends AbstractApplicationLauncher {
|
||||
|
||||
private final File exploded = new File("target/run");
|
||||
|
||||
BootRunApplicationLauncher(ApplicationBuilder applicationBuilder) {
|
||||
super(applicationBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getArguments(File archive) {
|
||||
try {
|
||||
explodeArchive(archive);
|
||||
deleteLauncherClasses();
|
||||
File targetClasses = populateTargetClasses(archive);
|
||||
File dependencies = populateDependencies(archive);
|
||||
populateSrcMainWebapp();
|
||||
List<String> classpath = new ArrayList<String>();
|
||||
classpath.add(targetClasses.getAbsolutePath());
|
||||
for (File dependency : dependencies.listFiles()) {
|
||||
classpath.add(dependency.getAbsolutePath());
|
||||
}
|
||||
return Arrays.asList("-cp",
|
||||
StringUtils.collectionToDelimitedString(classpath,
|
||||
File.pathSeparator),
|
||||
"com.example.ResourceHandlingApplication");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteLauncherClasses() {
|
||||
FileSystemUtils.deleteRecursively(new File(this.exploded, "org"));
|
||||
}
|
||||
|
||||
private File populateTargetClasses(File archive) {
|
||||
File targetClasses = new File(this.exploded, "target/classes");
|
||||
targetClasses.mkdirs();
|
||||
new File(this.exploded, getClassesPath(archive)).renameTo(targetClasses);
|
||||
return targetClasses;
|
||||
}
|
||||
|
||||
private File populateDependencies(File archive) {
|
||||
File dependencies = new File(this.exploded, "dependencies");
|
||||
dependencies.mkdirs();
|
||||
List<String> libPaths = getLibPaths(archive);
|
||||
for (String libPath : libPaths) {
|
||||
for (File jar : new File(this.exploded, libPath).listFiles()) {
|
||||
jar.renameTo(new File(dependencies, jar.getName()));
|
||||
}
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private void populateSrcMainWebapp() {
|
||||
File srcMainWebapp = new File(this.exploded, "src/main/webapp");
|
||||
srcMainWebapp.mkdirs();
|
||||
new File(this.exploded, "webapp-resource.txt")
|
||||
.renameTo(new File(srcMainWebapp, "webapp-resource.txt"));
|
||||
}
|
||||
|
||||
private String getClassesPath(File archive) {
|
||||
return archive.getName().endsWith(".jar") ? "BOOT-INF/classes"
|
||||
: "WEB-INF/classes";
|
||||
}
|
||||
|
||||
private List<String> getLibPaths(File archive) {
|
||||
return archive.getName().endsWith(".jar")
|
||||
? Collections.singletonList("BOOT-INF/lib")
|
||||
: Arrays.asList("WEB-INF/lib", "WEB-INF/lib-provided");
|
||||
}
|
||||
|
||||
private void explodeArchive(File archive) throws IOException {
|
||||
FileSystemUtils.deleteRecursively(this.exploded);
|
||||
JarFile jarFile = new JarFile(archive);
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry jarEntry = entries.nextElement();
|
||||
File extracted = new File(this.exploded, jarEntry.getName());
|
||||
if (jarEntry.isDirectory()) {
|
||||
extracted.mkdirs();
|
||||
}
|
||||
else {
|
||||
FileOutputStream extractedOutputStream = new FileOutputStream(extracted);
|
||||
StreamUtils.copy(jarFile.getInputStream(jarEntry), extractedOutputStream);
|
||||
extractedOutputStream.close();
|
||||
}
|
||||
}
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getWorkingDirectory() {
|
||||
return this.exploded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDescription(String packaging) {
|
||||
return "build system run " + packaging + " project";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring Boot's embedded servlet container support when developing
|
||||
* a jar application.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class EmbeddedServletContainerJarDevelopmentIntegrationTests
|
||||
extends AbstractEmbeddedServletContainerIntegrationTests {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Object[] parameters() {
|
||||
return AbstractEmbeddedServletContainerIntegrationTests.parameters("jar", Arrays
|
||||
.asList(BootRunApplicationLauncher.class, IdeApplicationLauncher.class));
|
||||
}
|
||||
|
||||
public EmbeddedServletContainerJarDevelopmentIntegrationTests(String name,
|
||||
AbstractApplicationLauncher launcher) {
|
||||
super(name, launcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaInfResourceFromDependencyIsAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaInfResourceFromDependencyIsAvailableViaServletContext()
|
||||
throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring Boot's embedded servlet container support when developing
|
||||
* a war application.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class EmbeddedServletContainerWarDevelopmentIntegrationTests
|
||||
extends AbstractEmbeddedServletContainerIntegrationTests {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Object[] parameters() {
|
||||
return AbstractEmbeddedServletContainerIntegrationTests.parameters("war", Arrays
|
||||
.asList(BootRunApplicationLauncher.class, IdeApplicationLauncher.class));
|
||||
}
|
||||
|
||||
public EmbeddedServletContainerWarDevelopmentIntegrationTests(String name,
|
||||
AbstractApplicationLauncher launcher) {
|
||||
super(name, launcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaInfResourceFromDependencyIsAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaInfResourceFromDependencyIsAvailableViaServletContext()
|
||||
throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webappResourcesAreAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest.getForEntity("/webapp-resource.txt",
|
||||
String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AbstractApplicationLauncher} that launches a Spring Boot application with a
|
||||
* classpath similar to that used when run in an IDE.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class IdeApplicationLauncher extends AbstractApplicationLauncher {
|
||||
|
||||
private final File exploded = new File("target/ide");
|
||||
|
||||
IdeApplicationLauncher(ApplicationBuilder applicationBuilder) {
|
||||
super(applicationBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getWorkingDirectory() {
|
||||
return this.exploded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDescription(String packaging) {
|
||||
return "IDE run " + packaging + " project";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getArguments(File archive) {
|
||||
try {
|
||||
explodeArchive(archive, this.exploded);
|
||||
deleteLauncherClasses();
|
||||
File targetClasses = populateTargetClasses(archive);
|
||||
File dependencies = populateDependencies(archive);
|
||||
File resourcesProject = explodedResourcesProject(dependencies);
|
||||
populateSrcMainWebapp();
|
||||
List<String> classpath = new ArrayList<String>();
|
||||
classpath.add(targetClasses.getAbsolutePath());
|
||||
for (File dependency : dependencies.listFiles()) {
|
||||
classpath.add(dependency.getAbsolutePath());
|
||||
}
|
||||
classpath.add(resourcesProject.getAbsolutePath());
|
||||
return Arrays.asList("-cp",
|
||||
StringUtils.collectionToDelimitedString(classpath,
|
||||
File.pathSeparator),
|
||||
"com.example.ResourceHandlingApplication");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private File populateTargetClasses(File archive) {
|
||||
File targetClasses = new File(this.exploded, "target/classes");
|
||||
targetClasses.mkdirs();
|
||||
new File(this.exploded, getClassesPath(archive)).renameTo(targetClasses);
|
||||
return targetClasses;
|
||||
}
|
||||
|
||||
private File populateDependencies(File archive) {
|
||||
File dependencies = new File(this.exploded, "dependencies");
|
||||
dependencies.mkdirs();
|
||||
List<String> libPaths = getLibPaths(archive);
|
||||
for (String libPath : libPaths) {
|
||||
for (File jar : new File(this.exploded, libPath).listFiles()) {
|
||||
jar.renameTo(new File(dependencies, jar.getName()));
|
||||
}
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private File explodedResourcesProject(File dependencies) throws IOException {
|
||||
File resourcesProject = new File(this.exploded,
|
||||
"resources-project/target/classes");
|
||||
File resourcesJar = new File(dependencies, "resources-1.0.jar");
|
||||
explodeArchive(resourcesJar, resourcesProject);
|
||||
resourcesJar.delete();
|
||||
return resourcesProject;
|
||||
}
|
||||
|
||||
private void populateSrcMainWebapp() {
|
||||
File srcMainWebapp = new File(this.exploded, "src/main/webapp");
|
||||
srcMainWebapp.mkdirs();
|
||||
new File(this.exploded, "webapp-resource.txt")
|
||||
.renameTo(new File(srcMainWebapp, "webapp-resource.txt"));
|
||||
}
|
||||
|
||||
private void deleteLauncherClasses() {
|
||||
FileSystemUtils.deleteRecursively(new File(this.exploded, "org"));
|
||||
}
|
||||
|
||||
private String getClassesPath(File archive) {
|
||||
return archive.getName().endsWith(".jar") ? "BOOT-INF/classes"
|
||||
: "WEB-INF/classes";
|
||||
}
|
||||
|
||||
private List<String> getLibPaths(File archive) {
|
||||
return archive.getName().endsWith(".jar")
|
||||
? Collections.singletonList("BOOT-INF/lib")
|
||||
: Arrays.asList("WEB-INF/lib", "WEB-INF/lib-provided");
|
||||
}
|
||||
|
||||
private void explodeArchive(File archive, File destination) throws IOException {
|
||||
FileSystemUtils.deleteRecursively(destination);
|
||||
JarFile jarFile = new JarFile(archive);
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry jarEntry = entries.nextElement();
|
||||
File extracted = new File(destination, jarEntry.getName());
|
||||
if (jarEntry.isDirectory()) {
|
||||
extracted.mkdirs();
|
||||
}
|
||||
else {
|
||||
FileOutputStream extractedOutputStream = new FileOutputStream(extracted);
|
||||
StreamUtils.copy(jarFile.getInputStream(jarEntry), extractedOutputStream);
|
||||
extractedOutputStream.close();
|
||||
}
|
||||
}
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue