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 {
 | 
			
		||||
	layered {
 | 
			
		||||
		application {
 | 
			
		||||
			intoLayer("application")
 | 
			
		||||
		}
 | 
			
		||||
		dependencies {
 | 
			
		||||
			intoLayer("snapshot-dependencies") {
 | 
			
		||||
				include "*:*:*SNAPSHOT"
 | 
			
		||||
			}
 | 
			
		||||
			intoLayer("dependencies")
 | 
			
		||||
		}
 | 
			
		||||
			layerContent("dependencies") {
 | 
			
		||||
				coordinates {
 | 
			
		||||
					include "*:*"
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		application {
 | 
			
		||||
			layerContent("application") {
 | 
			
		||||
				locations {
 | 
			
		||||
					include "**"
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		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 {
 | 
			
		||||
	layered {
 | 
			
		||||
		application {
 | 
			
		||||
			intoLayer("application")
 | 
			
		||||
		}
 | 
			
		||||
		dependencies {
 | 
			
		||||
			intoLayer("snapshot-dependencies") {
 | 
			
		||||
				include("*:*:*SNAPSHOT")
 | 
			
		||||
			}
 | 
			
		||||
			intoLayer("dependencies") {
 | 
			
		||||
		}
 | 
			
		||||
			layerContent("dependencies") {
 | 
			
		||||
				coordinates {
 | 
			
		||||
					include("*:*")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		application {
 | 
			
		||||
			layerContent("application") {
 | 
			
		||||
				locations {
 | 
			
		||||
					include("**")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		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 createCopyAction(jar, null, false);
 | 
			
		||||
	}
 | 
			
		||||
		return new ReproducibleOrderingCopyAction(copyAction);
 | 
			
		||||
 | 
			
		||||
	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();
 | 
			
		||||
	public void layered(Closure<?> closure) {
 | 
			
		||||
		layered(ConfigureUtil.configureUsing(closure));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		return this.layerConfiguration;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED;
 | 
			
		||||
	}
 | 
			
		||||
		return 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);
 | 
			
		||||
			writeArchive(copyActions, output);
 | 
			
		||||
		}
 | 
			
		||||
				Processor processor = new Processor(zipOutputStream);
 | 
			
		||||
				stream.process(processor::process);
 | 
			
		||||
		finally {
 | 
			
		||||
			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(zipOutputStream);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		finally {
 | 
			
		||||
			closeQuietly(outputStream);
 | 
			
		||||
			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/**"
 | 
			
		||||
			intoLayer("static") {
 | 
			
		||||
				include "META-INF/resources/**", "resources/**", "static/**", "public/**"
 | 
			
		||||
			}
 | 
			
		||||
			intoLayer("app")
 | 
			
		||||
		}
 | 
			
		||||
			layerContent("app") {
 | 
			
		||||
				locations {
 | 
			
		||||
					include "**"
 | 
			
		||||
		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"
 | 
			
		||||
<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-configuration-2.3.xsd">
 | 
			
		||||
	<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>
 | 
			
		||||
	</layerOrder>
 | 
			
		||||
</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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return new CustomLayers(layers, resourceStrategies, libraryStrategies);
 | 
			
		||||
		List<ContentSelector<String>> applicationSelectors = getApplicationSelectors(root);
 | 
			
		||||
		List<ContentSelector<Library>> librarySelectors = getLibrarySelectors(root);
 | 
			
		||||
		List<Layer> layers = getLayers(root);
 | 
			
		||||
		return new CustomLayers(layers, applicationSelectors, librarySelectors);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 List<ContentSelector<String>> getApplicationSelectors(Element root) {
 | 
			
		||||
		return getSelectors(root, "application", ApplicationContentFilter::new);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 List<ContentSelector<Library>> getLibrarySelectors(Element root) {
 | 
			
		||||
		return getSelectors(root, "dependencies", LibraryContentFilter::new);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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));
 | 
			
		||||
	private List<Layer> getLayers(Element root) {
 | 
			
		||||
		Element layerOrder = getChildElement(root, "layerOrder");
 | 
			
		||||
		if (layerOrder == null) {
 | 
			
		||||
			return Collections.emptyList();
 | 
			
		||||
		}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return contents;
 | 
			
		||||
		return getChildNodeTextContent(layerOrder, "layer").stream().map(Layer::new).collect(Collectors.toList());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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));
 | 
			
		||||
	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 filters;
 | 
			
		||||
		return selectors;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<String> getPatterns(Element element, String key) {
 | 
			
		||||
	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<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);
 | 
			
		||||
 | 
			
		||||
	private Element getChildElement(Element element, String tagName) {
 | 
			
		||||
		NodeList nodes = element.getElementsByTagName(tagName);
 | 
			
		||||
		if (nodes.getLength() == 0) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	interface FilterFactory<E> {
 | 
			
		||||
 | 
			
		||||
		E getFilter(List<String> includes, List<String> excludes);
 | 
			
		||||
 | 
			
		||||
		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"
 | 
			
		||||
<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-configuration-2.3.xsd">
 | 
			
		||||
	<layers>
 | 
			
		||||
		<layer>configuration</layer>
 | 
			
		||||
		<layer>application</layer>
 | 
			
		||||
		<layer>my-resources</layer>
 | 
			
		||||
		<layer>snapshot-dependencies</layer>
 | 
			
		||||
					  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>
 | 
			
		||||
		<layer>snapshot-dependencies</layer>
 | 
			
		||||
		<layer>my-resources</layer>
 | 
			
		||||
		<layer>configuration</layer>
 | 
			
		||||
		<layer>application</layer>
 | 
			
		||||
	</layerOrder>	
 | 
			
		||||
</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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
<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-configuration-2.3.xsd">
 | 
			
		||||
	<layers>
 | 
			
		||||
		<layer>my-layer</layer>
 | 
			
		||||
	</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