View Javadoc
1   package org.apache.maven.shared.dependency.graph.internal;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  
26  import org.apache.maven.RepositoryUtils;
27  import org.apache.maven.artifact.Artifact;
28  import org.apache.maven.artifact.repository.ArtifactRepository;
29  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
30  import org.apache.maven.model.Dependency;
31  import org.apache.maven.project.MavenProject;
32  import org.apache.maven.project.ProjectBuildingRequest;
33  import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
34  import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
35  import org.apache.maven.shared.dependency.graph.DependencyNode;
36  import org.apache.maven.shared.dependency.graph.internal.maven31.Maven31DirectScopeDependencySelector;
37  import org.apache.maven.shared.dependency.graph.internal.maven31.VerboseJavaScopeSelector;
38  import org.codehaus.plexus.component.annotations.Component;
39  import org.codehaus.plexus.component.annotations.Requirement;
40  import org.codehaus.plexus.logging.AbstractLogEnabled;
41  import org.eclipse.aether.DefaultRepositorySystemSession;
42  import org.eclipse.aether.RepositorySystem;
43  import org.eclipse.aether.artifact.ArtifactTypeRegistry;
44  import org.eclipse.aether.collection.CollectRequest;
45  import org.eclipse.aether.collection.CollectResult;
46  import org.eclipse.aether.collection.DependencyCollectionException;
47  import org.eclipse.aether.collection.DependencyGraphTransformer;
48  import org.eclipse.aether.collection.DependencySelector;
49  import org.eclipse.aether.graph.DependencyVisitor;
50  import org.eclipse.aether.graph.Exclusion;
51  import org.eclipse.aether.util.artifact.JavaScopes;
52  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
53  import org.eclipse.aether.util.graph.selector.AndDependencySelector;
54  import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
55  import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
56  import org.eclipse.aether.util.graph.transformer.ConflictResolver;
57  import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
58  import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
59  import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
60  import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
61  import org.eclipse.aether.version.VersionConstraint;
62  
63  /**
64   * Project dependency raw dependency collector API, abstracting Maven 3.1+'s Aether implementation.
65   * 
66   * @author Gabriel Belingueres
67   * @since 3.1.0
68   */
69  @Component( role = DependencyCollectorBuilder.class, hint = "maven31" )
70  public class Maven31DependencyCollectorBuilder
71      extends AbstractLogEnabled
72      implements DependencyCollectorBuilder
73  {
74      @Requirement
75      private RepositorySystem repositorySystem;
76      
77      private final ExceptionHandler<DependencyCollectorBuilderException> exceptionHandler;
78      
79      public Maven31DependencyCollectorBuilder()
80      {
81          this.exceptionHandler = DependencyCollectorBuilderException::new;
82      }
83  
84      @Override
85      public DependencyNode collectDependencyGraph( ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
86          throws DependencyCollectorBuilderException
87      {
88          DefaultRepositorySystemSession session = null;
89          try
90          {
91              MavenProject project = buildingRequest.getProject();
92  
93              Artifact projectArtifact = project.getArtifact();
94              List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
95  
96              DefaultRepositorySystemSession repositorySession =
97                  (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession",
98                                                                   exceptionHandler );
99  
100             session = new DefaultRepositorySystemSession( repositorySession );
101 
102             DependencyGraphTransformer transformer =
103                 new ConflictResolver( new NearestVersionSelector(), new VerboseJavaScopeSelector(),
104                                       new SimpleOptionalitySelector(), new JavaScopeDeriver() );
105             session.setDependencyGraphTransformer( transformer );
106 
107             DependencySelector depFilter =
108                 new AndDependencySelector( new Maven31DirectScopeDependencySelector( JavaScopes.TEST ),
109                                            new OptionalDependencySelector(),
110                                            new ExclusionDependencySelector() );
111             session.setDependencySelector( depFilter );
112 
113             session.setConfigProperty( ConflictResolver.CONFIG_PROP_VERBOSE, true );
114             session.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, true );
115 
116             org.eclipse.aether.artifact.Artifact aetherArtifact =
117                 (org.eclipse.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
118                                                                        Artifact.class, projectArtifact,
119                                                                        exceptionHandler );
120 
121             @SuppressWarnings( "unchecked" )
122             List<org.eclipse.aether.repository.RemoteRepository> aetherRepos =
123                 (List<org.eclipse.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class, "toRepos",
124                                                                                        List.class,
125                                                                                        remoteArtifactRepositories,
126                                                                                        exceptionHandler );
127 
128             CollectRequest collectRequest = new CollectRequest();
129             collectRequest.setRootArtifact( aetherArtifact );
130             collectRequest.setRepositories( aetherRepos );
131 
132             org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
133             collectDependencyList( collectRequest, project, stereotypes );
134             collectManagedDependencyList( collectRequest, project, stereotypes );
135 
136             CollectResult collectResult = repositorySystem.collectDependencies( session, collectRequest );
137 
138             org.eclipse.aether.graph.DependencyNode rootNode = collectResult.getRoot();
139 
140             if ( getLogger().isDebugEnabled() )
141             {
142                 logTree( rootNode );
143             }
144 
145             return buildDependencyNode( null, rootNode, projectArtifact, filter );
146         }
147         catch ( DependencyCollectionException e )
148         {
149             throw new DependencyCollectorBuilderException( "Could not collect dependencies: " + e.getResult(), e );
150         }
151         finally
152         {
153             if ( session != null )
154             {
155                 session.setReadOnly();
156             }
157         }
158     }
159 
160     private void logTree( org.eclipse.aether.graph.DependencyNode rootNode )
161     {
162         // print the node tree with its associated data Map
163         rootNode.accept( new TreeDependencyVisitor( new DependencyVisitor()
164         {
165             String indent = "";
166 
167             @Override
168             public boolean visitEnter( org.eclipse.aether.graph.DependencyNode dependencyNode )
169             {
170                 getLogger().debug( indent + "Aether node: " + dependencyNode + " data map: "
171                     + dependencyNode.getData() );
172                 indent += "    ";
173                 return true;
174             }
175 
176             @Override
177             public boolean visitLeave( org.eclipse.aether.graph.DependencyNode dependencyNode )
178             {
179                 indent = indent.substring( 0, indent.length() - 4 );
180                 return true;
181             }
182         } ) );
183     }
184 
185     private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
186                                                ArtifactTypeRegistry stereotypes )
187         throws DependencyCollectorBuilderException
188     {
189         if ( project.getDependencyManagement() != null )
190         {
191             for ( Dependency dependency : project.getDependencyManagement().getDependencies() )
192             {
193                 org.eclipse.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
194                 collectRequest.addManagedDependency( aetherDep );
195             }
196         }
197     }
198 
199     private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
200                                         org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes )
201         throws DependencyCollectorBuilderException
202     {
203         for ( Dependency dependency : project.getDependencies() )
204         {
205             org.eclipse.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
206             collectRequest.addDependency( aetherDep );
207         }
208     }
209 
210     // CHECKSTYLE_OFF: LineLength
211     private org.eclipse.aether.graph.Dependency toAetherDependency( org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes,
212                                                                     Dependency dependency )
213         throws DependencyCollectorBuilderException
214     {
215         return (org.eclipse.aether.graph.Dependency) Invoker.invoke( RepositoryUtils.class, "toDependency",
216                                                               Dependency.class,
217                                                               ArtifactTypeRegistry.class,
218                                                               dependency, stereotypes, exceptionHandler );
219     }
220     // CHECKSTYLE_ON: LineLength
221 
222     private Artifact getDependencyArtifact( org.eclipse.aether.graph.Dependency dep )
223     {
224         org.eclipse.aether.artifact.Artifact artifact = dep.getArtifact();
225 
226         try
227         {
228             Artifact mavenArtifact =
229                 (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
230                                            org.eclipse.aether.artifact.Artifact.class, artifact, exceptionHandler );
231 
232             mavenArtifact.setScope( dep.getScope() );
233             mavenArtifact.setOptional( dep.isOptional() );
234 
235             return mavenArtifact;
236         }
237         catch ( DependencyCollectorBuilderException e )
238         {
239             // ReflectionException should not happen
240             throw new RuntimeException( e.getMessage(), e );
241         }
242     }
243 
244     private DependencyNode buildDependencyNode( DependencyNode parent, org.eclipse.aether.graph.DependencyNode node,
245                                                 Artifact artifact, ArtifactFilter filter )
246     {
247         String premanagedVersion = DependencyManagerUtils.getPremanagedVersion( node );
248         String premanagedScope = DependencyManagerUtils.getPremanagedScope( node );
249 
250         Boolean optional = null;
251         if ( node.getDependency() != null )
252         {
253             optional = node.getDependency().isOptional();
254         }
255         
256         List<org.apache.maven.model.Exclusion> exclusions = null;
257         if ( node.getDependency() != null )
258         {
259             exclusions = new ArrayList<>( node.getDependency().getExclusions().size() );
260             for ( Exclusion exclusion : node.getDependency().getExclusions() )
261             {
262                 org.apache.maven.model.Exclusion modelExclusion = new org.apache.maven.model.Exclusion();
263                 modelExclusion.setGroupId( exclusion.getGroupId() );
264                 modelExclusion.setArtifactId( exclusion.getArtifactId() );
265                 exclusions.add( modelExclusion );
266             }
267         }
268 
269         org.eclipse.aether.graph.DependencyNode winner =
270             (org.eclipse.aether.graph.DependencyNode) node.getData().get( ConflictResolver.NODE_DATA_WINNER );
271         String winnerVersion = null;
272         String ignoredScope = null;
273         if ( winner != null )
274         {
275             winnerVersion = winner.getArtifact().getBaseVersion();
276         }
277         else
278         {
279             ignoredScope = (String) node.getData().get( VerboseJavaScopeSelector.REDUCED_SCOPE );
280         }
281         
282         ConflictData data = new ConflictData( winnerVersion, ignoredScope );
283         
284         VerboseDependencyNode current =
285             new VerboseDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
286                                        getVersionSelectedFromRange( node.getVersionConstraint() ), optional,
287                                        exclusions, data );
288 
289         List<DependencyNode> nodes = new ArrayList<>( node.getChildren().size() );
290         for ( org.eclipse.aether.graph.DependencyNode child : node.getChildren() )
291         {
292             Artifact childArtifact = getDependencyArtifact( child.getDependency() );
293 
294             if ( ( filter == null ) || filter.include( childArtifact ) )
295             {
296                 nodes.add( buildDependencyNode( current, child, childArtifact, filter ) );
297             }
298         }
299 
300         current.setChildren( Collections.unmodifiableList( nodes ) );
301 
302         return current;
303     }
304 
305     private String getVersionSelectedFromRange( VersionConstraint constraint )
306     {
307         if ( ( constraint == null ) || ( constraint.getVersion() != null ) )
308         {
309             return null;
310         }
311 
312         return constraint.getRange().toString();
313     }
314 }