Add support for unpacking nested JARs

Update the executable JAR code to automatically unpack any entries
which include an entry comment starting `UNPACK:` to the temp folder.
The existing Maven and Gradle plugins have been updated with new
configuration options and the `spring-boot-tools` project has been
updated to write the appropriate entry comment based on a flag passed
in via the `Library` class.

This support has been added to allow libraries such a JRuby (which
assumes that `jruby-complete.jar` is always accessible as file) to work
with Spring Boot executable jars.

Fixes gh-1070
This commit is contained in:
Phillip Webb
2014-06-24 00:35:01 -07:00
parent 5f8fbfd73a
commit f30b962ff9
24 changed files with 477 additions and 39 deletions

View File

@@ -17,13 +17,19 @@
package org.springframework.boot.loader.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Utilities for manipulating files and directories in Spring Boot tooling.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class FileUtils {
public abstract class FileUtils {
/**
* Utility to remove duplicate files from an "output" directory if they already exist
@@ -50,4 +56,37 @@ public class FileUtils {
}
}
/**
* Generate a SHA.1 Hash for a given file.
* @param file the file to hash
* @return the hash value as a String
* @throws IOException
*/
public static String sha1Hash(File file) throws IOException {
try {
DigestInputStream inputStream = new DigestInputStream(new FileInputStream(
file), MessageDigest.getInstance("SHA-1"));
try {
byte[] buffer = new byte[4098];
while (inputStream.read(buffer) != -1) {
// Read the entire stream
}
return bytesToHex(inputStream.getMessageDigest().digest());
}
finally {
inputStream.close();
}
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException(ex);
}
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hex = new StringBuilder();
for (byte b : bytes) {
hex.append(String.format("%02x", b));
}
return hex.toString();
}
}

View File

@@ -50,7 +50,7 @@ public class JarWriter {
private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar";
private static final int BUFFER_SIZE = 4096;
private static final int BUFFER_SIZE = 32 * 1024;
private final JarOutputStream jarOutput;
@@ -122,11 +122,16 @@ public class JarWriter {
/**
* Write a nested library.
* @param destination the destination of the library
* @param file the library file
* @param library the library
* @throws IOException if the write fails
*/
public void writeNestedLibrary(String destination, File file) throws IOException {
public void writeNestedLibrary(String destination, Library library)
throws IOException {
File file = library.getFile();
JarEntry entry = new JarEntry(destination + file.getName());
if (library.isUnpackRequired()) {
entry.setComment("UNPACK:" + FileUtils.sha1Hash(file));
}
new CrcAndSize(file).setupStoredEntry(entry);
writeEntry(entry, new InputStreamEntryWriter(new FileInputStream(file), true));
}

View File

@@ -31,14 +31,27 @@ public class Library {
private final LibraryScope scope;
private final boolean unpackRequired;
/**
* Create a new {@link Library}.
* @param file the source file
* @param scope the scope of the library
*/
public Library(File file, LibraryScope scope) {
this(file, scope, false);
}
/**
* Create a new {@link 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
*/
public Library(File file, LibraryScope scope, boolean unpackRequired) {
this.file = file;
this.scope = scope;
this.unpackRequired = unpackRequired;
}
/**
@@ -55,4 +68,12 @@ public class Library {
return this.scope;
}
/**
* @return if the file cannot be used directly as a nested jar and needs to be
* unpacked.
*/
public boolean isUnpackRequired() {
return this.unpackRequired;
}
}

View File

@@ -147,7 +147,7 @@ public class Repackager {
String destination = Repackager.this.layout
.getLibraryDestination(file.getName(), library.getScope());
if (destination != null) {
writer.writeNestedLibrary(destination, file);
writer.writeNestedLibrary(destination, library);
}
}
}