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