From b790c82732ca59a281deb050750279050bcbc9aa Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 8 Jun 2021 17:23:04 -0700 Subject: [PATCH] Apply exclusions to existing war entries Update `RepackageMojo` and supporting classes so that `exclusions` on the repackage goal apply to both the contributed libraries and any existing jar entries already contained in the original war. Prior to this commit, exclusions would apply to contributed jars (for example, those in `WEB-INF/lib-provided`) but not jars that were packaged directly into `WEB-INF/lib` by the war plugin Fixes gh-15808 Co-authored-by: Phillip Webb --- .../cli/command/archive/ArchiveCommand.java | 4 +- .../gradle/tasks/bundling/LayerResolver.java | 10 ++- .../boot/loader/tools/AbstractJarWriter.java | 42 +++++++--- .../boot/loader/tools/JarModeLibrary.java | 2 +- .../boot/loader/tools/Library.java | 44 +++++++++++ .../boot/loader/tools/Packager.java | 75 +++++++++++++----- .../loader/tools/AbstractPackagerTests.java | 79 +++++++++++++------ .../boot/maven/WarIntegrationTests.java | 9 +++ .../projects/war-exclude-entry/pom.xml | 63 +++++++++++++++ .../main/java/org/test/SampleApplication.java | 24 ++++++ .../src/main/webapp/index.html | 1 + .../maven/AbstractDependencyFilterMojo.java | 2 +- .../boot/maven/AbstractPackagerMojo.java | 5 +- .../boot/maven/ArtifactsLibraries.java | 43 +++++++--- .../boot/maven/BuildImageMojo.java | 4 +- .../boot/maven/RepackageMojo.java | 4 +- .../boot/maven/ArtifactsLibrariesTests.java | 16 ++++ 17 files changed, 346 insertions(+), 81 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html diff --git a/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java b/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java index 3ab71247129..8d77644a014 100644 --- a/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java +++ b/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java @@ -198,7 +198,7 @@ abstract class ArchiveCommand extends OptionParsingCommand { List libraries = new ArrayList<>(); for (URL dependency : dependencies) { File file = new File(dependency.toURI()); - libraries.add(new Library(file, getLibraryScope(file))); + libraries.add(new Library(null, file, getLibraryScope(file), null, false, false, true)); } return libraries; } @@ -256,7 +256,7 @@ abstract class ArchiveCommand extends OptionParsingCommand { List libraries = new ArrayList<>(); for (MatchedResource entry : entries) { if (entry.isRoot()) { - libraries.add(new Library(entry.getFile(), LibraryScope.COMPILE)); + libraries.add(new Library(null, entry.getFile(), LibraryScope.COMPILE, null, false, false, true)); } else { writeClasspathEntry(writer, entry); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java index 8efa6e6a3e6..a5212339138 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java @@ -24,6 +24,7 @@ import org.gradle.api.specs.Spec; import org.springframework.boot.gradle.tasks.bundling.ResolvedDependencies.DependencyDescriptor; import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.Library; +import org.springframework.boot.loader.tools.LibraryCoordinates; /** * Resolver backed by a {@link LayeredSpec} that provides the destination {@link Layer} @@ -77,9 +78,12 @@ class LayerResolver { private Library asLibrary(FileCopyDetails details) { File file = details.getFile(); DependencyDescriptor dependency = this.resolvedDependencies.find(file); - return (dependency != null) - ? new Library(null, file, null, dependency.getCoordinates(), false, dependency.isProjectDependency()) - : new Library(file, null); + if (dependency == null) { + return new Library(null, file, null, null, false, false, true); + } + LibraryCoordinates coordinates = dependency.getCoordinates(); + boolean projectDependency = dependency.isProjectDependency(); + return new Library(null, file, null, coordinates, false, projectDependency, true); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java index 67b1ad5eb3d..675ca0a6df5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java @@ -30,6 +30,7 @@ import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; +import java.util.function.Predicate; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; @@ -88,23 +89,42 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter { * Write all entries from the specified jar file. * @param jarFile the source jar file * @throws IOException if the entries cannot be written + * @deprecated since 2.4.8 for removal in 2.6.0 in favor of + * {@link #writeEntries(JarFile, EntryTransformer, UnpackHandler, Predicate)} */ + @Deprecated public void writeEntries(JarFile jarFile) throws IOException { - writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER); + writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER, (entry) -> true); } - final void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler) - throws IOException { + /** + * Write required entries from the specified jar file. + * @param jarFile the source jar file + * @param entryTransformer the entity transformer used to change the entry + * @param unpackHandler the unpack handler + * @param entryFilter a predicate used to filter the written entries + * @throws IOException if the entries cannot be written + * @since 2.4.8 + */ + public void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler, + Predicate entryFilter) throws IOException { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { - JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement()); - setUpEntry(jarFile, entry); - try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) { - EntryWriter entryWriter = new InputStreamEntryWriter(inputStream); - JarArchiveEntry transformedEntry = entryTransformer.transform(entry); - if (transformedEntry != null) { - writeEntry(transformedEntry, entryWriter, unpackHandler, true); - } + JarEntry entry = entries.nextElement(); + if (entryFilter.test(entry)) { + writeEntry(jarFile, entryTransformer, unpackHandler, new JarArchiveEntry(entry)); + } + } + } + + private void writeEntry(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler, + JarArchiveEntry entry) throws IOException { + setUpEntry(jarFile, entry); + try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) { + EntryWriter entryWriter = new InputStreamEntryWriter(inputStream); + JarArchiveEntry transformedEntry = entryTransformer.transform(entry); + if (transformedEntry != null) { + writeEntry(transformedEntry, entryWriter, unpackHandler, true); } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java index acacd916c90..b10a7be9911 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java @@ -42,7 +42,7 @@ public class JarModeLibrary extends Library { } public JarModeLibrary(LibraryCoordinates coordinates) { - super(getJarName(coordinates), null, LibraryScope.RUNTIME, coordinates, false); + super(getJarName(coordinates), null, LibraryScope.RUNTIME, coordinates, false, false, true); } private static LibraryCoordinates createCoordinates(String artifactId) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java index 9c82bae24d9..6f6e348359c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java @@ -43,11 +43,16 @@ public class Library { private final boolean local; + private final boolean included; + /** * Create a new {@link Library}. * @param file the source file * @param scope the scope of the library + * @deprecated since 2.4.8 for removal in 2.6.0 in favor of + * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)} */ + @Deprecated public Library(File file, LibraryScope scope) { this(file, scope, false); } @@ -57,7 +62,10 @@ public class Library { * @param file the source file * @param scope the scope of the library * @param unpackRequired if the library needs to be unpacked before it can be used + * @deprecated since 2.4.8 for removal in 2.6.0 in favor of + * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)} */ + @Deprecated public Library(File file, LibraryScope scope, boolean unpackRequired) { this(null, file, scope, unpackRequired); } @@ -69,7 +77,10 @@ public class Library { * @param file the source file * @param scope the scope of the library * @param unpackRequired if the library needs to be unpacked before it can be used + * @deprecated since 2.4.8 for removal in 2.6.0 in favor of + * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)} */ + @Deprecated public Library(String name, File file, LibraryScope scope, boolean unpackRequired) { this(name, file, scope, null, unpackRequired); } @@ -82,7 +93,10 @@ public class Library { * @param scope the scope of the library * @param coordinates the library coordinates or {@code null} * @param unpackRequired if the library needs to be unpacked before it can be used + * @deprecated since 2.4.8 for removal in 2.6.0 in favor of + * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)} */ + @Deprecated public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired) { this(name, file, scope, coordinates, unpackRequired, false); } @@ -98,15 +112,37 @@ public class Library { * @param local if the library is local (part of the same build) to the application * that is being packaged * @since 2.4.0 + * @deprecated since 2.4.8 for removal in 2.6.0 in favor of + * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)} */ + @Deprecated public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired, boolean local) { + this(name, file, scope, coordinates, unpackRequired, local, true); + } + + /** + * Create a new {@link Library}. + * @param name the name of the library as it should be written or {@code null} to use + * the file name + * @param file the source file + * @param scope the scope of the library + * @param coordinates the library coordinates or {@code null} + * @param unpackRequired if the library needs to be unpacked before it can be used + * @param local if the library is local (part of the same build) to the application + * that is being packaged + * @param included if the library is included in the fat jar + * @since 2.4.8 + */ + public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired, + boolean local, boolean included) { this.name = (name != null) ? name : file.getName(); this.file = file; this.scope = scope; this.coordinates = coordinates; this.unpackRequired = unpackRequired; this.local = local; + this.included = included; } /** @@ -172,4 +208,12 @@ public class Library { return this.local; } + /** + * Return if the library is included in the fat jar. + * @return if the library is included + */ + public boolean isIncluded() { + return this.included; + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java index 46ab824573a..d78081a3160 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java @@ -25,7 +25,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.jar.Attributes; +import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.stream.Collectors; @@ -191,14 +193,18 @@ public abstract class Packager { protected final void write(JarFile sourceJar, Libraries libraries, AbstractJarWriter writer) throws IOException { Assert.notNull(libraries, "Libraries must not be null"); - WritableLibraries writeableLibraries = new WritableLibraries(libraries); + write(sourceJar, writer, new PackagedLibraries(libraries)); + } + + private void write(JarFile sourceJar, AbstractJarWriter writer, PackagedLibraries libraries) throws IOException { if (isLayered()) { writer.useLayers(this.layers, this.layersIndex); } writer.writeManifest(buildManifest(sourceJar)); writeLoaderClasses(writer); - writer.writeEntries(sourceJar, getEntityTransformer(), writeableLibraries); - writeableLibraries.write(writer); + writer.writeEntries(sourceJar, getEntityTransformer(), libraries.getUnpackHandler(), + libraries.getEntryFilter()); + libraries.write(writer); if (isLayered()) { writeLayerIndex(writer); } @@ -456,11 +462,15 @@ public abstract class Packager { * An {@link UnpackHandler} that determines that an entry needs to be unpacked if a * library that requires unpacking has a matching entry name. */ - private final class WritableLibraries implements UnpackHandler { + private final class PackagedLibraries { private final Map libraries = new LinkedHashMap<>(); - WritableLibraries(Libraries libraries) throws IOException { + private final UnpackHandler unpackHandler; + + private final Predicate entryFilter; + + PackagedLibraries(Libraries libraries) throws IOException { libraries.doWithLibraries((library) -> { if (isZip(library::openStream)) { addLibrary(library); @@ -469,6 +479,8 @@ public abstract class Packager { if (isLayered() && Packager.this.includeRelevantJarModeJars) { addLibrary(JarModeLibrary.LAYER_TOOLS); } + this.unpackHandler = new PackagedLibrariesUnpackHandler(); + this.entryFilter = this::isIncluded; } private void addLibrary(Library library) { @@ -480,37 +492,58 @@ public abstract class Packager { } } - @Override - public boolean requiresUnpack(String name) { - Library library = this.libraries.get(name); - return library != null && library.isUnpackRequired(); + private boolean isIncluded(JarEntry entry) { + Library library = this.libraries.get(entry.getName()); + return library == null || library.isIncluded(); } - @Override - public String sha1Hash(String name) throws IOException { - Library library = this.libraries.get(name); - Assert.notNull(library, () -> "No library found for entry name '" + name + "'"); - return Digest.sha1(library::openStream); + UnpackHandler getUnpackHandler() { + return this.unpackHandler; + } + + Predicate getEntryFilter() { + return this.entryFilter; } - private void write(AbstractJarWriter writer) throws IOException { + void write(AbstractJarWriter writer) throws IOException { + List writtenPaths = new ArrayList<>(); for (Entry entry : this.libraries.entrySet()) { String path = entry.getKey(); Library library = entry.getValue(); - String location = path.substring(0, path.lastIndexOf('/') + 1); - writer.writeNestedLibrary(location, library); + if (library.isIncluded()) { + String location = path.substring(0, path.lastIndexOf('/') + 1); + writer.writeNestedLibrary(location, library); + writtenPaths.add(path); + } } if (getLayout() instanceof RepackagingLayout) { - writeClasspathIndex((RepackagingLayout) getLayout(), writer); + writeClasspathIndex(writtenPaths, (RepackagingLayout) getLayout(), writer); } } - private void writeClasspathIndex(RepackagingLayout layout, AbstractJarWriter writer) throws IOException { - List names = this.libraries.keySet().stream().map((path) -> "- \"" + path + "\"") - .collect(Collectors.toList()); + private void writeClasspathIndex(List paths, RepackagingLayout layout, AbstractJarWriter writer) + throws IOException { + List names = paths.stream().map((path) -> "- \"" + path + "\"").collect(Collectors.toList()); writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names); } + private class PackagedLibrariesUnpackHandler implements UnpackHandler { + + @Override + public boolean requiresUnpack(String name) { + Library library = PackagedLibraries.this.libraries.get(name); + return library != null && library.isUnpackRequired(); + } + + @Override + public String sha1Hash(String name) throws IOException { + Library library = PackagedLibraries.this.libraries.get(name); + Assert.notNull(library, () -> "No library found for entry name '" + name + "'"); + return Digest.sha1(library::openStream); + } + + } + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java index 8bcfc938269..83389a6e18a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java @@ -197,9 +197,9 @@ abstract class AbstractPackagerTests

{ libJarFile.setLastModified(JAN_1_1980); P packager = createPackager(); execute(packager, (callback) -> { - callback.library(new Library(libJarFile, LibraryScope.COMPILE)); - callback.library(new Library(libJarFileToUnpack, LibraryScope.COMPILE, true)); - callback.library(new Library(libNonJarFile, LibraryScope.COMPILE)); + callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libJarFileToUnpack, LibraryScope.COMPILE, true)); + callback.library(newLibrary(libNonJarFile, LibraryScope.COMPILE, false)); }); assertThat(hasPackagedEntry("BOOT-INF/lib/" + libJarFile.getName())).isTrue(); assertThat(hasPackagedEntry("BOOT-INF/lib/" + libJarFileToUnpack.getName())).isTrue(); @@ -226,9 +226,9 @@ abstract class AbstractPackagerTests

{ File file = this.testJarFile.getFile(); P packager = createPackager(file); execute(packager, (callback) -> { - callback.library(new Library(libJarFile1, LibraryScope.COMPILE)); - callback.library(new Library(libJarFile2, LibraryScope.COMPILE)); - callback.library(new Library(libJarFile3, LibraryScope.COMPILE)); + callback.library(newLibrary(libJarFile1, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libJarFile2, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libJarFile3, LibraryScope.COMPILE, false)); }); assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue(); String index = getPackagedEntryContent("BOOT-INF/classpath.idx"); @@ -258,9 +258,9 @@ abstract class AbstractPackagerTests

{ packager.setLayers(layers); packager.setIncludeRelevantJarModeJars(false); execute(packager, (callback) -> { - callback.library(new Library(libJarFile1, LibraryScope.COMPILE)); - callback.library(new Library(libJarFile2, LibraryScope.COMPILE)); - callback.library(new Library(libJarFile3, LibraryScope.COMPILE)); + callback.library(newLibrary(libJarFile1, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libJarFile2, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libJarFile3, LibraryScope.COMPILE, false)); }); assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue(); String classpathIndex = getPackagedEntryContent("BOOT-INF/classpath.idx"); @@ -316,8 +316,8 @@ abstract class AbstractPackagerTests

{ this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class); P packager = createPackager(); assertThatIllegalStateException().isThrownBy(() -> execute(packager, (callback) -> { - callback.library(new Library(libJarFile, LibraryScope.COMPILE, false)); - callback.library(new Library(libJarFile, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false)); })).withMessageContaining("Duplicate library"); } @@ -334,7 +334,7 @@ abstract class AbstractPackagerTests

{ given(layout.getLibraryLocation(anyString(), eq(scope))).willReturn("test/"); given(layout.getLibraryLocation(anyString(), eq(LibraryScope.COMPILE))).willReturn("test-lib/"); packager.setLayout(layout); - execute(packager, (callback) -> callback.library(new Library(libJarFile, scope))); + execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false))); assertThat(hasPackagedEntry("test/" + libJarFile.getName())).isTrue(); assertThat(getPackagedManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo("test-lib/"); assertThat(getPackagedManifest().getMainAttributes().getValue("Main-Class")).isEqualTo("testLauncher"); @@ -351,7 +351,7 @@ abstract class AbstractPackagerTests

{ LibraryScope scope = mock(LibraryScope.class); given(layout.getLauncherClassName()).willReturn("testLauncher"); packager.setLayout(layout); - execute(packager, (callback) -> callback.library(new Library(libJarFile, scope))); + execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false))); assertThat(getPackagedManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isNull(); assertThat(getPackagedManifest().getMainAttributes().getValue("Main-Class")).isEqualTo("testLauncher"); } @@ -405,7 +405,7 @@ abstract class AbstractPackagerTests

{ this.testJarFile.addFile("test/nested.jar", nestedFile); this.testJarFile.addClass("A.class", ClassWithMainMethod.class); P packager = createPackager(); - execute(packager, (callback) -> callback.library(new Library(nestedFile, LibraryScope.COMPILE))); + execute(packager, (callback) -> callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, false))); assertThat(getPackagedEntry("BOOT-INF/lib/" + nestedFile.getName()).getMethod()).isEqualTo(ZipEntry.STORED); assertThat(getPackagedEntry("BOOT-INF/classes/test/nested.jar").getMethod()).isEqualTo(ZipEntry.STORED); } @@ -419,7 +419,7 @@ abstract class AbstractPackagerTests

{ this.testJarFile.addFile(name, nested.getFile()); this.testJarFile.addClass("A.class", ClassWithMainMethod.class); P packager = createPackager(); - execute(packager, (callback) -> callback.library(new Library(nestedFile, LibraryScope.COMPILE, true))); + execute(packager, (callback) -> callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, true))); assertThat(getPackagedEntry(name).getComment()).startsWith("UNPACK:"); } @@ -437,7 +437,7 @@ abstract class AbstractPackagerTests

{ File toZip = new File(this.tempDir, "to-zip"); toZip.createNewFile(); ZipUtil.packEntry(toZip, nestedFile); - callback.library(new Library(nestedFile, LibraryScope.COMPILE)); + callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, false)); }); assertThat(getPackagedEntry("BOOT-INF/lib/" + nestedFile.getName()).getSize()).isEqualTo(sourceLength); } @@ -498,14 +498,14 @@ abstract class AbstractPackagerTests

{ @Test void loaderIsWrittenFirstThenApplicationClassesThenLibraries() throws IOException { this.testJarFile.addClass("com/example/Application.class", ClassWithMainMethod.class); - File libraryOne = createLibrary(); - File libraryTwo = createLibrary(); - File libraryThree = createLibrary(); + File libraryOne = createLibraryJar(); + File libraryTwo = createLibraryJar(); + File libraryThree = createLibraryJar(); P packager = createPackager(); execute(packager, (callback) -> { - callback.library(new Library(libraryOne, LibraryScope.COMPILE, false)); - callback.library(new Library(libraryTwo, LibraryScope.COMPILE, true)); - callback.library(new Library(libraryThree, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libraryOne, LibraryScope.COMPILE, false)); + callback.library(newLibrary(libraryTwo, LibraryScope.COMPILE, true)); + callback.library(newLibrary(libraryThree, LibraryScope.COMPILE, false)); }); assertThat(getPackagedEntryNames()).containsSubsequence("org/springframework/boot/loader/", "BOOT-INF/classes/com/example/Application.class", "BOOT-INF/lib/" + libraryOne.getName(), @@ -514,12 +514,12 @@ abstract class AbstractPackagerTests

{ @Test void existingEntryThatMatchesUnpackLibraryIsMarkedForUnpack() throws IOException { - File library = createLibrary(); + File library = createLibraryJar(); this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class); this.testJarFile.addFile("WEB-INF/lib/" + library.getName(), library); P packager = createPackager(this.testJarFile.getFile("war")); packager.setLayout(new Layouts.War()); - execute(packager, (callback) -> callback.library(new Library(library, LibraryScope.COMPILE, true))); + execute(packager, (callback) -> callback.library(newLibrary(library, LibraryScope.COMPILE, true))); assertThat(getPackagedEntryNames()).containsSubsequence("org/springframework/boot/loader/", "WEB-INF/classes/com/example/Application.class", "WEB-INF/lib/" + library.getName()); ZipEntry unpackLibrary = getPackagedEntry("WEB-INF/lib/" + library.getName()); @@ -536,7 +536,7 @@ abstract class AbstractPackagerTests

{ Layout layout = mock(Layout.class); LibraryScope scope = mock(LibraryScope.class); packager.setLayout(layout); - execute(packager, (callback) -> callback.library(new Library(libJarFile, scope))); + execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false))); assertThat(getPackagedEntryNames()).containsExactly("META-INF/", "META-INF/MANIFEST.MF", "a/", "a/b/", "a/b/C.class"); } @@ -583,12 +583,39 @@ abstract class AbstractPackagerTests

{ assertThat(getPackagedEntry("BOOT-INF/classes/META-INF/test.kotlin_module")).isNotNull(); } - private File createLibrary() throws IOException { + @Test + void entryFiltering() throws Exception { + File webLibrary = createLibraryJar(); + File libraryOne = createLibraryJar(); + File libraryTwo = createLibraryJar(); + this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class); + this.testJarFile.addFile("WEB-INF/lib/" + webLibrary.getName(), webLibrary); + P packager = createPackager(this.testJarFile.getFile("war")); + packager.setLayout(new Layouts.War()); + execute(packager, (callback) -> { + callback.library(newLibrary(webLibrary, LibraryScope.COMPILE, false, false)); + callback.library(newLibrary(libraryOne, LibraryScope.COMPILE, false, false)); + callback.library(newLibrary(libraryTwo, LibraryScope.COMPILE, false, true)); + }); + Collection packagedEntryNames = getPackagedEntryNames(); + packagedEntryNames.removeIf((name) -> !name.endsWith(".jar")); + assertThat(packagedEntryNames).containsExactly("WEB-INF/lib/" + libraryTwo.getName()); + } + + private File createLibraryJar() throws IOException { TestJarFile library = new TestJarFile(this.tempDir); library.addClass("com/example/library/Library.class", ClassWithoutMainMethod.class); return library.getFile(); } + private Library newLibrary(File file, LibraryScope scope, boolean unpackRequired) { + return new Library(null, file, scope, null, unpackRequired, false, true); + } + + private Library newLibrary(File file, LibraryScope scope, boolean unpackRequired, boolean included) { + return new Library(null, file, scope, null, unpackRequired, false, included); + } + protected final P createPackager() throws IOException { return createPackager(this.testJarFile.getFile()); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java index 349d76a3e0a..8165f30cca7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java @@ -89,4 +89,13 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests { }); } + @TestTemplate + void whenEntryIsExcludedItShouldNotBePresentInTheRepackagedWar(MavenBuild mavenBuild) { + mavenBuild.project("war-exclude-entry").execute((project) -> { + File war = new File(project, "target/war-exclude-entry-0.0.1.BUILD-SNAPSHOT.war"); + assertThat(jar(war)).hasEntryWithNameStartingWith("WEB-INF/lib/spring-context") + .doesNotHaveEntryWithNameStartingWith("WEB-INF/lib/spring-core"); + }); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml new file mode 100644 index 00000000000..6657f2c8982 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + org.springframework.boot.maven.it + war-exclude-entry + 0.0.1.BUILD-SNAPSHOT + war + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + repackage + + + + + org.springframework + spring-core + + + + + + + + org.apache.maven.plugins + maven-war-plugin + @maven-war-plugin.version@ + + + + Foo + + + + + + + + + org.springframework + spring-context + @spring-framework.version@ + + + jakarta.servlet + jakarta.servlet-api + @jakarta-servlet.version@ + provided + + + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java new file mode 100644 index 00000000000..ca2b9a2f0e5 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.test; + +public class SampleApplication { + + public static void main(String[] args) { + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html new file mode 100644 index 00000000000..18ecdcb795c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html @@ -0,0 +1 @@ + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java index c05653a2b44..99394e0a543 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java @@ -76,7 +76,7 @@ public abstract class AbstractDependencyFilterMojo extends AbstractMojo { this.excludeGroupIds = excludeGroupIds; } - protected Set filterDependencies(Set dependencies, FilterArtifacts filters) + protected final Set filterDependencies(Set dependencies, FilterArtifacts filters) throws MojoExecutionException { try { Set filtered = new LinkedHashSet<>(dependencies); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java index fd92dd2f997..3ce19bf4c10 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java @@ -182,8 +182,9 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo * @throws MojoExecutionException on execution error */ protected final Libraries getLibraries(Collection unpacks) throws MojoExecutionException { - Set artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); - return new ArtifactsLibraries(artifacts, this.session.getProjects(), unpacks, getLog()); + Set artifacts = this.project.getArtifacts(); + Set includedArtifacts = filterDependencies(artifacts, getFilters(getAdditionalFilters())); + return new ArtifactsLibraries(artifacts, includedArtifacts, this.session.getProjects(), unpacks, getLog()); } private ArtifactsFilter[] getAdditionalFilters() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java index e73263eb1b2..3e9b5764040 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java @@ -16,6 +16,7 @@ package org.springframework.boot.maven; +import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; @@ -59,6 +60,8 @@ public class ArtifactsLibraries implements Libraries { private final Set artifacts; + private final Set includedArtifacts; + private final Collection localProjects; private final Collection unpacks; @@ -89,7 +92,23 @@ public class ArtifactsLibraries implements Libraries { */ public ArtifactsLibraries(Set artifacts, Collection localProjects, Collection unpacks, Log log) { + this(artifacts, artifacts, localProjects, unpacks, log); + } + + /** + * Creates a new {@code ArtifactsLibraries} from the given {@code artifacts}. + * @param artifacts all artifacts that can be represented as libraries + * @param includedArtifacts the actual artifacts to include in the fat jar + * @param localProjects projects for which {@link Library#isLocal() local} libraries + * should be created + * @param unpacks artifacts that should be unpacked on launch + * @param log the log + * @since 2.4.8 + */ + public ArtifactsLibraries(Set artifacts, Set includedArtifacts, + Collection localProjects, Collection unpacks, Log log) { this.artifacts = artifacts; + this.includedArtifacts = includedArtifacts; this.localProjects = localProjects; this.unpacks = unpacks; this.log = log; @@ -99,18 +118,22 @@ public class ArtifactsLibraries implements Libraries { public void doWithLibraries(LibraryCallback callback) throws IOException { Set duplicates = getDuplicates(this.artifacts); for (Artifact artifact : this.artifacts) { + String name = getFileName(artifact); + File file = artifact.getFile(); LibraryScope scope = SCOPES.get(artifact.getScope()); - if (scope != null && artifact.getFile() != null) { - String name = getFileName(artifact); - if (duplicates.contains(name)) { - this.log.debug("Duplicate found: " + name); - name = artifact.getGroupId() + "-" + name; - this.log.debug("Renamed to: " + name); - } - LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact); - callback.library(new Library(name, artifact.getFile(), scope, coordinates, isUnpackRequired(artifact), - isLocal(artifact))); + if (scope == null || file == null) { + continue; + } + if (duplicates.contains(name)) { + this.log.debug("Duplicate found: " + name); + name = artifact.getGroupId() + "-" + name; + this.log.debug("Renamed to: " + name); } + LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact); + boolean unpackRequired = isUnpackRequired(artifact); + boolean local = isLocal(artifact); + boolean included = this.includedArtifacts.contains(artifact); + callback.library(new Library(name, file, scope, coordinates, unpackRequired, local, included)); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java index efec7b35d43..340e98ff785 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java @@ -189,8 +189,8 @@ public class BuildImageMojo extends AbstractPackagerMojo { } /** - * Return the layout factory that will be used to determine the {@link LayoutType} if - * no explicit layout is set. + * Return the layout factory that will be used to determine the + * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. * @return the value of the {@code layoutFactory} parameter, or {@code null} if the * parameter is not provided */ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java index d2639b93e8f..56bac144ff4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java @@ -183,8 +183,8 @@ public class RepackageMojo extends AbstractPackagerMojo { } /** - * Return the layout factory that will be used to determine the {@link LayoutType} if - * no explicit layout is set. + * Return the layout factory that will be used to determine the + * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. * @return the value of the {@code layoutFactory} parameter, or {@code null} if the * parameter is not provided */ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java index 022affaaecb..988516c2f64 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java @@ -144,6 +144,7 @@ class ArtifactsLibrariesTests { this.artifacts = Collections.singleton(snapshotArtifact); new ArtifactsLibraries(this.artifacts, Collections.emptyList(), null, mock(Log.class)) .doWithLibraries((library) -> { + assertThat(library.isIncluded()).isTrue(); assertThat(library.isLocal()).isFalse(); assertThat(library.getCoordinates().getVersion()).isEqualTo("1.0-SNAPSHOT"); }); @@ -181,4 +182,19 @@ class ArtifactsLibrariesTests { .doWithLibraries((library) -> assertThat(library.isLocal()).isTrue()); } + @Test + void nonIncludedArtifact() throws IOException { + Artifact artifact = mock(Artifact.class); + given(artifact.getScope()).willReturn("compile"); + given(artifact.getArtifactId()).willReturn("artifact"); + given(artifact.getBaseVersion()).willReturn("1.0-SNAPSHOT"); + given(artifact.getFile()).willReturn(new File("a")); + given(artifact.getArtifactHandler()).willReturn(this.artifactHandler); + MavenProject mavenProject = mock(MavenProject.class); + given(mavenProject.getArtifact()).willReturn(artifact); + this.artifacts = Collections.singleton(artifact); + new ArtifactsLibraries(this.artifacts, Collections.emptySet(), Collections.singleton(mavenProject), null, + mock(Log.class)).doWithLibraries((library) -> assertThat(library.isIncluded()).isFalse()); + } + }