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:
committed by
Stephane Nicoll
parent
896d2c8579
commit
e49e62df5c
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user