View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.enforcer.rules.dependency;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  
24  import java.util.Arrays;
25  import java.util.List;
26  import java.util.Objects;
27  import java.util.stream.Collectors;
28  
29  import org.apache.maven.RepositoryUtils;
30  import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
31  import org.apache.maven.execution.MavenSession;
32  import org.apache.maven.model.DependencyManagement;
33  import org.apache.maven.project.MavenProject;
34  import org.eclipse.aether.DefaultRepositorySystemSession;
35  import org.eclipse.aether.RepositorySystem;
36  import org.eclipse.aether.RepositorySystemSession;
37  import org.eclipse.aether.artifact.ArtifactTypeRegistry;
38  import org.eclipse.aether.collection.CollectRequest;
39  import org.eclipse.aether.collection.DependencyCollectionException;
40  import org.eclipse.aether.graph.Dependency;
41  import org.eclipse.aether.graph.DependencyNode;
42  import org.eclipse.aether.graph.DependencyVisitor;
43  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
44  import org.eclipse.aether.util.graph.transformer.ConflictResolver;
45  import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
46  
47  import static java.util.Optional.ofNullable;
48  import static org.apache.maven.artifact.Artifact.SCOPE_PROVIDED;
49  import static org.apache.maven.artifact.Artifact.SCOPE_TEST;
50  
51  /**
52   * Resolver helper class.
53   */
54  @Named
55  class ResolverUtil {
56  
57      private final RepositorySystem repositorySystem;
58  
59      private final MavenSession session;
60  
61      /**
62       * Default constructor
63       */
64      @Inject
65      ResolverUtil(RepositorySystem repositorySystem, MavenSession session) {
66          this.repositorySystem = Objects.requireNonNull(repositorySystem);
67          this.session = Objects.requireNonNull(session);
68      }
69  
70      /**
71       * Retrieves the {@link DependencyNode} instance containing the result of the transitive dependency
72       * for the current {@link MavenProject} in verbose mode.
73       * <p>
74       * In verbose mode all nodes participating in a conflict are retained.
75       * </p>
76       * <p>
77       * Please consult {@link ConflictResolver} and {@link DependencyManagerUtils}>
78       * </p>
79       *
80       * @param excludedScopes the scopes of direct dependencies to ignore
81       * @return a Dependency Node which is the root of the project's dependency tree
82       * @throws EnforcerRuleException thrown if the lookup fails
83       */
84      DependencyNode resolveTransitiveDependenciesVerbose(List<String> excludedScopes) throws EnforcerRuleException {
85          return resolveTransitiveDependencies(true, true, excludedScopes);
86      }
87  
88      /**
89       * Retrieves the {@link DependencyNode} instance containing the result of the transitive dependency
90       * for the current {@link MavenProject}.
91       *
92       * @return a Dependency Node which is the root of the project's dependency tree
93       * @throws EnforcerRuleException thrown if the lookup fails
94       */
95      DependencyNode resolveTransitiveDependencies() throws EnforcerRuleException {
96          return resolveTransitiveDependencies(false, true, Arrays.asList(SCOPE_TEST, SCOPE_PROVIDED));
97      }
98  
99      /**
100      * Retrieves the {@link DependencyNode} instance containing the result of the transitive dependency
101      * for the current {@link MavenProject}.
102      *
103      * @param excludeOptional ignore optional project artifacts
104      * @param excludedScopes the scopes of direct dependencies to ignore
105      * @return a Dependency Node which is the root of the project's dependency tree
106      * @throws EnforcerRuleException thrown if the lookup fails
107      */
108     DependencyNode resolveTransitiveDependencies(boolean excludeOptional, List<String> excludedScopes)
109             throws EnforcerRuleException {
110         return resolveTransitiveDependencies(false, excludeOptional, excludedScopes);
111     }
112 
113     private DependencyNode resolveTransitiveDependencies(
114             boolean verbose, boolean excludeOptional, List<String> excludedScopes) throws EnforcerRuleException {
115 
116         try {
117             RepositorySystemSession repositorySystemSession = session.getRepositorySession();
118 
119             if (verbose) {
120                 DefaultRepositorySystemSession defaultRepositorySystemSession =
121                         new DefaultRepositorySystemSession(repositorySystemSession);
122                 defaultRepositorySystemSession.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true);
123                 defaultRepositorySystemSession.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true);
124                 repositorySystemSession = defaultRepositorySystemSession;
125             }
126 
127             MavenProject project = session.getCurrentProject();
128             ArtifactTypeRegistry artifactTypeRegistry =
129                     session.getRepositorySession().getArtifactTypeRegistry();
130 
131             List<Dependency> dependencies = project.getDependencies().stream()
132                     .filter(d -> !(excludeOptional && d.isOptional()))
133                     .filter(d -> !excludedScopes.contains(d.getScope()))
134                     .map(d -> RepositoryUtils.toDependency(d, artifactTypeRegistry))
135                     .collect(Collectors.toList());
136 
137             List<Dependency> managedDependencies = ofNullable(project.getDependencyManagement())
138                     .map(DependencyManagement::getDependencies)
139                     .map(list -> list.stream()
140                             .map(d -> RepositoryUtils.toDependency(d, artifactTypeRegistry))
141                             .collect(Collectors.toList()))
142                     .orElse(null);
143 
144             CollectRequest collectRequest =
145                     new CollectRequest(dependencies, managedDependencies, project.getRemoteProjectRepositories());
146             collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact()));
147 
148             return repositorySystem
149                     .collectDependencies(repositorySystemSession, collectRequest)
150                     .getRoot();
151 
152         } catch (DependencyCollectionException e) {
153             throw new EnforcerRuleException("Could not build dependency tree " + e.getLocalizedMessage(), e);
154         }
155     }
156 
157     /**
158      * Dump a {@link DependencyNode} as a tree.
159      *
160      * @param rootNode node to inspect
161      * @return dependency tree as String
162      */
163     public CharSequence dumpTree(DependencyNode rootNode) {
164         StringBuilder result = new StringBuilder(System.lineSeparator());
165 
166         rootNode.accept(new TreeDependencyVisitor(new DependencyVisitor() {
167             String indent = "";
168 
169             @Override
170             public boolean visitEnter(org.eclipse.aether.graph.DependencyNode dependencyNode) {
171                 result.append(indent);
172                 result.append("Node: ").append(dependencyNode);
173                 result.append(" data map: ").append(dependencyNode.getData());
174                 result.append(System.lineSeparator());
175                 indent += "  ";
176                 return true;
177             }
178 
179             @Override
180             public boolean visitLeave(org.eclipse.aether.graph.DependencyNode dependencyNode) {
181                 indent = indent.substring(0, indent.length() - 2);
182                 return true;
183             }
184         }));
185 
186         return result;
187     }
188 }