Add support for customizing layers in Maven

This commit adds an additional 'layers/configuration' property that can
be used to refer to a separate layers configuration file. This separate
file defines:

* The layers and their order of precedence,
* How libraries are handled using filters that match against the
coordinates of each library, and
* How classes are handled using filters that match against the location
of the entry

An XSD to validate the XML configuration file is available.

Closes gh-20295

Co-authored-by: Stephane Nicoll <snicoll@pivotal.io>
This commit is contained in:
Madhura Bhave
2020-03-11 17:02:48 -07:00
committed by Stephane Nicoll
parent 896d2c8579
commit e49e62df5c
38 changed files with 2024 additions and 37 deletions

View File

@@ -16,12 +16,17 @@
package org.springframework.boot.maven;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
@@ -31,6 +36,8 @@ import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.LayoutFactory;
@@ -109,7 +116,7 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
* @since 2.3.0
*/
@Parameter
private Layered layered;
private Layers layers;
/**
* Return a {@link Packager} configured for this MOJO.
@@ -126,13 +133,31 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
getLog().info("Layout: " + this.layout);
packager.setLayout(this.layout.layout());
}
if (this.layered != null && this.layered.isEnabled()) {
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.setLayout(new LayeredJar());
packager.setIncludeRelevantJarModeJars(this.layered.isIncludeLayerTools());
packager.setIncludeRelevantJarModeJars(this.layers.isIncludeLayerTools());
}
return packager;
}
private Document getDocumentIfAvailable(File configurationFile) throws Exception {
InputSource inputSource = new InputSource(new FileInputStream(configurationFile));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(inputSource);
}
/**
* Return {@link Libraries} that the packager can use.
* @param unpacks any libraries that require unpack

View File

@@ -31,6 +31,7 @@ import org.apache.maven.plugin.logging.Log;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCallback;
import org.springframework.boot.loader.tools.LibraryCoordinates;
import org.springframework.boot.loader.tools.LibraryScope;
/**
@@ -39,6 +40,7 @@ import org.springframework.boot.loader.tools.LibraryScope;
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Scott Frederick
* @since 1.0.0
*/
public class ArtifactsLibraries implements Libraries {
@@ -78,7 +80,9 @@ public class ArtifactsLibraries implements Libraries {
name = artifact.getGroupId() + "-" + name;
this.log.debug("Renamed to: " + name);
}
callback.library(new Library(name, artifact.getFile(), scope, isUnpackRequired(artifact)));
LibraryCoordinates coordinates = new LibraryCoordinates(artifact.getGroupId(), artifact.getArtifactId(),
artifact.getVersion());
callback.library(new Library(name, artifact.getFile(), scope, isUnpackRequired(artifact), coordinates));
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.maven;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.layer.CustomLayers;
import org.springframework.boot.loader.tools.layer.classes.FilteredResourceStrategy;
import org.springframework.boot.loader.tools.layer.classes.ResourceFilter;
import org.springframework.boot.loader.tools.layer.classes.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;
/**
* Produces a {@link CustomLayers} based on the given {@link Document}.
*
* @author Madhura Bhave
* @since 2.3.0
*/
public class CustomLayersProvider {
public CustomLayers getLayers(Document document) {
Element root = document.getDocumentElement();
NodeList nl = root.getChildNodes();
List<Layer> layers = new ArrayList<>();
List<LibraryStrategy> libraryStrategies = new ArrayList<>();
List<ResourceStrategy> resourceStrategies = new ArrayList<>();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String nodeName = ele.getNodeName();
if ("layers".equals(nodeName)) {
layers.addAll(getLayers(ele));
}
if ("libraries".equals(nodeName)) {
libraryStrategies.addAll(getLibraryStrategies(ele.getChildNodes()));
}
if ("classes".equals(nodeName)) {
resourceStrategies.addAll(getResourceStrategies(ele.getChildNodes()));
}
}
}
return new CustomLayers(layers, resourceStrategies, libraryStrategies);
}
private List<LibraryStrategy> getLibraryStrategies(NodeList nodes) {
List<LibraryStrategy> strategy = new ArrayList<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node item = nodes.item(i);
if (item instanceof Element) {
Element element = (Element) item;
String layer = element.getAttribute("layer");
if ("layer-content".equals(element.getTagName())) {
List<LibraryFilter> filters = new ArrayList<>();
NodeList filterList = item.getChildNodes();
if (filterList.getLength() == 0) {
throw new IllegalArgumentException("Filters for layer-content must not be empty.");
}
for (int k = 0; k < filterList.getLength(); k++) {
Node filter = filterList.item(k);
if (filter instanceof Element) {
List<String> includeList = getPatterns((Element) filter, "include");
List<String> excludeList = getPatterns((Element) filter, "exclude");
addLibraryFilter(filters, filter, includeList, excludeList);
}
}
strategy.add(new FilteredLibraryStrategy(layer, filters));
}
}
}
return strategy;
}
private void addLibraryFilter(List<LibraryFilter> filters, Node filter, List<String> includeList,
List<String> excludeList) {
if ("coordinates".equals(filter.getNodeName())) {
filters.add(new CoordinateFilter(includeList, excludeList));
}
}
private List<ResourceStrategy> getResourceStrategies(NodeList strategies) {
List<ResourceStrategy> strategy = new ArrayList<>();
for (int i = 0; i < strategies.getLength(); i++) {
Node item = strategies.item(i);
List<ResourceFilter> filters = new ArrayList<>();
if (item instanceof Element) {
Element element = (Element) item;
String layer = element.getAttribute("layer");
if ("layer-content".equals(element.getTagName())) {
NodeList filterList = item.getChildNodes();
if (filterList.getLength() == 0) {
throw new IllegalArgumentException("Filters for layer-content must not be empty.");
}
for (int k = 0; k < filterList.getLength(); k++) {
Node filter = filterList.item(k);
if (filter instanceof Element) {
List<String> includeList = getPatterns((Element) filter, "include");
List<String> excludeList = getPatterns((Element) filter, "exclude");
addFilter(filters, filter, includeList, excludeList);
}
}
strategy.add(new FilteredResourceStrategy(layer, filters));
}
}
}
return strategy;
}
private void addFilter(List<ResourceFilter> filters, Node filter, List<String> includeList,
List<String> excludeList) {
if ("locations".equals(filter.getNodeName())) {
filters.add(
new org.springframework.boot.loader.tools.layer.classes.LocationFilter(includeList, excludeList));
}
}
private List<String> getPatterns(Element element, String key) {
NodeList patterns = element.getElementsByTagName(key);
List<String> values = new ArrayList<>();
for (int j = 0; j < patterns.getLength(); j++) {
Node item = patterns.item(j);
if (item instanceof Element) {
values.add(item.getTextContent());
}
}
return values;
}
private List<Layer> getLayers(Element element) {
List<Layer> layers = new ArrayList<>();
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String nodeName = ele.getNodeName();
if ("layer".equals(nodeName)) {
layers.add(new Layer(ele.getTextContent()));
}
}
}
return layers;
}
}

View File

@@ -16,18 +16,22 @@
package org.springframework.boot.maven;
import java.io.File;
/**
* Layer configuration options.
*
* @author Madhura Bhave
* @since 2.3.0
*/
public class Layered {
public class Layers {
private boolean enabled;
private boolean includeLayerTools = true;
private File configuration;
/**
* Whether layered jar layout is enabled.
* @return true if the layered layout is enabled.
@@ -44,4 +48,18 @@ public class Layered {
return this.includeLayerTools;
}
/**
* The location of the layers configuration file. If no file is provided, a default
* configuration is used with four layers: {@code application}, {@code resources},
* {@code snapshot-dependencies} and {@code dependencies}.
* @return the layers configuration file
*/
public File getConfiguration() {
return this.configuration;
}
public void setConfiguration(File configuration) {
this.configuration = configuration;
}
}

View File

@@ -0,0 +1,96 @@
<?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="classesType" name="classes" 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="classesType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Strategies that should be applied to classes. 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="classesLayerContentType" name="layer-content" maxOccurs="unbounded"
minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
Strategy to apply on classes.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="classesLayerContentType" 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>