View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package org.basepom.inline.mojo;
16  
17  import static com.google.common.base.Preconditions.checkNotNull;
18  import static com.google.common.collect.ImmutableSet.toImmutableSet;
19  
20  import java.util.List;
21  import java.util.Objects;
22  
23  import com.google.common.base.Throwables;
24  import com.google.common.collect.ImmutableList;
25  import com.google.common.collect.ImmutableSet;
26  import org.apache.maven.RepositoryUtils;
27  import org.apache.maven.execution.MavenSession;
28  import org.apache.maven.project.DefaultDependencyResolutionRequest;
29  import org.apache.maven.project.DefaultProjectBuildingRequest;
30  import org.apache.maven.project.DependencyResolutionException;
31  import org.apache.maven.project.DependencyResolutionRequest;
32  import org.apache.maven.project.DependencyResolutionResult;
33  import org.apache.maven.project.MavenProject;
34  import org.apache.maven.project.ProjectBuilder;
35  import org.apache.maven.project.ProjectBuildingException;
36  import org.apache.maven.project.ProjectBuildingRequest;
37  import org.apache.maven.project.ProjectBuildingResult;
38  import org.apache.maven.project.ProjectDependenciesResolver;
39  import org.eclipse.aether.artifact.Artifact;
40  import org.eclipse.aether.artifact.DefaultArtifact;
41  import org.eclipse.aether.graph.Dependency;
42  import org.eclipse.aether.graph.DependencyFilter;
43  import org.eclipse.aether.repository.RemoteRepository;
44  import org.eclipse.aether.transfer.ArtifactTransferException;
45  import org.eclipse.aether.transfer.NoRepositoryLayoutException;
46  import org.eclipse.aether.util.artifact.JavaScopes;
47  
48  /**
49   * Builds a map of dependencies required by a specific project or another dependency.
50   */
51  final class DependencyBuilder {
52  
53      private static final PluginLog LOG = new PluginLog(DependencyBuilder.class);
54  
55      private final MavenProject rootProject;
56      private final MavenSession mavenSession;
57      private final ProjectBuilder projectBuilder;
58      private final ProjectDependenciesResolver projectDependenciesResolver;
59      private final List<MavenProject> reactorProjects;
60  
61      DependencyBuilder(MavenProject rootProject, MavenSession mavenSession, ProjectBuilder projectBuilder,
62              ProjectDependenciesResolver projectDependenciesResolver, List<MavenProject> reactorProjects) {
63          this.rootProject = rootProject;
64          this.mavenSession = mavenSession;
65          this.projectBuilder = projectBuilder;
66          this.projectDependenciesResolver = projectDependenciesResolver;
67          this.reactorProjects = ImmutableList.copyOf(reactorProjects);
68      }
69  
70      /**
71       * Create a map of dependencies for a given dependency node (representing an element on the dependency tree).
72       *
73       * @param dependency         The dependency node to use.
74       * @param projectScopeFilter A scope limiting filter to mask out dependencies out of scope.
75       * @return A map of dependencies for this given dependency node.
76       * @throws DependencyResolutionException Dependency resolution failed.
77       * @throws ProjectBuildingException      Maven project could not be built.
78       */
79      ImmutableList<Dependency> mapDependency(final Dependency dependency,
80              final DependencyFilter projectScopeFilter)
81              throws DependencyResolutionException, ProjectBuildingException {
82          checkNotNull(dependency, "dependency is null");
83  
84          // build the project
85          final ProjectBuildingResult result = projectBuilder.build(convertFromAetherDependency(dependency), false, createProjectBuildingRequest());
86  
87          // now resolve the project representing the dependency.
88          final MavenProject project = result.getProject();
89          return mapProject(project, projectScopeFilter);
90      }
91  
92      /**
93       * Create a map of names to dependencies for a given project.
94       *
95       * @param project     The current maven project.
96       * @param scopeFilter A scope limiting filter to mask out dependencies out of scope.
97       * @return A map of dependencies for this given dependency node.
98       * @throws DependencyResolutionException Dependency resolution failed.
99       */
100     ImmutableList<Dependency> mapProject(final MavenProject project,
101             final DependencyFilter scopeFilter)
102             throws DependencyResolutionException {
103         checkNotNull(project, "project is null");
104 
105         final DependencyResolutionRequest request = new DefaultDependencyResolutionRequest();
106         request.setRepositorySession(createProjectBuildingRequest().getRepositorySession());
107         request.setMavenProject(project);
108         request.setResolutionFilter(scopeFilter);
109 
110         DependencyResolutionResult result;
111 
112         try {
113             result = projectDependenciesResolver.resolve(request);
114         } catch (DependencyResolutionException e) {
115             result = e.getResult();
116             // try to resolve using the reactor projects
117             final ImmutableSet<ProjectKey> reactorProjects = this.reactorProjects.stream()
118                     .map(ProjectKey::fromProject).collect(toImmutableSet());
119 
120             // resolve all dependencies that are matched by the reactor.
121             final ImmutableSet<Dependency> reactorDependencies = result.getUnresolvedDependencies().stream()
122                     .filter(d -> reactorProjects.contains(ProjectKey.fromDependency(d)))
123                     .collect(toImmutableSet());
124 
125             result.getUnresolvedDependencies().removeAll(reactorDependencies);
126             result.getResolvedDependencies().addAll(reactorDependencies);
127 
128             // remove all unresolved system dependencies
129             final ImmutableSet<Dependency> systemDependencies = result.getUnresolvedDependencies().stream()
130                     .filter(d -> JavaScopes.SYSTEM.equals(d.getScope()))
131                     .collect(toImmutableSet());
132 
133             result.getUnresolvedDependencies().removeAll(systemDependencies);
134             result.getResolvedDependencies().addAll(systemDependencies);
135 
136             // remove all unresolved optional dependencies
137             final ImmutableSet<Dependency> optionalDependencies = result.getUnresolvedDependencies().stream()
138                     .filter(Dependency::isOptional)
139                     .collect(toImmutableSet());
140 
141             result.getUnresolvedDependencies().removeAll(optionalDependencies);
142             result.getResolvedDependencies().addAll(optionalDependencies);
143 
144             if (!result.getUnresolvedDependencies().isEmpty()) {
145                 final Throwable t = Throwables.getRootCause(e);
146                 RemoteRepository repository = null;
147 
148                 if (t instanceof NoRepositoryLayoutException) {
149                     repository = ((NoRepositoryLayoutException) t).getRepository();
150                 } else if (t instanceof ArtifactTransferException) {
151                     repository = ((ArtifactTransferException) t).getRepository();
152                 }
153 
154                 if (repository != null && "legacy".equals(repository.getContentType())) {
155                     LOG.warn("Could not access a legacy repository for artifacts:  %s; Reason: %s", result.getUnresolvedDependencies(), t.getMessage());
156                 } else {
157                     throw e;
158                 }
159             }
160         }
161 
162         return ImmutableList.copyOf(result.getResolvedDependencies());
163     }
164 
165     static org.apache.maven.artifact.Artifact convertFromAetherDependency(final Dependency dependency) {
166         final var mavenArtifact = RepositoryUtils.toArtifact(convertToPomArtifact(dependency.getArtifact()));
167         mavenArtifact.setScope(dependency.getScope());
168         mavenArtifact.setOptional(dependency.isOptional());
169 
170         return mavenArtifact;
171     }
172 
173     private ProjectBuildingRequest createProjectBuildingRequest() {
174         DefaultProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(mavenSession.getProjectBuildingRequest());
175         buildingRequest.setRemoteRepositories(rootProject.getRemoteArtifactRepositories());
176         return buildingRequest;
177     }
178 
179 
180     static Artifact convertToPomArtifact(final Artifact artifact) {
181         // pom artifact has no classifier. If this is already a pom artifact, don't touch it.
182         if (artifact.getClassifier().isEmpty() && "pom".equals(artifact.getExtension())) {
183             return artifact;
184         }
185 
186         // create a POM artifact.
187         return new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), "pom", artifact.getVersion());
188     }
189 
190     private static final class ProjectKey {
191 
192         private final String groupId;
193         private final String artifactId;
194         private final String version;
195 
196         public static ProjectKey fromProject(final MavenProject project) {
197             checkNotNull(project, "project; is null");
198             return new ProjectKey(project.getGroupId(), project.getArtifactId(), project.getVersion());
199         }
200 
201         public static ProjectKey fromDependency(final Dependency dependency) {
202             checkNotNull(dependency, "artifact; is null");
203             return new ProjectKey(dependency.getArtifact().getGroupId(),
204                     dependency.getArtifact().getArtifactId(),
205                     dependency.getArtifact().getVersion());
206         }
207 
208         private ProjectKey(final String groupId, final String artifactId, final String version) {
209             this.groupId = checkNotNull(groupId, "groupId is null");
210             this.artifactId = checkNotNull(artifactId, "artifactId is null");
211             this.version = checkNotNull(version, "version is null");
212         }
213 
214         @Override
215         public boolean equals(Object o) {
216             if (this == o) {
217                 return true;
218             }
219             if (o == null || getClass() != o.getClass()) {
220                 return false;
221             }
222             ProjectKey that = (ProjectKey) o;
223             return groupId.equals(that.groupId)
224                     && artifactId.equals(that.artifactId)
225                     && version.equals(that.version);
226         }
227 
228         @Override
229         public int hashCode() {
230             return Objects.hash(groupId, artifactId, version);
231         }
232     }
233 }