Merge branch 'gh-1070'
This commit is contained in:
commit
9f464e9c49
|
|
@ -56,6 +56,8 @@ import org.springframework.boot.cli.jar.PackagedSpringApplicationLauncher;
|
|||
import org.springframework.boot.loader.tools.JarWriter;
|
||||
import org.springframework.boot.loader.tools.Layout;
|
||||
import org.springframework.boot.loader.tools.Layouts;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.util.Assert;
|
||||
|
|
@ -248,7 +250,8 @@ public class JarCommand extends OptionParsingCommand {
|
|||
private void addDependency(JarWriter writer, File dependency)
|
||||
throws FileNotFoundException, IOException {
|
||||
if (dependency.isFile()) {
|
||||
writer.writeNestedLibrary("lib/", dependency);
|
||||
writer.writeNestedLibrary("lib/", new Library(dependency,
|
||||
LibraryScope.COMPILE));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -511,6 +511,11 @@ The following configuration options are available:
|
|||
|`layout`
|
||||
|The type of archive, corresponding to how the dependencies are laid out inside
|
||||
(defaults to a guess based on the archive type).
|
||||
|
||||
|`requiresUnpack`
|
||||
|A list of dependencies (in the form ``groupId:artifactId'' that must be unpacked from
|
||||
fat jars in order to run. Items are still packaged into the fat jar, but they will be
|
||||
automatically unpacked when it runs.
|
||||
|===
|
||||
|
||||
|
||||
|
|
@ -619,7 +624,7 @@ Here is a typical example repackage:
|
|||
@Override
|
||||
public void doWithLibraries(LibraryCallback callback) throws IOException {
|
||||
// Build system specific implementation, callback for each dependency
|
||||
// callback.library(nestedFile, LibraryScope.COMPILE);
|
||||
// callback.library(new Library(nestedFile, LibraryScope.COMPILE));
|
||||
}
|
||||
});
|
||||
----
|
||||
|
|
|
|||
|
|
@ -1618,6 +1618,50 @@ For Gradle users the steps are similar. Example:
|
|||
|
||||
|
||||
|
||||
[[howto-extract-specific-libraries-when-an-executable-jar-runs]]
|
||||
=== Extract specific libraries when an executable jar runs
|
||||
Most nested libraries in an executable jar do not need to be unpacked in order to run,
|
||||
however, certain libraries can have problems. For example, JRuby includes its own nested
|
||||
jar support which assumes that the `jruby-complete.jar` is always directly available as a
|
||||
file in its own right.
|
||||
|
||||
To deal with any problematic libraries, you can flag that specific nested jars should be
|
||||
automatically unpacked to the ``temp folder'' when the executable jar first runs.
|
||||
|
||||
For example, to indicate that JRuby should be flagged for unpack using the Maven Plugin
|
||||
you would add the following configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<requiresUnpack>
|
||||
<dependency>
|
||||
<groupId>org.jruby</groupId>
|
||||
<artifactId>jruby-complete</artifactId>
|
||||
</dependency>
|
||||
</requiresUnpack>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
----
|
||||
|
||||
And to do that same with Gradle:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
springBoot {
|
||||
requiresUnpack = ['org.jruby:jruby-complete']
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[howto-create-a-nonexecutable-jar]]
|
||||
=== Create a non-executable JAR with exclusions
|
||||
Often if you have an executable and a non-executable jar as build products, the executable
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,17 @@ 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;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
|
||||
|
|
@ -36,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) {
|
||||
|
|
@ -64,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);
|
||||
|
|
@ -93,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(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.boot.gradle.repackage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
|
|
@ -27,6 +28,8 @@ import org.gradle.api.plugins.BasePlugin;
|
|||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.springframework.boot.gradle.PluginFeatures;
|
||||
import org.springframework.boot.gradle.SpringBootPluginExtension;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCallback;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -124,11 +127,24 @@ public class RepackagePluginFeatures implements PluginFeatures {
|
|||
+ classifier + "." + StringUtils.getFilenameExtension(outputName);
|
||||
File outputFile = new File(inputFile.getParentFile(), outputName);
|
||||
this.task.getInputs().file(jarTask);
|
||||
this.task.getInputs().file(this.task.getDependencies());
|
||||
addLibraryDependencies(this.task);
|
||||
this.task.getOutputs().file(outputFile);
|
||||
this.task.setOutputFile(outputFile);
|
||||
}
|
||||
|
||||
private void addLibraryDependencies(final RepackageTask task) {
|
||||
try {
|
||||
task.getLibraries().doWithLibraries(new LibraryCallback() {
|
||||
public void library(Library library) throws IOException {
|
||||
task.getInputs().file(library.getFile());
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ package org.springframework.boot.gradle.repackage;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
|
|
@ -29,8 +27,6 @@ import org.gradle.api.tasks.TaskAction;
|
|||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.springframework.boot.gradle.SpringBootPluginExtension;
|
||||
import org.springframework.boot.loader.tools.LibraryCallback;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
import org.springframework.boot.loader.tools.Repackager;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
|
|
@ -101,27 +97,11 @@ public class RepackageTask extends DefaultTask {
|
|||
project.getTasks().withType(Jar.class, new RepackageAction(extension, libraries));
|
||||
}
|
||||
|
||||
public File[] getDependencies() {
|
||||
ProjectLibraries libraries = getLibraries();
|
||||
final List<File> files = new ArrayList<File>();
|
||||
try {
|
||||
libraries.doWithLibraries(new LibraryCallback() {
|
||||
@Override
|
||||
public void library(File file, LibraryScope scope) throws IOException {
|
||||
files.add(file);
|
||||
}
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Cannot retrieve dependencies", ex);
|
||||
}
|
||||
return files.toArray(new File[files.size()]);
|
||||
}
|
||||
|
||||
private ProjectLibraries getLibraries() {
|
||||
public ProjectLibraries getLibraries() {
|
||||
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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Encapsulates information about a single library that may be packed into the archive.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.1.2
|
||||
* @see Libraries
|
||||
*/
|
||||
public class Library {
|
||||
|
||||
private final File file;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the library file
|
||||
*/
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scope of the library
|
||||
*/
|
||||
public LibraryScope getScope() {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,10 +28,9 @@ public interface LibraryCallback {
|
|||
|
||||
/**
|
||||
* Callback to for a single library backed by a {@link File}.
|
||||
* @param file the library file
|
||||
* @param scope the scope of the library
|
||||
* @param library the library
|
||||
* @throws IOException
|
||||
*/
|
||||
void library(File file, LibraryScope scope) throws IOException;
|
||||
void library(Library library) throws IOException;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,12 +141,13 @@ public class Repackager {
|
|||
|
||||
libraries.doWithLibraries(new LibraryCallback() {
|
||||
@Override
|
||||
public void library(File file, LibraryScope scope) throws IOException {
|
||||
public void library(Library library) throws IOException {
|
||||
File file = library.getFile();
|
||||
if (isZip(file)) {
|
||||
String destination = Repackager.this.layout
|
||||
.getLibraryDestination(file.getName(), scope);
|
||||
.getLibraryDestination(file.getName(), library.getScope());
|
||||
if (destination != null) {
|
||||
writer.writeNestedLibrary(destination, file);
|
||||
writer.writeNestedLibrary(destination, library);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -266,12 +269,18 @@ public class RepackagerTests {
|
|||
repackager.repackage(new Libraries() {
|
||||
@Override
|
||||
public void doWithLibraries(LibraryCallback callback) throws IOException {
|
||||
callback.library(libJarFile, LibraryScope.COMPILE);
|
||||
callback.library(libNonJarFile, LibraryScope.COMPILE);
|
||||
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
|
||||
|
|
@ -290,7 +299,7 @@ public class RepackagerTests {
|
|||
repackager.repackage(new Libraries() {
|
||||
@Override
|
||||
public void doWithLibraries(LibraryCallback callback) throws IOException {
|
||||
callback.library(libJarFile, scope);
|
||||
callback.library(new Library(libJarFile, scope));
|
||||
}
|
||||
});
|
||||
assertThat(hasEntry(file, "test/" + libJarFile.getName()), equalTo(true));
|
||||
|
|
@ -331,7 +340,7 @@ public class RepackagerTests {
|
|||
repackager.repackage(new Libraries() {
|
||||
@Override
|
||||
public void doWithLibraries(LibraryCallback callback) throws IOException {
|
||||
callback.library(nestedFile, LibraryScope.COMPILE);
|
||||
callback.library(new Library(nestedFile, LibraryScope.COMPILE));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ public class Handler extends URLStreamHandler {
|
|||
return new JarURLConnection(url, this.jarFile);
|
||||
}
|
||||
try {
|
||||
return new JarURLConnection(url, getJarFileFromUrl(url));
|
||||
return new JarURLConnection(url, getRootJarFileFromUrl(url));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return openFallbackConnection(url, ex);
|
||||
|
|
@ -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);
|
||||
|
|
@ -135,24 +135,14 @@ public class Handler extends URLStreamHandler {
|
|||
return (URLConnection) OPEN_CONNECTION_METHOD.invoke(handler, url);
|
||||
}
|
||||
|
||||
public JarFile getJarFileFromUrl(URL url) throws IOException {
|
||||
|
||||
public JarFile getRootJarFileFromUrl(URL url) throws IOException {
|
||||
String spec = url.getFile();
|
||||
|
||||
int separatorIndex = spec.indexOf(SEPARATOR);
|
||||
if (separatorIndex == -1) {
|
||||
throw new MalformedURLException("Jar URL does not contain !/ separator");
|
||||
}
|
||||
|
||||
JarFile jar = null;
|
||||
while (separatorIndex != -1) {
|
||||
String name = spec.substring(0, separatorIndex);
|
||||
jar = (jar == null ? getRootJarFile(name) : getNestedJarFile(jar, name));
|
||||
spec = spec.substring(separatorIndex + SEPARATOR.length());
|
||||
separatorIndex = spec.indexOf(SEPARATOR);
|
||||
}
|
||||
|
||||
return jar;
|
||||
String name = spec.substring(0, separatorIndex);
|
||||
return getRootJarFile(name);
|
||||
}
|
||||
|
||||
private JarFile getRootJarFile(String name) throws IOException {
|
||||
|
|
@ -175,15 +165,6 @@ public class Handler extends URLStreamHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
|
||||
JarEntry jarEntry = jarFile.getJarEntry(name);
|
||||
if (jarEntry == null) {
|
||||
throw new IOException("Unable to find nested jar '" + name + "' from '"
|
||||
+ jarFile + "'");
|
||||
}
|
||||
return jarFile.getNestedJarFile(jarEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given {@link JarFile} to the root file cache.
|
||||
* @param sourceFile the source file to add
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ class JarURLConnection extends java.net.JarURLConnection {
|
|||
|
||||
private static ThreadLocal<Boolean> useFastExceptions = new ThreadLocal<Boolean>();
|
||||
|
||||
private final String jarFileUrlSpec;
|
||||
|
||||
private final JarFile jarFile;
|
||||
|
||||
private JarEntryData jarEntryData;
|
||||
|
|
@ -71,19 +69,26 @@ class JarURLConnection extends java.net.JarURLConnection {
|
|||
|
||||
private JarEntryName jarEntryName;
|
||||
|
||||
protected JarURLConnection(URL url, JarFile jarFile) throws MalformedURLException {
|
||||
protected JarURLConnection(URL url, JarFile jarFile) throws IOException {
|
||||
// What we pass to super is ultimately ignored
|
||||
super(EMPTY_JAR_URL);
|
||||
this.url = url;
|
||||
this.jarFile = jarFile;
|
||||
String spec = url.getFile();
|
||||
int separator = spec.lastIndexOf(SEPARATOR);
|
||||
if (separator == -1) {
|
||||
throw new MalformedURLException("no " + SEPARATOR + " found in url spec:"
|
||||
+ spec);
|
||||
String spec = url.getFile().substring(jarFile.getUrl().getFile().length());
|
||||
int separator;
|
||||
while ((separator = spec.indexOf(SEPARATOR)) > 0) {
|
||||
jarFile = getNestedJarFile(jarFile, spec.substring(0, separator));
|
||||
spec = spec.substring(separator + SEPARATOR.length());
|
||||
}
|
||||
this.jarFileUrlSpec = spec.substring(0, separator);
|
||||
this.jarEntryName = getJarEntryName(spec.substring(separator + 2));
|
||||
this.jarFile = jarFile;
|
||||
this.jarEntryName = getJarEntryName(spec);
|
||||
}
|
||||
|
||||
private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
|
||||
JarEntry jarEntry = jarFile.getJarEntry(name);
|
||||
if (jarEntry == null) {
|
||||
throwFileNotFound(jarEntry, jarFile);
|
||||
}
|
||||
return jarFile.getNestedJarFile(jarEntry);
|
||||
}
|
||||
|
||||
private JarEntryName getJarEntryName(String spec) {
|
||||
|
|
@ -99,16 +104,20 @@ class JarURLConnection extends java.net.JarURLConnection {
|
|||
this.jarEntryData = this.jarFile.getJarEntryData(this.jarEntryName
|
||||
.asAsciiBytes());
|
||||
if (this.jarEntryData == null) {
|
||||
if (Boolean.TRUE.equals(useFastExceptions.get())) {
|
||||
throw FILE_NOT_FOUND_EXCEPTION;
|
||||
}
|
||||
throw new FileNotFoundException("JAR entry " + this.jarEntryName
|
||||
+ " not found in " + this.jarFile.getName());
|
||||
throwFileNotFound(this.jarEntryName, this.jarFile);
|
||||
}
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
|
||||
private void throwFileNotFound(Object entry, JarFile jarFile) throws FileNotFoundException {
|
||||
if (Boolean.TRUE.equals(useFastExceptions.get())) {
|
||||
throw FILE_NOT_FOUND_EXCEPTION;
|
||||
}
|
||||
throw new FileNotFoundException("JAR entry " + entry + " not found in "
|
||||
+ jarFile.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
try {
|
||||
|
|
@ -135,10 +144,14 @@ class JarURLConnection extends java.net.JarURLConnection {
|
|||
|
||||
private URL buildJarFileUrl() {
|
||||
try {
|
||||
if (this.jarFileUrlSpec.indexOf(SEPARATOR) == -1) {
|
||||
return new URL(this.jarFileUrlSpec);
|
||||
String spec = this.jarFile.getUrl().getFile();
|
||||
if (spec.endsWith(SEPARATOR)) {
|
||||
spec = spec.substring(0, spec.length() - SEPARATOR.length());
|
||||
}
|
||||
return new URL("jar:" + this.jarFileUrlSpec);
|
||||
if (spec.indexOf(SEPARATOR) == -1) {
|
||||
return new URL(spec);
|
||||
}
|
||||
return new URL("jar:" + spec);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
|
|
|
|||
|
|
@ -16,21 +16,32 @@
|
|||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.loader.jar.JarFile;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for {@link LaunchedURLClassLoader}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public class LaunchedURLClassLoaderTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void resolveResourceFromWindowsFilesystem() throws Exception {
|
||||
// This path is invalid - it should return null even on Windows.
|
||||
|
|
@ -76,4 +87,17 @@ public class LaunchedURLClassLoaderTests {
|
|||
assertTrue(loader.getResources("").hasMoreElements());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveFromNested() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
TestJarCreator.createTestJar(file);
|
||||
JarFile jarFile = new JarFile(file);
|
||||
URL url = jarFile.getUrl();
|
||||
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url },
|
||||
null);
|
||||
URL resource = loader.getResource("nested.jar!/3.dat");
|
||||
assertThat(resource.toString(), equalTo(url + "nested.jar!/3.dat"));
|
||||
assertThat(resource.openConnection().getInputStream().read(), equalTo(3));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -408,4 +408,16 @@ public class JarFileTests {
|
|||
getEntries();
|
||||
getNestedJarFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotLoadMissingJar() throws Exception {
|
||||
// relates to gh-1070
|
||||
JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
|
||||
.getEntry("nested.jar"));
|
||||
URL nestedUrl = nestedJarFile.getUrl();
|
||||
URL url = new URL(nestedUrl, nestedJarFile.getUrl() + "missing.jar!/3.dat");
|
||||
this.thrown.expect(FileNotFoundException.class);
|
||||
url.openConnection().getInputStream();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.test;
|
||||
|
||||
public class SampleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
@ -17,13 +17,16 @@
|
|||
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;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
|
||||
|
|
@ -46,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
|
||||
|
|
@ -55,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(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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,19 @@ 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;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCallback;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
|
|
@ -36,7 +42,7 @@ import static org.mockito.Mockito.verify;
|
|||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ArtifactsLibrariesTest {
|
||||
public class ArtifactsLibrariesTests {
|
||||
|
||||
@Mock
|
||||
private Artifact artifact;
|
||||
|
|
@ -50,11 +56,14 @@ public class ArtifactsLibrariesTest {
|
|||
@Mock
|
||||
private LibraryCallback callback;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Library> libraryCaptor;
|
||||
|
||||
@Before
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +72,25 @@ public class ArtifactsLibrariesTest {
|
|||
given(this.artifact.getType()).willReturn("jar");
|
||||
given(this.artifact.getScope()).willReturn("compile");
|
||||
this.libs.doWithLibraries(this.callback);
|
||||
verify(this.callback).library(this.file, LibraryScope.COMPILE);
|
||||
verify(this.callback).library(this.libraryCaptor.capture());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue