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()); + } + }