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.project.MavenProject;
022import org.eclipse.aether.RepositorySystem;
023import org.eclipse.aether.RepositorySystemSession;
024import org.eclipse.aether.artifact.Artifact;
025import org.eclipse.aether.artifact.DefaultArtifact;
026import org.eclipse.aether.collection.CollectRequest;
027import org.eclipse.aether.graph.Dependency;
028import org.eclipse.aether.graph.DependencyFilter;
029import org.eclipse.aether.resolution.ArtifactResult;
030import org.eclipse.aether.resolution.DependencyRequest;
031import org.eclipse.aether.resolution.DependencyResolutionException;
032import org.eclipse.aether.resolution.DependencyResult;
033import org.eclipse.aether.util.artifact.JavaScopes;
034import org.eclipse.aether.util.filter.DependencyFilterUtils;
035
036import javax.inject.Inject;
037import javax.inject.Singleton;
038import java.io.File;
039import java.util.ArrayList;
040import java.util.HashMap;
041import java.util.List;
042import java.util.stream.Stream;
043
044/**
045 * Utility methods for performing dependency resolution.
046 */
047@Singleton
048public class DependencyResolutionService {
049
050    public static final String TEST_RESOURCES_GROUP = "io.micronaut.testresources";
051    public static final String TEST_RESOURCES_ARTIFACT_ID_PREFIX = "micronaut-test-resources-";
052
053    private static final String JAR_EXTENSION = "jar";
054
055    private final MavenSession mavenSession;
056
057    private final MavenProject mavenProject;
058
059    private final RepositorySystem repositorySystem;
060
061    @Inject
062    public DependencyResolutionService(MavenSession mavenSession, MavenProject mavenProject, RepositorySystem repositorySystem) {
063        this.mavenSession = mavenSession;
064        this.mavenProject = mavenProject;
065        this.repositorySystem = repositorySystem;
066    }
067
068    private static Stream<File> streamClasspath(List<ArtifactResult> resolutionResult) {
069        return resolutionResult
070            .stream()
071            .map(ArtifactResult::getArtifact)
072            .map(Artifact::getFile);
073    }
074
075    public static List<String> toClasspath(List<ArtifactResult> resolutionResult) {
076        return streamClasspath(resolutionResult)
077            .map(File::getAbsolutePath)
078            .toList();
079    }
080
081    public static List<File> toClasspathFiles(List<ArtifactResult> resolutionResult) {
082        return streamClasspath(resolutionResult)
083            .toList();
084    }
085
086    public static Dependency mavenDependencyToAetherDependency(org.apache.maven.model.Dependency d) {
087        Artifact artifact = mavenDependencyToAetherArtifact(d);
088        return new Dependency(artifact, d.getScope(), Boolean.valueOf(d.getOptional()));
089    }
090
091    public static Artifact mavenDependencyToAetherArtifact(org.apache.maven.model.Dependency d) {
092        return new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getClassifier(), d.getType(), d.getVersion());
093    }
094
095    public static MavenDependency mavenDependencyToTestResourcesDependency(org.apache.maven.model.Dependency d) {
096        return new MavenDependency(d.getGroupId(), d.getArtifactId(), d.getVersion());
097    }
098
099    public static Artifact testResourcesDependencyToAetherArtifact(MavenDependency d) {
100        return new DefaultArtifact(d.getGroup(), d.getArtifact(), JAR_EXTENSION, d.getVersion());
101    }
102
103    /**
104     * Performs a dependency request to compute the transitive dependencies of the given artifacts.
105     *
106     * @param artifacts The artifacts to resolve
107     * @param applyManagedDependencies Whether to apply the managed dependencies of the project
108     * @return The resolution result
109     */
110    public List<ArtifactResult> artifactResultsFor(Stream<Artifact> artifacts, boolean applyManagedDependencies) throws DependencyResolutionException {
111        RepositorySystemSession repositorySession = mavenSession.getRepositorySession();
112        DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME);
113        var collectRequest = new CollectRequest();
114        collectRequest.setRepositories(mavenProject.getRemoteProjectRepositories());
115
116        if (applyManagedDependencies) {
117            List<org.apache.maven.model.Dependency> dependencies = mavenProject.getDependencyManagement().getDependencies();
118            var dependencyMap = new HashMap<String, Dependency>(dependencies.size());
119            for (org.apache.maven.model.Dependency dependency : dependencies) {
120                String ga = dependency.getGroupId() + ":" + dependency.getArtifactId();
121                dependencyMap.putIfAbsent(ga, DependencyResolutionService.mavenDependencyToAetherDependency(dependency));
122            }
123            collectRequest.setManagedDependencies(new ArrayList<>(dependencyMap.values()));
124
125            artifacts.forEach(a -> {
126                if (StringUtils.isEmpty(a.getVersion())) {
127                    dependencyMap.computeIfPresent(a.getGroupId() + ":" + a.getArtifactId(), (coord, d) -> {
128                        collectRequest.addDependency(new Dependency(new DefaultArtifact(a.getGroupId(), a.getArtifactId(), a.getExtension(), d.getArtifact().getVersion()), JavaScopes.RUNTIME));
129                        return d;
130                    });
131                } else {
132                    collectRequest.addDependency(new Dependency(a, JavaScopes.RUNTIME));
133                }
134            });
135        } else {
136            artifacts.map(a -> new Dependency(a, JavaScopes.RUNTIME)).forEach(collectRequest::addDependency);
137        }
138
139        var dependencyRequest = new DependencyRequest(collectRequest, classpathFilter);
140
141        DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySession, dependencyRequest);
142        return dependencyResult.getArtifactResults();
143    }
144}