001/*
002 * Copyright 2017-2022 original authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.micronaut.maven.services;
017
018import io.micronaut.core.util.StringUtils;
019import io.micronaut.testresources.buildtools.MavenDependency;
020import org.apache.maven.execution.MavenSession;
021import org.apache.maven.model.DependencyManagement;
022import org.apache.maven.project.MavenProject;
023import org.eclipse.aether.RepositorySystem;
024import org.eclipse.aether.RepositorySystemSession;
025import org.eclipse.aether.artifact.Artifact;
026import org.eclipse.aether.artifact.DefaultArtifact;
027import org.eclipse.aether.collection.CollectRequest;
028import org.eclipse.aether.graph.Dependency;
029import org.eclipse.aether.graph.DependencyFilter;
030import org.eclipse.aether.resolution.ArtifactResult;
031import org.eclipse.aether.resolution.DependencyRequest;
032import org.eclipse.aether.resolution.DependencyResolutionException;
033import org.eclipse.aether.resolution.DependencyResult;
034import org.eclipse.aether.util.artifact.JavaScopes;
035import org.eclipse.aether.util.filter.DependencyFilterUtils;
036
037import javax.inject.Inject;
038import javax.inject.Singleton;
039import java.io.File;
040import java.util.ArrayList;
041import java.util.HashMap;
042import java.util.List;
043import java.util.stream.Stream;
044
045/**
046 * Utility methods for performing dependency resolution.
047 *
048 * @author Álvaro Sánchez-Mariscal
049 * @since 5.0.0
050 */
051@Singleton
052public class DependencyResolutionService {
053
054    public static final String TEST_RESOURCES_GROUP = "io.micronaut.testresources";
055    public static final String TEST_RESOURCES_ARTIFACT_ID_PREFIX = "micronaut-test-resources-";
056
057    private static final String JAR_EXTENSION = "jar";
058
059    private final MavenSession mavenSession;
060    private final MavenProject mavenProject;
061    private final RepositorySystem repositorySystem;
062
063    @Inject
064    public DependencyResolutionService(MavenSession mavenSession,
065                                       MavenProject mavenProject,
066                                       RepositorySystem repositorySystem) {
067        this.mavenSession = mavenSession;
068        this.mavenProject = mavenProject;
069        this.repositorySystem = repositorySystem;
070    }
071
072    private static Stream<File> streamClasspath(List<ArtifactResult> resolutionResult) {
073        return resolutionResult.stream()
074            .map(ArtifactResult::getArtifact)
075            .map(Artifact::getFile);
076    }
077
078    public static List<String> toClasspath(List<ArtifactResult> resolutionResult) {
079        return streamClasspath(resolutionResult)
080            .map(File::getAbsolutePath)
081            .toList();
082    }
083
084    public static List<File> toClasspathFiles(List<ArtifactResult> resolutionResult) {
085        return streamClasspath(resolutionResult).toList();
086    }
087
088    public static Dependency mavenDependencyToAetherDependency(org.apache.maven.model.Dependency dependency) {
089        Artifact artifact = mavenDependencyToAetherArtifact(dependency);
090        return new Dependency(artifact, dependency.getScope(), Boolean.valueOf(dependency.getOptional()));
091    }
092
093    public static Artifact mavenDependencyToAetherArtifact(org.apache.maven.model.Dependency dependency) {
094        return new DefaultArtifact(
095            dependency.getGroupId(),
096            dependency.getArtifactId(),
097            dependency.getClassifier(),
098            dependency.getType(),
099            dependency.getVersion()
100        );
101    }
102
103    public static MavenDependency mavenDependencyToTestResourcesDependency(org.apache.maven.model.Dependency dependency) {
104        return new MavenDependency(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
105    }
106
107    public static Artifact testResourcesDependencyToAetherArtifact(MavenDependency dependency) {
108        return new DefaultArtifact(dependency.getGroup(), dependency.getArtifact(), JAR_EXTENSION, dependency.getVersion());
109    }
110
111    /**
112     * Performs a dependency request to compute the transitive dependencies of the given artifacts.
113     *
114     * @param artifacts The artifacts to resolve
115     * @param applyManagedDependencies Whether to apply the managed dependencies of the project
116     * @return The resolution result
117     */
118    public List<ArtifactResult> artifactResultsFor(Stream<Artifact> artifacts, boolean applyManagedDependencies) throws DependencyResolutionException {
119        RepositorySystemSession repositorySession = mavenSession.getRepositorySession();
120        DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME);
121        CollectRequest collectRequest = new CollectRequest();
122        collectRequest.setRepositories(mavenProject.getRemoteProjectRepositories());
123
124        if (applyManagedDependencies) {
125            DependencyManagement dependencyManagement = mavenProject.getDependencyManagement();
126            List<org.apache.maven.model.Dependency> dependencies = dependencyManagement == null || dependencyManagement.getDependencies() == null
127                ? List.of()
128                : dependencyManagement.getDependencies();
129            HashMap<String, Dependency> dependencyMap = new HashMap<>(dependencies.size());
130            for (org.apache.maven.model.Dependency dependency : dependencies) {
131                String ga = dependency.getGroupId() + ":" + dependency.getArtifactId();
132                dependencyMap.putIfAbsent(ga, mavenDependencyToAetherDependency(dependency));
133            }
134            collectRequest.setManagedDependencies(new ArrayList<>(dependencyMap.values()));
135
136            artifacts.forEach(artifact -> {
137                if (StringUtils.isEmpty(artifact.getVersion())) {
138                    dependencyMap.computeIfPresent(artifact.getGroupId() + ":" + artifact.getArtifactId(), (coordinates, dependency) -> {
139                        collectRequest.addDependency(new Dependency(
140                            new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), dependency.getArtifact().getVersion()),
141                            JavaScopes.RUNTIME
142                        ));
143                        return dependency;
144                    });
145                } else {
146                    collectRequest.addDependency(new Dependency(artifact, JavaScopes.RUNTIME));
147                }
148            });
149        } else {
150            artifacts.map(artifact -> new Dependency(artifact, JavaScopes.RUNTIME)).forEach(collectRequest::addDependency);
151        }
152
153        DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, classpathFilter);
154        DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySession, dependencyRequest);
155        return dependencyResult.getArtifactResults();
156    }
157}