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

@@ -25,6 +25,7 @@ import java.io.InputStream;
* Encapsulates information about a single library that may be packed into the archive.
*
* @author Phillip Webb
* @author Scott Frederick
* @since 1.1.2
* @see Libraries
*/
@@ -38,6 +39,8 @@ public class Library {
private final boolean unpackRequired;
private final LibraryCoordinates coordinates;
/**
* Create a new {@link Library}.
* @param file the source file
@@ -66,10 +69,15 @@ public class Library {
* @param unpackRequired if the library needs to be unpacked before it can be used
*/
public Library(String name, File file, LibraryScope scope, boolean unpackRequired) {
this(name, file, scope, unpackRequired, null);
}
public Library(String name, File file, LibraryScope scope, boolean unpackRequired, LibraryCoordinates coordinates) {
this.name = (name != null) ? name : file.getName();
this.file = file;
this.scope = scope;
this.unpackRequired = unpackRequired;
this.coordinates = coordinates;
}
/**
@@ -114,6 +122,14 @@ public class Library {
return this.unpackRequired;
}
/**
* Return the {@linkplain LibraryCoordinates coordinates} of the library.
* @return the coordinates
*/
public LibraryCoordinates getCoordinates() {
return this.coordinates;
}
long getLastModified() {
return this.file.lastModified();
}

View File

@@ -0,0 +1,77 @@
/*
* 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;
import org.springframework.util.Assert;
/**
* Encapsulates information about the Maven artifact coordinates of a library.
*
* @author Scott Frederick
* @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];
if (elements.length > 2) {
this.version = elements[2];
}
else {
this.version = null;
}
}
public String getGroupId() {
return this.groupId;
}
public String getArtifactId() {
return this.artifactId;
}
public String getVersion() {
return this.version;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.Iterator;
import java.util.List;
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.classes.ResourceStrategy;
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
/**
* Implementation of {@link Layers} representing user-provided layers.
*
* @author Madhura Bhave
* @since 2.3.0
*/
public class CustomLayers implements Layers {
private final List<Layer> layers;
private final List<ResourceStrategy> resourceStrategies;
private final List<LibraryStrategy> libraryStrategies;
public CustomLayers(List<Layer> layers, List<ResourceStrategy> resourceStrategies,
List<LibraryStrategy> libraryStrategies) {
this.layers = new ArrayList<>(layers);
this.resourceStrategies = new ArrayList<>(resourceStrategies);
this.libraryStrategies = new ArrayList<>(libraryStrategies);
}
@Override
public Iterator<Layer> iterator() {
return this.layers.iterator();
}
@Override
public Layer getLayer(String resourceName) {
for (ResourceStrategy strategy : this.resourceStrategies) {
Layer matchingLayer = strategy.getMatchingLayer(resourceName);
if (matchingLayer != null) {
return matchingLayer;
}
}
throw new IllegalStateException("Resource '" + resourceName + "' did not match any layer.");
}
@Override
public Layer getLayer(Library library) {
for (LibraryStrategy strategy : this.libraryStrategies) {
Layer matchingLayer = strategy.getMatchingLayer(library);
if (matchingLayer != null) {
return matchingLayer;
}
}
throw new IllegalStateException("Library '" + library.getName() + "' did not match any layer.");
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.classes;
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);
}

View File

@@ -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.classes;
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;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.classes;
import java.util.List;
import org.springframework.util.AntPathMatcher;
/**
* An implementation of {@link ResourceFilter} based on the resource location.
*
* @author Madhura Bhave
* @since 2.3.0
*/
public class LocationFilter extends AbstractResourceFilter {
private static final AntPathMatcher MATCHER = new AntPathMatcher();
public LocationFilter(List<String> includes, List<String> excludes) {
super(includes, excludes);
}
@Override
protected boolean isMatch(String resourceName, List<String> toMatch) {
return toMatch.stream().anyMatch((pattern) -> MATCHER.match(pattern, resourceName));
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.classes;
/**
* A filter that can tell if a resource has been included or excluded.
*
* @author Madhura Bhave
* @since 2.3.0
*/
public interface ResourceFilter {
/**
* 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);
}

View File

@@ -0,0 +1,37 @@
/*
* 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.classes;
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 {
/**
* 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);
}

View File

@@ -0,0 +1,100 @@
/*
* 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 java.util.regex.Pattern;
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 final List<String> includes = new ArrayList<>();
private final List<String> excludes = new ArrayList<>();
public CoordinateFilter(List<String> includes, List<String> excludes) {
this.includes.addAll(includes);
this.excludes.addAll(excludes);
}
@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<String> toMatch) {
StringBuilder builder = new StringBuilder();
LibraryCoordinates coordinates = library.getCoordinates();
if (coordinates != null) {
if (coordinates.getGroupId() != null) {
builder.append(coordinates.getGroupId());
}
builder.append(":");
if (coordinates.getArtifactId() != null) {
builder.append(coordinates.getArtifactId());
}
builder.append(":");
if (coordinates.getVersion() != null) {
builder.append(coordinates.getVersion());
}
}
else {
builder.append("::");
}
String input = builder.toString();
for (String patternString : toMatch) {
Pattern pattern = buildPatternForString(patternString);
if (pattern.matcher(input).matches()) {
return true;
}
}
return false;
}
private Pattern buildPatternForString(String pattern) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (c == '.') {
builder.append("\\.");
}
else if (c == '*') {
builder.append(".*");
}
else {
builder.append(c);
}
}
return Pattern.compile(builder.toString());
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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 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 {
/**
* 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);
}

View File

@@ -0,0 +1,38 @@
/*
* 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 org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.Library;
/**
* A strategy used to match a library to a layer.
*
* @author Madhura Bhave
* @since 2.3.0
*/
public interface LibraryStrategy {
/**
* 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}
*/
Layer getMatchingLayer(Library library);
}