diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/build/api/Dependency.java b/components/sbm-core/src/main/java/org/springframework/sbm/build/api/Dependency.java index 0be85a001..848598518 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/build/api/Dependency.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/build/api/Dependency.java @@ -17,9 +17,11 @@ import io.micrometer.core.lang.Nullable; import lombok.*; +import org.openrewrite.semver.LatestRelease; import javax.validation.constraints.NotNull; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; @Getter @@ -66,6 +68,15 @@ public String toString() { ""; } + public boolean isRecentThen(Dependency that){ + return this.equals(that) && comparator().compare(this, that) >= 0; + } + + private Comparator comparator(){ + LatestRelease latestRelease = new LatestRelease(null); + return Comparator.comparing(Dependency::getVersion, latestRelease::compare); + } + private String exclusionString() { if (exclusions.isEmpty()) { return ""; diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/build/api/SpringManagedDependencies.java b/components/sbm-core/src/main/java/org/springframework/sbm/build/api/SpringManagedDependencies.java new file mode 100644 index 000000000..92f85babe --- /dev/null +++ b/components/sbm-core/src/main/java/org/springframework/sbm/build/api/SpringManagedDependencies.java @@ -0,0 +1,51 @@ +package org.springframework.sbm.build.api; + +import org.openrewrite.maven.internal.MavenPomDownloader; +import org.openrewrite.maven.tree.GroupArtifactVersion; +import org.openrewrite.maven.tree.MavenRepository; +import org.springframework.sbm.openrewrite.RewriteExecutionContext; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * This class holds all the dependencies included in a spring artifact + */ +public class SpringManagedDependencies { + + private static List SPRING_REPOSITORIES = List.of( + new MavenRepository("spring-release", "https://repo.spring.io/release", true, false, null, null) + ); + + private List dependencies; + private static Map INSTANCES = new HashMap<>(); + + public static SpringManagedDependencies by(String groupId, String artifact, String version){ + final GroupArtifactVersion groupArtifactVersion = + new GroupArtifactVersion(groupId, artifact, version); + + INSTANCES.computeIfAbsent(groupArtifactVersion, SpringManagedDependencies::new); + return INSTANCES.get(groupArtifactVersion); + } + + private SpringManagedDependencies(GroupArtifactVersion groupArtifactVersion){ + dependencies = new MavenPomDownloader(Collections.emptyMap(), new RewriteExecutionContext()) + .download(groupArtifactVersion, null, null, SPRING_REPOSITORIES) + .getDependencies(); + } + + public Stream stream(){ + return dependencies.stream() + .map(d -> Dependency.builder() + .groupId(d.getGroupId()) + .artifactId(d.getArtifactId()) + .version(d.getVersion()) + .build() + ); + } + + +} diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/build/migration/actions/RemoveManagedDependencies.java b/components/sbm-core/src/main/java/org/springframework/sbm/build/migration/actions/RemoveManagedDependencies.java new file mode 100644 index 000000000..b89571d27 --- /dev/null +++ b/components/sbm-core/src/main/java/org/springframework/sbm/build/migration/actions/RemoveManagedDependencies.java @@ -0,0 +1,52 @@ +package org.springframework.sbm.build.migration.actions; + +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.build.api.SpringManagedDependencies; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static org.openrewrite.maven.tree.Scope.Compile; + +/** + * The action removes the dependencies directly managed by Spring from the project dependencies + * Add this action at the end of recipe so that any spring artifact inclusions as part of the + * other actions are also included while removing the dependencies. + */ +public class RemoveManagedDependencies extends AbstractAction { + + @Override + public void apply(ProjectContext context) { + //FIXME handle multi-module projects + final List springManagedDependencies = context.getBuildFile() + .getDeclaredDependencies(Compile) + .stream() + .filter(this::isSpringFrameworkDependency) + .map(d -> SpringManagedDependencies.by(d.getGroupId(),d.getArtifactId(),d.getVersion())) + .flatMap(SpringManagedDependencies::stream) + .distinct() + .collect(Collectors.toList()); + + Predicate isAlreadyManagedBySpring = d -> springManagedDependencies + .stream() + .filter(d::equals) + .anyMatch(s -> s.isRecentThen(d)); + + final List dependenciesToBeRemoved = context.getBuildFile() + .getDeclaredDependencies(Compile) + .stream() + .filter(isAlreadyManagedBySpring) + .collect(Collectors.toList()); + + RemoveDependencies removeDependenciesAction = new RemoveDependencies(); + removeDependenciesAction.setDependencies(dependenciesToBeRemoved); + removeDependenciesAction.apply(context); + } + + private boolean isSpringFrameworkDependency(Dependency dependency){ + return dependency.getGroupId().startsWith("org.springframework"); + } +} diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/build/api/SpringManagedDependenciesTest.java b/components/sbm-core/src/test/java/org/springframework/sbm/build/api/SpringManagedDependenciesTest.java new file mode 100644 index 000000000..fd4a28bfa --- /dev/null +++ b/components/sbm-core/src/test/java/org/springframework/sbm/build/api/SpringManagedDependenciesTest.java @@ -0,0 +1,18 @@ +package org.springframework.sbm.build.api; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +public class SpringManagedDependenciesTest { + + @Test + public void pullBootStarter274Dependencies_expectJakartaAnnotationDependency(){ + String jakartaCoordinates = "jakarta.annotation:jakarta.annotation-api:1.3.5"; + + assertThat( SpringManagedDependencies.by("org.springframework.boot", "spring-boot-starter", "2.7.4") + .stream() + .map(Dependency::getCoordinates) + .anyMatch(jakartaCoordinates::equals) + ).isTrue(); + } +} diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/build/migration/actions/RemoveManagedDependenciesTest.java b/components/sbm-core/src/test/java/org/springframework/sbm/build/migration/actions/RemoveManagedDependenciesTest.java new file mode 100644 index 000000000..1146c83b4 --- /dev/null +++ b/components/sbm-core/src/test/java/org/springframework/sbm/build/migration/actions/RemoveManagedDependenciesTest.java @@ -0,0 +1,76 @@ +package org.springframework.sbm.build.migration.actions; + +import org.junit.jupiter.api.Test; +import org.openrewrite.semver.LatestRelease; +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class RemoveManagedDependenciesTest { + + @Test + public void givenProjectWithManagedDependency_removeSpringManagedDependencies_expectHibernateDependencyRemoved(){ + + LatestRelease latestRelease = new LatestRelease(null); + System.out.println(latestRelease.compare(null, "5.6.11.Final", "5.6.11.Final")); + + final String hibernateCoordinates = "org.hibernate:hibernate-core:5.6.11.Final"; + final String springBootDataJpaCoordinates = "org.springframework.boot:spring-boot-starter-data-jpa:2.7.4"; + + final ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies(hibernateCoordinates, springBootDataJpaCoordinates) + .build(); + + RemoveManagedDependencies removeManagedDependencies = new RemoveManagedDependencies(); + removeManagedDependencies.apply(projectContext); + + assertThat(projectContext.getBuildFile() + .getDeclaredDependencies() + .stream() + .map(Dependency::getCoordinates) + .anyMatch(hibernateCoordinates::equals) + ).isFalse(); + } + + @Test + public void givenProjectWithLowerVersionedManagedDependency_removeSpringManagedDependencies_expectDependencyRemoved(){ + final String hibernateCoordinates = "org.hibernate:hibernate-core:5.3.2.Final"; + final String springBootDataJpaCoordinates = "org.springframework.boot:spring-boot-starter-data-jpa:2.7.4"; + + final ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies(hibernateCoordinates, springBootDataJpaCoordinates) + .build(); + + RemoveManagedDependencies removeManagedDependencies = new RemoveManagedDependencies(); + removeManagedDependencies.apply(projectContext); + + assertThat(projectContext.getBuildFile() + .getDeclaredDependencies() + .stream() + .map(Dependency::getCoordinates) + .anyMatch(hibernateCoordinates::equals) + ).isFalse(); + } + + @Test + public void givenProjectWithHigherVersionedManagedDependency_removeSpringManagedDependencies_expectDependencyRemoved(){ + final String hibernateCoordinates = "org.hibernate:hibernate-core:5.12.2.Final"; + final String springBootDataJpaCoordinates = "org.springframework.boot:spring-boot-starter-data-jpa:2.7.4"; + + final ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies(hibernateCoordinates, springBootDataJpaCoordinates) + .build(); + + RemoveManagedDependencies removeManagedDependencies = new RemoveManagedDependencies(); + removeManagedDependencies.apply(projectContext); + + assertThat(projectContext.getBuildFile() + .getDeclaredDependencies() + .stream() + .map(Dependency::getCoordinates) + .anyMatch(hibernateCoordinates::equals) + ).isTrue(); + } +}