Skip to content

Commit

Permalink
Add dependency range info
Browse files Browse the repository at this point in the history
This commit improves the `info` endpoint to contain a
`dependency-ranges` entry that is similar than the `bom-ranges` entry
for BOMs.

Each dependency that has a version mapping is listed with the range and
the related version. Some dependencies weren't managed and are now. For
those a `managed` version is used to indicate which Spring Boot versions
do not require to specify a version for the dependency.

Closes spring-iogh-453
  • Loading branch information
snicoll committed Jun 25, 2017
1 parent 2205ab3 commit 8832201
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.spring.initializr.actuate.autoconfigure;

import io.spring.initializr.actuate.info.BomRangesInfoContributor;
import io.spring.initializr.actuate.info.DependencyRangesInfoContributor;
import io.spring.initializr.metadata.InitializrMetadataProvider;

import org.springframework.context.annotation.Bean;
Expand All @@ -37,4 +38,10 @@ public BomRangesInfoContributor bomRangesInfoContributor(
return new BomRangesInfoContributor(metadataProvider);
}

@Bean
public DependencyRangesInfoContributor dependencyRangesInfoContributor(
InitializrMetadataProvider metadataProvider) {
return new DependencyRangesInfoContributor(metadataProvider);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2012-2017 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
*
* http://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 io.spring.initializr.actuate.info;

import java.util.LinkedHashMap;
import java.util.Map;

import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.util.Version;
import io.spring.initializr.util.VersionRange;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.util.ObjectUtils;

/**
* An {@link InfoContributor} that exposes the actual ranges used by dependencies
* defined in the project that have an explicit version (i.e. not relying on a bom).
*
* @author Stephane Nicoll
*/
public class DependencyRangesInfoContributor implements InfoContributor {

private final InitializrMetadataProvider metadataProvider;

public DependencyRangesInfoContributor(InitializrMetadataProvider metadataProvider) {
this.metadataProvider = metadataProvider;
}

@Override
public void contribute(Info.Builder builder) {
Map<String, Object> details = new LinkedHashMap<>();
metadataProvider.get().getDependencies().getAll().forEach(d -> {
if (d.getBom() == null) {
if (!ObjectUtils.isEmpty(d.getMappings())) {
Map<String, VersionRange> dep = new LinkedHashMap<>();
d.getMappings().forEach(it -> {
if (it.getRange() != null && it.getVersion() != null) {
dep.put(it.getVersion(), it.getRange());
}
});
if (!dep.isEmpty()) {
if (d.getRange() == null) {
boolean openRange = dep.values().stream().anyMatch(
v -> v.getHigherVersion() == null);
if (!openRange) {
Version higher = null;
for (VersionRange versionRange : dep.values()) {
Version candidate = versionRange.getHigherVersion();
if (higher == null) {
higher = candidate;
}
else if (candidate.compareTo(higher) > 0) {
higher = candidate;
}
} ;
dep.put("managed", new VersionRange(higher));
}
}
Map<String, Object> depInfo = new LinkedHashMap<>();
dep.forEach((k, r) -> {
depInfo.put(k, "Spring Boot " + r);
});
details.put(d.getId(), depInfo);
}
}
else if (d.getVersion() != null && d.getRange() != null) {
Map<String, Object> dep = new LinkedHashMap<>();
String requirement = "Spring Boot " + d.getRange();
dep.put(d.getVersion(), requirement);
details.put(d.getId(), dep);
}
}
});
if (!details.isEmpty()) {
builder.withDetail("dependency-ranges", details);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright 2012-2017 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
*
* http://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 io.spring.initializr.actuate.info;

import java.util.Map;

import io.spring.initializr.metadata.BillOfMaterials;
import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
import org.junit.Test;

import org.springframework.boot.actuate.info.Info;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

/**
* Tests for {@link DependencyRangesInfoContributor}.
*
* @author Stephane Nicoll
*/
public class DependencyRangesInfoContributorTests {

@Test
public void noDependencyWithVersion() {
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).doesNotContainKeys("dependency-ranges");
}

@Test
public void dependencyWithNoMapping() {
Dependency dependency = Dependency.withId("foo", "com.example", "foo",
"1.2.3.RELEASE");
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup("foo", dependency).build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).doesNotContainKeys("dependency-ranges");
}


@Test
public void dependencyWithRangeOnArtifact() {
Dependency dependency = Dependency.withId("foo", "com.example", "foo",
"1.2.3.RELEASE");
dependency.getMappings().add(Dependency.Mapping
.create("[1.1.0.RELEASE, 1.2.0.RELEASE)", null, "foo2", null));
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup("foo", dependency).build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).doesNotContainKeys("dependency-ranges");
}

@Test
public void dependencyWithRangeAndBom() {
BillOfMaterials bom = BillOfMaterials.create("com.example", "bom", "1.0.0");
Dependency dependency = Dependency.withId("foo", "com.example", "foo",
"1.2.3.RELEASE");
dependency.getMappings().add(Dependency.Mapping
.create("[1.1.0.RELEASE, 1.2.0.RELEASE)", null, null, "0.1.0.RELEASE"));
dependency.setBom("bom");
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addBom("bom", bom)
.addDependencyGroup("foo", dependency).build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).doesNotContainKeys("dependency-ranges");
}

@Test
public void dependencyNoMappingSimpleRange() {
Dependency dependency = Dependency.withId("foo", "com.example", "foo",
"1.2.3.RELEASE");
dependency.setVersionRange("[1.1.0.RELEASE, 1.5.0.RELEASE)");
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup("foo", dependency).build();
Info info = getInfo(metadata);
assertThat(info.getDetails()).containsKeys("dependency-ranges");
@SuppressWarnings("unchecked")
Map<String, Object> ranges = (Map<String, Object>) info.getDetails()
.get("dependency-ranges");
assertThat(ranges).containsOnlyKeys("foo");
@SuppressWarnings("unchecked")
Map<String, Object> foo = (Map<String, Object>) ranges.get("foo");
assertThat(foo).containsExactly(
entry("1.2.3.RELEASE", "Spring Boot >=1.1.0.RELEASE and <1.5.0.RELEASE"));
}

@Test
public void dependencyWithMappingAndOpenRange() {
Dependency dependency = Dependency.withId("foo", null, null, "0.3.0.RELEASE");
dependency.getMappings().add(Dependency.Mapping
.create("[1.1.0.RELEASE, 1.2.0.RELEASE)", null, null, "0.1.0.RELEASE"));
dependency.getMappings().add(Dependency.Mapping
.create("1.2.0.RELEASE", null, null, "0.2.0.RELEASE"));
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup("test", dependency).build();
Info info = getInfo(metadata);
assertDependencyId(info, "foo");
Map<String, Object> foo = getDependencyRangeInfo(info, "foo");
assertThat(foo).containsExactly(
entry("0.1.0.RELEASE", "Spring Boot >=1.1.0.RELEASE and <1.2.0.RELEASE"),
entry("0.2.0.RELEASE", "Spring Boot >=1.2.0.RELEASE"));
}

@Test
public void dependencyWithMappingAndNoOpenRange() {
Dependency dependency = Dependency.withId("foo", null, null, "0.3.0.RELEASE");
dependency.getMappings().add(Dependency.Mapping
.create("[1.1.0.RELEASE, 1.2.0.RELEASE)", null, null, "0.1.0.RELEASE"));
dependency.getMappings().add(Dependency.Mapping
.create("[1.2.0.RELEASE, 1.3.0.RELEASE)", null, null, "0.2.0.RELEASE"));
InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup("test", dependency).build();
Info info = getInfo(metadata);
assertDependencyId(info, "foo");
Map<String, Object> foo = getDependencyRangeInfo(info, "foo");
assertThat(foo).containsExactly(
entry("0.1.0.RELEASE", "Spring Boot >=1.1.0.RELEASE and <1.2.0.RELEASE"),
entry("0.2.0.RELEASE", "Spring Boot >=1.2.0.RELEASE and <1.3.0.RELEASE"),
entry("managed", "Spring Boot >=1.3.0.RELEASE"));
}

@SuppressWarnings("unchecked")
private void assertDependencyId(Info info, String... dependencyIds) {
assertThat(info.getDetails()).containsKeys("dependency-ranges");
Map<String, Object> ranges = (Map<String, Object>) info.getDetails()
.get("dependency-ranges");
assertThat(ranges).containsOnlyKeys(dependencyIds);
}

@SuppressWarnings("unchecked")
private Map<String,Object> getDependencyRangeInfo(Info info, String id) {
assertThat(info.getDetails()).containsKeys("dependency-ranges");
Map<String, Object> ranges = (Map<String, Object>) info.getDetails()
.get("dependency-ranges");
return (Map<String, Object>) ranges.get(id);
}

private static Info getInfo(InitializrMetadata metadata) {
Info.Builder builder = new Info.Builder();
new DependencyRangesInfoContributor(new SimpleInitializrMetadataProvider(metadata))
.contribute(builder);
return builder.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ protected VersionRange(Version lowerVersion, boolean lowerInclusive,
this.higherInclusive = higherInclusive;
}

public VersionRange(Version startingVersion) {
this(startingVersion, true, null, false);
}

/**
* Specify if the {@link Version} matches this range. Returns {@code true}
* if the version is contained within this range, {@code false} otherwise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertThat;

Expand All @@ -36,6 +37,12 @@ public class VersionRangeTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();

@Test
public void simpleStartingRange() {
assertThat(new VersionRange(Version.parse("1.3.0.RELEASE")).toString(),
equalTo(">=1.3.0.RELEASE"));
}

@Test
public void matchSimpleRange() {
assertThat("1.2.0.RC3", match("[1.2.0.RC1,1.2.0.RC5]"));
Expand Down

0 comments on commit 8832201

Please sign in to comment.