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

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

View File

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

View File

@@ -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-]+$");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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