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

@@ -107,6 +107,12 @@ public class SpringBootPluginExtension {
(layout == null ? null : layout.layout)
}
/**
* Libraries that must be unpacked from fat jars in order to run. Use Strings in the
* form {@literal groupId:artifactId}.
*/
Set<String> requiresUnpack;
/**
* Location of an agent jar to attach to the VM when running the application with runJar task.
*/
@@ -121,4 +127,5 @@ public class SpringBootPluginExtension {
* If exclude rules should be applied to dependencies based on the spring-dependencies-bom
*/
boolean applyExcludeRules = true;
}

View File

@@ -18,10 +18,15 @@ package org.springframework.boot.gradle.repackage;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.springframework.boot.gradle.SpringBootPluginExtension;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCallback;
@@ -37,22 +42,24 @@ class ProjectLibraries implements Libraries {
private final Project project;
private final SpringBootPluginExtension extension;
private String providedConfigurationName = "providedRuntime";
private String customConfigurationName = null;
/**
* Create a new {@link ProjectLibraries} instance of the specified {@link Project}.
*
* @param project the gradle project
* @param extension the extension
*/
public ProjectLibraries(Project project) {
public ProjectLibraries(Project project, SpringBootPluginExtension extension) {
this.project = project;
this.extension = extension;
}
/**
* Set the name of the provided configuration. Defaults to 'providedRuntime'.
*
* @param providedConfigurationName the providedConfigurationName to set
*/
public void setProvidedConfigurationName(String providedConfigurationName) {
@@ -65,27 +72,20 @@ class ProjectLibraries implements Libraries {
@Override
public void doWithLibraries(LibraryCallback callback) throws IOException {
FileCollection custom = this.customConfigurationName != null ? this.project
.getConfigurations().findByName(this.customConfigurationName) : null;
Set<ResolvedArtifact> custom = getArtifacts(this.customConfigurationName);
if (custom != null) {
libraries(LibraryScope.CUSTOM, custom, callback);
}
else {
FileCollection compile = this.project.getConfigurations()
.getByName("compile");
Set<ResolvedArtifact> compile = getArtifacts("compile");
FileCollection runtime = this.project.getConfigurations()
.getByName("runtime");
runtime = runtime.minus(compile);
FileCollection provided = this.project.getConfigurations()
.findByName(this.providedConfigurationName);
Set<ResolvedArtifact> runtime = getArtifacts("runtime");
runtime = minus(runtime, compile);
Set<ResolvedArtifact> provided = getArtifacts(this.providedConfigurationName);
if (provided != null) {
compile = compile.minus(provided);
runtime = runtime.minus(provided);
compile = minus(compile, provided);
runtime = minus(runtime, provided);
}
libraries(LibraryScope.COMPILE, compile, callback);
@@ -94,12 +94,47 @@ class ProjectLibraries implements Libraries {
}
}
private void libraries(LibraryScope scope, FileCollection files,
private Set<ResolvedArtifact> getArtifacts(String configurationName) {
Configuration configuration = (configurationName == null ? null : this.project
.getConfigurations().findByName(configurationName));
return (configuration == null ? null : configuration.getResolvedConfiguration()
.getResolvedArtifacts());
}
private Set<ResolvedArtifact> minus(Set<ResolvedArtifact> source,
Set<ResolvedArtifact> toRemove) {
if (source == null || toRemove == null) {
return source;
}
Set<File> filesToRemove = new HashSet<File>();
for (ResolvedArtifact artifact : toRemove) {
filesToRemove.add(artifact.getFile());
}
Set<ResolvedArtifact> result = new LinkedHashSet<ResolvedArtifact>();
for (ResolvedArtifact artifact : source) {
if (!toRemove.contains(artifact.getFile())) {
result.add(artifact);
}
}
return result;
}
private void libraries(LibraryScope scope, Set<ResolvedArtifact> artifacts,
LibraryCallback callback) throws IOException {
if (files != null) {
for (File file: files) {
callback.library(new Library(file, scope));
if (artifacts != null) {
for (ResolvedArtifact artifact : artifacts) {
callback.library(new Library(artifact.getFile(), scope, isUnpackRequired(artifact)));
}
}
}
private boolean isUnpackRequired(ResolvedArtifact artifact) {
if (this.extension.getRequiresUnpack() != null) {
ModuleVersionIdentifier id = artifact.getModuleVersion().getId();
return this.extension.getRequiresUnpack().contains(
id.getGroup() + ":" + id.getName());
}
return false;
}
}

View File

@@ -101,7 +101,7 @@ public class RepackageTask extends DefaultTask {
Project project = getProject();
SpringBootPluginExtension extension = project.getExtensions().getByType(
SpringBootPluginExtension.class);
ProjectLibraries libraries = new ProjectLibraries(project);
ProjectLibraries libraries = new ProjectLibraries(project, extension);
if (extension.getProvidedConfiguration() != null) {
libraries.setProvidedConfigurationName(extension.getProvidedConfiguration());
}

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

View File

@@ -17,22 +17,32 @@
package org.springframework.boot.loader.tools;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.util.FileSystemUtils;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests fir {@link FileUtils}.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class FileUtilsTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private File outputDirectory;
private File originDirectory;
@@ -91,4 +101,18 @@ public class FileUtilsTests {
assertTrue(file.exists());
}
@Test
public void hash() throws Exception {
File file = this.temporaryFolder.newFile();
OutputStream outputStream = new FileOutputStream(file);
try {
outputStream.write(new byte[] { 1, 2, 3 });
}
finally {
outputStream.close();
}
assertThat(FileUtils.sha1Hash(file),
equalTo("7037807198c22a7d2b0807371d763779a84fdfcf"));
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.loader.tools;
import java.io.File;
import java.io.IOException;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
@@ -33,6 +34,7 @@ import org.springframework.boot.loader.tools.sample.ClassWithoutMainMethod;
import org.springframework.util.FileCopyUtils;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyString;
@@ -258,6 +260,7 @@ public class RepackagerTests {
TestJarFile libJar = new TestJarFile(this.temporaryFolder);
libJar.addClass("a/b/C.class", ClassWithoutMainMethod.class);
final File libJarFile = libJar.getFile();
final File libJarFileToUnpack = libJar.getFile();
final File libNonJarFile = this.temporaryFolder.newFile();
FileCopyUtils.copy(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, libNonJarFile);
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
@@ -267,11 +270,17 @@ public class RepackagerTests {
@Override
public void doWithLibraries(LibraryCallback callback) throws IOException {
callback.library(new Library(libJarFile, LibraryScope.COMPILE));
callback.library(new Library(libJarFileToUnpack, LibraryScope.COMPILE,
true));
callback.library(new Library(libNonJarFile, LibraryScope.COMPILE));
}
});
assertThat(hasEntry(file, "lib/" + libJarFile.getName()), equalTo(true));
assertThat(hasEntry(file, "lib/" + libJarFileToUnpack.getName()), equalTo(true));
assertThat(hasEntry(file, "lib/" + libNonJarFile.getName()), equalTo(false));
JarEntry entry = getEntry(file, "lib/" + libJarFileToUnpack.getName());
assertThat(entry.getComment(), startsWith("UNPACK:"));
assertThat(entry.getComment().length(), equalTo(47));
}
@Test
@@ -345,7 +354,6 @@ public class RepackagerTests {
finally {
jarFile.close();
}
}
private boolean hasLauncherClasses(File file) throws IOException {
@@ -354,9 +362,13 @@ public class RepackagerTests {
}
private boolean hasEntry(File file, String name) throws IOException {
return getEntry(file, name) != null;
}
private JarEntry getEntry(File file, String name) throws IOException {
JarFile jarFile = new JarFile(file);
try {
return jarFile.getEntry(name) != null;
return jarFile.getJarEntry(name);
}
finally {
jarFile.close();

View File

@@ -17,7 +17,10 @@
package org.springframework.boot.loader.archive;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
@@ -27,6 +30,7 @@ import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
import org.springframework.boot.loader.jar.JarEntryData;
import org.springframework.boot.loader.jar.JarEntryFilter;
import org.springframework.boot.loader.jar.JarFile;
@@ -39,12 +43,23 @@ import org.springframework.boot.loader.util.AsciiBytes;
*/
public class JarFileArchive extends Archive {
private static final AsciiBytes UNPACK_MARKER = new AsciiBytes("UNPACK:");
private static final int BUFFER_SIZE = 32 * 1024;
private final JarFile jarFile;
private final List<Entry> entries;
private URL url;
public JarFileArchive(File file) throws IOException {
this(file, null);
}
public JarFileArchive(File file, URL url) throws IOException {
this(new JarFile(file));
this.url = url;
}
public JarFileArchive(JarFile jarFile) {
@@ -58,6 +73,9 @@ public class JarFileArchive extends Archive {
@Override
public URL getUrl() throws MalformedURLException {
if (this.url != null) {
return this.url;
}
return this.jarFile.getUrl();
}
@@ -84,10 +102,54 @@ public class JarFileArchive extends Archive {
protected Archive getNestedArchive(Entry entry) throws IOException {
JarEntryData data = ((JarFileEntry) entry).getJarEntryData();
if (data.getComment().startsWith(UNPACK_MARKER)) {
return getUnpackedNestedArchive(data);
}
JarFile jarFile = this.jarFile.getNestedJarFile(data);
return new JarFileArchive(jarFile);
}
private Archive getUnpackedNestedArchive(JarEntryData data) throws IOException {
AsciiBytes hash = data.getComment().substring(UNPACK_MARKER.length());
String name = data.getName().toString();
if (name.lastIndexOf("/") != -1) {
name = name.substring(name.lastIndexOf("/") + 1);
}
File file = new File(getTempUnpackFolder(), hash.toString() + "-" + name);
if (!file.exists() || file.length() != data.getSize()) {
unpack(data, file);
}
return new JarFileArchive(file, file.toURI().toURL());
}
private File getTempUnpackFolder() {
File tempFolder = new File(System.getProperty("java.io.tmpdir"));
File unpackFolder = new File(tempFolder, "spring-boot-libs");
unpackFolder.mkdirs();
return unpackFolder;
}
private void unpack(JarEntryData data, File file) throws IOException {
InputStream inputStream = data.getData().getInputStream(ResourceAccess.ONCE);
try {
OutputStream outputStream = new FileOutputStream(file);
try {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
}
finally {
outputStream.close();
}
}
finally {
inputStream.close();
}
}
@Override
public Archive getFilteredArchive(final EntryRenameFilter filter) throws IOException {
JarFile filteredJar = this.jarFile.getFilteredJarFile(new JarEntryFilter() {

View File

@@ -96,10 +96,11 @@ public class Handler extends URLStreamHandler {
return openConnection(getFallbackHandler(), url);
}
catch (Exception ex) {
this.logger.log(Level.WARNING, "Unable to open fallback handler", ex);
if (reason instanceof IOException) {
this.logger.log(Level.FINEST, "Unable to open fallback handler", ex);
throw (IOException) reason;
}
this.logger.log(Level.WARNING, "Unable to open fallback handler", ex);
if (reason instanceof RuntimeException) {
throw (RuntimeException) reason;
}
@@ -111,7 +112,6 @@ public class Handler extends URLStreamHandler {
if (this.fallbackHandler != null) {
return this.fallbackHandler;
}
for (String handlerClassName : FALLBACK_HANDLERS) {
try {
Class<?> handlerClass = Class.forName(handlerClassName);

View File

@@ -93,7 +93,13 @@ public final class JarEntryData {
return inputStream;
}
RandomAccessData getData() throws IOException {
/**
* @return the underlying {@link RandomAccessData} for this entry. Generally this
* method should not be called directly and instead data should be accessed via
* {@link JarFile#getInputStream(ZipEntry)}.
* @throws IOException
*/
public RandomAccessData getData() throws IOException {
if (this.data == null) {
// aspectjrt-1.7.4.jar has a different ext bytes length in the
// local directory to the central directory. We need to re-read

View File

@@ -35,6 +35,10 @@ import java.util.zip.ZipEntry;
public abstract class TestJarCreator {
public static void createTestJar(File file) throws Exception {
createTestJar(file, false);
}
public static void createTestJar(File file, boolean unpackNested) throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream(file);
JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream);
try {
@@ -50,6 +54,9 @@ public abstract class TestJarCreator {
byte[] nestedJarData = getNestedJarData();
nestedEntry.setSize(nestedJarData.length);
nestedEntry.setCompressedSize(nestedJarData.length);
if (unpackNested) {
nestedEntry.setComment("UNPACK:0000000000000000000000000000000000000000");
}
CRC32 crc32 = new CRC32();
crc32.update(nestedJarData);
nestedEntry.setCrc(crc32.getValue());

View File

@@ -29,7 +29,9 @@ import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.archive.Archive.Entry;
import org.springframework.boot.loader.util.AsciiBytes;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
/**
@@ -48,8 +50,12 @@ public class JarFileArchiveTests {
@Before
public void setup() throws Exception {
setup(false);
}
private void setup(boolean unpackNested) throws Exception {
this.rootJarFile = this.temporaryFolder.newFile();
TestJarCreator.createTestJar(this.rootJarFile);
TestJarCreator.createTestJar(this.rootJarFile, unpackNested);
this.archive = new JarFileArchive(this.rootJarFile);
}
@@ -80,6 +86,15 @@ public class JarFileArchiveTests {
equalTo("jar:file:" + this.rootJarFile.getPath() + "!/nested.jar!/"));
}
@Test
public void getNestedUnpackedArchive() throws Exception {
setup(true);
Entry entry = getEntriesMap(this.archive).get("nested.jar");
Archive nested = this.archive.getNestedArchive(entry);
assertThat(nested.getUrl().toString(), startsWith("file:"));
assertThat(nested.getUrl().toString(), endsWith(".jar"));
}
@Test
public void getFilteredArchive() throws Exception {
Archive filteredArchive = this.archive

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-with-unpack</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<requiresUnpack>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
</requiresUnpack>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,8 @@
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2014 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
*
* http://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.
*/
import java.io.*;
import org.springframework.boot.maven.*;
File f = new File( basedir, "target/jar-with-unpack-0.0.1.BUILD-SNAPSHOT.jar");
new Verify.JarArchiveVerification(f, Verify.SAMPLE_APP) {
@Override
protected void verifyZipEntries(Verify.ArchiveVerifier verifier) throws Exception {
super.verifyZipEntries(verifier)
verifier.hasUnpackEntry("lib/spring-core-4.0.5.RELEASE.jar")
verifier.hasNonUnpackEntry("lib/spring-context-4.0.5.RELEASE.jar")
}
}.verify();

View File

@@ -17,12 +17,14 @@
package org.springframework.boot.maven;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCallback;
@@ -47,8 +49,11 @@ public class ArtifactsLibraries implements Libraries {
private final Set<Artifact> artifacts;
public ArtifactsLibraries(Set<Artifact> artifacts) {
private final Collection<Dependency> unpacks;
public ArtifactsLibraries(Set<Artifact> artifacts, Collection<Dependency> unpacks) {
this.artifacts = artifacts;
this.unpacks = unpacks;
}
@Override
@@ -56,8 +61,21 @@ public class ArtifactsLibraries implements Libraries {
for (Artifact artifact : this.artifacts) {
LibraryScope scope = SCOPES.get(artifact.getScope());
if (scope != null && artifact.getFile() != null) {
callback.library(new Library(artifact.getFile(), scope));
callback.library(new Library(artifact.getFile(), scope,
isUnpackRequired(artifact)));
}
}
}
private boolean isUnpackRequired(Artifact artifact) {
if (this.unpacks != null) {
for (Dependency unpack : this.unpacks) {
if (artifact.getGroupId().equals(unpack.getGroupId())
&& artifact.getArtifactId().equals(unpack.getArtifactId())) {
return true;
}
}
}
return false;
}
}

View File

@@ -18,11 +18,13 @@ package org.springframework.boot.maven;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
@@ -108,6 +110,13 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
@Parameter
private LayoutType layout;
/**
* A list of the libraries that must be unpacked from fat jars in order to run.
* @since 1.1
*/
@Parameter
private List<Dependency> requiresUnpack;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.project.getPackaging().equals("pom")) {
@@ -144,7 +153,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
getFilters());
Libraries libraries = new ArtifactsLibraries(artifacts);
Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack);
try {
repackager.repackage(target, libraries);
}

View File

@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -62,7 +63,7 @@ public class ArtifactsLibrariesTests {
public void setup() {
MockitoAnnotations.initMocks(this);
this.artifacts = Collections.singleton(this.artifact);
this.libs = new ArtifactsLibraries(this.artifacts);
this.libs = new ArtifactsLibraries(this.artifacts, null);
given(this.artifact.getFile()).willReturn(this.file);
}
@@ -75,6 +76,21 @@ public class ArtifactsLibrariesTests {
Library library = this.libraryCaptor.getValue();
assertThat(library.getFile(), equalTo(this.file));
assertThat(library.getScope(), equalTo(LibraryScope.COMPILE));
assertThat(library.isUnpackRequired(), equalTo(false));
}
@Test
public void callbackWithUnpack() throws Exception {
given(this.artifact.getGroupId()).willReturn("gid");
given(this.artifact.getArtifactId()).willReturn("aid");
given(this.artifact.getType()).willReturn("jar");
given(this.artifact.getScope()).willReturn("compile");
Dependency unpack = new Dependency();
unpack.setGroupId("gid");
unpack.setArtifactId("aid");
this.libs = new ArtifactsLibraries(this.artifacts, Collections.singleton(unpack));
this.libs.doWithLibraries(this.callback);
verify(this.callback).library(this.libraryCaptor.capture());
assertThat(this.libraryCaptor.getValue().isUnpackRequired(), equalTo(true));
}
}

View File

@@ -87,6 +87,15 @@ public class Verify {
}
}
public boolean hasNonUnpackEntry(String entry) {
return !hasUnpackEntry(entry);
}
public boolean hasUnpackEntry(String entry) {
String comment = this.content.get(entry).getComment();
return comment != null && comment.startsWith("UNPACK:");
}
public boolean hasEntry(String entry) {
return this.content.containsKey(entry);
}