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

@@ -0,0 +1,68 @@
/*
* 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.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link LibraryCoordinates}.
*
* @author Scott Frederick
*/
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");
}
@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();
}
@Test
void parseCoordinatesWithEmptyElements() {
LibraryCoordinates coordinates = new LibraryCoordinates(":my-library:");
assertThat(coordinates.getGroupId()).isEqualTo("");
assertThat(coordinates.getArtifactId()).isEqualTo("my-library");
assertThat(coordinates.getVersion()).isNull();
}
@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"));
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.classes.FilteredResourceStrategy;
import org.springframework.boot.loader.tools.layer.classes.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 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")));
}
private Library mockLibrary(String coordinates) {
Library library = mock(Library.class);
given(library.getCoordinates()).willReturn(new LibraryCoordinates(coordinates));
return library;
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.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

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

@@ -0,0 +1,111 @@
/*
* 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

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