Polish loader and loader-tools
Polish and refactor `spring-boot-loader` and `spring-boot-loader-tools` to make it easier to add indexing and layering support. Closes gh-19766
This commit is contained in:
parent
56e30258fb
commit
ad72f86bdb
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -56,12 +56,14 @@ import org.apache.commons.compress.archivers.zip.UnixStat;
|
|||
*/
|
||||
public class JarWriter implements LoaderClassesWriter, AutoCloseable {
|
||||
|
||||
private static final UnpackHandler NEVER_UNPACK = new NeverUnpackHandler();
|
||||
|
||||
private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar";
|
||||
|
||||
private static final int BUFFER_SIZE = 32 * 1024;
|
||||
|
||||
private static final int UNIX_FILE_MODE = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM;
|
||||
|
||||
private static final int UNIX_DIR_MODE = UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM;
|
||||
|
||||
private final JarArchiveOutputStream jarOutput;
|
||||
|
||||
private final Set<String> writtenEntries = new HashSet<>();
|
||||
|
@ -121,11 +123,11 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
|
|||
* @throws IOException if the entries cannot be written
|
||||
*/
|
||||
public void writeEntries(JarFile jarFile) throws IOException {
|
||||
this.writeEntries(jarFile, new IdentityEntryTransformer(), NEVER_UNPACK);
|
||||
this.writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER);
|
||||
}
|
||||
|
||||
void writeEntries(JarFile jarFile, UnpackHandler unpackHandler) throws IOException {
|
||||
this.writeEntries(jarFile, new IdentityEntryTransformer(), unpackHandler);
|
||||
this.writeEntries(jarFile, EntryTransformer.NONE, unpackHandler);
|
||||
}
|
||||
|
||||
void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler)
|
||||
|
@ -244,7 +246,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
|
|||
}
|
||||
|
||||
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws IOException {
|
||||
writeEntry(entry, entryWriter, NEVER_UNPACK);
|
||||
writeEntry(entry, entryWriter, UnpackHandler.NEVER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -257,22 +259,10 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
|
|||
*/
|
||||
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter, UnpackHandler unpackHandler)
|
||||
throws IOException {
|
||||
String parent = entry.getName();
|
||||
if (parent.endsWith("/")) {
|
||||
parent = parent.substring(0, parent.length() - 1);
|
||||
entry.setUnixMode(UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
|
||||
}
|
||||
else {
|
||||
entry.setUnixMode(UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
|
||||
}
|
||||
if (parent.lastIndexOf('/') != -1) {
|
||||
parent = parent.substring(0, parent.lastIndexOf('/') + 1);
|
||||
if (!parent.isEmpty()) {
|
||||
writeEntry(new JarArchiveEntry(parent), null, unpackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.writtenEntries.add(entry.getName())) {
|
||||
String name = entry.getName();
|
||||
writeParentFolderEntries(name);
|
||||
if (this.writtenEntries.add(name)) {
|
||||
entry.setUnixMode(name.endsWith("/") ? UNIX_DIR_MODE : UNIX_FILE_MODE);
|
||||
entryWriter = addUnpackCommentIfNecessary(entry, entryWriter, unpackHandler);
|
||||
this.jarOutput.putArchiveEntry(entry);
|
||||
if (entryWriter != null) {
|
||||
|
@ -282,6 +272,16 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
private void writeParentFolderEntries(String name) throws IOException {
|
||||
String parent = name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
|
||||
while (parent.lastIndexOf('/') != -1) {
|
||||
parent = parent.substring(0, parent.lastIndexOf('/'));
|
||||
if (!parent.isEmpty()) {
|
||||
writeEntry(new JarArchiveEntry(parent + "/"), null, UnpackHandler.NEVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EntryWriter addUnpackCommentIfNecessary(JarArchiveEntry entry, EntryWriter entryWriter,
|
||||
UnpackHandler unpackHandler) throws IOException {
|
||||
if (entryWriter == null || !unpackHandler.requiresUnpack(entry.getName())) {
|
||||
|
@ -444,50 +444,44 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
|
|||
* An {@code EntryTransformer} enables the transformation of {@link JarEntry jar
|
||||
* entries} during the writing process.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface EntryTransformer {
|
||||
|
||||
/**
|
||||
* No-op entity transformer.
|
||||
*/
|
||||
EntryTransformer NONE = (jarEntry) -> jarEntry;
|
||||
|
||||
JarArchiveEntry transform(JarArchiveEntry jarEntry);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code EntryTransformer} that returns the entry unchanged.
|
||||
*/
|
||||
private static final class IdentityEntryTransformer implements EntryTransformer {
|
||||
|
||||
@Override
|
||||
public JarArchiveEntry transform(JarArchiveEntry jarEntry) {
|
||||
return jarEntry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code UnpackHandler} determines whether or not unpacking is required and
|
||||
* provides a SHA1 hash if required.
|
||||
*/
|
||||
interface UnpackHandler {
|
||||
|
||||
UnpackHandler NEVER = new UnpackHandler() {
|
||||
|
||||
@Override
|
||||
public boolean requiresUnpack(String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sha1Hash(String name) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
boolean requiresUnpack(String name);
|
||||
|
||||
String sha1Hash(String name) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
private static final class NeverUnpackHandler implements UnpackHandler {
|
||||
|
||||
@Override
|
||||
public boolean requiresUnpack(String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sha1Hash(String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class LibraryUnpackHandler implements UnpackHandler {
|
||||
|
||||
private final Library library;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -35,13 +35,26 @@ public interface Layout {
|
|||
*/
|
||||
String getLauncherClassName();
|
||||
|
||||
/**
|
||||
* Returns the destination path for a given library.
|
||||
* @param libraryName the name of the library (excluding any path)
|
||||
* @param scope the scope of the library
|
||||
* @return the location of the library relative to the root of the archive (should end
|
||||
* with '/') or {@code null} if the library should not be included.
|
||||
*/
|
||||
default String getLibraryLocation(String libraryName, LibraryScope scope) {
|
||||
return getLibraryDestination(libraryName, scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination path for a given library.
|
||||
* @param libraryName the name of the library (excluding any path)
|
||||
* @param scope the scope of the library
|
||||
* @return the destination relative to the root of the archive (should end with '/')
|
||||
* or {@code null} if the library should not be included.
|
||||
* @deprecated since 2.3.0 in favor of {@link #getLibraryLocation}
|
||||
*/
|
||||
@Deprecated
|
||||
String getLibraryDestination(String libraryName, LibraryScope scope);
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -67,6 +67,12 @@ public final class Layouts {
|
|||
return "org.springframework.boot.loader.JarLauncher";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLibraryLocation(String libraryName, LibraryScope scope) {
|
||||
return "BOOT-INF/lib/";
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getLibraryDestination(String libraryName, LibraryScope scope) {
|
||||
return "BOOT-INF/lib/";
|
||||
|
@ -123,15 +129,15 @@ public final class Layouts {
|
|||
*/
|
||||
public static class War implements Layout {
|
||||
|
||||
private static final Map<LibraryScope, String> SCOPE_DESTINATIONS;
|
||||
private static final Map<LibraryScope, String> SCOPE_LOCATION;
|
||||
|
||||
static {
|
||||
Map<LibraryScope, String> map = new HashMap<>();
|
||||
map.put(LibraryScope.COMPILE, "WEB-INF/lib/");
|
||||
map.put(LibraryScope.CUSTOM, "WEB-INF/lib/");
|
||||
map.put(LibraryScope.RUNTIME, "WEB-INF/lib/");
|
||||
map.put(LibraryScope.PROVIDED, "WEB-INF/lib-provided/");
|
||||
SCOPE_DESTINATIONS = Collections.unmodifiableMap(map);
|
||||
Map<LibraryScope, String> locations = new HashMap<>();
|
||||
locations.put(LibraryScope.COMPILE, "WEB-INF/lib/");
|
||||
locations.put(LibraryScope.CUSTOM, "WEB-INF/lib/");
|
||||
locations.put(LibraryScope.RUNTIME, "WEB-INF/lib/");
|
||||
locations.put(LibraryScope.PROVIDED, "WEB-INF/lib-provided/");
|
||||
SCOPE_LOCATION = Collections.unmodifiableMap(locations);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,9 +145,15 @@ public final class Layouts {
|
|||
return "org.springframework.boot.loader.WarLauncher";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLibraryLocation(String libraryName, LibraryScope scope) {
|
||||
return SCOPE_LOCATION.get(scope);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getLibraryDestination(String libraryName, LibraryScope scope) {
|
||||
return SCOPE_DESTINATIONS.get(scope);
|
||||
return SCOPE_LOCATION.get(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
|
@ -54,10 +55,10 @@ public class Repackager {
|
|||
|
||||
private static final String BOOT_VERSION_ATTRIBUTE = "Spring-Boot-Version";
|
||||
|
||||
private static final String BOOT_LIB_ATTRIBUTE = "Spring-Boot-Lib";
|
||||
|
||||
private static final String BOOT_CLASSES_ATTRIBUTE = "Spring-Boot-Classes";
|
||||
|
||||
private static final String BOOT_LIB_ATTRIBUTE = "Spring-Boot-Lib";
|
||||
|
||||
private static final byte[] ZIP_FILE_HEADER = new byte[] { 'P', 'K', 3, 4 };
|
||||
|
||||
private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
|
@ -81,13 +82,9 @@ public class Repackager {
|
|||
}
|
||||
|
||||
public Repackager(File source, LayoutFactory layoutFactory) {
|
||||
if (source == null) {
|
||||
throw new IllegalArgumentException("Source file must be provided");
|
||||
}
|
||||
if (!source.exists() || !source.isFile()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Source must refer to an existing file, got " + source.getAbsolutePath());
|
||||
}
|
||||
Assert.notNull(source, "Source file must be provided");
|
||||
Assert.isTrue(source.exists() && source.isFile(),
|
||||
"Source must refer to an existing file, got " + source.getAbsolutePath());
|
||||
this.source = source.getAbsoluteFile();
|
||||
this.layoutFactory = layoutFactory;
|
||||
}
|
||||
|
@ -124,9 +121,7 @@ public class Repackager {
|
|||
* @param layout the layout
|
||||
*/
|
||||
public void setLayout(Layout layout) {
|
||||
if (layout == null) {
|
||||
throw new IllegalArgumentException("Layout must not be null");
|
||||
}
|
||||
Assert.notNull(layout, "Layout must not be null");
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
|
@ -169,12 +164,8 @@ public class Repackager {
|
|||
* @since 1.3.0
|
||||
*/
|
||||
public void repackage(File destination, Libraries libraries, LaunchScript launchScript) throws IOException {
|
||||
if (destination == null || destination.isDirectory()) {
|
||||
throw new IllegalArgumentException("Invalid destination");
|
||||
}
|
||||
if (libraries == null) {
|
||||
throw new IllegalArgumentException("Libraries must not be null");
|
||||
}
|
||||
Assert.isTrue(destination != null && !destination.isDirectory(), "Invalid destination");
|
||||
Assert.notNull(libraries, "Libraries must not be null");
|
||||
if (this.layout == null) {
|
||||
this.layout = getLayoutFactory().getLayout(this.source);
|
||||
}
|
||||
|
@ -234,14 +225,7 @@ public class Repackager {
|
|||
try (JarWriter writer = new JarWriter(destination, launchScript)) {
|
||||
writer.writeManifest(buildManifest(sourceJar));
|
||||
writeLoaderClasses(writer);
|
||||
if (this.layout instanceof RepackagingLayout) {
|
||||
writer.writeEntries(sourceJar,
|
||||
new RenamingEntryTransformer(((RepackagingLayout) this.layout).getRepackagedClassesLocation()),
|
||||
writeableLibraries);
|
||||
}
|
||||
else {
|
||||
writer.writeEntries(sourceJar, writeableLibraries);
|
||||
}
|
||||
writer.writeEntries(sourceJar, getEntityTransformer(), writeableLibraries);
|
||||
writeableLibraries.write(writer);
|
||||
}
|
||||
}
|
||||
|
@ -255,6 +239,13 @@ public class Repackager {
|
|||
}
|
||||
}
|
||||
|
||||
private EntryTransformer getEntityTransformer() {
|
||||
if (this.layout instanceof RepackagingLayout) {
|
||||
return new RepackagingEntryTransformer((RepackagingLayout) this.layout);
|
||||
}
|
||||
return EntryTransformer.NONE;
|
||||
}
|
||||
|
||||
private boolean isZip(File file) {
|
||||
try {
|
||||
try (FileInputStream fileInputStream = new FileInputStream(file)) {
|
||||
|
@ -276,41 +267,45 @@ public class Repackager {
|
|||
}
|
||||
|
||||
private Manifest buildManifest(JarFile source) throws IOException {
|
||||
Manifest manifest = source.getManifest();
|
||||
if (manifest == null) {
|
||||
manifest = new Manifest();
|
||||
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
|
||||
}
|
||||
manifest = new Manifest(manifest);
|
||||
String startClass = this.mainClass;
|
||||
if (startClass == null) {
|
||||
startClass = manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE);
|
||||
}
|
||||
if (startClass == null) {
|
||||
startClass = findMainMethodWithTimeoutWarning(source);
|
||||
}
|
||||
String launcherClassName = this.layout.getLauncherClassName();
|
||||
if (launcherClassName != null) {
|
||||
manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, launcherClassName);
|
||||
if (startClass == null) {
|
||||
throw new IllegalStateException("Unable to find main class");
|
||||
}
|
||||
manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE, startClass);
|
||||
}
|
||||
else if (startClass != null) {
|
||||
manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, startClass);
|
||||
}
|
||||
String bootVersion = getClass().getPackage().getImplementationVersion();
|
||||
manifest.getMainAttributes().putValue(BOOT_VERSION_ATTRIBUTE, bootVersion);
|
||||
manifest.getMainAttributes().putValue(BOOT_CLASSES_ATTRIBUTE, (this.layout instanceof RepackagingLayout)
|
||||
? ((RepackagingLayout) this.layout).getRepackagedClassesLocation() : this.layout.getClassesLocation());
|
||||
String lib = this.layout.getLibraryDestination("", LibraryScope.COMPILE);
|
||||
if (StringUtils.hasLength(lib)) {
|
||||
manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, lib);
|
||||
}
|
||||
Manifest manifest = createInitialManifest(source);
|
||||
addMainAndStartAttributes(source, manifest);
|
||||
addBootAttributes(manifest.getMainAttributes());
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private Manifest createInitialManifest(JarFile source) throws IOException {
|
||||
if (source.getManifest() != null) {
|
||||
return new Manifest(source.getManifest());
|
||||
}
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private void addMainAndStartAttributes(JarFile source, Manifest manifest) throws IOException {
|
||||
String mainClass = getMainClass(source, manifest);
|
||||
String launcherClass = this.layout.getLauncherClassName();
|
||||
if (launcherClass != null) {
|
||||
Assert.state(mainClass != null, "Unable to find main class");
|
||||
manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, launcherClass);
|
||||
manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE, mainClass);
|
||||
}
|
||||
else if (mainClass != null) {
|
||||
manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, mainClass);
|
||||
}
|
||||
}
|
||||
|
||||
private String getMainClass(JarFile source, Manifest manifest) throws IOException {
|
||||
if (this.mainClass != null) {
|
||||
return this.mainClass;
|
||||
}
|
||||
String attributeValue = manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE);
|
||||
if (attributeValue != null) {
|
||||
return attributeValue;
|
||||
}
|
||||
return findMainMethodWithTimeoutWarning(source);
|
||||
}
|
||||
|
||||
private String findMainMethodWithTimeoutWarning(JarFile source) throws IOException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
String mainMethod = findMainMethod(source);
|
||||
|
@ -328,6 +323,32 @@ public class Repackager {
|
|||
SPRING_BOOT_APPLICATION_CLASS_NAME);
|
||||
}
|
||||
|
||||
private void addBootAttributes(Attributes attributes) {
|
||||
attributes.putValue(BOOT_VERSION_ATTRIBUTE, getClass().getPackage().getImplementationVersion());
|
||||
if (this.layout instanceof RepackagingLayout) {
|
||||
addBootBootAttributesForRepackagingLayout(attributes, (RepackagingLayout) this.layout);
|
||||
}
|
||||
else {
|
||||
addBootBootAttributesForPlainLayout(attributes, this.layout);
|
||||
}
|
||||
}
|
||||
|
||||
private void addBootBootAttributesForRepackagingLayout(Attributes attributes, RepackagingLayout layout) {
|
||||
attributes.putValue(BOOT_CLASSES_ATTRIBUTE, layout.getRepackagedClassesLocation());
|
||||
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, this.layout.getLibraryLocation("", LibraryScope.COMPILE));
|
||||
}
|
||||
|
||||
private void addBootBootAttributesForPlainLayout(Attributes attributes, Layout layout) {
|
||||
attributes.putValue(BOOT_CLASSES_ATTRIBUTE, this.layout.getClassesLocation());
|
||||
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, this.layout.getLibraryLocation("", LibraryScope.COMPILE));
|
||||
}
|
||||
|
||||
private void putIfHasLength(Attributes attributes, String name, String value) {
|
||||
if (StringUtils.hasLength(value)) {
|
||||
attributes.putValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void renameFile(File file, File dest) {
|
||||
if (!file.renameTo(dest)) {
|
||||
throw new IllegalStateException("Unable to rename '" + file + "' to '" + dest + "'");
|
||||
|
@ -359,12 +380,12 @@ public class Repackager {
|
|||
/**
|
||||
* An {@code EntryTransformer} that renames entries by applying a prefix.
|
||||
*/
|
||||
private static final class RenamingEntryTransformer implements EntryTransformer {
|
||||
private static final class RepackagingEntryTransformer implements EntryTransformer {
|
||||
|
||||
private final String namePrefix;
|
||||
private final RepackagingLayout layout;
|
||||
|
||||
private RenamingEntryTransformer(String namePrefix) {
|
||||
this.namePrefix = namePrefix;
|
||||
private RepackagingEntryTransformer(RepackagingLayout layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -372,33 +393,40 @@ public class Repackager {
|
|||
if (entry.getName().equals("META-INF/INDEX.LIST")) {
|
||||
return null;
|
||||
}
|
||||
if ((entry.getName().startsWith("META-INF/") && !entry.getName().equals("META-INF/aop.xml")
|
||||
&& !entry.getName().endsWith(".kotlin_module")) || entry.getName().startsWith("BOOT-INF/")
|
||||
|| entry.getName().equals("module-info.class")) {
|
||||
if (!isTransformable(entry)) {
|
||||
return entry;
|
||||
}
|
||||
JarArchiveEntry renamedEntry = new JarArchiveEntry(this.namePrefix + entry.getName());
|
||||
renamedEntry.setTime(entry.getTime());
|
||||
renamedEntry.setSize(entry.getSize());
|
||||
renamedEntry.setMethod(entry.getMethod());
|
||||
String transformedName = this.layout.getRepackagedClassesLocation() + entry.getName();
|
||||
JarArchiveEntry transformedEntry = new JarArchiveEntry(transformedName);
|
||||
transformedEntry.setTime(entry.getTime());
|
||||
transformedEntry.setSize(entry.getSize());
|
||||
transformedEntry.setMethod(entry.getMethod());
|
||||
if (entry.getComment() != null) {
|
||||
renamedEntry.setComment(entry.getComment());
|
||||
transformedEntry.setComment(entry.getComment());
|
||||
}
|
||||
renamedEntry.setCompressedSize(entry.getCompressedSize());
|
||||
renamedEntry.setCrc(entry.getCrc());
|
||||
transformedEntry.setCompressedSize(entry.getCompressedSize());
|
||||
transformedEntry.setCrc(entry.getCrc());
|
||||
if (entry.getCreationTime() != null) {
|
||||
renamedEntry.setCreationTime(entry.getCreationTime());
|
||||
transformedEntry.setCreationTime(entry.getCreationTime());
|
||||
}
|
||||
if (entry.getExtra() != null) {
|
||||
renamedEntry.setExtra(entry.getExtra());
|
||||
transformedEntry.setExtra(entry.getExtra());
|
||||
}
|
||||
if (entry.getLastAccessTime() != null) {
|
||||
renamedEntry.setLastAccessTime(entry.getLastAccessTime());
|
||||
transformedEntry.setLastAccessTime(entry.getLastAccessTime());
|
||||
}
|
||||
if (entry.getLastModifiedTime() != null) {
|
||||
renamedEntry.setLastModifiedTime(entry.getLastModifiedTime());
|
||||
transformedEntry.setLastModifiedTime(entry.getLastModifiedTime());
|
||||
}
|
||||
return renamedEntry;
|
||||
return transformedEntry;
|
||||
}
|
||||
|
||||
private boolean isTransformable(JarArchiveEntry entry) {
|
||||
String name = entry.getName();
|
||||
if (name.startsWith("META-INF/")) {
|
||||
return name.equals("META-INF/aop.xml") || name.endsWith(".kotlin_module");
|
||||
}
|
||||
return !name.startsWith("BOOT-INF/") && !name.equals("module-info.class");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -414,19 +442,19 @@ public class Repackager {
|
|||
private WritableLibraries(Libraries libraries) throws IOException {
|
||||
libraries.doWithLibraries((library) -> {
|
||||
if (isZip(library.getFile())) {
|
||||
String libraryDestination = Repackager.this.layout.getLibraryDestination(library.getName(),
|
||||
library.getScope());
|
||||
if (libraryDestination != null) {
|
||||
Library existing = this.libraryEntryNames.putIfAbsent(libraryDestination + library.getName(),
|
||||
library);
|
||||
if (existing != null) {
|
||||
throw new IllegalStateException("Duplicate library " + library.getName());
|
||||
}
|
||||
String location = getLocation(library);
|
||||
if (location != null) {
|
||||
Library existing = this.libraryEntryNames.putIfAbsent(location + library.getName(), library);
|
||||
Assert.state(existing == null, "Duplicate library " + library.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getLocation(Library library) {
|
||||
return Repackager.this.layout.getLibraryLocation(library.getName(), library.getScope());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUnpack(String name) {
|
||||
Library library = this.libraryEntryNames.get(name);
|
||||
|
@ -436,9 +464,7 @@ public class Repackager {
|
|||
@Override
|
||||
public String sha1Hash(String name) throws IOException {
|
||||
Library library = this.libraryEntryNames.get(name);
|
||||
if (library == null) {
|
||||
throw new IllegalArgumentException("No library found for entry name '" + name + "'");
|
||||
}
|
||||
Assert.notNull(library, "No library found for entry name '" + name + "'");
|
||||
return FileUtils.sha1Hash(library.getFile());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -56,19 +56,19 @@ class LayoutsTests {
|
|||
@Test
|
||||
void jarLayout() {
|
||||
Layout layout = new Layouts.Jar();
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.COMPILE)).isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.CUSTOM)).isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.PROVIDED)).isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.RUNTIME)).isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.COMPILE)).isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.CUSTOM)).isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.PROVIDED)).isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.RUNTIME)).isEqualTo("BOOT-INF/lib/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void warLayout() {
|
||||
Layout layout = new Layouts.War();
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.COMPILE)).isEqualTo("WEB-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.CUSTOM)).isEqualTo("WEB-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.PROVIDED)).isEqualTo("WEB-INF/lib-provided/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.RUNTIME)).isEqualTo("WEB-INF/lib/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.COMPILE)).isEqualTo("WEB-INF/lib/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.CUSTOM)).isEqualTo("WEB-INF/lib/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.PROVIDED)).isEqualTo("WEB-INF/lib-provided/");
|
||||
assertThat(layout.getLibraryLocation("lib.jar", LibraryScope.RUNTIME)).isEqualTo("WEB-INF/lib/");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -324,8 +324,8 @@ class RepackagerTests {
|
|||
Layout layout = mock(Layout.class);
|
||||
LibraryScope scope = mock(LibraryScope.class);
|
||||
given(layout.getLauncherClassName()).willReturn("testLauncher");
|
||||
given(layout.getLibraryDestination(anyString(), eq(scope))).willReturn("test/");
|
||||
given(layout.getLibraryDestination(anyString(), eq(LibraryScope.COMPILE))).willReturn("test-lib/");
|
||||
given(layout.getLibraryLocation(anyString(), eq(scope))).willReturn("test/");
|
||||
given(layout.getLibraryLocation(anyString(), eq(LibraryScope.COMPILE))).willReturn("test-lib/");
|
||||
repackager.setLayout(layout);
|
||||
repackager.repackage((callback) -> callback.library(new Library(libJarFile, scope)));
|
||||
assertThat(hasEntry(file, "test/" + libJarFile.getName())).isTrue();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -32,6 +32,8 @@ import org.springframework.boot.loader.archive.Archive;
|
|||
*/
|
||||
public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||
|
||||
private static final String START_CLASS_ATTRIBUTE = "Start-Class";
|
||||
|
||||
private final Archive archive;
|
||||
|
||||
public ExecutableArchiveLauncher() {
|
||||
|
@ -44,11 +46,12 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
|||
}
|
||||
|
||||
protected ExecutableArchiveLauncher(Archive archive) {
|
||||
this.archive = archive;
|
||||
}
|
||||
|
||||
protected final Archive getArchive() {
|
||||
return this.archive;
|
||||
try {
|
||||
this.archive = archive;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -56,7 +59,7 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
|||
Manifest manifest = this.archive.getManifest();
|
||||
String mainClass = null;
|
||||
if (manifest != null) {
|
||||
mainClass = manifest.getMainAttributes().getValue("Start-Class");
|
||||
mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
|
||||
}
|
||||
if (mainClass == null) {
|
||||
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
|
||||
|
@ -125,4 +128,12 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
|||
return this.archive.supportsNestedJars();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root archive.
|
||||
* @return the root archive
|
||||
*/
|
||||
protected final Archive getArchive() {
|
||||
return this.archive;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -77,7 +77,9 @@ public abstract class Launcher {
|
|||
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
|
||||
List<URL> urls = new ArrayList<>(50);
|
||||
while (archives.hasNext()) {
|
||||
urls.add(archives.next().getUrl());
|
||||
Archive archive = archives.next();
|
||||
urls.add(archive.getUrl());
|
||||
archive.close();
|
||||
}
|
||||
return createClassLoader(urls.toArray(new URL[0]));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -23,7 +23,7 @@ import java.io.IOException;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
|
@ -53,7 +53,15 @@ public abstract class AbstractExecutableArchiveLauncherTests {
|
|||
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/"));
|
||||
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classes/"));
|
||||
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/lib/"));
|
||||
JarEntry libFoo = new JarEntry(entryPrefix + "/lib/foo.jar");
|
||||
addNestedJars(entryPrefix, "/lib/foo.jar", jarOutputStream);
|
||||
addNestedJars(entryPrefix, "/lib/bar.jar", jarOutputStream);
|
||||
addNestedJars(entryPrefix, "/lib/baz.jar", jarOutputStream);
|
||||
jarOutputStream.close();
|
||||
return archive;
|
||||
}
|
||||
|
||||
private void addNestedJars(String entryPrefix, String lib, JarOutputStream jarOutputStream) throws IOException {
|
||||
JarEntry libFoo = new JarEntry(entryPrefix + lib);
|
||||
libFoo.setMethod(ZipEntry.STORED);
|
||||
ByteArrayOutputStream fooJarStream = new ByteArrayOutputStream();
|
||||
new JarOutputStream(fooJarStream).close();
|
||||
|
@ -63,8 +71,6 @@ public abstract class AbstractExecutableArchiveLauncherTests {
|
|||
libFoo.setCrc(crc32.getValue());
|
||||
jarOutputStream.putNextEntry(libFoo);
|
||||
jarOutputStream.write(fooJarStream.toByteArray());
|
||||
jarOutputStream.close();
|
||||
return archive;
|
||||
}
|
||||
|
||||
protected File explode(File archive) throws IOException {
|
||||
|
@ -87,11 +93,20 @@ public abstract class AbstractExecutableArchiveLauncherTests {
|
|||
}
|
||||
|
||||
protected Set<URL> getUrls(List<Archive> archives) throws MalformedURLException {
|
||||
Set<URL> urls = new HashSet<>(archives.size());
|
||||
Set<URL> urls = new LinkedHashSet<>(archives.size());
|
||||
for (Archive archive : archives) {
|
||||
urls.add(archive.getUrl());
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
protected final URL toUrl(File file) {
|
||||
try {
|
||||
return file.toURI().toURL();
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -42,9 +42,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
|||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).hasSize(2);
|
||||
assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "BOOT-INF/classes").toURI().toURL(),
|
||||
new File(explodedRoot, "BOOT-INF/lib/foo.jar").toURI().toURL());
|
||||
assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
|
||||
for (Archive archive : archives) {
|
||||
archive.close();
|
||||
}
|
||||
|
@ -57,14 +55,29 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
|||
JarLauncher launcher = new JarLauncher(archive);
|
||||
List<Archive> classPathArchives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add);
|
||||
assertThat(classPathArchives).hasSize(2);
|
||||
assertThat(classPathArchives).hasSize(4);
|
||||
assertThat(getUrls(classPathArchives)).containsOnly(
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/"));
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/bar.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/baz.jar!/"));
|
||||
for (Archive classPathArchive : classPathArchives) {
|
||||
classPathArchive.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final URL[] getExpectedFileUrls(File explodedRoot) {
|
||||
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
|
||||
}
|
||||
|
||||
protected final List<File> getExpectedFiles(File parent) {
|
||||
List<File> expected = new ArrayList<>();
|
||||
expected.add(new File(parent, "BOOT-INF/classes"));
|
||||
expected.add(new File(parent, "BOOT-INF/lib/foo.jar"));
|
||||
expected.add(new File(parent, "BOOT-INF/lib/bar.jar"));
|
||||
expected.add(new File(parent, "BOOT-INF/lib/baz.jar"));
|
||||
return expected;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* 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.
|
||||
|
@ -42,29 +42,41 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
|||
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).hasSize(2);
|
||||
assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "WEB-INF/classes").toURI().toURL(),
|
||||
new File(explodedRoot, "WEB-INF/lib/foo.jar").toURI().toURL());
|
||||
assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
|
||||
for (Archive archive : archives) {
|
||||
archive.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void archivedWarHasOnlyWebInfClassesAndContentsOWebInfLibOnClasspath() throws Exception {
|
||||
void archivedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception {
|
||||
File jarRoot = createJarArchive("archive.war", "WEB-INF");
|
||||
try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
|
||||
WarLauncher launcher = new WarLauncher(archive);
|
||||
List<Archive> classPathArchives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add);
|
||||
assertThat(classPathArchives).hasSize(2);
|
||||
assertThat(getUrls(classPathArchives)).containsOnly(
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/"));
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/bar.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/baz.jar!/"));
|
||||
for (Archive classPathArchive : classPathArchives) {
|
||||
classPathArchive.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final URL[] getExpectedFileUrls(File explodedRoot) {
|
||||
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
|
||||
}
|
||||
|
||||
protected final List<File> getExpectedFiles(File parent) {
|
||||
List<File> expected = new ArrayList<>();
|
||||
expected.add(new File(parent, "WEB-INF/classes"));
|
||||
expected.add(new File(parent, "WEB-INF/lib/foo.jar"));
|
||||
expected.add(new File(parent, "WEB-INF/lib/bar.jar"));
|
||||
expected.add(new File(parent, "WEB-INF/lib/baz.jar"));
|
||||
return expected;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue