Allow bootWar to package webapp resources in dirs that overlap loader

Previously, the presence of a src/main/webapp/org directory would
cause the execution of BootWar to fail due to an attempt to write
a duplicate org/ entry to the zip output stream.

This commit updates BootZipCopyAction so that FileTreeElements that
match a directory entry created while writing the loader classes are
skipped.

Closes gh-8972
This commit is contained in:
Andy Wilkinson 2017-04-21 20:58:21 +01:00
parent 15b9707fbe
commit a6d24a3bfc
2 changed files with 48 additions and 10 deletions

View File

@ -20,6 +20,8 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
@ -34,6 +36,7 @@ import org.gradle.api.internal.file.copy.CopyAction;
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.api.tasks.WorkResult;
import org.gradle.util.GUtil;
@ -78,19 +81,20 @@ class BootZipCopyAction implements CopyAction {
@Override
public WorkResult execute(CopyActionProcessingStream stream) {
ZipOutputStream zipStream;
Spec<FileTreeElement> loaderEntries;
try {
FileOutputStream fileStream = new FileOutputStream(this.output);
writeLaunchScriptIfNecessary(fileStream);
zipStream = new ZipOutputStream(fileStream);
writeLoaderClassesIfNecessary(zipStream);
loaderEntries = writeLoaderClassesIfNecessary(zipStream);
}
catch (IOException ex) {
throw new GradleException("Failed to create " + this.output, ex);
}
try {
stream.process(new ZipStreamAction(zipStream, this.output,
this.preserveFileTimestamps, this.requiresUnpack, this.exclusions,
this.compressionResolver));
this.preserveFileTimestamps, this.requiresUnpack,
createExclusionSpec(loaderEntries), this.compressionResolver));
}
finally {
try {
@ -103,24 +107,40 @@ class BootZipCopyAction implements CopyAction {
return () -> true;
}
private void writeLoaderClassesIfNecessary(ZipOutputStream out) {
if (this.includeDefaultLoader) {
writeLoaderClasses(out);
}
@SuppressWarnings("unchecked")
private Spec<FileTreeElement> createExclusionSpec(
Spec<FileTreeElement> loaderEntries) {
return Specs.union(loaderEntries, this.exclusions);
}
private void writeLoaderClasses(ZipOutputStream out) {
ZipEntry entry;
private Spec<FileTreeElement> writeLoaderClassesIfNecessary(ZipOutputStream out) {
if (!this.includeDefaultLoader) {
return Specs.satisfyNone();
}
return writeLoaderClasses(out);
}
private Spec<FileTreeElement> writeLoaderClasses(ZipOutputStream out) {
try (ZipInputStream in = new ZipInputStream(getClass()
.getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
Set<String> entries = new HashSet<String>();
ZipEntry entry;
while ((entry = in.getNextEntry()) != null) {
if (entry.isDirectory() && !entry.getName().startsWith("META-INF/")) {
writeDirectory(entry, out);
entries.add(entry.getName());
}
if (entry.getName().endsWith(".class")) {
else if (entry.getName().endsWith(".class")) {
writeClass(entry, in, out);
}
}
return (element) -> {
String path = element.getRelativePath().getPathString();
if (element.isDirectory() && !path.endsWith(("/"))) {
path += "/";
}
return entries.contains(path);
};
}
catch (IOException ex) {
throw new GradleException("Failed to write loader classes", ex);

View File

@ -16,6 +16,7 @@
package org.springframework.boot.gradle.tasks.bundling;
import java.io.File;
import java.io.IOException;
import java.util.jar.JarFile;
@ -76,4 +77,21 @@ public class BootWarTests extends AbstractBootArchiveTests<BootWar> {
}
}
@Test
public void webappResourcesInDirectoriesThatOverlapWithLoaderCanBePackaged()
throws IOException {
File webappFolder = this.temp.newFolder("src", "main", "webapp");
File orgFolder = new File(webappFolder, "org");
orgFolder.mkdir();
new File(orgFolder, "foo.txt").createNewFile();
getTask().from(webappFolder);
getTask().setMainClass("com.example.Main");
getTask().execute();
assertThat(getTask().getArchivePath().exists());
try (JarFile jarFile = new JarFile(getTask().getArchivePath())) {
assertThat(jarFile.getEntry("org/")).isNotNull();
assertThat(jarFile.getEntry("org/foo.txt")).isNotNull();
}
}
}