Refine layer customization for Maven and Gradle
Simplify layer customization logic for both Maven and Gradle and refactor some internals of the Gradle plugin. Both Maven and Gradle now use a simpler customization format that consists of `application`, `dependencies` and `layer order` sections. The `application`, `dependencies` configurations support one or more `into` blocks that are used to select content for a specific layer. Closes gh-20526
This commit is contained in:
parent
14718f3e8a
commit
7bc7d86ad4
|
@ -9,27 +9,17 @@ bootJar {
|
|||
|
||||
// tag::layered[]
|
||||
bootJar {
|
||||
layers {
|
||||
layersOrder "dependencies", "snapshot-dependencies", "application"
|
||||
libraries {
|
||||
layerContent("snapshot-dependencies") {
|
||||
coordinates {
|
||||
include "*:*:*SNAPSHOT"
|
||||
}
|
||||
}
|
||||
layerContent("dependencies") {
|
||||
coordinates {
|
||||
include "*:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
layered {
|
||||
application {
|
||||
layerContent("application") {
|
||||
locations {
|
||||
include "**"
|
||||
}
|
||||
}
|
||||
intoLayer("application")
|
||||
}
|
||||
dependencies {
|
||||
intoLayer("snapshot-dependencies") {
|
||||
include "*:*:*SNAPSHOT"
|
||||
}
|
||||
intoLayer("dependencies")
|
||||
}
|
||||
layerOrder "dependencies", "snapshot-dependencies", "application"
|
||||
}
|
||||
}
|
||||
// end::layered[]
|
||||
|
|
|
@ -7,27 +7,17 @@ plugins {
|
|||
|
||||
// tag::layered[]
|
||||
tasks.getByName<BootJar>("bootJar") {
|
||||
layers {
|
||||
layersOrder("dependencies", "snapshot-dependencies", "application")
|
||||
libraries {
|
||||
layerContent("snapshot-dependencies") {
|
||||
coordinates {
|
||||
include("*:*:*SNAPSHOT")
|
||||
}
|
||||
}
|
||||
layerContent("dependencies") {
|
||||
coordinates {
|
||||
include("*:*")
|
||||
}
|
||||
}
|
||||
}
|
||||
layered {
|
||||
application {
|
||||
layerContent("application") {
|
||||
locations {
|
||||
include("**")
|
||||
}
|
||||
}
|
||||
intoLayer("application")
|
||||
}
|
||||
dependencies {
|
||||
intoLayer("snapshot-dependencies") {
|
||||
include("*:*:*SNAPSHOT")
|
||||
}
|
||||
intoLayer("dependencies") {
|
||||
}
|
||||
layersOrder("dependencies", "snapshot-dependencies", "application")
|
||||
}
|
||||
}
|
||||
// end::layered[]
|
||||
|
|
|
@ -9,6 +9,6 @@ bootJar {
|
|||
|
||||
// tag::layered[]
|
||||
bootJar {
|
||||
layers()
|
||||
layered()
|
||||
}
|
||||
// end::layered[]
|
||||
|
|
|
@ -11,6 +11,6 @@ tasks.getByName<BootJar>("bootJar") {
|
|||
|
||||
// tag::layered[]
|
||||
tasks.getByName<BootJar>("bootJar") {
|
||||
layers()
|
||||
layered()
|
||||
}
|
||||
// end::layered[]
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.gradle.api.Action;
|
|||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact;
|
||||
import org.gradle.api.plugins.ApplicationPlugin;
|
||||
|
@ -94,9 +93,6 @@ final class JavaPluginAction implements PluginApplicationAction {
|
|||
SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets()
|
||||
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||
bootJar.classpath((Callable<FileCollection>) mainSourceSet::getRuntimeClasspath);
|
||||
Configuration runtimeClasspathConfiguration = project.getConfigurations()
|
||||
.getByName(mainSourceSet.getRuntimeClasspathConfigurationName());
|
||||
runtimeClasspathConfiguration.getIncoming().afterResolve(bootJar::resolvedDependencies);
|
||||
bootJar.conventionMapping("mainClassName", new MainClassConvention(project, bootJar::getClasspath));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Set;
|
|||
import java.util.TreeMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
import org.gradle.api.file.RelativePath;
|
||||
|
@ -34,6 +35,7 @@ import org.gradle.api.internal.file.copy.CopyAction;
|
|||
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
|
||||
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.java.archives.Manifest;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.specs.Specs;
|
||||
import org.gradle.api.tasks.WorkResult;
|
||||
|
@ -44,6 +46,9 @@ import org.gradle.api.tasks.util.PatternSet;
|
|||
* Support class for implementations of {@link BootArchive}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @see BootJar
|
||||
* @see BootWar
|
||||
*/
|
||||
class BootArchiveSupport {
|
||||
|
||||
|
@ -61,46 +66,71 @@ class BootArchiveSupport {
|
|||
|
||||
private final PatternSet requiresUnpack = new PatternSet();
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
|
||||
private final PatternSet exclusions = new PatternSet();
|
||||
|
||||
private final String loaderMainClass;
|
||||
|
||||
private final Spec<FileCopyDetails> librarySpec;
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
|
||||
private LaunchScriptConfiguration launchScript;
|
||||
|
||||
private boolean excludeDevtools = true;
|
||||
|
||||
BootArchiveSupport(String loaderMainClass, Function<FileCopyDetails, ZipCompression> compressionResolver) {
|
||||
BootArchiveSupport(String loaderMainClass, Spec<FileCopyDetails> librarySpec,
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver) {
|
||||
this.loaderMainClass = loaderMainClass;
|
||||
this.librarySpec = librarySpec;
|
||||
this.compressionResolver = compressionResolver;
|
||||
this.requiresUnpack.include(Specs.satisfyNone());
|
||||
configureExclusions();
|
||||
}
|
||||
|
||||
void configureManifest(Jar jar, String mainClassName, String springBootClasses, String springBootLib) {
|
||||
Attributes attributes = jar.getManifest().getAttributes();
|
||||
void configureManifest(Manifest manifest, String mainClass, String classes, String lib, String classPathIndex,
|
||||
String layersIndex) {
|
||||
Attributes attributes = manifest.getAttributes();
|
||||
attributes.putIfAbsent("Main-Class", this.loaderMainClass);
|
||||
attributes.putIfAbsent("Start-Class", mainClassName);
|
||||
attributes.computeIfAbsent("Spring-Boot-Version", (key) -> determineSpringBootVersion());
|
||||
attributes.putIfAbsent("Spring-Boot-Classes", springBootClasses);
|
||||
attributes.putIfAbsent("Spring-Boot-Lib", springBootLib);
|
||||
attributes.putIfAbsent("Start-Class", mainClass);
|
||||
attributes.computeIfAbsent("Spring-Boot-Version", (name) -> determineSpringBootVersion());
|
||||
if (classes != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Classes", classes);
|
||||
}
|
||||
if (lib != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Lib", lib);
|
||||
}
|
||||
if (classPathIndex != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Classpath-Index", classPathIndex);
|
||||
}
|
||||
if (layersIndex != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Layers-Index", layersIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private String determineSpringBootVersion() {
|
||||
String implementationVersion = getClass().getPackage().getImplementationVersion();
|
||||
return (implementationVersion != null) ? implementationVersion : "unknown";
|
||||
String version = getClass().getPackage().getImplementationVersion();
|
||||
return (version != null) ? version : "unknown";
|
||||
}
|
||||
|
||||
CopyAction createCopyAction(Jar jar) {
|
||||
CopyAction copyAction = new BootZipCopyAction(jar.getArchiveFile().get().getAsFile(),
|
||||
jar.isPreserveFileTimestamps(), isUsingDefaultLoader(jar), this.requiresUnpack.getAsSpec(),
|
||||
this.exclusions.getAsExcludeSpec(), this.launchScript, this.compressionResolver,
|
||||
jar.getMetadataCharset());
|
||||
if (!jar.isReproducibleFileOrder()) {
|
||||
return copyAction;
|
||||
}
|
||||
return new ReproducibleOrderingCopyAction(copyAction);
|
||||
return createCopyAction(jar, null, false);
|
||||
}
|
||||
|
||||
CopyAction createCopyAction(Jar jar, LayerResolver layerResolver, boolean includeLayerTools) {
|
||||
File output = jar.getArchiveFile().get().getAsFile();
|
||||
Manifest manifest = jar.getManifest();
|
||||
boolean preserveFileTimestamps = jar.isPreserveFileTimestamps();
|
||||
boolean includeDefaultLoader = isUsingDefaultLoader(jar);
|
||||
Spec<FileTreeElement> requiresUnpack = this.requiresUnpack.getAsSpec();
|
||||
Spec<FileTreeElement> exclusions = this.exclusions.getAsExcludeSpec();
|
||||
LaunchScriptConfiguration launchScript = this.launchScript;
|
||||
Spec<FileCopyDetails> librarySpec = this.librarySpec;
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver = this.compressionResolver;
|
||||
String encoding = jar.getMetadataCharset();
|
||||
CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, includeDefaultLoader,
|
||||
includeLayerTools, requiresUnpack, exclusions, launchScript, librarySpec, compressionResolver, encoding,
|
||||
layerResolver);
|
||||
return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action;
|
||||
}
|
||||
|
||||
private boolean isUsingDefaultLoader(Jar jar) {
|
||||
|
@ -132,7 +162,19 @@ class BootArchiveSupport {
|
|||
configureExclusions();
|
||||
}
|
||||
|
||||
boolean isZip(File file) {
|
||||
void excludeNonZipLibraryFiles(FileCopyDetails details) {
|
||||
if (this.librarySpec.isSatisfiedBy(details)) {
|
||||
excludeNonZipFiles(details);
|
||||
}
|
||||
}
|
||||
|
||||
void excludeNonZipFiles(FileCopyDetails details) {
|
||||
if (!isZip(details.getFile())) {
|
||||
details.exclude();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isZip(File file) {
|
||||
try {
|
||||
try (FileInputStream fileInputStream = new FileInputStream(file)) {
|
||||
return isZip(fileInputStream);
|
||||
|
@ -160,6 +202,17 @@ class BootArchiveSupport {
|
|||
this.exclusions.setExcludes(excludes);
|
||||
}
|
||||
|
||||
void moveModuleInfoToRoot(CopySpec spec) {
|
||||
spec.filesMatching("module-info.class", BootArchiveSupport::moveToRoot);
|
||||
}
|
||||
|
||||
private static void moveToRoot(FileCopyDetails details) {
|
||||
details.setRelativePath(details.getRelativeSourcePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link CopyAction} variant that sorts entries to ensure reproducible ordering.
|
||||
*/
|
||||
private static final class ReproducibleOrderingCopyAction implements CopyAction {
|
||||
|
||||
private final CopyAction delegate;
|
||||
|
|
|
@ -16,45 +16,24 @@
|
|||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.artifacts.ArtifactCollection;
|
||||
import org.gradle.api.artifacts.ResolvableDependencies;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
import org.gradle.api.internal.file.copy.CopyAction;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
import org.springframework.boot.loader.tools.layer.CustomLayers;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.gradle.util.ConfigureUtil;
|
||||
|
||||
/**
|
||||
* A custom {@link Jar} task that produces a Spring Boot executable jar.
|
||||
|
@ -62,82 +41,88 @@ import org.springframework.util.FileCopyUtils;
|
|||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class BootJar extends Jar implements BootArchive {
|
||||
|
||||
private final BootArchiveSupport support = new BootArchiveSupport("org.springframework.boot.loader.JarLauncher",
|
||||
this::resolveZipCompression);
|
||||
private static final String LAUNCHER = "org.springframework.boot.loader.JarLauncher";
|
||||
|
||||
private final CopySpec bootInf;
|
||||
private static final String CLASSES_FOLDER = "BOOT-INF/classes/";
|
||||
|
||||
private static final String LIB_FOLDER = "BOOT-INF/lib/";
|
||||
|
||||
private static final String LAYERS_INDEX = "BOOT-INF/layers.idx";
|
||||
|
||||
private static final String CLASSPATH_INDEX = "BOOT-INF/classpath.idx";
|
||||
|
||||
private final BootArchiveSupport support;
|
||||
|
||||
private final CopySpec bootInfSpec;
|
||||
|
||||
private String mainClassName;
|
||||
|
||||
private FileCollection classpath;
|
||||
|
||||
private Layers layers;
|
||||
|
||||
private LayerConfiguration layerConfiguration;
|
||||
|
||||
private static final String BOOT_INF_LAYERS = "BOOT-INF/layers";
|
||||
|
||||
private final List<String> dependencies = new ArrayList<>();
|
||||
|
||||
private final Map<String, String> coordinatesByFileName = new HashMap<>();
|
||||
private LayeredSpec layered;
|
||||
|
||||
/**
|
||||
* Creates a new {@code BootJar} task.
|
||||
*/
|
||||
public BootJar() {
|
||||
this.bootInf = getProject().copySpec().into("BOOT-INF");
|
||||
getMainSpec().with(this.bootInf);
|
||||
this.bootInf.into("classes", classpathFiles(File::isDirectory));
|
||||
this.bootInf.into("lib", classpathFiles(File::isFile))
|
||||
.eachFile((details) -> BootJar.this.dependencies.add(details.getPath()));
|
||||
this.bootInf.into("",
|
||||
(spec) -> spec.from((Callable<File>) () -> createClasspathIndex(BootJar.this.dependencies)));
|
||||
this.bootInf.filesMatching("module-info.class",
|
||||
(details) -> details.setRelativePath(details.getRelativeSourcePath()));
|
||||
getRootSpec().eachFile((details) -> {
|
||||
String pathString = details.getRelativePath().getPathString();
|
||||
if (pathString.startsWith("BOOT-INF/lib/") && !this.support.isZip(details.getFile())) {
|
||||
details.exclude();
|
||||
}
|
||||
});
|
||||
this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression);
|
||||
this.bootInfSpec = getProject().copySpec().into("BOOT-INF");
|
||||
configureBootInfSpec(this.bootInfSpec);
|
||||
getMainSpec().with(this.bootInfSpec);
|
||||
}
|
||||
|
||||
private Action<CopySpec> classpathFiles(Spec<File> filter) {
|
||||
return (copySpec) -> copySpec.from((Callable<Iterable<File>>) () -> (this.classpath != null)
|
||||
? this.classpath.filter(filter) : Collections.emptyList());
|
||||
private void configureBootInfSpec(CopySpec bootInfSpec) {
|
||||
bootInfSpec.into("classes", fromCallTo(this::classpathDirectories));
|
||||
bootInfSpec.into("lib", fromCallTo(this::classpathFiles)).eachFile(this.support::excludeNonZipFiles);
|
||||
bootInfSpec.filesMatching("module-info.class",
|
||||
(details) -> details.setRelativePath(details.getRelativeSourcePath()));
|
||||
}
|
||||
|
||||
private Iterable<File> classpathDirectories() {
|
||||
return classpathEntries(File::isDirectory);
|
||||
}
|
||||
|
||||
private Iterable<File> classpathFiles() {
|
||||
return classpathEntries(File::isFile);
|
||||
}
|
||||
|
||||
private Iterable<File> classpathEntries(Spec<File> filter) {
|
||||
return (this.classpath != null) ? this.classpath.filter(filter) : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(this, getMainClassName(), "BOOT-INF/classes/", "BOOT-INF/lib/");
|
||||
Attributes attributes = this.getManifest().getAttributes();
|
||||
if (this.layers != null) {
|
||||
attributes.remove("Spring-Boot-Classes");
|
||||
attributes.remove("Spring-Boot-Lib");
|
||||
attributes.putIfAbsent("Spring-Boot-Layers-Index", "BOOT-INF/layers.idx");
|
||||
if (this.layered != null) {
|
||||
this.support.configureManifest(getManifest(), getMainClassName(), null, null, CLASSPATH_INDEX,
|
||||
LAYERS_INDEX);
|
||||
}
|
||||
else {
|
||||
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER,
|
||||
CLASSPATH_INDEX, null);
|
||||
}
|
||||
attributes.putIfAbsent("Spring-Boot-Classpath-Index", "BOOT-INF/classpath.idx");
|
||||
super.copy();
|
||||
}
|
||||
|
||||
private File createClasspathIndex(List<String> dependencies) {
|
||||
String content = dependencies.stream().map((name) -> name.substring(name.lastIndexOf('/') + 1))
|
||||
.collect(Collectors.joining("\n", "", "\n"));
|
||||
File source = getProject().getResources().getText().fromString(content).asFile();
|
||||
File indexFile = new File(source.getParentFile(), "classpath.idx");
|
||||
source.renameTo(indexFile);
|
||||
return indexFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CopyAction createCopyAction() {
|
||||
if (this.layered != null) {
|
||||
LayerResolver layerResolver = new LayerResolver(getConfigurations(), this.layered, this::isLibrary);
|
||||
boolean includeLayerTools = this.layered.isIncludeLayerTools();
|
||||
return this.support.createCopyAction(this, layerResolver, includeLayerTools);
|
||||
}
|
||||
return this.support.createCopyAction(this);
|
||||
}
|
||||
|
||||
@Internal
|
||||
protected Iterable<Configuration> getConfigurations() {
|
||||
return getProject().getConfigurations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMainClassName() {
|
||||
if (this.mainClassName == null) {
|
||||
|
@ -179,110 +164,28 @@ public class BootJar extends Jar implements BootArchive {
|
|||
action.execute(enableLaunchScriptIfNecessary());
|
||||
}
|
||||
|
||||
@Optional
|
||||
@Nested
|
||||
public LayerConfiguration getLayerConfiguration() {
|
||||
return this.layerConfiguration;
|
||||
@Optional
|
||||
public LayeredSpec getLayered() {
|
||||
return this.layered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the archive to have layers.
|
||||
*/
|
||||
public void layers() {
|
||||
enableLayers();
|
||||
public void layered() {
|
||||
layered(true);
|
||||
}
|
||||
|
||||
public void layers(Action<LayerConfiguration> action) {
|
||||
action.execute(enableLayers());
|
||||
public void layered(boolean layered) {
|
||||
this.layered = layered ? new LayeredSpec() : null;
|
||||
}
|
||||
|
||||
private LayerConfiguration enableLayers() {
|
||||
if (this.layerConfiguration == null) {
|
||||
this.layerConfiguration = new LayerConfiguration();
|
||||
}
|
||||
|
||||
return this.layerConfiguration;
|
||||
public void layered(Closure<?> closure) {
|
||||
layered(ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
private void applyLayers() {
|
||||
if (this.layerConfiguration == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.layerConfiguration.getLayersOrder() == null || this.layerConfiguration.getLayersOrder().isEmpty()) {
|
||||
this.layers = Layers.IMPLICIT;
|
||||
}
|
||||
else {
|
||||
List<Layer> customLayers = this.layerConfiguration.getLayersOrder().stream().map(Layer::new)
|
||||
.collect(Collectors.toList());
|
||||
this.layers = new CustomLayers(customLayers, this.layerConfiguration.getApplication(),
|
||||
this.layerConfiguration.getLibraries());
|
||||
}
|
||||
|
||||
if (this.layerConfiguration.isIncludeLayerTools()) {
|
||||
this.bootInf.into("lib", (spec) -> spec.from((Callable<File>) () -> {
|
||||
String jarName = "spring-boot-jarmode-layertools.jar";
|
||||
InputStream stream = getClass().getClassLoader().getResourceAsStream("META-INF/jarmode/" + jarName);
|
||||
File taskTmp = new File(getProject().getBuildDir(), "tmp/" + getName());
|
||||
taskTmp.mkdirs();
|
||||
File layerToolsJar = new File(taskTmp, jarName);
|
||||
FileCopyUtils.copy(stream, new FileOutputStream(layerToolsJar));
|
||||
return layerToolsJar;
|
||||
}));
|
||||
}
|
||||
this.bootInf.eachFile((details) -> {
|
||||
Layer layer = layerForFileDetails(details);
|
||||
if (layer != null) {
|
||||
String relativePath = details.getPath().substring("BOOT-INF/".length());
|
||||
details.setPath(BOOT_INF_LAYERS + "/" + layer + "/" + relativePath);
|
||||
}
|
||||
}).setIncludeEmptyDirs(false);
|
||||
this.bootInf.into("", (spec) -> spec.from(createLayersIndex()));
|
||||
}
|
||||
|
||||
private Layer layerForFileDetails(FileCopyDetails details) {
|
||||
String path = details.getPath();
|
||||
if (path.startsWith("BOOT-INF/lib/")) {
|
||||
String coordinates = this.coordinatesByFileName.get(details.getName());
|
||||
LibraryCoordinates libraryCoordinates = (coordinates != null) ? new LibraryCoordinates(coordinates)
|
||||
: new LibraryCoordinates("?:?:?");
|
||||
return this.layers.getLayer(new Library(null, details.getFile(), null, libraryCoordinates, false));
|
||||
}
|
||||
if (path.startsWith("BOOT-INF/classes/")) {
|
||||
return this.layers.getLayer(details.getSourcePath());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private File createLayersIndex() {
|
||||
try {
|
||||
StringWriter content = new StringWriter();
|
||||
BufferedWriter writer = new BufferedWriter(content);
|
||||
for (Layer layer : this.layers) {
|
||||
writer.write(layer.toString());
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.flush();
|
||||
File source = getProject().getResources().getText().fromString(content.toString()).asFile();
|
||||
File indexFile = new File(source.getParentFile(), "layers.idx");
|
||||
source.renameTo(indexFile);
|
||||
return indexFile;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException("Failed to create layers.idx", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveCoordinatesForFiles(ResolvableDependencies resolvableDependencies) {
|
||||
ArtifactCollection resolvedArtifactResults = resolvableDependencies.getArtifacts();
|
||||
Set<ResolvedArtifactResult> artifacts = resolvedArtifactResults.getArtifacts();
|
||||
artifacts.forEach((artifact) -> this.coordinatesByFileName.put(artifact.getFile().getName(),
|
||||
artifact.getId().getComponentIdentifier().getDisplayName()));
|
||||
}
|
||||
|
||||
@Input
|
||||
boolean isLayered() {
|
||||
return this.layerConfiguration != null;
|
||||
public void layered(Action<LayeredSpec> action) {
|
||||
LayeredSpec layered = new LayeredSpec();
|
||||
action.execute(layered);
|
||||
this.layered = layered;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -317,13 +220,6 @@ public class BootJar extends Jar implements BootArchive {
|
|||
this.support.setExcludeDevtools(excludeDevtools);
|
||||
}
|
||||
|
||||
public void resolvedDependencies(ResolvableDependencies resolvableDependencies) {
|
||||
if (resolvableDependencies != null) {
|
||||
resolveCoordinatesForFiles(resolvableDependencies);
|
||||
}
|
||||
applyLayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code CopySpec} that can be used to add content to the {@code BOOT-INF}
|
||||
* directory of the jar.
|
||||
|
@ -333,7 +229,7 @@ public class BootJar extends Jar implements BootArchive {
|
|||
@Internal
|
||||
public CopySpec getBootInf() {
|
||||
CopySpec child = getProject().copySpec();
|
||||
this.bootInf.with(child);
|
||||
this.bootInfSpec.with(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
|
@ -352,30 +248,26 @@ public class BootJar extends Jar implements BootArchive {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar.
|
||||
* <p>
|
||||
* By default, any file in {@code BOOT-INF/lib/} or
|
||||
* {@code BOOT-INF/layers/<layer>/lib} is stored and all other files are deflated.
|
||||
* @param details the details
|
||||
* Return the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar. By default, any
|
||||
* {@link #isLibrary(FileCopyDetails) library} is {@link ZipCompression#STORED stored}
|
||||
* and all other files are {@link ZipCompression#DEFLATED deflated}.
|
||||
* @param details the file copy details
|
||||
* @return the compression to use
|
||||
*/
|
||||
protected ZipCompression resolveZipCompression(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
for (String prefix : getLibPathPrefixes()) {
|
||||
if (path.startsWith(prefix)) {
|
||||
return ZipCompression.STORED;
|
||||
}
|
||||
}
|
||||
return ZipCompression.DEFLATED;
|
||||
return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED;
|
||||
}
|
||||
|
||||
private Set<String> getLibPathPrefixes() {
|
||||
if (this.layers == null) {
|
||||
return Collections.singleton("BOOT-INF/lib/");
|
||||
}
|
||||
return StreamSupport.stream(this.layers.spliterator(), false)
|
||||
.map((layer) -> "BOOT-INF/layers/" + layer + "/lib/").collect(Collectors.toSet());
|
||||
/**
|
||||
* Return if the {@link FileCopyDetails} are for a library. By default any file in
|
||||
* {@code BOOT-INF/lib} is considered to be a library.
|
||||
* @param details the file copy details
|
||||
* @return {@code true} if the details are for a library
|
||||
*/
|
||||
protected boolean isLibrary(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
return path.startsWith(LIB_FOLDER);
|
||||
}
|
||||
|
||||
private LaunchScriptConfiguration enableLaunchScriptIfNecessary() {
|
||||
|
@ -387,4 +279,24 @@ public class BootJar extends Jar implements BootArchive {
|
|||
return launchScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return an action to add the callable to the spec
|
||||
*/
|
||||
private static <T> Action<CopySpec> fromCallTo(Callable<T> callable) {
|
||||
return (spec) -> spec.from(callTo(callable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#from} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return the callable
|
||||
*/
|
||||
private static <T> Callable<T> callTo(Callable<T> callable) {
|
||||
return callable;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -16,12 +16,12 @@
|
|||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
|
@ -35,12 +35,20 @@ import org.gradle.api.tasks.bundling.War;
|
|||
* A custom {@link War} task that produces a Spring Boot executable war.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class BootWar extends War implements BootArchive {
|
||||
|
||||
private final BootArchiveSupport support = new BootArchiveSupport("org.springframework.boot.loader.WarLauncher",
|
||||
this::resolveZipCompression);
|
||||
private static final String LAUNCHER = "org.springframework.boot.loader.WarLauncher";
|
||||
|
||||
private static final String CLASSES_FOLDER = "WEB-INF/classes/";
|
||||
|
||||
private static final String LIB_PROVIDED_FOLDER = "WEB-INF/lib-provided/";
|
||||
|
||||
private static final String LIB_FOLDER = "WEB-INF/lib/";
|
||||
|
||||
private final BootArchiveSupport support;
|
||||
|
||||
private String mainClassName;
|
||||
|
||||
|
@ -50,23 +58,19 @@ public class BootWar extends War implements BootArchive {
|
|||
* Creates a new {@code BootWar} task.
|
||||
*/
|
||||
public BootWar() {
|
||||
getWebInf().into("lib-provided",
|
||||
(copySpec) -> copySpec.from((Callable<Iterable<File>>) () -> (this.providedClasspath != null)
|
||||
? this.providedClasspath : Collections.emptyList()));
|
||||
getRootSpec().filesMatching("module-info.class",
|
||||
(details) -> details.setRelativePath(details.getRelativeSourcePath()));
|
||||
getRootSpec().eachFile((details) -> {
|
||||
String pathString = details.getRelativePath().getPathString();
|
||||
if ((pathString.startsWith("WEB-INF/lib/") || pathString.startsWith("WEB-INF/lib-provided/"))
|
||||
&& !this.support.isZip(details.getFile())) {
|
||||
details.exclude();
|
||||
}
|
||||
});
|
||||
this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression);
|
||||
getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles));
|
||||
this.support.moveModuleInfoToRoot(getRootSpec());
|
||||
getRootSpec().eachFile(this.support::excludeNonZipLibraryFiles);
|
||||
}
|
||||
|
||||
private Object getProvidedLibFiles() {
|
||||
return (this.providedClasspath != null) ? this.providedClasspath : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(this, getMainClassName(), "WEB-INF/classes/", "WEB-INF/lib/");
|
||||
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER, null, null);
|
||||
super.copy();
|
||||
}
|
||||
|
||||
|
@ -171,20 +175,26 @@ public class BootWar extends War implements BootArchive {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar.
|
||||
* <p>
|
||||
* By default, any file in {@code WEB-INF/lib/} or {@code WEB-INF/lib-provided/} is
|
||||
* stored and all other files are deflated.
|
||||
* @param details the details
|
||||
* Return the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar. By default, any
|
||||
* {@link #isLibrary(FileCopyDetails) library} is {@link ZipCompression#STORED stored}
|
||||
* and all other files are {@link ZipCompression#DEFLATED deflated}.
|
||||
* @param details the file copy details
|
||||
* @return the compression to use
|
||||
*/
|
||||
protected ZipCompression resolveZipCompression(FileCopyDetails details) {
|
||||
String relativePath = details.getRelativePath().getPathString();
|
||||
if (relativePath.startsWith("WEB-INF/lib/") || relativePath.startsWith("WEB-INF/lib-provided/")) {
|
||||
return ZipCompression.STORED;
|
||||
}
|
||||
return ZipCompression.DEFLATED;
|
||||
return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the {@link FileCopyDetails} are for a library. By default any file in
|
||||
* {@code WEB-INF/lib} or {@code WEB-INF/lib-provided} is considered to be a library.
|
||||
* @param details the file copy details
|
||||
* @return {@code true} if the details are for a library
|
||||
*/
|
||||
protected boolean isLibrary(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
return path.startsWith(LIB_FOLDER) || path.startsWith(LIB_PROVIDED_FOLDER);
|
||||
}
|
||||
|
||||
private LaunchScriptConfiguration enableLaunchScriptIfNecessary() {
|
||||
|
@ -196,4 +206,24 @@ public class BootWar extends War implements BootArchive {
|
|||
return launchScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return an action to add the callable to the spec
|
||||
*/
|
||||
private static <T> Action<CopySpec> fromCallTo(Callable<T> callable) {
|
||||
return (spec) -> spec.from(callTo(callable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#from} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return the callable
|
||||
*/
|
||||
private static <T> Callable<T> callTo(Callable<T> callable) {
|
||||
return callable;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -19,10 +19,15 @@ package org.springframework.boot.gradle.tasks.bundling;
|
|||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
|
@ -34,11 +39,16 @@ import org.gradle.api.file.FileCopyDetails;
|
|||
import org.gradle.api.file.FileTreeElement;
|
||||
import org.gradle.api.internal.file.copy.CopyAction;
|
||||
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.java.archives.Manifest;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.tasks.WorkResult;
|
||||
import org.gradle.api.tasks.WorkResults;
|
||||
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.FileUtils;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* A {@link CopyAction} for creating a Spring Boot zip archive (typically a jar or war).
|
||||
|
@ -49,69 +59,84 @@ import org.springframework.boot.loader.tools.FileUtils;
|
|||
*/
|
||||
class BootZipCopyAction implements CopyAction {
|
||||
|
||||
static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0)
|
||||
.getTimeInMillis();
|
||||
static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = OffsetDateTime.of(1980, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC)
|
||||
.toInstant().toEpochMilli();
|
||||
|
||||
private final File output;
|
||||
|
||||
private final Manifest manifest;
|
||||
|
||||
private final boolean preserveFileTimestamps;
|
||||
|
||||
private final boolean includeDefaultLoader;
|
||||
|
||||
private final boolean includeLayerTools;
|
||||
|
||||
private final Spec<FileTreeElement> requiresUnpack;
|
||||
|
||||
private final Spec<FileTreeElement> exclusions;
|
||||
|
||||
private final LaunchScriptConfiguration launchScript;
|
||||
|
||||
private final Spec<FileCopyDetails> librarySpec;
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
|
||||
private final String encoding;
|
||||
|
||||
BootZipCopyAction(File output, boolean preserveFileTimestamps, boolean includeDefaultLoader,
|
||||
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
|
||||
LaunchScriptConfiguration launchScript, Function<FileCopyDetails, ZipCompression> compressionResolver,
|
||||
String encoding) {
|
||||
private final LayerResolver layerResolver;
|
||||
|
||||
BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, boolean includeDefaultLoader,
|
||||
boolean includeLayerTools, Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
|
||||
LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec,
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver, String encoding,
|
||||
LayerResolver layerResolver) {
|
||||
this.output = output;
|
||||
this.manifest = manifest;
|
||||
this.preserveFileTimestamps = preserveFileTimestamps;
|
||||
this.includeDefaultLoader = includeDefaultLoader;
|
||||
this.includeLayerTools = includeLayerTools;
|
||||
this.requiresUnpack = requiresUnpack;
|
||||
this.exclusions = exclusions;
|
||||
this.launchScript = launchScript;
|
||||
this.librarySpec = librarySpec;
|
||||
this.compressionResolver = compressionResolver;
|
||||
this.encoding = encoding;
|
||||
this.layerResolver = layerResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkResult execute(CopyActionProcessingStream stream) {
|
||||
public WorkResult execute(CopyActionProcessingStream copyActions) {
|
||||
try {
|
||||
writeArchive(stream);
|
||||
return () -> true;
|
||||
writeArchive(copyActions);
|
||||
return WorkResults.didWork(true);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new GradleException("Failed to create " + this.output, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeArchive(CopyActionProcessingStream stream) throws IOException {
|
||||
OutputStream outputStream = new FileOutputStream(this.output);
|
||||
private void writeArchive(CopyActionProcessingStream copyActions) throws IOException {
|
||||
OutputStream output = new FileOutputStream(this.output);
|
||||
try {
|
||||
writeLaunchScriptIfNecessary(outputStream);
|
||||
ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream(outputStream);
|
||||
try {
|
||||
if (this.encoding != null) {
|
||||
zipOutputStream.setEncoding(this.encoding);
|
||||
}
|
||||
Processor processor = new Processor(zipOutputStream);
|
||||
stream.process(processor::process);
|
||||
processor.finish();
|
||||
}
|
||||
finally {
|
||||
closeQuietly(zipOutputStream);
|
||||
}
|
||||
writeArchive(copyActions, output);
|
||||
}
|
||||
finally {
|
||||
closeQuietly(outputStream);
|
||||
closeQuietly(output);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeArchive(CopyActionProcessingStream copyActions, OutputStream output) throws IOException {
|
||||
writeLaunchScriptIfNecessary(output);
|
||||
ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(output);
|
||||
try {
|
||||
setEncodingIfNecessary(zipOutput);
|
||||
Processor processor = new Processor(zipOutput);
|
||||
copyActions.process(processor::process);
|
||||
processor.finish();
|
||||
}
|
||||
finally {
|
||||
closeQuietly(zipOutput);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,6 +156,12 @@ class BootZipCopyAction implements CopyAction {
|
|||
}
|
||||
}
|
||||
|
||||
private void setEncodingIfNecessary(ZipArchiveOutputStream zipOutputStream) {
|
||||
if (this.encoding != null) {
|
||||
zipOutputStream.setEncoding(this.encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeQuietly(OutputStream outputStream) {
|
||||
try {
|
||||
outputStream.close();
|
||||
|
@ -144,17 +175,20 @@ class BootZipCopyAction implements CopyAction {
|
|||
*/
|
||||
private class Processor {
|
||||
|
||||
private ZipArchiveOutputStream outputStream;
|
||||
private ZipArchiveOutputStream out;
|
||||
|
||||
private Spec<FileTreeElement> writtenLoaderEntries;
|
||||
|
||||
Processor(ZipArchiveOutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
private Set<String> writtenDirectories = new LinkedHashSet<>();
|
||||
|
||||
private Set<String> writtenLibraries = new LinkedHashSet<>();
|
||||
|
||||
Processor(ZipArchiveOutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
void process(FileCopyDetails details) {
|
||||
if (BootZipCopyAction.this.exclusions.isSatisfiedBy(details)
|
||||
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details))) {
|
||||
if (skipProcessing(details)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
@ -171,8 +205,90 @@ class BootZipCopyAction implements CopyAction {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean skipProcessing(FileCopyDetails details) {
|
||||
return BootZipCopyAction.this.exclusions.isSatisfiedBy(details)
|
||||
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details));
|
||||
}
|
||||
|
||||
private void processDirectory(FileCopyDetails details) throws IOException {
|
||||
String name = getEntryName(details);
|
||||
long time = getTime(details);
|
||||
writeParentDirectoriesIfNecessary(name, time);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(name + '/');
|
||||
entry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
||||
entry.setTime(time);
|
||||
this.out.putArchiveEntry(entry);
|
||||
this.out.closeArchiveEntry();
|
||||
this.writtenDirectories.add(name);
|
||||
}
|
||||
|
||||
private void processFile(FileCopyDetails details) throws IOException {
|
||||
String name = getEntryName(details);
|
||||
long time = getTime(details);
|
||||
writeParentDirectoriesIfNecessary(name, time);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(name);
|
||||
entry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
||||
entry.setTime(time);
|
||||
ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details);
|
||||
if (compression == ZipCompression.STORED) {
|
||||
prepareStoredEntry(details, entry);
|
||||
}
|
||||
this.out.putArchiveEntry(entry);
|
||||
details.copyTo(this.out);
|
||||
this.out.closeArchiveEntry();
|
||||
if (BootZipCopyAction.this.librarySpec.isSatisfiedBy(details)) {
|
||||
this.writtenLibraries.add(name.substring(name.lastIndexOf('/') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeParentDirectoriesIfNecessary(String name, long time) throws IOException {
|
||||
String parentDirectory = getParentDirectory(name);
|
||||
if (parentDirectory != null && this.writtenDirectories.add(parentDirectory)) {
|
||||
writeParentDirectoriesIfNecessary(parentDirectory, time);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(parentDirectory + '/');
|
||||
entry.setUnixMode(UnixStat.DIR_FLAG);
|
||||
entry.setTime(time);
|
||||
this.out.putArchiveEntry(entry);
|
||||
this.out.closeArchiveEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private String getParentDirectory(String name) {
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
if (lastSlash == -1) {
|
||||
return null;
|
||||
}
|
||||
return name.substring(0, lastSlash);
|
||||
}
|
||||
|
||||
private String getEntryName(FileCopyDetails details) {
|
||||
if (BootZipCopyAction.this.layerResolver == null) {
|
||||
return details.getRelativePath().getPathString();
|
||||
}
|
||||
return BootZipCopyAction.this.layerResolver.getPath(details);
|
||||
}
|
||||
|
||||
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
|
||||
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
||||
archiveEntry.setSize(details.getSize());
|
||||
archiveEntry.setCompressedSize(details.getSize());
|
||||
archiveEntry.setCrc(getCrc(details));
|
||||
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
|
||||
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
|
||||
}
|
||||
}
|
||||
|
||||
private long getCrc(FileCopyDetails details) {
|
||||
Crc32OutputStream crcStream = new Crc32OutputStream();
|
||||
details.copyTo(crcStream);
|
||||
return crcStream.getCrc();
|
||||
}
|
||||
|
||||
void finish() throws IOException {
|
||||
writeLoaderEntriesIfNecessary(null);
|
||||
writeJarToolsIfNecessary();
|
||||
writeLayersIndexIfNecessary();
|
||||
writeClassPathIndexIfNecessary();
|
||||
}
|
||||
|
||||
private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOException {
|
||||
|
@ -183,9 +299,9 @@ class BootZipCopyAction implements CopyAction {
|
|||
// Don't write loader entries until after META-INF folder (see gh-16698)
|
||||
return;
|
||||
}
|
||||
LoaderZipEntries loaderEntries = new LoaderZipEntries(
|
||||
LoaderZipEntries entries = new LoaderZipEntries(
|
||||
BootZipCopyAction.this.preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||
this.writtenLoaderEntries = loaderEntries.writeTo(this.outputStream);
|
||||
this.writtenLoaderEntries = entries.writeTo(this.out);
|
||||
}
|
||||
|
||||
private boolean isInMetaInf(FileCopyDetails details) {
|
||||
|
@ -196,40 +312,47 @@ class BootZipCopyAction implements CopyAction {
|
|||
return segments.length > 0 && "META-INF".equals(segments[0]);
|
||||
}
|
||||
|
||||
private void processDirectory(FileCopyDetails details) throws IOException {
|
||||
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(details.getRelativePath().getPathString() + '/');
|
||||
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
||||
archiveEntry.setTime(getTime(details));
|
||||
this.outputStream.putArchiveEntry(archiveEntry);
|
||||
this.outputStream.closeArchiveEntry();
|
||||
private void writeJarToolsIfNecessary() throws IOException {
|
||||
if (BootZipCopyAction.this.layerResolver == null || !BootZipCopyAction.this.includeLayerTools) {
|
||||
return;
|
||||
}
|
||||
writeJarModeLibrary(JarModeLibrary.LAYER_TOOLS);
|
||||
}
|
||||
|
||||
private void processFile(FileCopyDetails details) throws IOException {
|
||||
String relativePath = details.getRelativePath().getPathString();
|
||||
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(relativePath);
|
||||
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
||||
archiveEntry.setTime(getTime(details));
|
||||
ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details);
|
||||
if (compression == ZipCompression.STORED) {
|
||||
prepareStoredEntry(details, archiveEntry);
|
||||
}
|
||||
this.outputStream.putArchiveEntry(archiveEntry);
|
||||
details.copyTo(this.outputStream);
|
||||
this.outputStream.closeArchiveEntry();
|
||||
private void writeJarModeLibrary(JarModeLibrary jarModeLibrary) throws IOException {
|
||||
String name = BootZipCopyAction.this.layerResolver.getPath(jarModeLibrary);
|
||||
writeFile(name, ZipEntryWriter.fromInputStream(jarModeLibrary.openStream()));
|
||||
}
|
||||
|
||||
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
|
||||
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
||||
archiveEntry.setSize(details.getSize());
|
||||
archiveEntry.setCompressedSize(details.getSize());
|
||||
Crc32OutputStream crcStream = new Crc32OutputStream();
|
||||
details.copyTo(crcStream);
|
||||
archiveEntry.setCrc(crcStream.getCrc());
|
||||
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
|
||||
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
|
||||
private void writeLayersIndexIfNecessary() throws IOException {
|
||||
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
|
||||
String layersIndex = (String) manifestAttributes.get("Spring-Boot-Layers-Index");
|
||||
if (layersIndex != null && BootZipCopyAction.this.layerResolver != null) {
|
||||
writeFile(layersIndex, ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding,
|
||||
BootZipCopyAction.this.layerResolver.getLayerNames()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClassPathIndexIfNecessary() throws IOException {
|
||||
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
|
||||
String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index");
|
||||
if (classPathIndex != null) {
|
||||
writeFile(classPathIndex,
|
||||
ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, this.writtenLibraries));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFile(String name, ZipEntryWriter entryWriter) throws IOException {
|
||||
writeParentDirectoriesIfNecessary(name, CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(name);
|
||||
entry.setUnixMode(UnixStat.FILE_FLAG);
|
||||
entry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||
this.out.putArchiveEntry(entry);
|
||||
entryWriter.writeTo(entry, this.out);
|
||||
this.out.closeArchiveEntry();
|
||||
|
||||
}
|
||||
|
||||
private long getTime(FileCopyDetails details) {
|
||||
return BootZipCopyAction.this.preserveFileTimestamps ? details.getLastModified()
|
||||
: CONSTANT_TIME_FOR_ZIP_ENTRIES;
|
||||
|
@ -237,6 +360,52 @@ class BootZipCopyAction implements CopyAction {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to write a zip entry data.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
private interface ZipEntryWriter {
|
||||
|
||||
/**
|
||||
* Write the entry data.
|
||||
* @param entry the entry being written
|
||||
* @param out the output stream used to write the data
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
void writeTo(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException;
|
||||
|
||||
/**
|
||||
* Create a new {@link ZipEntryWriter} that will copy content from the given
|
||||
* {@link InputStream}.
|
||||
* @param in the source input stream
|
||||
* @return a new {@link ZipEntryWriter} instance
|
||||
*/
|
||||
static ZipEntryWriter fromInputStream(InputStream in) {
|
||||
return (entry, out) -> {
|
||||
StreamUtils.copy(in, out);
|
||||
in.close();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ZipEntryWriter} that will copy content from the given
|
||||
* lines.
|
||||
* @param encoding the required character encoding
|
||||
* @param lines the lines to write
|
||||
* @return a new {@link ZipEntryWriter} instance
|
||||
*/
|
||||
static ZipEntryWriter fromLines(String encoding, Collection<String> lines) {
|
||||
return (entry, out) -> {
|
||||
OutputStreamWriter writer = new OutputStreamWriter(out, encoding);
|
||||
for (String line : lines) {
|
||||
writer.append(line + "\n");
|
||||
}
|
||||
writer.flush();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code OutputStream} that provides a CRC-32 of the data that is written to it.
|
||||
*/
|
||||
|
|
|
@ -1,237 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.tasks.Input;
|
||||
|
||||
import org.springframework.boot.loader.tools.layer.application.FilteredResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.application.LocationFilter;
|
||||
import org.springframework.boot.loader.tools.layer.application.ResourceFilter;
|
||||
import org.springframework.boot.loader.tools.layer.application.ResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.library.LibraryFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the configuration for a layered jar.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class LayerConfiguration {
|
||||
|
||||
private boolean includeLayerTools = true;
|
||||
|
||||
private List<String> layersOrder = new ArrayList<>();
|
||||
|
||||
private List<ResourceStrategy> resourceStrategies = new ArrayList<>();
|
||||
|
||||
private List<LibraryStrategy> libraryStrategies = new ArrayList<>();
|
||||
|
||||
private StrategySpec strategySpec;
|
||||
|
||||
/**
|
||||
* Whether to include the layer tools jar.
|
||||
* @return true if layer tools is included
|
||||
*/
|
||||
@Input
|
||||
public boolean isIncludeLayerTools() {
|
||||
return this.includeLayerTools;
|
||||
}
|
||||
|
||||
public void setIncludeLayerTools(boolean includeLayerTools) {
|
||||
this.includeLayerTools = includeLayerTools;
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<String> getLayersOrder() {
|
||||
return this.layersOrder;
|
||||
}
|
||||
|
||||
public void layersOrder(String... layers) {
|
||||
this.layersOrder = Arrays.asList(layers);
|
||||
}
|
||||
|
||||
public void layersOrder(List<String> layers) {
|
||||
this.layersOrder = layers;
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<ResourceStrategy> getApplication() {
|
||||
return this.resourceStrategies;
|
||||
}
|
||||
|
||||
public void application(ResourceStrategy... resourceStrategies) {
|
||||
assertLayersOrderConfigured();
|
||||
this.resourceStrategies = Arrays.asList(resourceStrategies);
|
||||
}
|
||||
|
||||
public void application(Action<LayerConfiguration> config) {
|
||||
assertLayersOrderConfigured();
|
||||
this.strategySpec = StrategySpec.forResources();
|
||||
config.execute(this);
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<LibraryStrategy> getLibraries() {
|
||||
return this.libraryStrategies;
|
||||
}
|
||||
|
||||
public void libraries(LibraryStrategy... strategies) {
|
||||
assertLayersOrderConfigured();
|
||||
this.libraryStrategies = Arrays.asList(strategies);
|
||||
}
|
||||
|
||||
public void libraries(Action<LayerConfiguration> configure) {
|
||||
assertLayersOrderConfigured();
|
||||
this.strategySpec = StrategySpec.forLibraries();
|
||||
configure.execute(this);
|
||||
}
|
||||
|
||||
private void assertLayersOrderConfigured() {
|
||||
Assert.state(!this.layersOrder.isEmpty(), "'layersOrder' must be configured before filters can be applied.");
|
||||
}
|
||||
|
||||
public void layerContent(String layerName, Action<LayerConfiguration> config) {
|
||||
this.strategySpec.newStrategy();
|
||||
config.execute(this);
|
||||
if (this.strategySpec.isLibrariesStrategy()) {
|
||||
this.libraryStrategies.add(new FilteredLibraryStrategy(layerName, this.strategySpec.libraryFilters()));
|
||||
}
|
||||
else {
|
||||
this.resourceStrategies.add(new FilteredResourceStrategy(layerName, this.strategySpec.resourceFilters()));
|
||||
}
|
||||
}
|
||||
|
||||
public void coordinates(Action<LayerConfiguration> config) {
|
||||
Assert.state(this.strategySpec.isLibrariesStrategy(),
|
||||
"The 'coordinates' filter must be used only with libraries");
|
||||
this.strategySpec.newFilter();
|
||||
config.execute(this);
|
||||
this.strategySpec
|
||||
.addLibraryFilter(new CoordinateFilter(this.strategySpec.includes(), this.strategySpec.excludes()));
|
||||
}
|
||||
|
||||
public void locations(Action<LayerConfiguration> config) {
|
||||
Assert.state(this.strategySpec.isResourcesStrategy(),
|
||||
"The 'locations' filter must be used only with application");
|
||||
this.strategySpec.newFilter();
|
||||
config.execute(this);
|
||||
this.strategySpec
|
||||
.addResourceFilter(new LocationFilter(this.strategySpec.includes(), this.strategySpec.excludes()));
|
||||
}
|
||||
|
||||
public void include(String... includes) {
|
||||
this.strategySpec.include(includes);
|
||||
}
|
||||
|
||||
public void exclude(String... excludes) {
|
||||
this.strategySpec.exclude(excludes);
|
||||
}
|
||||
|
||||
private static final class StrategySpec {
|
||||
|
||||
private final TYPE type;
|
||||
|
||||
private List<LibraryFilter> libraryFilters;
|
||||
|
||||
private List<ResourceFilter> resourceFilters;
|
||||
|
||||
private List<String> filterIncludes;
|
||||
|
||||
private List<String> filterExcludes;
|
||||
|
||||
private StrategySpec(TYPE type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private boolean isLibrariesStrategy() {
|
||||
return this.type == TYPE.LIBRARIES;
|
||||
}
|
||||
|
||||
private boolean isResourcesStrategy() {
|
||||
return this.type == TYPE.RESOURCES;
|
||||
}
|
||||
|
||||
private void newStrategy() {
|
||||
this.libraryFilters = new ArrayList<>();
|
||||
this.resourceFilters = new ArrayList<>();
|
||||
newFilter();
|
||||
}
|
||||
|
||||
private void newFilter() {
|
||||
this.filterIncludes = new ArrayList<>();
|
||||
this.filterExcludes = new ArrayList<>();
|
||||
}
|
||||
|
||||
private List<LibraryFilter> libraryFilters() {
|
||||
return this.libraryFilters;
|
||||
}
|
||||
|
||||
private void addLibraryFilter(LibraryFilter filter) {
|
||||
this.libraryFilters.add(filter);
|
||||
}
|
||||
|
||||
private List<ResourceFilter> resourceFilters() {
|
||||
return this.resourceFilters;
|
||||
}
|
||||
|
||||
private void addResourceFilter(ResourceFilter filter) {
|
||||
this.resourceFilters.add(filter);
|
||||
}
|
||||
|
||||
private List<String> includes() {
|
||||
return this.filterIncludes;
|
||||
}
|
||||
|
||||
private void include(String... includes) {
|
||||
this.filterIncludes.addAll(Arrays.asList(includes));
|
||||
}
|
||||
|
||||
private void exclude(String... excludes) {
|
||||
this.filterIncludes.addAll(Arrays.asList(excludes));
|
||||
}
|
||||
|
||||
private List<String> excludes() {
|
||||
return this.filterExcludes;
|
||||
}
|
||||
|
||||
private static StrategySpec forLibraries() {
|
||||
return new StrategySpec(TYPE.LIBRARIES);
|
||||
}
|
||||
|
||||
private static StrategySpec forResources() {
|
||||
return new StrategySpec(TYPE.RESOURCES);
|
||||
}
|
||||
|
||||
private enum TYPE {
|
||||
|
||||
LIBRARIES, RESOURCES;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.gradle.api.artifacts.ArtifactCollection;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.component.ComponentIdentifier;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.specs.Spec;
|
||||
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
|
||||
/**
|
||||
* Resolver backed by a {@link LayeredSpec} that provides the destination {@link Layer}
|
||||
* for each copied {@link FileCopyDetails}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @see BootZipCopyAction
|
||||
*/
|
||||
class LayerResolver {
|
||||
|
||||
private static final String BOOT_INF_FOLDER = "BOOT-INF/";
|
||||
|
||||
private final ResolvedDependencies resolvedDependencies;
|
||||
|
||||
private final LayeredSpec layeredConfiguration;
|
||||
|
||||
private final Spec<FileCopyDetails> librarySpec;
|
||||
|
||||
LayerResolver(Iterable<Configuration> configurations, LayeredSpec layeredConfiguration,
|
||||
Spec<FileCopyDetails> librarySpec) {
|
||||
this.resolvedDependencies = new ResolvedDependencies(configurations);
|
||||
this.layeredConfiguration = layeredConfiguration;
|
||||
this.librarySpec = librarySpec;
|
||||
}
|
||||
|
||||
String getPath(JarModeLibrary jarModeLibrary) {
|
||||
Layers layers = this.layeredConfiguration.asLayers();
|
||||
Layer layer = layers.getLayer(jarModeLibrary);
|
||||
if (layer != null) {
|
||||
return BOOT_INF_FOLDER + "layers/" + layer + "/lib/" + jarModeLibrary.getName();
|
||||
}
|
||||
return BOOT_INF_FOLDER + "lib/" + jarModeLibrary.getName();
|
||||
}
|
||||
|
||||
String getPath(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
Layer layer = getLayer(details);
|
||||
if (layer == null || !path.startsWith(BOOT_INF_FOLDER)) {
|
||||
return path;
|
||||
}
|
||||
path = path.substring(BOOT_INF_FOLDER.length());
|
||||
return BOOT_INF_FOLDER + "layers/" + layer + "/" + path;
|
||||
}
|
||||
|
||||
Layer getLayer(FileCopyDetails details) {
|
||||
Layers layers = this.layeredConfiguration.asLayers();
|
||||
try {
|
||||
if (this.librarySpec.isSatisfiedBy(details)) {
|
||||
return layers.getLayer(asLibrary(details));
|
||||
}
|
||||
return layers.getLayer(details.getSourcePath());
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getLayerNames() {
|
||||
return this.layeredConfiguration.asLayers().stream().map(Layer::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Library asLibrary(FileCopyDetails details) {
|
||||
File file = details.getFile();
|
||||
LibraryCoordinates coordinates = this.resolvedDependencies.find(file);
|
||||
return new Library(null, file, null, coordinates, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks and provides details of resolved dependencies in the project so we can find
|
||||
* {@link LibraryCoordinates}.
|
||||
*/
|
||||
private static class ResolvedDependencies {
|
||||
|
||||
private final Map<Configuration, ResolvedConfigurationDependencies> configurationDependencies = new LinkedHashMap<>();
|
||||
|
||||
ResolvedDependencies(Iterable<Configuration> configurations) {
|
||||
configurations.forEach(this::processConfiguration);
|
||||
}
|
||||
|
||||
private void processConfiguration(Configuration configuration) {
|
||||
if (configuration.isCanBeResolved()) {
|
||||
this.configurationDependencies.put(configuration,
|
||||
new ResolvedConfigurationDependencies(configuration.getIncoming().getArtifacts()));
|
||||
}
|
||||
}
|
||||
|
||||
LibraryCoordinates find(File file) {
|
||||
for (ResolvedConfigurationDependencies dependencies : this.configurationDependencies.values()) {
|
||||
LibraryCoordinates coordinates = dependencies.find(file);
|
||||
if (coordinates != null) {
|
||||
return coordinates;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores details of resolved configuration dependencies.
|
||||
*/
|
||||
private static class ResolvedConfigurationDependencies {
|
||||
|
||||
private final Map<File, LibraryCoordinates> artifactCoordinates = new LinkedHashMap<>();
|
||||
|
||||
ResolvedConfigurationDependencies(ArtifactCollection resolvedDependencies) {
|
||||
if (resolvedDependencies != null) {
|
||||
for (ResolvedArtifactResult resolvedArtifact : resolvedDependencies.getArtifacts()) {
|
||||
ComponentIdentifier identifier = resolvedArtifact.getId().getComponentIdentifier();
|
||||
if (identifier instanceof ModuleComponentIdentifier) {
|
||||
this.artifactCoordinates.put(resolvedArtifact.getFile(),
|
||||
new ModuleComponentIdentifierLibraryCoordinates(
|
||||
(ModuleComponentIdentifier) identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LibraryCoordinates find(File file) {
|
||||
return this.artifactCoordinates.get(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts a {@link ModuleComponentIdentifier} to {@link LibraryCoordinates}.
|
||||
*/
|
||||
private static class ModuleComponentIdentifierLibraryCoordinates implements LibraryCoordinates {
|
||||
|
||||
private final ModuleComponentIdentifier identifier;
|
||||
|
||||
ModuleComponentIdentifierLibraryCoordinates(ModuleComponentIdentifier identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupId() {
|
||||
return this.identifier.getGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return this.identifier.getModule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return this.identifier.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.identifier.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.util.ConfigureUtil;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.layer.ApplicationContentFilter;
|
||||
import org.springframework.boot.loader.tools.layer.ContentFilter;
|
||||
import org.springframework.boot.loader.tools.layer.ContentSelector;
|
||||
import org.springframework.boot.loader.tools.layer.CustomLayers;
|
||||
import org.springframework.boot.loader.tools.layer.IncludeExcludeContentSelector;
|
||||
import org.springframework.boot.loader.tools.layer.LibraryContentFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the configuration for a layered jar.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class LayeredSpec {
|
||||
|
||||
private boolean includeLayerTools = true;
|
||||
|
||||
private ApplicationSpec application = new ApplicationSpec();
|
||||
|
||||
private DependenciesSpec dependencies = new DependenciesSpec();
|
||||
|
||||
@Optional
|
||||
private List<String> layerOrder;
|
||||
|
||||
private Layers layers;
|
||||
|
||||
@Input
|
||||
public boolean isIncludeLayerTools() {
|
||||
return this.includeLayerTools;
|
||||
}
|
||||
|
||||
public void setIncludeLayerTools(boolean includeLayerTools) {
|
||||
this.includeLayerTools = includeLayerTools;
|
||||
}
|
||||
|
||||
@Input
|
||||
public ApplicationSpec getApplication() {
|
||||
return this.application;
|
||||
}
|
||||
|
||||
public void application(ApplicationSpec spec) {
|
||||
this.application = spec;
|
||||
}
|
||||
|
||||
public void application(Closure<?> closure) {
|
||||
application(ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
public void application(Action<ApplicationSpec> action) {
|
||||
action.execute(this.application);
|
||||
}
|
||||
|
||||
@Input
|
||||
public DependenciesSpec getDependencies() {
|
||||
return this.dependencies;
|
||||
}
|
||||
|
||||
public void dependencies(DependenciesSpec spec) {
|
||||
this.dependencies = spec;
|
||||
}
|
||||
|
||||
public void dependencies(Closure<?> closure) {
|
||||
dependencies(ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
public void dependencies(Action<DependenciesSpec> action) {
|
||||
action.execute(this.dependencies);
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<String> getLayerOrder() {
|
||||
return this.layerOrder;
|
||||
}
|
||||
|
||||
public void layerOrder(String... layerOrder) {
|
||||
this.layerOrder = Arrays.asList(layerOrder);
|
||||
}
|
||||
|
||||
public void layerOrder(List<String> layerOrder) {
|
||||
this.layerOrder = layerOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this configuration as a {@link Layers} instance. This method should only be
|
||||
* called when the configuration is complete and will no longer be changed.
|
||||
* @return the layers
|
||||
*/
|
||||
Layers asLayers() {
|
||||
Layers layers = this.layers;
|
||||
if (layers == null) {
|
||||
layers = createLayers();
|
||||
this.layers = layers;
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
private Layers createLayers() {
|
||||
if (this.layerOrder == null || this.layerOrder.isEmpty()) {
|
||||
Assert.state(this.application.isEmpty() && this.dependencies.isEmpty(),
|
||||
"The 'layerOrder' must be defined when using custom layering");
|
||||
return Layers.IMPLICIT;
|
||||
}
|
||||
List<Layer> layers = this.layerOrder.stream().map(Layer::new).collect(Collectors.toList());
|
||||
return new CustomLayers(layers, this.application.asSelectors(), this.dependencies.asSelectors());
|
||||
}
|
||||
|
||||
public abstract static class IntoLayersSpec implements Serializable {
|
||||
|
||||
private final List<IntoLayerSpec> intoLayers;
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.intoLayers.isEmpty();
|
||||
}
|
||||
|
||||
IntoLayersSpec(IntoLayerSpec... spec) {
|
||||
this.intoLayers = new ArrayList<>(Arrays.asList(spec));
|
||||
}
|
||||
|
||||
public void intoLayer(String layer) {
|
||||
this.intoLayers.add(new IntoLayerSpec(layer));
|
||||
}
|
||||
|
||||
public void intoLayer(String layer, Closure<?> closure) {
|
||||
intoLayer(layer, ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
public void intoLayer(String layer, Action<IntoLayerSpec> action) {
|
||||
IntoLayerSpec spec = new IntoLayerSpec(layer);
|
||||
action.execute(spec);
|
||||
this.intoLayers.add(spec);
|
||||
}
|
||||
|
||||
<T> List<ContentSelector<T>> asSelectors(Function<String, ContentFilter<T>> filterFactory) {
|
||||
return this.intoLayers.stream().map((content) -> content.asSelector(filterFactory))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class IntoLayerSpec implements Serializable {
|
||||
|
||||
private final String intoLayer;
|
||||
|
||||
private final List<String> includes = new ArrayList<>();
|
||||
|
||||
private final List<String> excludes = new ArrayList<>();
|
||||
|
||||
public IntoLayerSpec(String intoLayer) {
|
||||
this.intoLayer = intoLayer;
|
||||
}
|
||||
|
||||
public void include(String... patterns) {
|
||||
this.includes.addAll(Arrays.asList(patterns));
|
||||
}
|
||||
|
||||
public void exclude(String... patterns) {
|
||||
this.includes.addAll(Arrays.asList(patterns));
|
||||
}
|
||||
|
||||
<T> ContentSelector<T> asSelector(Function<String, ContentFilter<T>> filterFactory) {
|
||||
Layer layer = new Layer(this.intoLayer);
|
||||
return new IncludeExcludeContentSelector<>(layer, this.includes, this.excludes, filterFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ApplicationSpec extends IntoLayersSpec {
|
||||
|
||||
public ApplicationSpec(IntoLayerSpec... contents) {
|
||||
super(contents);
|
||||
}
|
||||
|
||||
List<ContentSelector<String>> asSelectors() {
|
||||
return asSelectors(ApplicationContentFilter::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class DependenciesSpec extends IntoLayersSpec {
|
||||
|
||||
public DependenciesSpec(IntoLayerSpec... contents) {
|
||||
super(contents);
|
||||
}
|
||||
|
||||
List<ContentSelector<Library>> asSelectors() {
|
||||
return asSelectors(LibraryContentFilter::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -44,18 +44,18 @@ class LoaderZipEntries {
|
|||
this.entryTime = entryTime;
|
||||
}
|
||||
|
||||
Spec<FileTreeElement> writeTo(ZipArchiveOutputStream zipOutputStream) throws IOException {
|
||||
Spec<FileTreeElement> writeTo(ZipArchiveOutputStream out) throws IOException {
|
||||
WrittenDirectoriesSpec writtenDirectoriesSpec = new WrittenDirectoriesSpec();
|
||||
try (ZipInputStream loaderJar = new ZipInputStream(
|
||||
getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
|
||||
java.util.zip.ZipEntry entry = loaderJar.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.isDirectory() && !entry.getName().equals("META-INF/")) {
|
||||
writeDirectory(new ZipArchiveEntry(entry), zipOutputStream);
|
||||
writeDirectory(new ZipArchiveEntry(entry), out);
|
||||
writtenDirectoriesSpec.add(entry);
|
||||
}
|
||||
else if (entry.getName().endsWith(".class")) {
|
||||
writeClass(new ZipArchiveEntry(entry), loaderJar, zipOutputStream);
|
||||
writeClass(new ZipArchiveEntry(entry), loaderJar, out);
|
||||
}
|
||||
entry = loaderJar.getNextEntry();
|
||||
}
|
||||
|
|
|
@ -25,11 +25,14 @@ import java.nio.file.Path;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.InvalidRunnerConfigurationException;
|
||||
import org.gradle.testkit.runner.TaskOutcome;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.junit.jupiter.api.TestTemplate;
|
||||
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
|
@ -75,8 +78,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
|||
writeResource();
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar"))
|
||||
.isNotNull();
|
||||
assertThat(jarFile.getEntry(jarModeLayerTools())).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/snapshot-dependencies/lib/commons-io-2.7-SNAPSHOT.jar"))
|
||||
.isNotNull();
|
||||
|
@ -89,10 +91,11 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
|||
void customLayers() throws IOException {
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
BuildResult build = this.gradleBuild.build("bootJar");
|
||||
System.out.println(build.getOutput());
|
||||
assertThat(build.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar"))
|
||||
.isNotNull();
|
||||
assertThat(jarFile.getEntry(jarModeLayerTools())).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/commons-dependencies/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/snapshot-dependencies/lib/commons-io-2.7-SNAPSHOT.jar"))
|
||||
.isNotNull();
|
||||
|
@ -101,6 +104,13 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
|||
}
|
||||
}
|
||||
|
||||
private String jarModeLayerTools() {
|
||||
JarModeLibrary library = JarModeLibrary.LAYER_TOOLS;
|
||||
String version = library.getCoordinates().getVersion();
|
||||
String layer = (version == null || !version.contains("SNAPSHOT")) ? "dependencies" : "snapshot-dependencies";
|
||||
return "BOOT-INF/layers/" + layer + "/lib/" + library.getName();
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||
examplePackage.mkdirs();
|
||||
|
|
|
@ -20,9 +20,8 @@ import java.io.BufferedReader;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarFile;
|
||||
|
@ -31,16 +30,15 @@ import java.util.zip.ZipEntry;
|
|||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.artifacts.ArtifactCollection;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ResolvableDependencies;
|
||||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
|
||||
import org.gradle.api.artifacts.component.ComponentIdentifier;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.layer.application.FilteredResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.application.LocationFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
|
||||
import org.springframework.boot.gradle.tasks.bundling.BootJarTests.TestBootJar;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
@ -53,10 +51,10 @@ import static org.mockito.Mockito.mock;
|
|||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
|
||||
|
||||
BootJarTests() {
|
||||
super(BootJar.class, "org.springframework.boot.loader.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/");
|
||||
super(TestBootJar.class, "org.springframework.boot.loader.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -121,13 +119,17 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
|
||||
@Test
|
||||
void whenJarIsLayeredWithCustomStrategiesThenContentsAreMovedToLayerDirectories() throws IOException {
|
||||
File jar = createLayeredJar((configuration) -> {
|
||||
configuration.layersOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application");
|
||||
configuration.libraries(createLibraryStrategy("my-snapshot-deps", "com.example:*:*.SNAPSHOT"),
|
||||
createLibraryStrategy("my-internal-deps", "com.example:*:*"),
|
||||
createLibraryStrategy("my-deps", "*:*"));
|
||||
configuration.application(createResourceStrategy("resources", "static/**"),
|
||||
createResourceStrategy("application", "**"));
|
||||
File jar = createLayeredJar((layered) -> {
|
||||
layered.application((application) -> {
|
||||
application.intoLayer("resources", (spec) -> spec.include("static/**"));
|
||||
application.intoLayer("application");
|
||||
});
|
||||
layered.dependencies((dependencies) -> {
|
||||
dependencies.intoLayer("my-snapshot-deps", (spec) -> spec.include("com.example:*:*.SNAPSHOT"));
|
||||
dependencies.intoLayer("my-internal-deps", (spec) -> spec.include("com.example:*:*"));
|
||||
dependencies.intoLayer("my-deps");
|
||||
});
|
||||
layered.layerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application");
|
||||
});
|
||||
List<String> entryNames = getEntryNames(jar);
|
||||
assertThat(entryNames)
|
||||
|
@ -139,16 +141,6 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
.contains("BOOT-INF/layers/resources/classes/static/test.css");
|
||||
}
|
||||
|
||||
private FilteredLibraryStrategy createLibraryStrategy(String layerName, String... includes) {
|
||||
return new FilteredLibraryStrategy(layerName,
|
||||
Collections.singletonList(new CoordinateFilter(Arrays.asList(includes), Collections.emptyList())));
|
||||
}
|
||||
|
||||
private FilteredResourceStrategy createResourceStrategy(String layerName, String... includes) {
|
||||
return new FilteredResourceStrategy(layerName,
|
||||
Collections.singletonList(new LocationFilter(Arrays.asList(includes), Collections.emptyList())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredJarsInLibAreStored() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
|
@ -172,7 +164,14 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
@Test
|
||||
void whenJarIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException {
|
||||
List<String> entryNames = getEntryNames(createLayeredJar());
|
||||
assertThat(entryNames).contains("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
|
||||
assertThat(entryNames).contains(jarModeLayerTools());
|
||||
}
|
||||
|
||||
private String jarModeLayerTools() {
|
||||
JarModeLibrary library = JarModeLibrary.LAYER_TOOLS;
|
||||
String version = library.getCoordinates().getVersion();
|
||||
String layer = (version == null || !version.contains("SNAPSHOT")) ? "dependencies" : "snapshot-dependencies";
|
||||
return "BOOT-INF/layers/" + layer + "/lib/" + library.getName();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -198,24 +197,24 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
return getTask().getArchiveFile().get().getAsFile();
|
||||
}
|
||||
|
||||
private File createLayeredJar(Action<LayerConfiguration> action) throws IOException {
|
||||
private File createLayeredJar() throws IOException {
|
||||
return createLayeredJar(null);
|
||||
}
|
||||
|
||||
private File createLayeredJar(Action<LayeredSpec> action) throws IOException {
|
||||
if (action != null) {
|
||||
getTask().layers(action);
|
||||
getTask().layered(action);
|
||||
}
|
||||
else {
|
||||
getTask().layers();
|
||||
getTask().layered();
|
||||
}
|
||||
addContent();
|
||||
executeTask();
|
||||
return getTask().getArchiveFile().get().getAsFile();
|
||||
}
|
||||
|
||||
private File createLayeredJar() throws IOException {
|
||||
return createLayeredJar(null);
|
||||
}
|
||||
|
||||
private void addContent() throws IOException {
|
||||
BootJar bootJar = getTask();
|
||||
TestBootJar bootJar = getTask();
|
||||
bootJar.setMainClassName("com.example.Main");
|
||||
File classesJavaMain = new File(this.temp, "classes/java/main");
|
||||
File applicationClass = new File(classesJavaMain, "com/example/Application.class");
|
||||
|
@ -231,32 +230,33 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
css.createNewFile();
|
||||
bootJar.classpath(classesJavaMain, resourcesMain, jarFile("first-library.jar"), jarFile("second-library.jar"),
|
||||
jarFile("third-library-SNAPSHOT.jar"));
|
||||
|
||||
Set<ResolvedArtifactResult> resolvedArtifacts = new HashSet<>();
|
||||
resolvedArtifacts.add(mockLibraryArtifact("first-library.jar", "com.example:first-library:1.0.0"));
|
||||
resolvedArtifacts.add(mockLibraryArtifact("second-library.jar", "com.example:second-library:1.0.0"));
|
||||
resolvedArtifacts
|
||||
.add(mockLibraryArtifact("third-library-SNAPSHOT.jar", "com.example:third-library:1.0.0.SNAPSHOT"));
|
||||
|
||||
ArtifactCollection artifacts = mock(ArtifactCollection.class);
|
||||
given(artifacts.getArtifacts()).willReturn(resolvedArtifacts);
|
||||
|
||||
ResolvableDependencies deps = mock(ResolvableDependencies.class);
|
||||
given(deps.getArtifacts()).willReturn(artifacts);
|
||||
bootJar.resolvedDependencies(deps);
|
||||
Set<ResolvedArtifactResult> artifacts = new LinkedHashSet<>();
|
||||
artifacts.add(mockLibraryArtifact("first-library.jar", "com.example", "first-library", "1.0.0"));
|
||||
artifacts.add(mockLibraryArtifact("second-library.jar", "com.example", "second-library", "1.0.0"));
|
||||
artifacts.add(
|
||||
mockLibraryArtifact("third-library-SNAPSHOT.jar", "com.example", "third-library", "1.0.0.SNAPSHOT"));
|
||||
ArtifactCollection resolvedDependencies = mock(ArtifactCollection.class);
|
||||
given(resolvedDependencies.getArtifacts()).willReturn(artifacts);
|
||||
ResolvableDependencies resolvableDependencies = mock(ResolvableDependencies.class);
|
||||
given(resolvableDependencies.getArtifacts()).willReturn(resolvedDependencies);
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
given(configuration.isCanBeResolved()).willReturn(true);
|
||||
given(configuration.getIncoming()).willReturn(resolvableDependencies);
|
||||
bootJar.setConfiguration(Collections.singleton(configuration));
|
||||
}
|
||||
|
||||
private ResolvedArtifactResult mockLibraryArtifact(String fileName, String coordinates) {
|
||||
ComponentIdentifier libraryId = mock(ComponentIdentifier.class);
|
||||
given(libraryId.getDisplayName()).willReturn(coordinates);
|
||||
|
||||
private ResolvedArtifactResult mockLibraryArtifact(String fileName, String group, String module, String version) {
|
||||
ModuleComponentIdentifier identifier = mock(ModuleComponentIdentifier.class);
|
||||
given(identifier.getGroup()).willReturn(group);
|
||||
given(identifier.getModule()).willReturn(module);
|
||||
given(identifier.getVersion()).willReturn(version);
|
||||
ComponentArtifactIdentifier libraryArtifactId = mock(ComponentArtifactIdentifier.class);
|
||||
given(libraryArtifactId.getComponentIdentifier()).willReturn(libraryId);
|
||||
|
||||
given(libraryArtifactId.getComponentIdentifier()).willReturn(identifier);
|
||||
ResolvedArtifactResult libraryArtifact = mock(ResolvedArtifactResult.class);
|
||||
given(libraryArtifact.getFile()).willReturn(new File(fileName));
|
||||
File file = new File(this.temp, fileName).getAbsoluteFile();
|
||||
System.out.println(file);
|
||||
given(libraryArtifact.getFile()).willReturn(file);
|
||||
given(libraryArtifact.getId()).willReturn(libraryArtifactId);
|
||||
|
||||
return libraryArtifact;
|
||||
}
|
||||
|
||||
|
@ -272,4 +272,19 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
getTask().copy();
|
||||
}
|
||||
|
||||
public static class TestBootJar extends BootJar {
|
||||
|
||||
private Iterable<Configuration> configurations = Collections.emptySet();
|
||||
|
||||
@Override
|
||||
protected Iterable<Configuration> getConfigurations() {
|
||||
return this.configurations;
|
||||
}
|
||||
|
||||
void setConfiguration(Iterable<Configuration> configurations) {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,26 +5,23 @@ plugins {
|
|||
|
||||
bootJar {
|
||||
mainClassName = 'com.example.Application'
|
||||
layers {
|
||||
layersOrder "dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"
|
||||
libraries {
|
||||
layerContent("snapshot-dependencies") { coordinates { include "*:*:*SNAPSHOT" } }
|
||||
layerContent("commons-dependencies") { coordinates { include "org.apache.commons:*" } }
|
||||
layerContent("dependencies") { coordinates { include "*:*" } }
|
||||
}
|
||||
layered {
|
||||
application {
|
||||
layerContent("static") {
|
||||
locations {
|
||||
include "META-INF/resources/**", "resources/**"
|
||||
include "static/**", "public/**"
|
||||
}
|
||||
}
|
||||
layerContent("app") {
|
||||
locations {
|
||||
include "**"
|
||||
}
|
||||
intoLayer("static") {
|
||||
include "META-INF/resources/**", "resources/**", "static/**", "public/**"
|
||||
}
|
||||
intoLayer("app")
|
||||
}
|
||||
dependencies {
|
||||
intoLayer("snapshot-dependencies") {
|
||||
include "*:*:*SNAPSHOT"
|
||||
}
|
||||
intoLayer("commons-dependencies") {
|
||||
include "org.apache.commons:*"
|
||||
}
|
||||
intoLayer("dependencies")
|
||||
}
|
||||
layerOrder "dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ plugins {
|
|||
|
||||
bootJar {
|
||||
mainClassName = 'com.example.Application'
|
||||
layers()
|
||||
layered()
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
|
|
@ -11,7 +11,7 @@ bootJar {
|
|||
}
|
||||
}
|
||||
if (project.hasProperty('layered') && project.getProperty('layered')) {
|
||||
layers {
|
||||
layered {
|
||||
includeLayerTools = project.hasProperty('excludeTools') && project.getProperty('excludeTools') ? false : true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools;
|
||||
|
||||
/**
|
||||
* Encapsulates information about the artifact coordinates of a library.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class DefaultLibraryCoordinates implements LibraryCoordinates {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
private final String version;
|
||||
|
||||
/**
|
||||
* Create a new instance from discrete elements.
|
||||
* @param groupId the group ID
|
||||
* @param artifactId the artifact ID
|
||||
* @param version the version
|
||||
*/
|
||||
DefaultLibraryCoordinates(String groupId, String artifactId, String version) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the group ID of the coordinates.
|
||||
* @return the group ID
|
||||
*/
|
||||
@Override
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the artifact ID of the coordinates.
|
||||
* @return the artifact ID
|
||||
*/
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return this.artifactId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version of the coordinates.
|
||||
* @return the version
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the coordinates in the form {@code groupId:artifactId:version}.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return LibraryCoordinates.toStandardNotationString(this);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,23 +22,48 @@ import java.io.InputStream;
|
|||
import java.net.URL;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Library} implementation for internal jarmode jars.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
class JarModeLibrary extends Library {
|
||||
public class JarModeLibrary extends Library {
|
||||
|
||||
static final JarModeLibrary LAYER_TOOLS = new JarModeLibrary("spring-boot-jarmode-layertools.jar");
|
||||
/**
|
||||
* {@link JarModeLibrary} for layer tools.
|
||||
*/
|
||||
public static final JarModeLibrary LAYER_TOOLS = new JarModeLibrary("spring-boot-jarmode-layertools");
|
||||
|
||||
JarModeLibrary(String name) {
|
||||
super(name, null, LibraryScope.RUNTIME, false);
|
||||
JarModeLibrary(String artifactId) {
|
||||
this(createCoordinates(artifactId));
|
||||
}
|
||||
|
||||
public JarModeLibrary(LibraryCoordinates coordinates) {
|
||||
super(getJarName(coordinates), null, LibraryScope.RUNTIME, coordinates, false);
|
||||
}
|
||||
|
||||
private static LibraryCoordinates createCoordinates(String artifactId) {
|
||||
String version = JarModeLibrary.class.getPackage().getImplementationVersion();
|
||||
return LibraryCoordinates.of("org.springframework.boot", artifactId, version);
|
||||
}
|
||||
|
||||
private static String getJarName(LibraryCoordinates coordinates) {
|
||||
String version = coordinates.getVersion();
|
||||
StringBuilder jarName = new StringBuilder(coordinates.getArtifactId());
|
||||
if (StringUtils.hasText(version)) {
|
||||
jarName.append('-');
|
||||
jarName.append(version);
|
||||
}
|
||||
jarName.append(".jar");
|
||||
return jarName.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream openStream() throws IOException {
|
||||
String path = "META-INF/jarmode/" + getName();
|
||||
public InputStream openStream() throws IOException {
|
||||
String path = "META-INF/jarmode/" + getCoordinates().getArtifactId() + ".jar";
|
||||
URL resource = getClass().getClassLoader().getResource(path);
|
||||
Assert.state(resource != null, "Unable to find resource " + path);
|
||||
return resource.openStream();
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.boot.loader.tools;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -29,7 +28,7 @@ import org.springframework.util.Assert;
|
|||
* @since 2.3.0
|
||||
* @see Layers
|
||||
*/
|
||||
public class Layer implements Serializable {
|
||||
public class Layer {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9-]+$");
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.boot.loader.tools;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Interface to provide information about layers to the {@link Repackager}.
|
||||
|
@ -36,16 +37,25 @@ public interface Layers extends Iterable<Layer> {
|
|||
/**
|
||||
* Return the jar layers in the order that they should be added (starting with the
|
||||
* least frequently changed layer).
|
||||
* @return the layers iterator
|
||||
*/
|
||||
@Override
|
||||
Iterator<Layer> iterator();
|
||||
|
||||
/**
|
||||
* Return a stream of the jar layers in the order that they should be added (starting
|
||||
* with the least frequently changed layer).
|
||||
* @return the layers stream
|
||||
*/
|
||||
Stream<Layer> stream();
|
||||
|
||||
/**
|
||||
* Return the layer that contains the given resource name.
|
||||
* @param resourceName the name of the resource (for example a {@code .class} file).
|
||||
* @param applicationResource the name of an application resource (for example a
|
||||
* {@code .class} file).
|
||||
* @return the layer that contains the resource (must never be {@code null})
|
||||
*/
|
||||
Layer getLayer(String resourceName);
|
||||
Layer getLayer(String applicationResource);
|
||||
|
||||
/**
|
||||
* Return the layer that contains the given library.
|
||||
|
|
|
@ -16,82 +16,60 @@
|
|||
|
||||
package org.springframework.boot.loader.tools;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates information about the Maven artifact coordinates of a library.
|
||||
* Encapsulates information about the artifact coordinates of a library.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public final class LibraryCoordinates {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
private final String version;
|
||||
|
||||
/**
|
||||
* Create a new instance from discrete elements.
|
||||
* @param groupId the group ID
|
||||
* @param artifactId the artifact ID
|
||||
* @param version the version
|
||||
*/
|
||||
public LibraryCoordinates(String groupId, String artifactId, String version) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance from a String value in the form
|
||||
* {@code groupId:artifactId:version} where the version is optional.
|
||||
* @param coordinates the coordinates
|
||||
*/
|
||||
public LibraryCoordinates(String coordinates) {
|
||||
String[] elements = coordinates.split(":");
|
||||
Assert.isTrue(elements.length >= 2, "Coordinates must contain at least 'groupId:artifactId'");
|
||||
this.groupId = elements[0];
|
||||
this.artifactId = elements[1];
|
||||
this.version = (elements.length > 2) ? elements[2] : null;
|
||||
}
|
||||
public interface LibraryCoordinates {
|
||||
|
||||
/**
|
||||
* Return the group ID of the coordinates.
|
||||
* @return the group ID
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
String getGroupId();
|
||||
|
||||
/**
|
||||
* Return the artifact ID of the coordinates.
|
||||
* @return the artifact ID
|
||||
*/
|
||||
public String getArtifactId() {
|
||||
return this.artifactId;
|
||||
}
|
||||
String getArtifactId();
|
||||
|
||||
/**
|
||||
* Return the version of the coordinates.
|
||||
* @return the version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
String getVersion();
|
||||
|
||||
/**
|
||||
* Factory method to create {@link LibraryCoordinates} with the specified values.
|
||||
* @param groupId the group ID
|
||||
* @param artifactId the artifact ID
|
||||
* @param version the version
|
||||
* @return a new {@link LibraryCoordinates} instance
|
||||
*/
|
||||
static LibraryCoordinates of(String groupId, String artifactId, String version) {
|
||||
return new DefaultLibraryCoordinates(groupId, artifactId, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the coordinates in the form {@code groupId:artifactId:version}.
|
||||
* Utility method that returns the given coordinates using the standard
|
||||
* {@code group:artifact:version} form.
|
||||
* @param coordinates the coordinates to convert (may be {@code null})
|
||||
* @return the standard notation form or {@code "::"} when the coordinates are null
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
static String toStandardNotationString(LibraryCoordinates coordinates) {
|
||||
if (coordinates == null) {
|
||||
return "::";
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append((this.groupId != null) ? this.groupId : "");
|
||||
builder.append((coordinates.getGroupId() != null) ? coordinates.getGroupId() : "");
|
||||
builder.append(":");
|
||||
builder.append((this.artifactId != null) ? this.artifactId : "");
|
||||
builder.append((coordinates.getArtifactId() != null) ? coordinates.getArtifactId() : "");
|
||||
builder.append(":");
|
||||
builder.append((this.version != null) ? this.version : "");
|
||||
builder.append((coordinates.getVersion() != null) ? coordinates.getVersion() : "");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Base class for the standard set of {@link Layers}. Defines the following layers:
|
||||
|
@ -64,4 +65,9 @@ public abstract class StandardLayers implements Layers {
|
|||
return LAYERS.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Layer> stream() {
|
||||
return LAYERS.stream();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,29 +14,33 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
|
||||
import java.util.List;
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of {@link ResourceFilter} based on the resource location.
|
||||
* {@link ContentFilter} that matches application items based on an Ant-style path
|
||||
* pattern.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class LocationFilter extends AbstractResourceFilter {
|
||||
public class ApplicationContentFilter implements ContentFilter<String> {
|
||||
|
||||
private static final AntPathMatcher MATCHER = new AntPathMatcher();
|
||||
|
||||
public LocationFilter(List<String> includes, List<String> excludes) {
|
||||
super(includes, excludes);
|
||||
private final String pattern;
|
||||
|
||||
public ApplicationContentFilter(String pattern) {
|
||||
Assert.hasText(pattern, "Pattern must not be empty");
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMatch(String resourceName, List<String> toMatch) {
|
||||
return toMatch.stream().anyMatch((pattern) -> MATCHER.match(pattern, resourceName));
|
||||
public boolean matches(String path) {
|
||||
return MATCHER.match(this.pattern, path);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,8 +14,24 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
/**
|
||||
* Support for custom layers for everything in BOOT-INF/classes.
|
||||
* Callback interface that can be used to filter layer contents.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @param <T> the content type
|
||||
* @since 2.3.0
|
||||
*/
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
@FunctionalInterface
|
||||
public interface ContentFilter<T> {
|
||||
|
||||
/**
|
||||
* Return if the filter matches the specified item.
|
||||
* @param item the item to test
|
||||
* @return if the filter matches
|
||||
*/
|
||||
boolean matches(T item);
|
||||
|
||||
}
|
|
@ -14,27 +14,32 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.library;
|
||||
|
||||
import java.io.Serializable;
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
|
||||
/**
|
||||
* A strategy used to match a library to a layer.
|
||||
* Strategy used by {@link CustomLayers} to select the layer of an item.
|
||||
*
|
||||
* @param <T> the content type
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
* @see IncludeExcludeContentSelector
|
||||
*/
|
||||
public interface LibraryStrategy extends Serializable {
|
||||
public interface ContentSelector<T> {
|
||||
|
||||
/**
|
||||
* Return a {@link Layer} for the given {@link Library}. If no matching layer is
|
||||
* found, {@code null} is returned.
|
||||
* @param library the library
|
||||
* @return the matching layer or {@code null}
|
||||
* Return the {@link Layer} that the selector represents.
|
||||
* @return the named layer
|
||||
*/
|
||||
Layer getMatchingLayer(Library library);
|
||||
Layer getLayer();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the specified item is contained in this selection.
|
||||
* @param item the item to test
|
||||
* @return if the item is contained
|
||||
*/
|
||||
boolean contains(T item);
|
||||
|
||||
}
|
|
@ -19,32 +19,52 @@ package org.springframework.boot.loader.tools.layer;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.layer.application.ResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Layers} representing user-provided layers.
|
||||
* Custom {@link Layers} implementation where layer content is selected by the user.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class CustomLayers implements Layers {
|
||||
|
||||
private final List<Layer> layers;
|
||||
|
||||
private final List<ResourceStrategy> resourceStrategies;
|
||||
private final List<ContentSelector<String>> applicationSelectors;
|
||||
|
||||
private final List<LibraryStrategy> libraryStrategies;
|
||||
private final List<ContentSelector<Library>> librarySelectors;
|
||||
|
||||
public CustomLayers(List<Layer> layers, List<ResourceStrategy> resourceStrategies,
|
||||
List<LibraryStrategy> libraryStrategies) {
|
||||
public CustomLayers(List<Layer> layers, List<ContentSelector<String>> applicationSelectors,
|
||||
List<ContentSelector<Library>> librarySelectors) {
|
||||
Assert.notNull(layers, "Layers must not be null");
|
||||
Assert.notNull(applicationSelectors, "ApplicationSelectors must not be null");
|
||||
validateSelectorLayers(applicationSelectors, layers);
|
||||
Assert.notNull(librarySelectors, "LibrarySelectors must not be null");
|
||||
validateSelectorLayers(librarySelectors, layers);
|
||||
this.layers = new ArrayList<>(layers);
|
||||
this.resourceStrategies = new ArrayList<>(resourceStrategies);
|
||||
this.libraryStrategies = new ArrayList<>(libraryStrategies);
|
||||
this.applicationSelectors = new ArrayList<>(applicationSelectors);
|
||||
this.librarySelectors = new ArrayList<>(librarySelectors);
|
||||
}
|
||||
|
||||
private static <T> void validateSelectorLayers(List<ContentSelector<T>> selectors, List<Layer> layers) {
|
||||
for (ContentSelector<?> selector : selectors) {
|
||||
validateSelectorLayers(selector, layers);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateSelectorLayers(ContentSelector<?> selector, List<Layer> layers) {
|
||||
Layer layer = selector.getLayer();
|
||||
Assert.state(layer != null, "Missing content selector layer");
|
||||
Assert.state(layers.contains(layer),
|
||||
"Content selector layer '" + selector.getLayer() + "' not found in " + layers);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,35 +72,28 @@ public class CustomLayers implements Layers {
|
|||
return this.layers.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Layer> stream() {
|
||||
return this.layers.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layer getLayer(String resourceName) {
|
||||
for (ResourceStrategy strategy : this.resourceStrategies) {
|
||||
Layer matchingLayer = strategy.getMatchingLayer(resourceName);
|
||||
if (matchingLayer != null) {
|
||||
validateLayerName(matchingLayer, "Resource '" + resourceName + "'");
|
||||
return matchingLayer;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Resource '" + resourceName + "' did not match any layer.");
|
||||
return selectLayer(resourceName, this.applicationSelectors, () -> "Resource '" + resourceName + "'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layer getLayer(Library library) {
|
||||
for (LibraryStrategy strategy : this.libraryStrategies) {
|
||||
Layer matchingLayer = strategy.getMatchingLayer(library);
|
||||
if (matchingLayer != null) {
|
||||
validateLayerName(matchingLayer, "Library '" + library.getName() + "'");
|
||||
return matchingLayer;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Library '" + library.getName() + "' did not match any layer.");
|
||||
return selectLayer(library, this.librarySelectors, () -> "Library '" + library.getName() + "'");
|
||||
}
|
||||
|
||||
private void validateLayerName(Layer layer, String nameText) {
|
||||
if (!this.layers.contains(layer)) {
|
||||
throw new IllegalStateException(nameText + " matched a layer '" + layer
|
||||
+ "' that is not included in the configured layers " + this.layers + ".");
|
||||
private <T> Layer selectLayer(T item, List<ContentSelector<T>> selectors, Supplier<String> name) {
|
||||
for (ContentSelector<T> selector : selectors) {
|
||||
if (selector.contains(item)) {
|
||||
return selector.getLayer();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(name.get() + " did not match any layer");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ContentSelector} backed by {@code include}/{@code exclude} {@link ContentFilter
|
||||
* filters}.
|
||||
*
|
||||
* @param <T> the content type
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class IncludeExcludeContentSelector<T> implements ContentSelector<T> {
|
||||
|
||||
private final Layer layer;
|
||||
|
||||
private final List<ContentFilter<T>> includes;
|
||||
|
||||
private final List<ContentFilter<T>> excludes;
|
||||
|
||||
public IncludeExcludeContentSelector(Layer layer, List<ContentFilter<T>> includes,
|
||||
List<ContentFilter<T>> excludes) {
|
||||
this(layer, includes, excludes, Function.identity());
|
||||
}
|
||||
|
||||
public <S> IncludeExcludeContentSelector(Layer layer, List<S> includes, List<S> excludes,
|
||||
Function<S, ContentFilter<T>> filterFactory) {
|
||||
Assert.notNull(layer, "Layer must not be null");
|
||||
Assert.notNull(filterFactory, "FilterFactory must not be null");
|
||||
this.layer = layer;
|
||||
this.includes = (includes != null) ? adapt(includes, filterFactory) : Collections.emptyList();
|
||||
this.excludes = (excludes != null) ? adapt(excludes, filterFactory) : Collections.emptyList();
|
||||
}
|
||||
|
||||
private <S> List<ContentFilter<T>> adapt(List<S> list, Function<S, ContentFilter<T>> mapper) {
|
||||
return list.stream().map(mapper).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layer getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(T item) {
|
||||
return isIncluded(item) && !isExcluded(item);
|
||||
}
|
||||
|
||||
private boolean isIncluded(T item) {
|
||||
if (this.includes.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (ContentFilter<T> include : this.includes) {
|
||||
if (include.matches(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isExcluded(T item) {
|
||||
if (this.excludes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (ContentFilter<T> exclude : this.excludes) {
|
||||
if (exclude.matches(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ContentFilter} that matches {@link Library} items based on a coordinates
|
||||
* pattern.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class LibraryContentFilter implements ContentFilter<Library> {
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
public LibraryContentFilter(String coordinatesPattern) {
|
||||
Assert.hasText(coordinatesPattern, "CoordinatesPattern must not be empty");
|
||||
StringBuilder regex = new StringBuilder();
|
||||
for (int i = 0; i < coordinatesPattern.length(); i++) {
|
||||
char c = coordinatesPattern.charAt(i);
|
||||
if (c == '.') {
|
||||
regex.append("\\.");
|
||||
}
|
||||
else if (c == '*') {
|
||||
regex.append(".*");
|
||||
}
|
||||
else {
|
||||
regex.append(c);
|
||||
}
|
||||
}
|
||||
this.pattern = Pattern.compile(regex.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Library library) {
|
||||
return this.pattern.matcher(LibraryCoordinates.toStandardNotationString(library.getCoordinates())).matches();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link ResourceFilter} implementations.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public abstract class AbstractResourceFilter implements ResourceFilter {
|
||||
|
||||
private final List<String> includes = new ArrayList<>();
|
||||
|
||||
private final List<String> excludes = new ArrayList<>();
|
||||
|
||||
public AbstractResourceFilter(List<String> includes, List<String> excludes) {
|
||||
this.includes.addAll(includes);
|
||||
this.excludes.addAll(excludes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceIncluded(String resourceName) {
|
||||
return isMatch(resourceName, this.includes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceExcluded(String resourceName) {
|
||||
return isMatch(resourceName, this.excludes);
|
||||
}
|
||||
|
||||
protected abstract boolean isMatch(String resourceName, List<String> toMatch);
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ResourceStrategy} with custom filters.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class FilteredResourceStrategy implements ResourceStrategy {
|
||||
|
||||
private final Layer layer;
|
||||
|
||||
private final List<ResourceFilter> filters = new ArrayList<>();
|
||||
|
||||
public FilteredResourceStrategy(String layer, List<ResourceFilter> filters) {
|
||||
Assert.notEmpty(filters, "Filters should not be empty for custom strategy.");
|
||||
this.layer = new Layer(layer);
|
||||
this.filters.addAll(filters);
|
||||
}
|
||||
|
||||
public Layer getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layer getMatchingLayer(String resourceName) {
|
||||
boolean isIncluded = false;
|
||||
for (ResourceFilter filter : this.filters) {
|
||||
if (filter.isResourceExcluded(resourceName)) {
|
||||
return null;
|
||||
}
|
||||
if (!isIncluded && filter.isResourceIncluded(resourceName)) {
|
||||
isIncluded = true;
|
||||
}
|
||||
}
|
||||
return (isIncluded) ? this.layer : null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A filter that can tell if a resource has been included or excluded.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public interface ResourceFilter extends Serializable {
|
||||
|
||||
/**
|
||||
* Return true if the resource is included by the filter.
|
||||
* @param resourceName the resource name
|
||||
* @return true if the resource is included
|
||||
*/
|
||||
boolean isResourceIncluded(String resourceName);
|
||||
|
||||
/**
|
||||
* Return true if the resource is included by the filter.
|
||||
* @param resourceName the resource name
|
||||
* @return true if the resource is excluded
|
||||
*/
|
||||
boolean isResourceExcluded(String resourceName);
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
|
||||
/**
|
||||
* A strategy used to match a resource to a layer.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public interface ResourceStrategy extends Serializable {
|
||||
|
||||
/**
|
||||
* Return a {@link Layer} for the given resource. If no matching layer is found,
|
||||
* {@code null} is returned.
|
||||
* @param resourceName the name of the resource
|
||||
* @return the matching layer or {@code null}
|
||||
*/
|
||||
Layer getMatchingLayer(String resourceName);
|
||||
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.library;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
|
||||
/**
|
||||
* An implementation of {@link LibraryFilter} based on the library's coordinates.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class CoordinateFilter implements LibraryFilter {
|
||||
|
||||
private static final String EMPTY_COORDINATES = "::";
|
||||
|
||||
private final List<Pattern> includes;
|
||||
|
||||
private final List<Pattern> excludes;
|
||||
|
||||
public CoordinateFilter(List<String> includes, List<String> excludes) {
|
||||
this.includes = includes.stream().map(this::asPattern).collect(Collectors.toList());
|
||||
this.excludes = excludes.stream().map(this::asPattern).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Pattern asPattern(String string) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < string.length(); i++) {
|
||||
char c = string.charAt(i);
|
||||
if (c == '.') {
|
||||
builder.append("\\.");
|
||||
}
|
||||
else if (c == '*') {
|
||||
builder.append(".*");
|
||||
}
|
||||
else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
return Pattern.compile(builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLibraryIncluded(Library library) {
|
||||
return isMatch(library, this.includes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLibraryExcluded(Library library) {
|
||||
return isMatch(library, this.excludes);
|
||||
}
|
||||
|
||||
private boolean isMatch(Library library, List<Pattern> patterns) {
|
||||
LibraryCoordinates coordinates = library.getCoordinates();
|
||||
String input = (coordinates != null) ? coordinates.toString() : EMPTY_COORDINATES;
|
||||
for (Pattern pattern : patterns) {
|
||||
if (pattern.matcher(input).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.library;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link LibraryStrategy} with custom filters.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class FilteredLibraryStrategy implements LibraryStrategy {
|
||||
|
||||
private final Layer layer;
|
||||
|
||||
private final List<LibraryFilter> filters = new ArrayList<>();
|
||||
|
||||
public FilteredLibraryStrategy(String layer, List<LibraryFilter> filters) {
|
||||
Assert.notEmpty(filters, "Filters should not be empty for custom strategy.");
|
||||
this.layer = new Layer(layer);
|
||||
this.filters.addAll(filters);
|
||||
}
|
||||
|
||||
public Layer getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layer getMatchingLayer(Library library) {
|
||||
boolean isIncluded = false;
|
||||
for (LibraryFilter filter : this.filters) {
|
||||
if (filter.isLibraryExcluded(library)) {
|
||||
return null;
|
||||
}
|
||||
if (!isIncluded && filter.isLibraryIncluded(library)) {
|
||||
isIncluded = true;
|
||||
}
|
||||
}
|
||||
return (isIncluded) ? this.layer : null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.library;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
|
||||
/**
|
||||
* A filter that can tell if a {@link Library} has been included or excluded.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public interface LibraryFilter extends Serializable {
|
||||
|
||||
/**
|
||||
* Return true if the {@link Library} is included by the filter.
|
||||
* @param library the library
|
||||
* @return true if the library is included
|
||||
*/
|
||||
boolean isLibraryIncluded(Library library);
|
||||
|
||||
/**
|
||||
* Return true if the {@link Library} is excluded by the filter.
|
||||
* @param library the library
|
||||
* @return true if the library is excluded
|
||||
*/
|
||||
boolean isLibraryExcluded(Library library);
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for custom layers for everything in BOOT-INF/lib.
|
||||
*
|
||||
*/
|
||||
package org.springframework.boot.loader.tools.layer.library;
|
|
@ -34,6 +34,7 @@ import java.util.Set;
|
|||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
@ -650,6 +651,11 @@ abstract class AbstractPackagerTests<P extends Packager> {
|
|||
return this.layers.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Layer> stream() {
|
||||
return this.layers.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layer getLayer(String name) {
|
||||
return DEFAULT_LAYER;
|
||||
|
|
|
@ -19,56 +19,42 @@ package org.springframework.boot.loader.tools;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link LibraryCoordinates}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LibraryCoordinatesTests {
|
||||
|
||||
@Test
|
||||
void parseCoordinatesWithAllElements() {
|
||||
LibraryCoordinates coordinates = new LibraryCoordinates("com.acme:my-library:1.0.0");
|
||||
assertThat(coordinates.getGroupId()).isEqualTo("com.acme");
|
||||
assertThat(coordinates.getArtifactId()).isEqualTo("my-library");
|
||||
assertThat(coordinates.getVersion()).isEqualTo("1.0.0");
|
||||
void ofCreateLibraryCoordinates() {
|
||||
LibraryCoordinates coordinates = LibraryCoordinates.of("g", "a", "v");
|
||||
assertThat(coordinates.getGroupId()).isEqualTo("g");
|
||||
assertThat(coordinates.getArtifactId()).isEqualTo("a");
|
||||
assertThat(coordinates.getVersion()).isEqualTo("v");
|
||||
assertThat(coordinates.toString()).isEqualTo("g:a:v");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCoordinatesWithoutVersion() {
|
||||
LibraryCoordinates coordinates = new LibraryCoordinates("com.acme:my-library");
|
||||
assertThat(coordinates.getGroupId()).isEqualTo("com.acme");
|
||||
assertThat(coordinates.getArtifactId()).isEqualTo("my-library");
|
||||
assertThat(coordinates.getVersion()).isNull();
|
||||
void toStandardNotationStringWhenCoordinatesAreNull() {
|
||||
assertThat(LibraryCoordinates.toStandardNotationString(null)).isEqualTo("::");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCoordinatesWithEmptyElements() {
|
||||
LibraryCoordinates coordinates = new LibraryCoordinates(":my-library:");
|
||||
assertThat(coordinates.getGroupId()).isEqualTo("");
|
||||
assertThat(coordinates.getArtifactId()).isEqualTo("my-library");
|
||||
assertThat(coordinates.getVersion()).isNull();
|
||||
void toStandardNotationStringWhenCoordinatesElementsNull() {
|
||||
assertThat(LibraryCoordinates.toStandardNotationString(mock(LibraryCoordinates.class))).isEqualTo("::");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCoordinatesWithExtraElements() {
|
||||
LibraryCoordinates coordinates = new LibraryCoordinates("com.acme:my-library:1.0.0.BUILD-SNAPSHOT:11111");
|
||||
assertThat(coordinates.getGroupId()).isEqualTo("com.acme");
|
||||
assertThat(coordinates.getArtifactId()).isEqualTo("my-library");
|
||||
assertThat(coordinates.getVersion()).isEqualTo("1.0.0.BUILD-SNAPSHOT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCoordinatesWithoutMinimumElements() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LibraryCoordinates("com.acme"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringReturnsString() {
|
||||
assertThat(new LibraryCoordinates("com.acme:my-library:1.0.0")).hasToString("com.acme:my-library:1.0.0");
|
||||
assertThat(new LibraryCoordinates("com.acme:my-library")).hasToString("com.acme:my-library:");
|
||||
void toStandardNotationString() {
|
||||
LibraryCoordinates coordinates = mock(LibraryCoordinates.class);
|
||||
given(coordinates.getGroupId()).willReturn("a");
|
||||
given(coordinates.getArtifactId()).willReturn("b");
|
||||
given(coordinates.getVersion()).willReturn("c");
|
||||
assertThat(LibraryCoordinates.toStandardNotationString(coordinates)).isEqualTo("a:b:c");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ApplicationContentFilter}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ApplicationContentFilterTests {
|
||||
|
||||
@Test
|
||||
void createWhenPatternIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ApplicationContentFilter(null))
|
||||
.withMessage("Pattern must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenPatternIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ApplicationContentFilter(""))
|
||||
.withMessage("Pattern must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenWildcardPatternMatchesReturnsTrue() {
|
||||
ApplicationContentFilter filter = new ApplicationContentFilter("META-INF/**");
|
||||
assertThat(filter.matches("META-INF/resources/application.yml")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenWildcardPatternDoesNotMatchReturnsFalse() {
|
||||
ApplicationContentFilter filter = new ApplicationContentFilter("META-INF/**");
|
||||
assertThat(filter.matches("src/main/resources/application.yml")).isFalse();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
import org.springframework.boot.loader.tools.layer.application.FilteredResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.application.LocationFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link CustomLayers}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CustomLayersTests {
|
||||
|
||||
@Test
|
||||
void customLayersAreAvailable() {
|
||||
Layer first = new Layer("first");
|
||||
Layer second = new Layer("second");
|
||||
CustomLayers customLayers = new CustomLayers(Arrays.asList(first, second), Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
List<Layer> actualLayers = new ArrayList<>();
|
||||
customLayers.iterator().forEachRemaining(actualLayers::add);
|
||||
assertThat(actualLayers).containsExactly(first, second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void layerForResourceIsFound() {
|
||||
FilteredResourceStrategy resourceStrategy = new FilteredResourceStrategy("test", Collections
|
||||
.singletonList(new LocationFilter(Collections.singletonList("META-INF/**"), Collections.emptyList())));
|
||||
Layer targetLayer = new Layer("test");
|
||||
CustomLayers customLayers = new CustomLayers(Collections.singletonList(targetLayer),
|
||||
Collections.singletonList(resourceStrategy), Collections.emptyList());
|
||||
assertThat(customLayers.getLayer("META-INF/manifest.mf")).isNotNull().isEqualTo(targetLayer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void layerForResourceIsNotFound() {
|
||||
FilteredResourceStrategy resourceStrategy = new FilteredResourceStrategy("test", Collections
|
||||
.singletonList(new LocationFilter(Collections.singletonList("META-INF/**"), Collections.emptyList())));
|
||||
CustomLayers customLayers = new CustomLayers(Collections.singletonList(new Layer("test")),
|
||||
Collections.singletonList(resourceStrategy), Collections.emptyList());
|
||||
assertThatIllegalStateException().isThrownBy(() -> customLayers.getLayer("com/acme"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void layerForResourceIsNotInListedLayers() {
|
||||
FilteredResourceStrategy resourceStrategy = new FilteredResourceStrategy("test-not-listed", Collections
|
||||
.singletonList(new LocationFilter(Collections.singletonList("META-INF/**"), Collections.emptyList())));
|
||||
Layer targetLayer = new Layer("test");
|
||||
CustomLayers customLayers = new CustomLayers(Collections.singletonList(targetLayer),
|
||||
Collections.singletonList(resourceStrategy), Collections.emptyList());
|
||||
assertThatIllegalStateException().isThrownBy(() -> customLayers.getLayer("META-INF/manifest.mf"))
|
||||
.withMessageContaining("META-INF/manifest.mf").withMessageContaining("test-not-listed")
|
||||
.withMessageContaining("[test]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void layerForLibraryIsFound() {
|
||||
FilteredLibraryStrategy libraryStrategy = new FilteredLibraryStrategy("test", Collections
|
||||
.singletonList(new CoordinateFilter(Collections.singletonList("com.acme:*"), Collections.emptyList())));
|
||||
Layer targetLayer = new Layer("test");
|
||||
CustomLayers customLayers = new CustomLayers(Collections.singletonList(targetLayer), Collections.emptyList(),
|
||||
Collections.singletonList(libraryStrategy));
|
||||
assertThat(customLayers.getLayer(mockLibrary("com.acme:test"))).isNotNull().isEqualTo(targetLayer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void layerForLibraryIsNotFound() {
|
||||
FilteredLibraryStrategy libraryStrategy = new FilteredLibraryStrategy("test", Collections
|
||||
.singletonList(new CoordinateFilter(Collections.singletonList("com.acme:*"), Collections.emptyList())));
|
||||
CustomLayers customLayers = new CustomLayers(Collections.singletonList(new Layer("test")),
|
||||
Collections.emptyList(), Collections.singletonList(libraryStrategy));
|
||||
assertThatIllegalStateException().isThrownBy(() -> customLayers.getLayer(mockLibrary("org.another:test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void layerForLibraryIsNotInListedLayers() {
|
||||
FilteredLibraryStrategy libraryStrategy = new FilteredLibraryStrategy("test-not-listed", Collections
|
||||
.singletonList(new CoordinateFilter(Collections.singletonList("com.acme:*"), Collections.emptyList())));
|
||||
Layer targetLayer = new Layer("test");
|
||||
CustomLayers customLayers = new CustomLayers(Collections.singletonList(targetLayer), Collections.emptyList(),
|
||||
Collections.singletonList(libraryStrategy));
|
||||
assertThatIllegalStateException().isThrownBy(() -> customLayers.getLayer(mockLibrary("com.acme:test")))
|
||||
.withMessageContaining("com.acme:test").withMessageContaining("test-not-listed")
|
||||
.withMessageContaining("[test]");
|
||||
}
|
||||
|
||||
private Library mockLibrary(String coordinates) {
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates(coordinates));
|
||||
given(library.getName()).willReturn(coordinates);
|
||||
return library;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link IncludeExcludeContentSelector}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class IncludeExcludeContentSelectorTests {
|
||||
|
||||
private static final Layer LAYER = new Layer("test");
|
||||
|
||||
@Test
|
||||
void createWhenLayerIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new IncludeExcludeContentSelector<>(null, Collections.emptyList(), Collections.emptyList()))
|
||||
.withMessage("Layer must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenFactoryIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new IncludeExcludeContentSelector<>(LAYER, null, null, null))
|
||||
.withMessage("FilterFactory must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLayerReturnsLayer() {
|
||||
IncludeExcludeContentSelector<?> selector = new IncludeExcludeContentSelector<>(LAYER, null, null);
|
||||
assertThat(selector.getLayer()).isEqualTo(LAYER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsWhenEmptyIncludesAndEmptyExcludesReturnsTrue() {
|
||||
List<String> includes = Arrays.asList();
|
||||
List<String> excludes = Arrays.asList();
|
||||
IncludeExcludeContentSelector<String> selector = new IncludeExcludeContentSelector<>(LAYER, includes, excludes,
|
||||
TestContentsFilter::new);
|
||||
assertThat(selector.contains("A")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsWhenNullIncludesAndEmptyExcludesReturnsTrue() {
|
||||
List<String> includes = null;
|
||||
List<String> excludes = null;
|
||||
IncludeExcludeContentSelector<String> selector = new IncludeExcludeContentSelector<>(LAYER, includes, excludes,
|
||||
TestContentsFilter::new);
|
||||
assertThat(selector.contains("A")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsWhenEmptyIncludesAndNotExcludedReturnsTrue() {
|
||||
List<String> includes = Arrays.asList();
|
||||
List<String> excludes = Arrays.asList("B");
|
||||
IncludeExcludeContentSelector<String> selector = new IncludeExcludeContentSelector<>(LAYER, includes, excludes,
|
||||
TestContentsFilter::new);
|
||||
assertThat(selector.contains("A")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsWhenEmptyIncludesAndExcludedReturnsFalse() {
|
||||
List<String> includes = Arrays.asList();
|
||||
List<String> excludes = Arrays.asList("A");
|
||||
IncludeExcludeContentSelector<String> selector = new IncludeExcludeContentSelector<>(LAYER, includes, excludes,
|
||||
TestContentsFilter::new);
|
||||
assertThat(selector.contains("A")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsWhenIncludedAndEmptyExcludesReturnsTrue() {
|
||||
List<String> includes = Arrays.asList("A", "B");
|
||||
List<String> excludes = Arrays.asList();
|
||||
IncludeExcludeContentSelector<String> selector = new IncludeExcludeContentSelector<>(LAYER, includes, excludes,
|
||||
TestContentsFilter::new);
|
||||
assertThat(selector.contains("B")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsWhenIncludedAndNotExcludedReturnsTrue() {
|
||||
List<String> includes = Arrays.asList("A", "B");
|
||||
List<String> excludes = Arrays.asList("C", "D");
|
||||
IncludeExcludeContentSelector<String> selector = new IncludeExcludeContentSelector<>(LAYER, includes, excludes,
|
||||
TestContentsFilter::new);
|
||||
assertThat(selector.contains("B")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsWhenIncludedAndExcludedReturnsFalse() {
|
||||
List<String> includes = Arrays.asList("A", "B");
|
||||
List<String> excludes = Arrays.asList("C", "D");
|
||||
IncludeExcludeContentSelector<String> selector = new IncludeExcludeContentSelector<>(LAYER, includes, excludes,
|
||||
TestContentsFilter::new);
|
||||
assertThat(selector.contains("C")).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ContentFilter} used for testing.
|
||||
*/
|
||||
static class TestContentsFilter implements ContentFilter<String> {
|
||||
|
||||
private final String match;
|
||||
|
||||
TestContentsFilter(String match) {
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String item) {
|
||||
return this.match.equals(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link LibraryContentFilter}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LibraryContentFilterTests {
|
||||
|
||||
@Test
|
||||
void createWhenCoordinatesPatternIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LibraryContentFilter(null))
|
||||
.withMessage("CoordinatesPattern must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenCoordinatesPatternIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LibraryContentFilter(""))
|
||||
.withMessage("CoordinatesPattern must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenGroupIdIsNullAndToMatchHasWildcardReturnsTrue() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("*:*");
|
||||
assertThat(filter.matches(mockLibrary(null, null, null))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenArtifactIdIsNullAndToMatchHasWildcardReturnsTrue() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("org.acme:*");
|
||||
assertThat(filter.matches(mockLibrary("org.acme", null, null))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenVersionIsNullAndToMatchHasWildcardReturnsTrue() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("org.acme:something:*");
|
||||
assertThat(filter.matches(mockLibrary("org.acme", "something", null))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenGroupIdDoesNotMatchReturnsFalse() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("org.acme:*");
|
||||
assertThat(filter.matches(mockLibrary("other.foo", null, null))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenWhenArtifactIdDoesNotMatchReturnsFalse() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("org.acme:test:*");
|
||||
assertThat(filter.matches(mockLibrary("org.acme", "other", null))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenArtifactIdMatchesReturnsTrue() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("org.acme:test:*");
|
||||
assertThat(filter.matches(mockLibrary("org.acme", "test", null))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenVersionDoesNotMatchReturnsFalse() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("org.acme:test:*SNAPSHOT");
|
||||
assertThat(filter.matches(mockLibrary("org.acme", "test", "1.0.0"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenVersionMatchesReturnsTrue() {
|
||||
LibraryContentFilter filter = new LibraryContentFilter("org.acme:test:*SNAPSHOT");
|
||||
assertThat(filter.matches(mockLibrary("org.acme", "test", "1.0.0-SNAPSHOT"))).isTrue();
|
||||
}
|
||||
|
||||
private Library mockLibrary(String groupId, String artifactId, String version) {
|
||||
return mockLibrary(LibraryCoordinates.of(groupId, artifactId, version));
|
||||
}
|
||||
|
||||
private Library mockLibrary(LibraryCoordinates coordinates) {
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(coordinates);
|
||||
return library;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link FilteredResourceStrategy}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class FilteredResourceStrategyTests {
|
||||
|
||||
@Test
|
||||
void createWhenFiltersNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new FilteredResourceStrategy("custom", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenFiltersEmptyShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new FilteredResourceStrategy("custom", Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLayerShouldReturnLayerName() {
|
||||
FilteredResourceStrategy strategy = new FilteredResourceStrategy("custom",
|
||||
Collections.singletonList(new TestFilter1()));
|
||||
assertThat(strategy.getLayer().toString()).isEqualTo("custom");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchingLayerWhenFilterMatchesIncludes() {
|
||||
FilteredResourceStrategy strategy = new FilteredResourceStrategy("custom",
|
||||
Collections.singletonList(new TestFilter1()));
|
||||
assertThat(strategy.getMatchingLayer("ABCD").toString()).isEqualTo("custom");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenFilterMatchesIncludesAndExcludesFromSameFilter() {
|
||||
FilteredResourceStrategy strategy = new FilteredResourceStrategy("custom",
|
||||
Collections.singletonList(new TestFilter1()));
|
||||
assertThat(strategy.getMatchingLayer("AZ")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenFilterMatchesIncludesAndExcludesFromAnotherFilter() {
|
||||
List<ResourceFilter> filters = new ArrayList<>();
|
||||
filters.add(new TestFilter1());
|
||||
filters.add(new TestFilter2());
|
||||
FilteredResourceStrategy strategy = new FilteredResourceStrategy("custom", filters);
|
||||
assertThat(strategy.getMatchingLayer("AY")).isNull();
|
||||
}
|
||||
|
||||
private static class TestFilter1 implements ResourceFilter {
|
||||
|
||||
@Override
|
||||
public boolean isResourceIncluded(String resourceName) {
|
||||
return resourceName.startsWith("A");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceExcluded(String resourceName) {
|
||||
return resourceName.endsWith("Z");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestFilter2 implements ResourceFilter {
|
||||
|
||||
@Override
|
||||
public boolean isResourceIncluded(String resourceName) {
|
||||
return resourceName.startsWith("B");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceExcluded(String resourceName) {
|
||||
return resourceName.endsWith("Y");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.application;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link LocationFilter}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class LocationFilterTests {
|
||||
|
||||
@Test
|
||||
void isResourceIncludedWhenPatternMatchesWithWildcard() {
|
||||
LocationFilter filter = new LocationFilter(Collections.singletonList("META-INF/**"), Collections.emptyList());
|
||||
assertThat(filter.isResourceIncluded("META-INF/resources/application.yml")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isResourceIncludedWhenPatternDoesNotMatch() {
|
||||
LocationFilter filter = new LocationFilter(Collections.singletonList("META-INF/**"), Collections.emptyList());
|
||||
assertThat(filter.isResourceIncluded("src/main/resources/application.yml")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isResourceExcludedWhenPatternMatchesWithWildcard() {
|
||||
LocationFilter filter = new LocationFilter(Collections.emptyList(), Collections.singletonList("META-INF/**"));
|
||||
assertThat(filter.isResourceExcluded("META-INF/resources/application.yml")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isResourceExcludedWhenPatternDoesNotMatch() {
|
||||
LocationFilter filter = new LocationFilter(Collections.emptyList(), Collections.singletonList("META-INF/**"));
|
||||
assertThat(filter.isResourceExcluded("src/main/resources/application.yml")).isFalse();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.library;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link CoordinateFilter}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class CoordinateFilterTests {
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenGroupIdIsNullAndToMatchHasWildcard() {
|
||||
List<String> includes = Collections.singletonList("*:*");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates(null, null, null));
|
||||
assertThat(filter.isLibraryIncluded(library)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenArtifactIdIsNullAndToMatchHasWildcard() {
|
||||
List<String> includes = Collections.singletonList("org.acme:*");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates("org.acme", null, null));
|
||||
assertThat(filter.isLibraryIncluded(library)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenVersionIsNullAndToMatchHasWildcard() {
|
||||
List<String> includes = Collections.singletonList("org.acme:something:*");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates("org.acme", "something", null));
|
||||
assertThat(filter.isLibraryIncluded(library)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenGroupIdDoesNotMatch() {
|
||||
List<String> includes = Collections.singletonList("org.acme:*");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates("other.foo", null, null));
|
||||
assertThat(filter.isLibraryIncluded(library)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenArtifactIdDoesNotMatch() {
|
||||
List<String> includes = Collections.singletonList("org.acme:test:*");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates("org.acme", "other", null));
|
||||
assertThat(filter.isLibraryIncluded(library)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenArtifactIdMatches() {
|
||||
List<String> includes = Collections.singletonList("org.acme:test:*");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates("org.acme", "test", null));
|
||||
assertThat(filter.isLibraryIncluded(library)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenVersionDoesNotMatch() {
|
||||
List<String> includes = Collections.singletonList("org.acme:test:*SNAPSHOT");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates("org.acme", "test", "1.0.0"));
|
||||
assertThat(filter.isLibraryIncluded(library)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLibraryIncludedWhenVersionMatches() {
|
||||
List<String> includes = Collections.singletonList("org.acme:test:*SNAPSHOT");
|
||||
CoordinateFilter filter = new CoordinateFilter(includes, Collections.emptyList());
|
||||
Library library = mock(Library.class);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates("org.acme", "test", "1.0.0-SNAPSHOT"));
|
||||
assertThat(filter.isLibraryIncluded(library)).isTrue();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.tools.layer.library;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryScope;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link FilteredLibraryStrategy}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class FilteredLibraryStrategyTests {
|
||||
|
||||
@Test
|
||||
void createWhenFiltersNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new FilteredLibraryStrategy("custom", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenFiltersEmptyShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new FilteredLibraryStrategy("custom", Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLayerShouldReturnLayerName() {
|
||||
FilteredLibraryStrategy strategy = new FilteredLibraryStrategy("custom",
|
||||
Collections.singletonList(new TestFilter1Library()));
|
||||
assertThat(strategy.getLayer().toString()).isEqualTo("custom");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchingLayerWhenFilterMatchesIncludes() {
|
||||
FilteredLibraryStrategy strategy = new FilteredLibraryStrategy("custom",
|
||||
Collections.singletonList(new TestFilter1Library()));
|
||||
Library library = mockLibrary("A-Compile", LibraryScope.COMPILE);
|
||||
assertThat(strategy.getMatchingLayer(library).toString()).isEqualTo("custom");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenFilterMatchesIncludesAndExcludesFromSameFilter() {
|
||||
FilteredLibraryStrategy strategy = new FilteredLibraryStrategy("custom",
|
||||
Collections.singletonList(new TestFilter1Library()));
|
||||
Library library = mockLibrary("A-Runtime", LibraryScope.RUNTIME);
|
||||
assertThat(strategy.getMatchingLayer(library)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenFilterMatchesIncludesAndExcludesFromAnotherFilter() {
|
||||
List<LibraryFilter> filters = new ArrayList<>();
|
||||
filters.add(new TestFilter1Library());
|
||||
filters.add(new TestFilter2Library());
|
||||
FilteredLibraryStrategy strategy = new FilteredLibraryStrategy("custom", filters);
|
||||
Library library = mockLibrary("A-Provided", LibraryScope.PROVIDED);
|
||||
assertThat(strategy.getMatchingLayer(library)).isNull();
|
||||
}
|
||||
|
||||
private Library mockLibrary(String name, LibraryScope runtime) {
|
||||
Library library = mock(Library.class);
|
||||
given(library.getName()).willReturn(name);
|
||||
given(library.getScope()).willReturn(runtime);
|
||||
return library;
|
||||
}
|
||||
|
||||
private static class TestFilter1Library implements LibraryFilter {
|
||||
|
||||
@Override
|
||||
public boolean isLibraryIncluded(Library library) {
|
||||
return library.getName().contains("A");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLibraryExcluded(Library library) {
|
||||
return library.getScope().equals(LibraryScope.RUNTIME);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestFilter2Library implements LibraryFilter {
|
||||
|
||||
@Override
|
||||
public boolean isLibraryIncluded(Library library) {
|
||||
return library.getName().contains("B");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLibraryExcluded(Library library) {
|
||||
return library.getScope().equals(LibraryScope.PROVIDED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import org.junit.jupiter.api.TestTemplate;
|
|||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.loader.tools.FileUtils;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -296,8 +297,7 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
|
|||
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/layers/application/classes/")
|
||||
.hasEntryWithNameStartingWith("BOOT-INF/layers/dependencies/lib/jar-release")
|
||||
.hasEntryWithNameStartingWith("BOOT-INF/layers/snapshot-dependencies/lib/jar-snapshot")
|
||||
.hasEntryWithNameStartingWith(
|
||||
"BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
|
||||
.hasEntryWithNameStartingWith(jarModeLayerTools());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -308,8 +308,7 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
|
|||
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/layers/application/classes/")
|
||||
.hasEntryWithNameStartingWith("BOOT-INF/layers/dependencies/lib/jar-release")
|
||||
.hasEntryWithNameStartingWith("BOOT-INF/layers/snapshot-dependencies/lib/jar-snapshot")
|
||||
.doesNotHaveEntryWithNameStartingWith(
|
||||
"BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
|
||||
.doesNotHaveEntryWithNameStartingWith(jarModeLayerTools());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -333,6 +332,13 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
|
|||
assertThat(firstHash).isEqualTo(secondHash);
|
||||
}
|
||||
|
||||
private String jarModeLayerTools() {
|
||||
JarModeLibrary library = JarModeLibrary.LAYER_TOOLS;
|
||||
String version = library.getCoordinates().getVersion();
|
||||
String layer = (version == null || !version.contains("SNAPSHOT")) ? "dependencies" : "snapshot-dependencies";
|
||||
return "BOOT-INF/layers/" + layer + "/lib/" + library.getName();
|
||||
}
|
||||
|
||||
private String buildJarWithOutputTimestamp(MavenBuild mavenBuild) {
|
||||
AtomicReference<String> jarHash = new AtomicReference<>();
|
||||
mavenBuild.project("jar-output-timestamp").execute((project) -> {
|
||||
|
|
|
@ -1,35 +1,23 @@
|
|||
<layers-configuration xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/layers/layers-configuration-2.3.xsd">
|
||||
<layers>
|
||||
<layers xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/layers/layers-2.3.xsd">
|
||||
<application>
|
||||
<into layer="configuration">
|
||||
<include>**/application*.*</include>
|
||||
</into>
|
||||
<into layer="application" />
|
||||
</application>
|
||||
<dependencies>
|
||||
<into layer="snapshot-dependencies">
|
||||
<include>*:*:*-SNAPSHOT</include>
|
||||
</into>
|
||||
<into layer="my-dependencies-name" />
|
||||
</dependencies>
|
||||
<layerOrder>
|
||||
<layer>configuration</layer>
|
||||
<layer>application</layer>
|
||||
<layer>snapshot-dependencies</layer>
|
||||
<layer>my-dependencies-name</layer>
|
||||
</layers>
|
||||
<libraries>
|
||||
<layer-content layer="snapshot-dependencies">
|
||||
<coordinates>
|
||||
<include>*:*:*-SNAPSHOT</include>
|
||||
</coordinates>
|
||||
</layer-content>
|
||||
<layer-content layer="my-dependencies-name">
|
||||
<coordinates>
|
||||
<include>*:*:*</include>
|
||||
</coordinates>
|
||||
</layer-content>
|
||||
</libraries>
|
||||
<application>
|
||||
<layer-content layer="configuration">
|
||||
<locations>
|
||||
<include>**/application*.*</include>
|
||||
</locations>
|
||||
</layer-content>
|
||||
<layer-content layer="application">
|
||||
<locations>
|
||||
<include>**</include>
|
||||
</locations>
|
||||
</layer-content>
|
||||
</application>
|
||||
</layers-configuration>
|
||||
</layerOrder>
|
||||
</layers>
|
|
@ -48,6 +48,7 @@ import org.springframework.boot.loader.tools.Layouts.None;
|
|||
import org.springframework.boot.loader.tools.Layouts.War;
|
||||
import org.springframework.boot.loader.tools.Libraries;
|
||||
import org.springframework.boot.loader.tools.Packager;
|
||||
import org.springframework.boot.loader.tools.layer.CustomLayers;
|
||||
|
||||
/**
|
||||
* Abstract base class for classes that work with an {@link Packager}.
|
||||
|
@ -135,15 +136,7 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
|
|||
}
|
||||
if (this.layers != null && this.layers.isEnabled()) {
|
||||
if (this.layers.getConfiguration() != null) {
|
||||
try {
|
||||
Document document = getDocumentIfAvailable(this.layers.getConfiguration());
|
||||
CustomLayersProvider customLayersProvider = new CustomLayersProvider();
|
||||
packager.setLayers(customLayersProvider.getLayers(document));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to process custom layers configuration "
|
||||
+ this.layers.getConfiguration().getAbsolutePath(), ex);
|
||||
}
|
||||
packager.setLayers(getCustomLayers(this.layers.getConfiguration()));
|
||||
}
|
||||
packager.setLayout(new LayeredJar());
|
||||
packager.setIncludeRelevantJarModeJars(this.layers.isIncludeLayerTools());
|
||||
|
@ -151,8 +144,19 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
|
|||
return packager;
|
||||
}
|
||||
|
||||
private Document getDocumentIfAvailable(File configurationFile) throws Exception {
|
||||
InputSource inputSource = new InputSource(new FileInputStream(configurationFile));
|
||||
private CustomLayers getCustomLayers(File configuration) {
|
||||
try {
|
||||
Document document = getDocumentIfAvailable(configuration);
|
||||
return new CustomLayersProvider().getLayers(document);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to process custom layers configuration " + configuration.getAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Document getDocumentIfAvailable(File xmlFile) throws Exception {
|
||||
InputSource inputSource = new InputSource(new FileInputStream(xmlFile));
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
return builder.parse(inputSource);
|
||||
|
|
|
@ -80,8 +80,7 @@ public class ArtifactsLibraries implements Libraries {
|
|||
name = artifact.getGroupId() + "-" + name;
|
||||
this.log.debug("Renamed to: " + name);
|
||||
}
|
||||
LibraryCoordinates coordinates = new LibraryCoordinates(artifact.getGroupId(), artifact.getArtifactId(),
|
||||
artifact.getVersion());
|
||||
LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact);
|
||||
callback.library(new Library(name, artifact.getFile(), scope, coordinates, isUnpackRequired(artifact)));
|
||||
}
|
||||
}
|
||||
|
@ -122,4 +121,37 @@ public class ArtifactsLibraries implements Libraries {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link LibraryCoordinates} backed by a Maven {@link Artifact}.
|
||||
*/
|
||||
private static class ArtifactLibraryCoordinates implements LibraryCoordinates {
|
||||
|
||||
private final Artifact artifact;
|
||||
|
||||
ArtifactLibraryCoordinates(Artifact artifact) {
|
||||
this.artifact = artifact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupId() {
|
||||
return this.artifact.getGroupId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return this.artifact.getArtifactId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return this.artifact.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.artifact.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
package org.springframework.boot.maven;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
@ -26,113 +28,76 @@ import org.w3c.dom.Node;
|
|||
import org.w3c.dom.NodeList;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.layer.ApplicationContentFilter;
|
||||
import org.springframework.boot.loader.tools.layer.ContentFilter;
|
||||
import org.springframework.boot.loader.tools.layer.ContentSelector;
|
||||
import org.springframework.boot.loader.tools.layer.CustomLayers;
|
||||
import org.springframework.boot.loader.tools.layer.application.FilteredResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.application.LocationFilter;
|
||||
import org.springframework.boot.loader.tools.layer.application.ResourceFilter;
|
||||
import org.springframework.boot.loader.tools.layer.application.ResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.library.LibraryFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.boot.loader.tools.layer.IncludeExcludeContentSelector;
|
||||
import org.springframework.boot.loader.tools.layer.LibraryContentFilter;
|
||||
|
||||
/**
|
||||
* Produces a {@link CustomLayers} based on the given {@link Document}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.3.0
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class CustomLayersProvider {
|
||||
class CustomLayersProvider {
|
||||
|
||||
public CustomLayers getLayers(Document document) {
|
||||
CustomLayers getLayers(Document document) {
|
||||
Element root = document.getDocumentElement();
|
||||
NodeList nodes = root.getChildNodes();
|
||||
List<Layer> layers = new ArrayList<>();
|
||||
List<LibraryStrategy> libraryStrategies = new ArrayList<>();
|
||||
List<ResourceStrategy> resourceStrategies = new ArrayList<>();
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node node = nodes.item(i);
|
||||
if (node instanceof Element) {
|
||||
processNode(layers, libraryStrategies, resourceStrategies, (Element) node);
|
||||
List<ContentSelector<String>> applicationSelectors = getApplicationSelectors(root);
|
||||
List<ContentSelector<Library>> librarySelectors = getLibrarySelectors(root);
|
||||
List<Layer> layers = getLayers(root);
|
||||
return new CustomLayers(layers, applicationSelectors, librarySelectors);
|
||||
}
|
||||
|
||||
private List<ContentSelector<String>> getApplicationSelectors(Element root) {
|
||||
return getSelectors(root, "application", ApplicationContentFilter::new);
|
||||
}
|
||||
|
||||
private List<ContentSelector<Library>> getLibrarySelectors(Element root) {
|
||||
return getSelectors(root, "dependencies", LibraryContentFilter::new);
|
||||
}
|
||||
|
||||
private List<Layer> getLayers(Element root) {
|
||||
Element layerOrder = getChildElement(root, "layerOrder");
|
||||
if (layerOrder == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return getChildNodeTextContent(layerOrder, "layer").stream().map(Layer::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T> List<ContentSelector<T>> getSelectors(Element root, String elementName,
|
||||
Function<String, ContentFilter<T>> filterFactory) {
|
||||
Element element = getChildElement(root, elementName);
|
||||
if (element == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ContentSelector<T>> selectors = new ArrayList<>();
|
||||
NodeList children = element.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node child = children.item(i);
|
||||
if (child instanceof Element) {
|
||||
ContentSelector<T> selector = getSelector((Element) child, filterFactory);
|
||||
selectors.add(selector);
|
||||
}
|
||||
}
|
||||
return new CustomLayers(layers, resourceStrategies, libraryStrategies);
|
||||
return selectors;
|
||||
}
|
||||
|
||||
private void processNode(List<Layer> layers, List<LibraryStrategy> libraryStrategies,
|
||||
List<ResourceStrategy> resourceStrategies, Element node) {
|
||||
String nodeName = node.getNodeName();
|
||||
if ("layers".equals(nodeName)) {
|
||||
layers.addAll(getLayers(node));
|
||||
}
|
||||
NodeList contents = node.getChildNodes();
|
||||
if ("libraries".equals(nodeName)) {
|
||||
libraryStrategies.addAll(getStrategies(contents,
|
||||
(StrategyFactory<LibraryFilter, LibraryStrategy>) FilteredLibraryStrategy::new,
|
||||
CoordinateFilter::new, "coordinates"::equals));
|
||||
}
|
||||
if ("application".equals(nodeName)) {
|
||||
resourceStrategies.addAll(getStrategies(contents,
|
||||
(StrategyFactory<ResourceFilter, ResourceStrategy>) FilteredResourceStrategy::new,
|
||||
LocationFilter::new, "locations"::equals));
|
||||
}
|
||||
private <T> ContentSelector<T> getSelector(Element element, Function<String, ContentFilter<T>> filterFactory) {
|
||||
Layer layer = new Layer(element.getAttribute("layer"));
|
||||
List<String> includes = getChildNodeTextContent(element, "include");
|
||||
List<String> excludes = getChildNodeTextContent(element, "exclude");
|
||||
return new IncludeExcludeContentSelector<>(layer, includes, excludes, filterFactory);
|
||||
}
|
||||
|
||||
private List<Layer> getLayers(Element element) {
|
||||
List<Layer> layers = new ArrayList<>();
|
||||
NodeList childNodes = element.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
Node childNode = childNodes.item(i);
|
||||
if (childNode instanceof Element) {
|
||||
Element childElement = (Element) childNode;
|
||||
if ("layer".equals(childElement.getNodeName())) {
|
||||
layers.add(new Layer(childElement.getTextContent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
private <T, E> List<T> getStrategies(NodeList nodes, StrategyFactory<E, T> strategyFactory,
|
||||
FilterFactory<E> filterFactory, Predicate<String> filterPredicate) {
|
||||
List<T> contents = new ArrayList<>();
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node node = nodes.item(i);
|
||||
if (node instanceof Element) {
|
||||
Element element = (Element) node;
|
||||
if ("layer-content".equals(element.getTagName())) {
|
||||
List<E> filters = getFilters(node, filterFactory, filterPredicate);
|
||||
String layer = element.getAttribute("layer");
|
||||
contents.add(strategyFactory.getStrategy(layer, filters));
|
||||
}
|
||||
}
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
private <E> List<E> getFilters(Node node, FilterFactory<E> factory, Predicate<String> predicate) {
|
||||
NodeList childNodes = node.getChildNodes();
|
||||
Assert.state(childNodes.getLength() > 0, "Filters for layer-content must not be empty.");
|
||||
List<E> filters = new ArrayList<>();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
Node childNode = childNodes.item(i);
|
||||
if (childNode instanceof Element) {
|
||||
List<String> include = getPatterns((Element) childNode, "include");
|
||||
List<String> exclude = getPatterns((Element) childNode, "exclude");
|
||||
if (predicate.test(childNode.getNodeName())) {
|
||||
filters.add(factory.getFilter(include, exclude));
|
||||
}
|
||||
}
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
private List<String> getPatterns(Element element, String key) {
|
||||
private List<String> getChildNodeTextContent(Element element, String tagName) {
|
||||
List<String> patterns = new ArrayList<>();
|
||||
NodeList nodes = element.getElementsByTagName(key);
|
||||
for (int j = 0; j < nodes.getLength(); j++) {
|
||||
Node node = nodes.item(j);
|
||||
NodeList nodes = element.getElementsByTagName(tagName);
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node node = nodes.item(i);
|
||||
if (node instanceof Element) {
|
||||
patterns.add(node.getTextContent());
|
||||
}
|
||||
|
@ -140,16 +105,15 @@ public class CustomLayersProvider {
|
|||
return patterns;
|
||||
}
|
||||
|
||||
interface StrategyFactory<E, T> {
|
||||
|
||||
T getStrategy(String layer, List<E> filters);
|
||||
|
||||
}
|
||||
|
||||
interface FilterFactory<E> {
|
||||
|
||||
E getFilter(List<String> includes, List<String> excludes);
|
||||
|
||||
private Element getChildElement(Element element, String tagName) {
|
||||
NodeList nodes = element.getElementsByTagName(tagName);
|
||||
if (nodes.getLength() == 0) {
|
||||
return null;
|
||||
}
|
||||
if (nodes.getLength() > 1) {
|
||||
throw new IllegalStateException("Multiple '" + tagName + "' nodes found");
|
||||
}
|
||||
return (Element) nodes.item(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xsd:schema elementFormDefault="qualified"
|
||||
xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://www.springframework.org/schema/boot/layers">
|
||||
<xsd:element name="layers" type="layersType" />
|
||||
<xsd:complexType name="layersType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="application" type="applicationType" />
|
||||
<xsd:element name="dependencies" type="dependenciesType" />
|
||||
<xsd:element name="layerOrder" type="layerOrderType" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="applicationType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The 'into layer' selections that should be applied to application classes and resources.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence maxOccurs="unbounded">
|
||||
<xsd:element name="into" type="intoType" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="dependenciesType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The 'into layer' selections that should be applied to dependencies.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence maxOccurs="unbounded">
|
||||
<xsd:element name="into" type="intoType" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="layerOrderType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The order that layers should be added (starting with the least frequently changed layer).
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="layer" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The layer name.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:minLength value="1" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="intoType">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element type="xsd:string" name="include"
|
||||
minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Pattern of the elements to include.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element type="xsd:string" name="exclude"
|
||||
minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Pattern of the elements to exclude.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
<xsd:attribute type="xsd:string" name="layer"
|
||||
use="required" />
|
||||
</xsd:complexType>
|
||||
|
||||
</xsd:schema>
|
|
@ -1,96 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xsd:schema elementFormDefault="qualified"
|
||||
xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://www.springframework.org/schema/boot/layers">
|
||||
<xsd:element name="layers-configuration" type="layersConfigurationType"/>
|
||||
<xsd:complexType name="layersConfigurationType">
|
||||
<xsd:sequence>
|
||||
<xsd:element type="layersType" name="layers"/>
|
||||
<xsd:element type="librariesType" name="libraries" minOccurs="0"/>
|
||||
<xsd:element type="applicationType" name="application" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="layersType">
|
||||
<xsd:sequence>
|
||||
<xsd:element type="xsd:string" name="layer" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The name of a layer. Each layer in the configuration must be referenced once and the
|
||||
order matches how the content is likely to change. Put layers that are frequently
|
||||
updated first, layers that are more stable (such as non-snapshot dependencies) last.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="librariesType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Strategies that should be applied to libraries. If no strategies are defined, two
|
||||
layers are created out-of-the-box. A "snapshot-dependencies" layer with SNAPSHOT
|
||||
libraries and a "dependencies" layer with all the other libraries.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element type="librariesLayerContentType" name="layer-content">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Strategy to apply on libraries.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="librariesLayerContentType" mixed="true">
|
||||
<xsd:sequence>
|
||||
<xsd:element type="filterType" name="coordinates" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute type="xsd:string" name="layer" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="applicationType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Strategies that should be applied to application classes and resources. If no strategies are defined, a single
|
||||
"application" layer is created out-of-the-box.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element type="applicationLayerContentType" name="layer-content" maxOccurs="unbounded"
|
||||
minOccurs="0">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Strategy to apply on application classes and resources.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="applicationLayerContentType" mixed="true">
|
||||
<xsd:sequence>
|
||||
<xsd:element type="filterType" name="locations" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute type="xsd:string" name="layer" use="required"/>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="filterType">
|
||||
<xsd:sequence>
|
||||
<xsd:element type="xsd:string" name="exclude" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Pattern of the elements to exclude. An exclude pattern takes precedence over an
|
||||
include pattern and must be declared first.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element type="xsd:string" name="include" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Pattern of the elements to include.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
</xsd:schema>
|
|
@ -52,8 +52,8 @@ public class CustomLayersProviderTests {
|
|||
@Test
|
||||
void getLayerResolverWhenDocumentValid() throws Exception {
|
||||
CustomLayers layers = this.customLayersProvider.getLayers(getDocument("layers.xml"));
|
||||
assertThat(layers).extracting("name").containsExactly("configuration", "application", "my-resources",
|
||||
"snapshot-dependencies", "my-deps", "my-dependencies-name");
|
||||
assertThat(layers).extracting("name").containsExactly("my-deps", "my-dependencies-name",
|
||||
"snapshot-dependencies", "my-resources", "configuration", "application");
|
||||
Library snapshot = mockLibrary("test-SNAPSHOT.jar", "org.foo", "1.0.0-SNAPSHOT");
|
||||
Library groupId = mockLibrary("my-library", "com.acme", null);
|
||||
Library otherDependency = mockLibrary("other-library", "org.foo", null);
|
||||
|
@ -68,22 +68,26 @@ public class CustomLayersProviderTests {
|
|||
private Library mockLibrary(String name, String groupId, String version) {
|
||||
Library library = mock(Library.class);
|
||||
given(library.getName()).willReturn(name);
|
||||
given(library.getCoordinates()).willReturn(new LibraryCoordinates(groupId, null, version));
|
||||
given(library.getCoordinates()).willReturn(LibraryCoordinates.of(groupId, null, version));
|
||||
return library;
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLayerResolverWhenDocumentContainsLibraryLayerWithNoFilters() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("library-layer-no-filter.xml")))
|
||||
.withMessage("Filters for layer-content must not be empty.");
|
||||
void getLayerResolverWhenDocumentContainsLibraryLayerWithNoFilters() throws Exception {
|
||||
CustomLayers layers = this.customLayersProvider.getLayers(getDocument("dependencies-layer-no-filter.xml"));
|
||||
Library library = mockLibrary("my-library", "com.acme", null);
|
||||
assertThat(layers.getLayer(library).toString()).isEqualTo("my-deps");
|
||||
assertThatIllegalStateException().isThrownBy(() -> layers.getLayer("application.yml"))
|
||||
.withMessageContaining("match any layer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLayerResolverWhenDocumentContainsResourceLayerWithNoFilters() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("resource-layer-no-filter.xml")))
|
||||
.withMessage("Filters for layer-content must not be empty.");
|
||||
void getLayerResolverWhenDocumentContainsResourceLayerWithNoFilters() throws Exception {
|
||||
CustomLayers layers = this.customLayersProvider.getLayers(getDocument("application-layer-no-filter.xml"));
|
||||
Library library = mockLibrary("my-library", "com.acme", null);
|
||||
assertThat(layers.getLayer("application.yml").toString()).isEqualTo("my-layer");
|
||||
assertThatIllegalStateException().isThrownBy(() -> layers.getLayer(library))
|
||||
.withMessageContaining("match any layer");
|
||||
}
|
||||
|
||||
private Document getDocument(String resourceName) throws Exception {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<layers xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers.xsd">
|
||||
<application>
|
||||
<into layer="my-layer" />
|
||||
</application>
|
||||
<layerOrder>
|
||||
<layer>my-layer</layer>
|
||||
</layerOrder>
|
||||
</layers>
|
|
@ -0,0 +1,11 @@
|
|||
<layers xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
|
||||
<dependencies>
|
||||
<into layer="my-deps" />
|
||||
</dependencies>
|
||||
<layerOrder>
|
||||
<layer>my-deps</layer>
|
||||
</layerOrder>
|
||||
</layers>
|
|
@ -1,47 +1,32 @@
|
|||
<layers-configuration xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers-configuration-2.3.xsd">
|
||||
<layers>
|
||||
<layer>configuration</layer>
|
||||
<layer>application</layer>
|
||||
<layer>my-resources</layer>
|
||||
<layer>snapshot-dependencies</layer>
|
||||
<layers xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
|
||||
<application>
|
||||
<into layer="my-resources">
|
||||
<include>META-INF/resources/**</include>
|
||||
<exclude>*.properties</exclude>
|
||||
</into>
|
||||
<into layer="configuration">
|
||||
<include>**/application*.*</include>
|
||||
</into>
|
||||
<into layer="application" />
|
||||
</application>
|
||||
<dependencies>
|
||||
<into layer="snapshot-dependencies">
|
||||
<include>*:*:*-SNAPSHOT</include>
|
||||
</into>
|
||||
<into layer="my-deps">
|
||||
<include>com.acme:*</include>
|
||||
</into>
|
||||
<into layer="my-dependencies-name"/>
|
||||
</dependencies>
|
||||
<layerOrder>
|
||||
<layer>my-deps</layer>
|
||||
<layer>my-dependencies-name</layer>
|
||||
</layers>
|
||||
<libraries>
|
||||
<layer-content layer="snapshot-dependencies">
|
||||
<coordinates>
|
||||
<include>*:*:*-SNAPSHOT</include>
|
||||
</coordinates>
|
||||
</layer-content>
|
||||
<layer-content layer="my-deps">
|
||||
<coordinates>
|
||||
<include>com.acme:*</include>
|
||||
</coordinates>
|
||||
</layer-content>
|
||||
<layer-content layer="my-dependencies-name">
|
||||
<coordinates>
|
||||
<include>*:*:*</include>
|
||||
</coordinates>
|
||||
</layer-content>
|
||||
</libraries>
|
||||
<application>
|
||||
<layer-content layer="my-resources">
|
||||
<locations>
|
||||
<include>META-INF/resources/**</include>
|
||||
</locations>
|
||||
</layer-content>
|
||||
<layer-content layer="configuration">
|
||||
<locations>
|
||||
<include>**/application*.*</include>
|
||||
</locations>
|
||||
</layer-content>
|
||||
<layer-content layer="application">
|
||||
<locations>
|
||||
<include>**</include>
|
||||
</locations>
|
||||
</layer-content>
|
||||
</application>
|
||||
</layers-configuration>
|
||||
<layer>snapshot-dependencies</layer>
|
||||
<layer>my-resources</layer>
|
||||
<layer>configuration</layer>
|
||||
<layer>application</layer>
|
||||
</layerOrder>
|
||||
</layers>
|
|
@ -1,11 +0,0 @@
|
|||
<layers-configuration xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers-configuration-2.3.xsd">
|
||||
<layers>
|
||||
<layer>my-deps</layer>
|
||||
</layers>
|
||||
<libraries>
|
||||
<layer-content layer="my-deps"/>
|
||||
</libraries>
|
||||
</layers-configuration>
|
|
@ -1,11 +1,11 @@
|
|||
<layers-configuration xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers-configuration-2.3.xsd">
|
||||
<layers>
|
||||
<layer>my-layer</layer>
|
||||
</layers>
|
||||
<layers xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
|
||||
<application>
|
||||
<layer-content layer="my-layer"/>
|
||||
<into layer="my-layer" />
|
||||
</application>
|
||||
</layers-configuration>
|
||||
<layerOrder>
|
||||
<layer>my-layer</layer>
|
||||
</layerOrder>
|
||||
</layers>
|
Loading…
Reference in New Issue