diff --git a/initializr-parent/pom.xml b/initializr-parent/pom.xml index dedf202c..4cb0ca76 100644 --- a/initializr-parent/pom.xml +++ b/initializr-parent/pom.xml @@ -17,8 +17,8 @@ 17 1.23.0 1.10.0 + 3.9.2 1.9.7 - 3.9.2 3.1.1 4.0.2 @@ -40,10 +40,15 @@ commons-text ${commons-text.version} + + org.apache.maven + maven-core + ${maven.version} + org.apache.maven maven-resolver-provider - ${maven-resolver-provider.version} + ${maven.version} org.apache.maven.resolver diff --git a/initializr-version-resolver/pom.xml b/initializr-version-resolver/pom.xml index 7c5d68e0..cb435803 100644 --- a/initializr-version-resolver/pom.xml +++ b/initializr-version-resolver/pom.xml @@ -21,6 +21,10 @@ + + org.apache.maven + maven-core + org.apache.maven maven-resolver-provider diff --git a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolver.java b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DefaultMavenVersionResolver.java similarity index 57% rename from initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolver.java rename to initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DefaultMavenVersionResolver.java index 562863c0..510248be 100644 --- a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolver.java +++ b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DefaultMavenVersionResolver.java @@ -22,14 +22,24 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.maven.model.Model; +import org.apache.maven.model.building.DefaultModelBuilder; +import org.apache.maven.model.building.DefaultModelBuilderFactory; +import org.apache.maven.model.building.DefaultModelBuildingRequest; +import org.apache.maven.model.building.ModelBuildingException; +import org.apache.maven.model.resolution.ModelResolver; +import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.project.ProjectModelResolver; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.RequestTrace; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.impl.DefaultServiceLocator; +import org.eclipse.aether.impl.RemoteRepositoryManager; import org.eclipse.aether.internal.impl.DefaultRepositorySystem; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; @@ -37,6 +47,9 @@ import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactDescriptorException; import org.eclipse.aether.resolution.ArtifactDescriptorRequest; import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; import org.eclipse.aether.spi.connector.transport.TransporterFactory; import org.eclipse.aether.spi.locator.ServiceLocator; @@ -44,14 +57,16 @@ import org.eclipse.aether.transport.http.HttpTransporterFactory; import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy; /** - * A {@link DependencyManagementVersionResolver} that resolves versions using Maven - * Resolver. Maven's default {@link LocalRepositoryManager} implementation is not - * thread-safe. To avoid corruption of the local repository, interaction with the - * {@link RepositorySystem} is single-threaded. + * A {@link MavenVersionResolver} that resolves versions using Maven Resolver. Maven's + * default {@link LocalRepositoryManager} implementation is not thread-safe. To avoid + * corruption of the local repository, interaction with the {@link RepositorySystem} is + * single-threaded. * * @author Andy Wilkinson + * @author Stephane Nicoll */ -class MavenResolverDependencyManagementVersionResolver implements DependencyManagementVersionResolver { +@SuppressWarnings("removal") +class DefaultMavenVersionResolver implements MavenVersionResolver, DependencyManagementVersionResolver { private static final RemoteRepository mavenCentral = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2") @@ -72,9 +87,11 @@ class MavenResolverDependencyManagementVersionResolver implements DependencyMana private final RepositorySystemSession repositorySystemSession; + private final RemoteRepositoryManager remoteRepositoryManager; + private final RepositorySystem repositorySystem; - MavenResolverDependencyManagementVersionResolver(Path cacheLocation) { + DefaultMavenVersionResolver(Path cacheLocation) { ServiceLocator serviceLocator = createServiceLocator(); DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(false, false)); @@ -84,10 +101,16 @@ class MavenResolverDependencyManagementVersionResolver implements DependencyMana session.setUserProperties(System.getProperties()); session.setReadOnly(); this.repositorySystemSession = session; + this.remoteRepositoryManager = serviceLocator.getService(RemoteRepositoryManager.class); } @Override public Map resolve(String groupId, String artifactId, String version) { + return resolveDependencies(groupId, artifactId, version); + } + + @Override + public Map resolveDependencies(String groupId, String artifactId, String version) { ArtifactDescriptorResult bom = resolveBom(groupId, artifactId, version); Map managedVersions = new HashMap<>(); bom.getManagedDependencies() @@ -98,6 +121,18 @@ class MavenResolverDependencyManagementVersionResolver implements DependencyMana return managedVersions; } + @Override + public Map resolvePlugins(String groupId, String artifactId, String version) { + Model model = buildEffectiveModel(groupId, artifactId, version); + Map managedPluginVersions = new HashMap<>(); + model.getBuild() + .getPluginManagement() + .getPlugins() + .forEach((plugin) -> managedPluginVersions.putIfAbsent(plugin.getGroupId() + ":" + plugin.getArtifactId(), + plugin.getVersion())); + return managedPluginVersions; + } + private ArtifactDescriptorResult resolveBom(String groupId, String artifactId, String version) { synchronized (this.monitor) { try { @@ -112,6 +147,40 @@ class MavenResolverDependencyManagementVersionResolver implements DependencyMana } } + private Model buildEffectiveModel(String groupId, String artifactId, String version) { + try { + ArtifactResult bom = resolvePom(groupId, artifactId, version); + RequestTrace requestTrace = new RequestTrace(null); + + ModelResolver modelResolver = new ProjectModelResolver(this.repositorySystemSession, requestTrace, + this.repositorySystem, this.remoteRepositoryManager, repositories, + ProjectBuildingRequest.RepositoryMerging.POM_DOMINANT, null); + DefaultModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest(); + modelBuildingRequest.setSystemProperties(System.getProperties()); + modelBuildingRequest.setPomFile(bom.getArtifact().getFile()); + modelBuildingRequest.setModelResolver(modelResolver); + DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance(); + return modelBuilder.build(modelBuildingRequest).getEffectiveModel(); + } + catch (ModelBuildingException ex) { + throw new IllegalStateException( + "Model for '" + groupId + ":" + artifactId + ":" + version + "' could not be built", ex); + } + } + + private ArtifactResult resolvePom(String groupId, String artifactId, String version) { + synchronized (this.monitor) { + try { + return this.repositorySystem.resolveArtifact(this.repositorySystemSession, new ArtifactRequest( + new DefaultArtifact(groupId, artifactId, "pom", version), repositories, null)); + } + catch (ArtifactResolutionException ex) { + throw new IllegalStateException( + "Pom '" + groupId + ":" + artifactId + ":" + version + "' could not be resolved", ex); + } + } + } + private static ServiceLocator createServiceLocator() { DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); locator.addService(RepositorySystem.class, DefaultRepositorySystem.class); diff --git a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java index f531a34e..bf19e885 100644 --- a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java +++ b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -24,7 +24,9 @@ import java.util.Map; * managed dependencies of a Maven bom. Implementations must be thread-safe. * * @author Andy Wilkinson + * @deprecated as of 0.20.0 in favor of {@link MavenVersionResolver}. */ +@Deprecated(since = "0.20.0", forRemoval = true) public interface DependencyManagementVersionResolver { /** @@ -47,7 +49,7 @@ public interface DependencyManagementVersionResolver { * @return the resolver */ static DependencyManagementVersionResolver withCacheLocation(Path location) { - return new MavenResolverDependencyManagementVersionResolver(location); + return new DefaultMavenVersionResolver(location); } } diff --git a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenVersionResolver.java b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenVersionResolver.java new file mode 100644 index 00000000..37007b7c --- /dev/null +++ b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenVersionResolver.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2023 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 io.spring.initializr.versionresolver; + +import java.nio.file.Path; +import java.util.Map; + +/** + * A {@code MavenVersionResolver} is used to resolve the versions of managed dependencies + * or plugins. Implementations must be thread-safe. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +public interface MavenVersionResolver { + + /** + * Resolves the versions in the managed dependencies of the bom identified by the + * given {@code groupId}, {@code artifactId}, and {@code version}. + * @param groupId bom group ID + * @param artifactId bom artifact ID + * @param version bom version + * @return the managed dependencies as a map of {@code groupId:artifactId} to + * {@code version} + */ + Map resolveDependencies(String groupId, String artifactId, String version); + + /** + * Resolves the versions in the managed plugins of the pom identified by the given + * {@code groupId}, {@code artifactId}, and {@code version}. + * @param groupId pom group ID + * @param artifactId pom artifact ID + * @param version pom version + * @return the managed plugins as a map of {@code groupId:artifactId} to + * {@code version} + */ + Map resolvePlugins(String groupId, String artifactId, String version); + + /** + * Creates a new {@code MavenVersionResolver} that uses the given {@code location} for + * its local cache. To avoid multiple instances attempting to write to the same + * location cache, callers should ensure that a unique location is used. The returned + * resolver can then be used concurrently by multiple threads. + * @param location cache location + * @return the resolver + */ + static MavenVersionResolver withCacheLocation(Path location) { + return new DefaultMavenVersionResolver(location); + } + +} diff --git a/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/DefaultMavenVersionResolverTests.java b/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/DefaultMavenVersionResolverTests.java new file mode 100644 index 00000000..8d5e96f3 --- /dev/null +++ b/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/DefaultMavenVersionResolverTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2012-2023 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 io.spring.initializr.versionresolver; + +import java.nio.file.Path; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link DefaultMavenVersionResolver}. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +class DefaultMavenVersionResolverTests { + + private MavenVersionResolver resolver; + + @BeforeEach + void createResolver(@TempDir Path temp) { + this.resolver = new DefaultMavenVersionResolver(temp); + } + + @Test + void resolveDependenciesForSpringBoot() { + Map versions = this.resolver.resolveDependencies("org.springframework.boot", + "spring-boot-dependencies", "2.1.5.RELEASE"); + assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4"); + } + + @Test + void resolveDependenciesForSpringCloud() { + Map versions = this.resolver.resolveDependencies("org.springframework.cloud", + "spring-cloud-dependencies", "Greenwich.SR1"); + assertThat(versions).containsEntry("com.netflix.ribbon:ribbon", "2.3.0"); + } + + @Test + void resolveDependenciesUsingMilestones() { + Map versions = this.resolver.resolveDependencies("org.springframework.boot", + "spring-boot-dependencies", "2.2.0.M3"); + assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4"); + } + + @Test + void resolveDependenciesUsingSnapshots() { + Map versions = this.resolver.resolveDependencies("org.springframework.boot", + "spring-boot-dependencies", "2.4.0-SNAPSHOT"); + assertThat(versions).isNotEmpty(); + } + + @Test + void resolveDependenciesForNonExistentDependency() { + assertThatIllegalStateException() + .isThrownBy(() -> this.resolver.resolveDependencies("org.springframework.boot", "spring-boot-bom", "1.0")) + .withMessage("Bom 'org.springframework.boot:spring-boot-bom:1.0' could not be resolved"); + } + + @Test + void resolvePluginsForSpringBoot() { + Map versions = this.resolver.resolvePlugins("org.springframework.boot", + "spring-boot-starter-parent", "3.1.1"); + assertThat(versions).containsEntry("org.springframework.boot:spring-boot-maven-plugin", "3.1.1"); + } + + @Test + void resolvePluginsUsingMilestones() { + Map versions = this.resolver.resolvePlugins("org.springframework.boot", + "spring-boot-dependencies", "2.2.0.M3"); + assertThat(versions).containsEntry("org.springframework.boot:spring-boot-maven-plugin", "2.2.0.M3"); + } + + @Test + void resolvePluginsUsingSnapshots() { + Map versions = this.resolver.resolvePlugins("org.springframework.boot", + "spring-boot-dependencies", "2.4.0-SNAPSHOT"); + assertThat(versions).isNotEmpty(); + } + + @Test + void resolvePluginsForNonExistentDependency() { + assertThatIllegalStateException() + .isThrownBy(() -> this.resolver.resolvePlugins("org.springframework.boot", "spring-boot-bom", "1.0")) + .withMessage("Pom 'org.springframework.boot:spring-boot-bom:1.0' could not be resolved"); + } + +} diff --git a/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolverTests.java b/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolverTests.java deleted file mode 100644 index 3c8ae776..00000000 --- a/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolverTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2012-2023 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 io.spring.initializr.versionresolver; - -import java.nio.file.Path; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; - -/** - * Tests for {@link MavenResolverDependencyManagementVersionResolver}. - * - * @author Andy Wilkinson - */ -class MavenResolverDependencyManagementVersionResolverTests { - - private DependencyManagementVersionResolver resolver; - - @BeforeEach - void createResolver(@TempDir Path temp) { - this.resolver = new MavenResolverDependencyManagementVersionResolver(temp); - } - - @Test - void springBootDependencies() { - Map versions = this.resolver.resolve("org.springframework.boot", "spring-boot-dependencies", - "2.1.5.RELEASE"); - assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4"); - } - - @Test - void springCloudDependencies() { - Map versions = this.resolver.resolve("org.springframework.cloud", "spring-cloud-dependencies", - "Greenwich.SR1"); - assertThat(versions).containsEntry("com.netflix.ribbon:ribbon", "2.3.0"); - } - - @Test - void milestoneBomCanBeResolved() { - Map versions = this.resolver.resolve("org.springframework.boot", "spring-boot-dependencies", - "2.2.0.M3"); - assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4"); - } - - @Test - void snapshotBomCanBeResolved() { - Map versions = this.resolver.resolve("org.springframework.boot", "spring-boot-dependencies", - "2.4.0-SNAPSHOT"); - assertThat(versions).isNotEmpty(); - } - - @Test - void nonExistentDependency() { - assertThatIllegalStateException() - .isThrownBy(() -> this.resolver.resolve("org.springframework.boot", "spring-boot-bom", "1.0")) - .withMessage("Bom 'org.springframework.boot:spring-boot-bom:1.0' could not be resolved"); - } - -}