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:
Phillip Webb
2020-03-24 17:00:39 -07:00
parent 14718f3e8a
commit 7bc7d86ad4
61 changed files with 2047 additions and 2054 deletions

View File

@@ -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) -> {

View File

@@ -1,35 +1,23 @@
<layers-configuration xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/layers/layers-configuration-2.3.xsd">
<layers>
<layers xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/layers/layers-2.3.xsd">
<application>
<into layer="configuration">
<include>**/application*.*</include>
</into>
<into layer="application" />
</application>
<dependencies>
<into layer="snapshot-dependencies">
<include>*:*:*-SNAPSHOT</include>
</into>
<into layer="my-dependencies-name" />
</dependencies>
<layerOrder>
<layer>configuration</layer>
<layer>application</layer>
<layer>snapshot-dependencies</layer>
<layer>my-dependencies-name</layer>
</layers>
<libraries>
<layer-content layer="snapshot-dependencies">
<coordinates>
<include>*:*:*-SNAPSHOT</include>
</coordinates>
</layer-content>
<layer-content layer="my-dependencies-name">
<coordinates>
<include>*:*:*</include>
</coordinates>
</layer-content>
</libraries>
<application>
<layer-content layer="configuration">
<locations>
<include>**/application*.*</include>
</locations>
</layer-content>
<layer-content layer="application">
<locations>
<include>**</include>
</locations>
</layer-content>
</application>
</layers-configuration>
</layerOrder>
</layers>

View File

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

View File

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

View File

@@ -17,8 +17,10 @@
package org.springframework.boot.maven;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -26,113 +28,76 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.layer.ApplicationContentFilter;
import org.springframework.boot.loader.tools.layer.ContentFilter;
import org.springframework.boot.loader.tools.layer.ContentSelector;
import org.springframework.boot.loader.tools.layer.CustomLayers;
import org.springframework.boot.loader.tools.layer.application.FilteredResourceStrategy;
import org.springframework.boot.loader.tools.layer.application.LocationFilter;
import org.springframework.boot.loader.tools.layer.application.ResourceFilter;
import org.springframework.boot.loader.tools.layer.application.ResourceStrategy;
import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
import org.springframework.boot.loader.tools.layer.library.LibraryFilter;
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
import org.springframework.util.Assert;
import org.springframework.boot.loader.tools.layer.IncludeExcludeContentSelector;
import org.springframework.boot.loader.tools.layer.LibraryContentFilter;
/**
* Produces a {@link CustomLayers} based on the given {@link Document}.
*
* @author Madhura Bhave
* @since 2.3.0
* @author Phillip Webb
*/
public class CustomLayersProvider {
class CustomLayersProvider {
public CustomLayers getLayers(Document document) {
CustomLayers getLayers(Document document) {
Element root = document.getDocumentElement();
NodeList nodes = root.getChildNodes();
List<Layer> layers = new ArrayList<>();
List<LibraryStrategy> libraryStrategies = new ArrayList<>();
List<ResourceStrategy> resourceStrategies = new ArrayList<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
processNode(layers, libraryStrategies, resourceStrategies, (Element) node);
List<ContentSelector<String>> applicationSelectors = getApplicationSelectors(root);
List<ContentSelector<Library>> librarySelectors = getLibrarySelectors(root);
List<Layer> layers = getLayers(root);
return new CustomLayers(layers, applicationSelectors, librarySelectors);
}
private List<ContentSelector<String>> getApplicationSelectors(Element root) {
return getSelectors(root, "application", ApplicationContentFilter::new);
}
private List<ContentSelector<Library>> getLibrarySelectors(Element root) {
return getSelectors(root, "dependencies", LibraryContentFilter::new);
}
private List<Layer> getLayers(Element root) {
Element layerOrder = getChildElement(root, "layerOrder");
if (layerOrder == null) {
return Collections.emptyList();
}
return getChildNodeTextContent(layerOrder, "layer").stream().map(Layer::new).collect(Collectors.toList());
}
private <T> List<ContentSelector<T>> getSelectors(Element root, String elementName,
Function<String, ContentFilter<T>> filterFactory) {
Element element = getChildElement(root, elementName);
if (element == null) {
return Collections.emptyList();
}
List<ContentSelector<T>> selectors = new ArrayList<>();
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof Element) {
ContentSelector<T> selector = getSelector((Element) child, filterFactory);
selectors.add(selector);
}
}
return new CustomLayers(layers, resourceStrategies, libraryStrategies);
return selectors;
}
private void processNode(List<Layer> layers, List<LibraryStrategy> libraryStrategies,
List<ResourceStrategy> resourceStrategies, Element node) {
String nodeName = node.getNodeName();
if ("layers".equals(nodeName)) {
layers.addAll(getLayers(node));
}
NodeList contents = node.getChildNodes();
if ("libraries".equals(nodeName)) {
libraryStrategies.addAll(getStrategies(contents,
(StrategyFactory<LibraryFilter, LibraryStrategy>) FilteredLibraryStrategy::new,
CoordinateFilter::new, "coordinates"::equals));
}
if ("application".equals(nodeName)) {
resourceStrategies.addAll(getStrategies(contents,
(StrategyFactory<ResourceFilter, ResourceStrategy>) FilteredResourceStrategy::new,
LocationFilter::new, "locations"::equals));
}
private <T> ContentSelector<T> getSelector(Element element, Function<String, ContentFilter<T>> filterFactory) {
Layer layer = new Layer(element.getAttribute("layer"));
List<String> includes = getChildNodeTextContent(element, "include");
List<String> excludes = getChildNodeTextContent(element, "exclude");
return new IncludeExcludeContentSelector<>(layer, includes, excludes, filterFactory);
}
private List<Layer> getLayers(Element element) {
List<Layer> layers = new ArrayList<>();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode instanceof Element) {
Element childElement = (Element) childNode;
if ("layer".equals(childElement.getNodeName())) {
layers.add(new Layer(childElement.getTextContent()));
}
}
}
return layers;
}
private <T, E> List<T> getStrategies(NodeList nodes, StrategyFactory<E, T> strategyFactory,
FilterFactory<E> filterFactory, Predicate<String> filterPredicate) {
List<T> contents = new ArrayList<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
Element element = (Element) node;
if ("layer-content".equals(element.getTagName())) {
List<E> filters = getFilters(node, filterFactory, filterPredicate);
String layer = element.getAttribute("layer");
contents.add(strategyFactory.getStrategy(layer, filters));
}
}
}
return contents;
}
private <E> List<E> getFilters(Node node, FilterFactory<E> factory, Predicate<String> predicate) {
NodeList childNodes = node.getChildNodes();
Assert.state(childNodes.getLength() > 0, "Filters for layer-content must not be empty.");
List<E> filters = new ArrayList<>();
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode instanceof Element) {
List<String> include = getPatterns((Element) childNode, "include");
List<String> exclude = getPatterns((Element) childNode, "exclude");
if (predicate.test(childNode.getNodeName())) {
filters.add(factory.getFilter(include, exclude));
}
}
}
return filters;
}
private List<String> getPatterns(Element element, String key) {
private List<String> getChildNodeTextContent(Element element, String tagName) {
List<String> patterns = new ArrayList<>();
NodeList nodes = element.getElementsByTagName(key);
for (int j = 0; j < nodes.getLength(); j++) {
Node node = nodes.item(j);
NodeList nodes = element.getElementsByTagName(tagName);
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
patterns.add(node.getTextContent());
}
@@ -140,16 +105,15 @@ public class CustomLayersProvider {
return patterns;
}
interface StrategyFactory<E, T> {
T getStrategy(String layer, List<E> filters);
}
interface FilterFactory<E> {
E getFilter(List<String> includes, List<String> excludes);
private Element getChildElement(Element element, String tagName) {
NodeList nodes = element.getElementsByTagName(tagName);
if (nodes.getLength() == 0) {
return null;
}
if (nodes.getLength() > 1) {
throw new IllegalStateException("Multiple '" + tagName + "' nodes found");
}
return (Element) nodes.item(0);
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,47 +1,32 @@
<layers-configuration xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/boot/layers/layers-configuration-2.3.xsd">
<layers>
<layer>configuration</layer>
<layer>application</layer>
<layer>my-resources</layer>
<layer>snapshot-dependencies</layer>
<layers xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
<application>
<into layer="my-resources">
<include>META-INF/resources/**</include>
<exclude>*.properties</exclude>
</into>
<into layer="configuration">
<include>**/application*.*</include>
</into>
<into layer="application" />
</application>
<dependencies>
<into layer="snapshot-dependencies">
<include>*:*:*-SNAPSHOT</include>
</into>
<into layer="my-deps">
<include>com.acme:*</include>
</into>
<into layer="my-dependencies-name"/>
</dependencies>
<layerOrder>
<layer>my-deps</layer>
<layer>my-dependencies-name</layer>
</layers>
<libraries>
<layer-content layer="snapshot-dependencies">
<coordinates>
<include>*:*:*-SNAPSHOT</include>
</coordinates>
</layer-content>
<layer-content layer="my-deps">
<coordinates>
<include>com.acme:*</include>
</coordinates>
</layer-content>
<layer-content layer="my-dependencies-name">
<coordinates>
<include>*:*:*</include>
</coordinates>
</layer-content>
</libraries>
<application>
<layer-content layer="my-resources">
<locations>
<include>META-INF/resources/**</include>
</locations>
</layer-content>
<layer-content layer="configuration">
<locations>
<include>**/application*.*</include>
</locations>
</layer-content>
<layer-content layer="application">
<locations>
<include>**</include>
</locations>
</layer-content>
</application>
</layers-configuration>
<layer>snapshot-dependencies</layer>
<layer>my-resources</layer>
<layer>configuration</layer>
<layer>application</layer>
</layerOrder>
</layers>

View File

@@ -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>

View File

@@ -1,11 +1,11 @@
<layers-configuration xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/boot/layers/layers-configuration-2.3.xsd">
<layers>
<layer>my-layer</layer>
</layers>
<layers xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
<application>
<layer-content layer="my-layer"/>
<into layer="my-layer" />
</application>
</layers-configuration>
<layerOrder>
<layer>my-layer</layer>
</layerOrder>
</layers>