001package org.apache.maven.project;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.maven.RepositoryUtils;
028import org.apache.maven.artifact.Artifact;
029import org.apache.maven.model.Dependency;
030import org.apache.maven.model.DependencyManagement;
031import org.apache.maven.model.Exclusion;
032import org.apache.maven.model.InputLocation;
033import org.apache.maven.model.InputSource;
034import org.codehaus.plexus.component.annotations.Component;
035import org.codehaus.plexus.component.annotations.Requirement;
036import org.codehaus.plexus.logging.Logger;
037import org.codehaus.plexus.util.StringUtils;
038import org.eclipse.aether.DefaultRepositorySystemSession;
039import org.eclipse.aether.RepositorySystem;
040import org.eclipse.aether.RepositorySystemSession;
041import org.eclipse.aether.RequestTrace;
042import org.eclipse.aether.artifact.ArtifactProperties;
043import org.eclipse.aether.artifact.ArtifactType;
044import org.eclipse.aether.artifact.ArtifactTypeRegistry;
045import org.eclipse.aether.collection.CollectRequest;
046import org.eclipse.aether.collection.DependencyCollectionException;
047import org.eclipse.aether.graph.DependencyFilter;
048import org.eclipse.aether.graph.DependencyNode;
049import org.eclipse.aether.graph.DependencyVisitor;
050import org.eclipse.aether.resolution.ArtifactResult;
051import org.eclipse.aether.resolution.DependencyRequest;
052import org.eclipse.aether.util.artifact.ArtifactIdUtils;
053import org.eclipse.aether.util.artifact.JavaScopes;
054import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
055
056/**
057 * @author Benjamin Bentmann
058 */
059@Component( role = ProjectDependenciesResolver.class )
060public class DefaultProjectDependenciesResolver
061    implements ProjectDependenciesResolver
062{
063
064    @Requirement
065    private Logger logger;
066
067    @Requirement
068    private RepositorySystem repoSystem;
069
070    @Requirement
071    private List<RepositorySessionDecorator> decorators;
072
073    public DependencyResolutionResult resolve( DependencyResolutionRequest request )
074        throws DependencyResolutionException
075    {
076        final RequestTrace trace = RequestTrace.newChild( null, request );
077
078        final DefaultDependencyResolutionResult result = new DefaultDependencyResolutionResult();
079
080        final MavenProject project = request.getMavenProject();
081        final DependencyFilter filter = request.getResolutionFilter();
082        RepositorySystemSession session = request.getRepositorySession();
083        ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
084
085        if ( logger.isDebugEnabled()
086            && session.getConfigProperties().get( DependencyManagerUtils.CONFIG_PROP_VERBOSE ) == null )
087        {
088            DefaultRepositorySystemSession verbose = new DefaultRepositorySystemSession( session );
089            verbose.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, Boolean.TRUE );
090            session = verbose;
091        }
092
093        for ( RepositorySessionDecorator decorator : decorators )
094        {
095            RepositorySystemSession decorated = decorator.decorate( project, session );
096            if ( decorated != null )
097            {
098                session = decorated;
099            }
100        }
101
102        CollectRequest collect = new CollectRequest();
103        collect.setRootArtifact( RepositoryUtils.toArtifact( project.getArtifact() ) );
104        collect.setRequestContext( "project" );
105        collect.setRepositories( project.getRemoteProjectRepositories() );
106
107        if ( project.getDependencyArtifacts() == null )
108        {
109            for ( Dependency dependency : project.getDependencies() )
110            {
111                if ( StringUtils.isEmpty( dependency.getGroupId() ) || StringUtils.isEmpty( dependency.getArtifactId() )
112                    || StringUtils.isEmpty( dependency.getVersion() ) )
113                {
114                    // guard against case where best-effort resolution for invalid models is requested
115                    continue;
116                }
117                collect.addDependency( RepositoryUtils.toDependency( dependency, stereotypes ) );
118            }
119        }
120        else
121        {
122            Map<String, Dependency> dependencies = new HashMap<>();
123            for ( Dependency dependency : project.getDependencies() )
124            {
125                String classifier = dependency.getClassifier();
126                if ( classifier == null )
127                {
128                    ArtifactType type = stereotypes.get( dependency.getType() );
129                    if ( type != null )
130                    {
131                        classifier = type.getClassifier();
132                    }
133                }
134                String key =
135                    ArtifactIdUtils.toVersionlessId( dependency.getGroupId(), dependency.getArtifactId(),
136                                                    dependency.getType(), classifier );
137                dependencies.put( key, dependency );
138            }
139            for ( Artifact artifact : project.getDependencyArtifacts() )
140            {
141                String key = artifact.getDependencyConflictId();
142                Dependency dependency = dependencies.get( key );
143                Collection<Exclusion> exclusions = dependency != null ? dependency.getExclusions() : null;
144                org.eclipse.aether.graph.Dependency dep = RepositoryUtils.toDependency( artifact, exclusions );
145                if ( !JavaScopes.SYSTEM.equals( dep.getScope() ) && dep.getArtifact().getFile() != null )
146                {
147                    // enable re-resolution
148                    org.eclipse.aether.artifact.Artifact art = dep.getArtifact();
149                    art = art.setFile( null ).setVersion( art.getBaseVersion() );
150                    dep = dep.setArtifact( art );
151                }
152                collect.addDependency( dep );
153            }
154        }
155
156        DependencyManagement depMngt = project.getDependencyManagement();
157        if ( depMngt != null )
158        {
159            for ( Dependency dependency : depMngt.getDependencies() )
160            {
161                collect.addManagedDependency( RepositoryUtils.toDependency( dependency, stereotypes ) );
162            }
163        }
164
165        DependencyRequest depRequest = new DependencyRequest( collect, filter );
166        depRequest.setTrace( trace );
167
168        DependencyNode node;
169        try
170        {
171            collect.setTrace( RequestTrace.newChild( trace, depRequest ) );
172            node = repoSystem.collectDependencies( session, collect ).getRoot();
173            result.setDependencyGraph( node );
174        }
175        catch ( DependencyCollectionException e )
176        {
177            result.setDependencyGraph( e.getResult().getRoot() );
178            result.setCollectionErrors( e.getResult().getExceptions() );
179
180            throw new DependencyResolutionException( result, "Could not resolve dependencies for project "
181                + project.getId() + ": " + e.getMessage(), e );
182        }
183
184        depRequest.setRoot( node );
185
186        if ( logger.isWarnEnabled() )
187        {
188            for ( DependencyNode child : node.getChildren() )
189            {
190                if ( !child.getRelocations().isEmpty() )
191                {
192                    logger.warn( "The artifact " + child.getRelocations().get( 0 ) + " has been relocated to "
193                        + child.getDependency().getArtifact() );
194                }
195            }
196        }
197
198        if ( logger.isDebugEnabled() )
199        {
200            node.accept( new GraphLogger( project ) );
201        }
202
203        try
204        {
205            process( result, repoSystem.resolveDependencies( session, depRequest ).getArtifactResults() );
206        }
207        catch ( org.eclipse.aether.resolution.DependencyResolutionException e )
208        {
209            process( result, e.getResult().getArtifactResults() );
210
211            throw new DependencyResolutionException( result, "Could not resolve dependencies for project "
212                + project.getId() + ": " + e.getMessage(), e );
213        }
214
215        return result;
216    }
217
218    private void process( DefaultDependencyResolutionResult result, Collection<ArtifactResult> results )
219    {
220        for ( ArtifactResult ar : results )
221        {
222            DependencyNode node = ar.getRequest().getDependencyNode();
223            if ( ar.isResolved() )
224            {
225                result.addResolvedDependency( node.getDependency() );
226            }
227            else
228            {
229                result.setResolutionErrors( node.getDependency(), ar.getExceptions() );
230            }
231        }
232    }
233
234    class GraphLogger
235        implements DependencyVisitor
236    {
237
238        private final MavenProject project;
239
240        private String indent = "";
241
242        private Map<String, Dependency> managed;
243
244        public GraphLogger( MavenProject project )
245        {
246            this.project = project;
247        }
248
249        public boolean visitEnter( DependencyNode node )
250        {
251            StringBuilder buffer = new StringBuilder( 128 );
252            buffer.append( indent );
253            org.eclipse.aether.graph.Dependency dep = node.getDependency();
254            if ( dep != null )
255            {
256                org.eclipse.aether.artifact.Artifact art = dep.getArtifact();
257
258                buffer.append( art );
259                buffer.append( ':' ).append( dep.getScope() );
260
261                String premanagedScope = DependencyManagerUtils.getPremanagedScope( node );
262                if ( premanagedScope != null && !premanagedScope.equals( dep.getScope() ) )
263                {
264                    buffer.append( " (scope managed from " ).append( premanagedScope );
265                    appendManagementSource( buffer, art, "scope" );
266                    buffer.append( ")" );
267                }
268
269                String premanagedVersion = DependencyManagerUtils.getPremanagedVersion( node );
270                if ( premanagedVersion != null && !premanagedVersion.equals( art.getVersion() ) )
271                {
272                    buffer.append( " (version managed from " ).append( premanagedVersion );
273                    appendManagementSource( buffer, art, "version" );
274                    buffer.append( ")" );
275                }
276            }
277            else
278            {
279                buffer.append( project.getGroupId() );
280                buffer.append( ':' ).append( project.getArtifactId() );
281                buffer.append( ':' ).append( project.getPackaging() );
282                buffer.append( ':' ).append( project.getVersion() );
283            }
284
285            logger.debug( buffer.toString() );
286            indent += "   ";
287            return true;
288        }
289
290        public boolean visitLeave( DependencyNode node )
291        {
292            indent = indent.substring( 0, indent.length() - 3 );
293            return true;
294        }
295
296        private void appendManagementSource( StringBuilder buffer, org.eclipse.aether.artifact.Artifact artifact,
297                                             String field )
298        {
299            if ( managed == null )
300            {
301                managed = new HashMap<>();
302                if ( project.getDependencyManagement() != null )
303                {
304                    for ( Dependency dep : project.getDependencyManagement().getDependencies() )
305                    {
306                        managed.put( dep.getManagementKey(), dep );
307                    }
308                }
309            }
310
311            String key =
312                ArtifactIdUtils.toVersionlessId( artifact.getGroupId(), artifact.getArtifactId(),
313                                                artifact.getProperty( ArtifactProperties.TYPE, "jar" ),
314                                                artifact.getClassifier() );
315
316            Dependency dependency = managed.get( key );
317            if ( dependency != null )
318            {
319                InputLocation location = dependency.getLocation( field );
320                if ( location != null )
321                {
322                    InputSource source = location.getSource();
323                    if ( source != null )
324                    {
325                        buffer.append( " by " ).append( source.getModelId() );
326                    }
327                }
328            }
329        }
330
331    }
332
333}