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.maven30.ConflictResolver;
37  import org.apache.maven.shared.dependency.graph.internal.maven30.JavaScopeDeriver;
38  import org.apache.maven.shared.dependency.graph.internal.maven30.Maven3DirectScopeDependencySelector;
39  import org.apache.maven.shared.dependency.graph.internal.maven30.NearestVersionSelector;
40  import org.apache.maven.shared.dependency.graph.internal.maven30.SimpleOptionalitySelector;
41  import org.apache.maven.shared.dependency.graph.internal.maven30.VerboseJavaScopeSelector;
42  import org.codehaus.plexus.component.annotations.Component;
43  import org.codehaus.plexus.component.annotations.Requirement;
44  import org.codehaus.plexus.logging.AbstractLogEnabled;
45  import org.sonatype.aether.RepositorySystem;
46  import org.sonatype.aether.RepositorySystemSession;
47  import org.sonatype.aether.artifact.ArtifactTypeRegistry;
48  import org.sonatype.aether.collection.CollectRequest;
49  import org.sonatype.aether.collection.CollectResult;
50  import org.sonatype.aether.collection.DependencyCollectionException;
51  import org.sonatype.aether.collection.DependencyGraphTransformer;
52  import org.sonatype.aether.collection.DependencySelector;
53  import org.sonatype.aether.graph.DependencyVisitor;
54  import org.sonatype.aether.graph.Exclusion;
55  import org.sonatype.aether.util.DefaultRepositorySystemSession;
56  import org.sonatype.aether.util.artifact.JavaScopes;
57  import org.sonatype.aether.util.graph.TreeDependencyVisitor;
58  import org.sonatype.aether.util.graph.selector.AndDependencySelector;
59  import org.sonatype.aether.util.graph.selector.ExclusionDependencySelector;
60  import org.sonatype.aether.util.graph.selector.OptionalDependencySelector;
61  import org.sonatype.aether.version.VersionConstraint;
62  
63  /**
64   * Project dependency raw dependency collector API, abstracting Maven 3's Aether implementation.
65   * 
66   * @author Gabriel Belingueres
67   * @since 3.1.0
68   */
69  @Component( role = DependencyCollectorBuilder.class, hint = "maven3" )
70  public class Maven3DependencyCollectorBuilder
71      extends AbstractLogEnabled
72      implements DependencyCollectorBuilder
73  {
74      @Requirement
75      private RepositorySystem repositorySystem;
76      
77      private final ExceptionHandler<DependencyCollectorBuilderException> exceptionHandler;
78      
79      public Maven3DependencyCollectorBuilder()
80      {
81          this.exceptionHandler = DependencyCollectorBuilderException::new;
82      }
83  
84      @Override
85      public DependencyNode collectDependencyGraph( ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
86          throws DependencyCollectorBuilderException
87      {
88          try
89          {
90              MavenProject project = buildingRequest.getProject();
91  
92              Artifact projectArtifact = project.getArtifact();
93              List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
94  
95              // throws ClassCastException (classloading issues?)
96              // DefaultRepositorySystemSession repositorySystemSession =
97              // (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession" );
98              RepositorySystemSession repositorySystemSession = buildingRequest.getRepositorySession();
99  
100             DefaultRepositorySystemSession session = new DefaultRepositorySystemSession( repositorySystemSession );
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 Maven3DirectScopeDependencySelector( JavaScopes.TEST ),
109                                            new OptionalDependencySelector(), 
110                                            new ExclusionDependencySelector() );
111             session.setDependencySelector( depFilter );
112 
113             session.setConfigProperty( ConflictResolver.CONFIG_PROP_VERBOSE, true );
114             session.setConfigProperty( "aether.dependencyManager.verbose", true );
115 
116             org.sonatype.aether.artifact.Artifact aetherArtifact =
117                 (org.sonatype.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
118                                                                         Artifact.class, projectArtifact,
119                                                                         exceptionHandler );
120 
121             @SuppressWarnings( "unchecked" )
122             List<org.sonatype.aether.repository.RemoteRepository> aetherRepos =
123                 (List<org.sonatype.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class,
124                                                                                         "toRepos", List.class,
125                                                                                         remoteArtifactRepositories,
126                                                                                         exceptionHandler );
127 
128             CollectRequest collectRequest = new CollectRequest();
129             collectRequest.setRoot( new org.sonatype.aether.graph.Dependency( aetherArtifact, "" ) );
130             collectRequest.setRepositories( aetherRepos );
131 
132             org.sonatype.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.sonatype.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     }
152 
153     private void logTree( org.sonatype.aether.graph.DependencyNode rootNode )
154     {
155         // print the node tree with its associated data Map
156         rootNode.accept( new TreeDependencyVisitor( new DependencyVisitor()
157         {
158             String indent = "";
159 
160             @Override
161             public boolean visitEnter( org.sonatype.aether.graph.DependencyNode dependencyNode )
162             {
163                 StringBuilder sb = new StringBuilder();
164                 sb.append( indent ).append( "Aether node: " ).append( dependencyNode );
165                 if ( !dependencyNode.getData().isEmpty() )
166                 {
167                     sb.append( "data map: " ).append( dependencyNode.getData() );
168                 }
169                 if ( dependencyNode.getPremanagedVersion() != null && !dependencyNode.getPremanagedVersion().isEmpty() )
170                 {
171                     sb.append( "Premanaged.version: " ).append( dependencyNode.getPremanagedVersion() );
172                 }
173                 if ( dependencyNode.getPremanagedScope() != null && !dependencyNode.getPremanagedScope().isEmpty() )
174                 {
175                     sb.append( "Premanaged.scope: " ).append( dependencyNode.getPremanagedScope() );
176                 }
177                 getLogger().debug( sb.toString() );
178                 indent += "    ";
179                 return true;
180             }
181 
182             @Override
183             public boolean visitLeave( org.sonatype.aether.graph.DependencyNode dependencyNode )
184             {
185                 indent = indent.substring( 0, indent.length() - 4 );
186                 return true;
187             }
188         } ) );
189     }
190 
191     private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
192                                                ArtifactTypeRegistry stereotypes )
193         throws DependencyCollectorBuilderException
194     {
195         if ( project.getDependencyManagement() != null )
196         {
197             for ( Dependency dependency : project.getDependencyManagement().getDependencies() )
198             {
199                 org.sonatype.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
200                 collectRequest.addManagedDependency( aetherDep );
201             }
202         }
203     }
204 
205     private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
206                                         org.sonatype.aether.artifact.ArtifactTypeRegistry stereotypes )
207         throws DependencyCollectorBuilderException
208     {
209         for ( Dependency dependency : project.getDependencies() )
210         {
211             org.sonatype.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
212             collectRequest.addDependency( aetherDep );
213         }
214     }
215 
216     // CHECKSTYLE_OFF: LineLength
217     private org.sonatype.aether.graph.Dependency toAetherDependency( org.sonatype.aether.artifact.ArtifactTypeRegistry stereotypes,
218                                                                     Dependency dependency )
219         throws DependencyCollectorBuilderException
220     {
221         return (org.sonatype.aether.graph.Dependency) Invoker.invoke( RepositoryUtils.class, "toDependency",
222                                                               Dependency.class,
223                                                                ArtifactTypeRegistry.class,
224                                                               dependency, stereotypes, exceptionHandler );
225     }
226     // CHECKSTYLE_ON: LineLength
227 
228     private Artifact getDependencyArtifact( org.sonatype.aether.graph.Dependency dep )
229     {
230         org.sonatype.aether.artifact.Artifact artifact = dep.getArtifact();
231 
232         try
233         {
234             Artifact mavenArtifact =
235                 (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
236                                            org.sonatype.aether.artifact.Artifact.class, artifact, exceptionHandler );
237 
238             mavenArtifact.setScope( dep.getScope() );
239             mavenArtifact.setOptional( dep.isOptional() );
240 
241             return mavenArtifact;
242         }
243         catch ( DependencyCollectorBuilderException e )
244         {
245             // ReflectionException should not happen
246             throw new RuntimeException( e.getMessage(), e );
247         }
248     }
249 
250     private DependencyNode buildDependencyNode( DependencyNode parent, org.sonatype.aether.graph.DependencyNode node,
251                                                 Artifact artifact, ArtifactFilter filter )
252     {
253         String premanagedVersion = node.getPremanagedVersion();
254         String premanagedScope = node.getPremanagedScope();
255 
256         Boolean optional = null;
257         if ( node.getDependency() != null )
258         {
259             optional = node.getDependency().isOptional();
260         }
261 
262         List<org.apache.maven.model.Exclusion> exclusions = null;
263         if ( node.getDependency() != null )
264         {
265             exclusions = new ArrayList<>( node.getDependency().getExclusions().size() );
266             for ( Exclusion exclusion : node.getDependency().getExclusions() )
267             {
268                 org.apache.maven.model.Exclusion modelExclusion = new org.apache.maven.model.Exclusion();
269                 modelExclusion.setGroupId( exclusion.getGroupId() );
270                 modelExclusion.setArtifactId( exclusion.getArtifactId() );
271                 exclusions.add( modelExclusion );
272             }
273         }
274 
275         org.sonatype.aether.graph.DependencyNode winner =
276             (org.sonatype.aether.graph.DependencyNode) node.getData().get( ConflictResolver.NODE_DATA_WINNER );
277         String winnerVersion = null;
278         String ignoredScope = null;
279         if ( winner != null )
280         {
281             winnerVersion = winner.getVersion().toString();
282         }
283         else
284         {
285             ignoredScope = (String) node.getData().get( VerboseJavaScopeSelector.REDUCED_SCOPE );
286         }
287 
288         ConflictData data = new ConflictData( winnerVersion, ignoredScope );
289 
290         VerboseDependencyNode current =
291             new VerboseDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
292                                        getVersionSelectedFromRange( node.getVersionConstraint() ), optional, exclusions,
293                                        data );
294 
295         List<DependencyNode> nodes = new ArrayList<>( node.getChildren().size() );
296         for ( org.sonatype.aether.graph.DependencyNode child : node.getChildren() )
297         {
298             Artifact childArtifact = getDependencyArtifact( child.getDependency() );
299 
300             if ( ( filter == null ) || filter.include( childArtifact ) )
301             {
302                 nodes.add( buildDependencyNode( current, child, childArtifact, filter ) );
303             }
304         }
305 
306         current.setChildren( Collections.unmodifiableList( nodes ) );
307 
308         return current;
309     }
310 
311     private String getVersionSelectedFromRange( VersionConstraint constraint )
312     {
313         if ( ( constraint == null ) || ( constraint.getVersion() != null ) || ( constraint.getRanges().isEmpty() ) )
314         {
315             return null;
316         }
317 
318         return constraint.getRanges().iterator().next().toString();
319     }
320 
321 }