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.mojo.dvc.dependency;
16  
17  import static com.google.common.base.Preconditions.checkNotNull;
18  import static com.google.common.collect.ImmutableSet.toImmutableSet;
19  
20  import org.basepom.mojo.dvc.Context;
21  import org.basepom.mojo.dvc.PluginLog;
22  import org.basepom.mojo.dvc.QualifiedName;
23  
24  import java.util.Objects;
25  
26  import com.google.common.base.Throwables;
27  import com.google.common.collect.ImmutableList;
28  import com.google.common.collect.ImmutableMap;
29  import com.google.common.collect.ImmutableSet;
30  import org.apache.maven.RepositoryUtils;
31  import org.apache.maven.project.DefaultDependencyResolutionRequest;
32  import org.apache.maven.project.DependencyResolutionException;
33  import org.apache.maven.project.DependencyResolutionRequest;
34  import org.apache.maven.project.DependencyResolutionResult;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.project.ProjectBuildingException;
37  import org.apache.maven.project.ProjectBuildingResult;
38  import org.eclipse.aether.artifact.Artifact;
39  import org.eclipse.aether.artifact.DefaultArtifact;
40  import org.eclipse.aether.graph.Dependency;
41  import org.eclipse.aether.graph.DependencyFilter;
42  import org.eclipse.aether.graph.DependencyNode;
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  public final class DependencyMapBuilder {
52  
53      private static final PluginLog LOG = new PluginLog(DependencyMapBuilder.class);
54  
55      private final Context context;
56  
57      public DependencyMapBuilder(final Context context) {
58          this.context = checkNotNull(context, "context is null");
59      }
60  
61      /**
62       * Create a map of dependencies for a given dependency node (representing an element on the dependency tree).
63       *
64       * @param dependencyNode     The dependency node to use.
65       * @param projectScopeFilter A scope limiting filter to mask out dependencies out of scope.
66       * @return A map of dependencies for this given dependency node.
67       * @throws DependencyResolutionException Dependency resolution failed.
68       * @throws ProjectBuildingException      Maven project could not be built.
69       */
70      public DependencyMap mapDependency(final DependencyNode dependencyNode,
71              final DependencyFilter projectScopeFilter)
72              throws DependencyResolutionException, ProjectBuildingException {
73          checkNotNull(dependencyNode, "dependencyNode is null");
74          checkNotNull(projectScopeFilter, "projectScopeFilter is null");
75  
76          // build the project
77          final ProjectBuildingResult result = context.getProjectBuilder()
78                  .build(convertFromAetherDependency(dependencyNode), false, context.createProjectBuildingRequest());
79  
80          // now resolve the project representing the dependency.
81          final MavenProject project = result.getProject();
82          return mapProject(project, projectScopeFilter);
83      }
84  
85      /**
86       * Create a map of names to dependencies for a given project.
87       *
88       * @param project     The current maven project.
89       * @param scopeFilter A scope limiting filter to mask out dependencies out of scope.
90       * @return A map of dependencies for this given dependency node.
91       * @throws DependencyResolutionException Dependency resolution failed.
92       */
93      public DependencyMap mapProject(final MavenProject project,
94              final DependencyFilter scopeFilter)
95              throws DependencyResolutionException {
96          checkNotNull(project, "project is null");
97          checkNotNull(scopeFilter, "scopeFilter is null");
98  
99          final DependencyResolutionRequest request = new DefaultDependencyResolutionRequest();
100         request.setRepositorySession(context.getRepositorySystemSession());
101         request.setMavenProject(project);
102         request.setResolutionFilter(scopeFilter);
103 
104         DependencyResolutionResult result;
105 
106         try {
107             result = context.getProjectDependenciesResolver().resolve(request);
108         } catch (DependencyResolutionException e) {
109             result = e.getResult();
110             // try to resolve using the reactor projects
111             final ImmutableSet<ProjectKey> reactorProjects = context.getReactorProjects().stream()
112                     .map(ProjectKey::fromProject).collect(toImmutableSet());
113 
114             // resolve all dependencies that are matched by the reactor.
115             final ImmutableSet<Dependency> reactorDependencies = result.getUnresolvedDependencies().stream()
116                     .filter(d -> reactorProjects.contains(ProjectKey.fromDependency(d)))
117                     .collect(toImmutableSet());
118 
119             result.getUnresolvedDependencies().removeAll(reactorDependencies);
120             result.getResolvedDependencies().addAll(reactorDependencies);
121 
122             if (!context.isUnresolvedSystemArtifactsFailBuild()) {
123                 final ImmutableSet<Dependency> systemDependencies = result.getUnresolvedDependencies().stream()
124                         .filter(d -> JavaScopes.SYSTEM.equals(d.getScope()))
125                         .collect(toImmutableSet());
126 
127                 result.getUnresolvedDependencies().removeAll(systemDependencies);
128                 result.getResolvedDependencies().addAll(systemDependencies);
129             }
130 
131             if (!context.isOptionalDependenciesMustExist()) {
132                 final ImmutableSet<Dependency> optionalDependencies = result.getUnresolvedDependencies().stream()
133                         .filter(Dependency::isOptional)
134                         .collect(toImmutableSet());
135 
136                 result.getUnresolvedDependencies().removeAll(optionalDependencies);
137                 result.getResolvedDependencies().addAll(optionalDependencies);
138             }
139 
140             if (!result.getUnresolvedDependencies().isEmpty()) {
141                 final Throwable t = Throwables.getRootCause(e);
142                 RemoteRepository repository = null;
143 
144                 if (t instanceof NoRepositoryLayoutException) {
145                     repository = ((NoRepositoryLayoutException) t).getRepository();
146                 } else if (t instanceof ArtifactTransferException) {
147                     repository = ((ArtifactTransferException) t).getRepository();
148                 }
149 
150                 if (repository != null && "legacy".equals(repository.getContentType())) {
151                     LOG.warn("Could not access a legacy repository for artifacts:  %s; Reason: %s", result.getUnresolvedDependencies(), t.getMessage());
152                 } else {
153                     throw e;
154                 }
155             }
156         }
157 
158         final DependencyNode graph = result.getDependencyGraph();
159         final ImmutableMap.Builder<QualifiedName, DependencyNode> dependencyCollector = ImmutableMap.builder();
160         final ImmutableMap<QualifiedName, DependencyNode> directDependencies = loadDependencyTree(graph, scopeFilter, dependencyCollector);
161         dependencyCollector.putAll(directDependencies);
162         return new DependencyMap(dependencyCollector.build(), directDependencies);
163     }
164 
165     private ImmutableMap<QualifiedName, DependencyNode> loadDependencyTree(final DependencyNode node,
166             final DependencyFilter filter,
167             final ImmutableMap.Builder<QualifiedName, DependencyNode> allDependencyCollector) {
168         final ImmutableMap.Builder<QualifiedName, DependencyNode> builder = ImmutableMap.builder();
169         for (final DependencyNode dependencyNode : node.getChildren()) {
170             if (dependencyNode.getManagedBits() != 0) {
171                 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_VERSION) != 0) {
172                     LOG.debug("%s -> Managed Version!", dependencyNode.getArtifact());
173                 }
174                 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_SCOPE) != 0) {
175                     LOG.debug("%s -> Managed Scope!", dependencyNode.getArtifact());
176                 }
177                 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) != 0) {
178                     LOG.debug("%s -> Managed Optional!", dependencyNode.getArtifact());
179                 }
180                 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_PROPERTIES) != 0) {
181                     LOG.debug("%s -> Managed Properties!", dependencyNode.getArtifact());
182                 }
183                 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_EXCLUSIONS) != 0) {
184                     LOG.debug("%s -> Managed Exclusions!", dependencyNode.getArtifact());
185                 }
186             }
187 
188             if (filter.accept(dependencyNode, ImmutableList.of(node))) {
189                 final QualifiedName name = QualifiedName.fromDependencyNode(dependencyNode);
190                 builder.put(name, dependencyNode);
191 
192                 allDependencyCollector.putAll(loadDependencyTree(dependencyNode, filter, allDependencyCollector));
193             }
194         }
195         return builder.build();
196     }
197 
198     static org.apache.maven.artifact.Artifact convertFromAetherDependency(final DependencyNode dependencyNode) {
199         Artifact aetherArtifact = convertToPomArtifact(dependencyNode.getArtifact());
200 
201         final org.apache.maven.artifact.Artifact mavenArtifact = RepositoryUtils.toArtifact(aetherArtifact);
202         mavenArtifact.setScope(dependencyNode.getDependency().getScope());
203         mavenArtifact.setOptional(dependencyNode.getDependency().isOptional());
204 
205         return mavenArtifact;
206     }
207 
208     static Artifact convertToPomArtifact(final Artifact artifact) {
209         // pom artifact has no classifier. If this is already a pom artifact, don't touch it.
210         if (artifact.getClassifier().isEmpty() && "pom".equals(artifact.getExtension())) {
211             return artifact;
212         }
213 
214         // create a POM artifact.
215         return new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), "pom", artifact.getVersion());
216     }
217 
218     private static final class ProjectKey {
219 
220         private final String groupId;
221         private final String artifactId;
222         private final String version;
223 
224         public static ProjectKey fromProject(final MavenProject project) {
225             checkNotNull(project, "project; is null");
226             return new ProjectKey(project.getGroupId(), project.getArtifactId(), project.getVersion());
227         }
228 
229         public static ProjectKey fromDependency(final Dependency dependency) {
230             checkNotNull(dependency, "artifact; is null");
231             return new ProjectKey(dependency.getArtifact().getGroupId(),
232                     dependency.getArtifact().getArtifactId(),
233                     dependency.getArtifact().getVersion());
234         }
235 
236         private ProjectKey(final String groupId, final String artifactId, final String version) {
237             this.groupId = checkNotNull(groupId, "groupId is null");
238             this.artifactId = checkNotNull(artifactId, "artifactId is null");
239             this.version = checkNotNull(version, "version is null");
240         }
241 
242         @Override
243         public boolean equals(Object o) {
244             if (this == o) {
245                 return true;
246             }
247             if (o == null || getClass() != o.getClass()) {
248                 return false;
249             }
250             ProjectKey that = (ProjectKey) o;
251             return groupId.equals(that.groupId)
252                     && artifactId.equals(that.artifactId)
253                     && version.equals(that.version);
254         }
255 
256         @Override
257         public int hashCode() {
258             return Objects.hash(groupId, artifactId, version);
259         }
260     }
261 }