001package org.apache.maven.lifecycle.internal;
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.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.maven.RepositoryUtils;
032import org.apache.maven.artifact.Artifact;
033import org.apache.maven.artifact.ArtifactUtils;
034import org.apache.maven.artifact.factory.ArtifactFactory;
035import org.apache.maven.eventspy.internal.EventSpyDispatcher;
036import org.apache.maven.execution.MavenSession;
037import org.apache.maven.lifecycle.LifecycleExecutionException;
038import org.apache.maven.project.DefaultDependencyResolutionRequest;
039import org.apache.maven.project.DependencyResolutionException;
040import org.apache.maven.project.DependencyResolutionResult;
041import org.apache.maven.project.MavenProject;
042import org.apache.maven.project.ProjectDependenciesResolver;
043import org.apache.maven.project.artifact.InvalidDependencyVersionException;
044import org.codehaus.plexus.component.annotations.Component;
045import org.codehaus.plexus.component.annotations.Requirement;
046import org.codehaus.plexus.logging.Logger;
047import org.eclipse.aether.graph.Dependency;
048import org.eclipse.aether.graph.DependencyFilter;
049import org.eclipse.aether.graph.DependencyNode;
050import org.eclipse.aether.util.filter.AndDependencyFilter;
051import org.eclipse.aether.util.filter.ScopeDependencyFilter;
052
053/**
054 * Resolves dependencies for the artifacts in context of the lifecycle build
055 * 
056 * @since 3.0
057 * @author Benjamin Bentmann
058 * @author Jason van Zyl
059 * @author Kristian Rosenvold (extracted class)
060 *         <p/>
061 *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
062 */
063@Component( role = LifecycleDependencyResolver.class )
064public class LifecycleDependencyResolver
065{
066
067    @Requirement
068    private ProjectDependenciesResolver dependenciesResolver;
069
070    @Requirement
071    private Logger logger;
072
073    @Requirement
074    private ArtifactFactory artifactFactory;
075
076    @Requirement
077    private EventSpyDispatcher eventSpyDispatcher;
078
079    public LifecycleDependencyResolver()
080    {
081    }
082
083    public LifecycleDependencyResolver( ProjectDependenciesResolver projectDependenciesResolver, Logger logger )
084    {
085        this.dependenciesResolver = projectDependenciesResolver;
086        this.logger = logger;
087    }
088
089    public static List<MavenProject> getProjects( MavenProject project, MavenSession session, boolean aggregator )
090    {
091        if ( aggregator )
092        {
093            return session.getProjects();
094        }
095        else
096        {
097            return Collections.singletonList( project );
098        }
099    }
100
101    public void resolveProjectDependencies( MavenProject project, Collection<String> scopesToCollect,
102                                            Collection<String> scopesToResolve, MavenSession session,
103                                            boolean aggregating, Set<Artifact> projectArtifacts )
104        throws LifecycleExecutionException
105    {
106        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
107        try
108        {
109            ClassLoader projectRealm = project.getClassRealm();
110            if ( projectRealm != null && projectRealm != tccl )
111            {
112                Thread.currentThread().setContextClassLoader( projectRealm );
113            }
114
115            if ( project.getDependencyArtifacts() == null )
116            {
117                try
118                {
119                    project.setDependencyArtifacts( project.createArtifacts( artifactFactory, null, null ) );
120                }
121                catch ( InvalidDependencyVersionException e )
122                {
123                    throw new LifecycleExecutionException( e );
124                }
125            }
126
127            Set<Artifact> artifacts =
128                getDependencies( project, scopesToCollect, scopesToResolve, session, aggregating, projectArtifacts );
129
130            project.setResolvedArtifacts( artifacts );
131
132            Map<String, Artifact> map = new HashMap<String, Artifact>();
133            for ( Artifact artifact : artifacts )
134            {
135                map.put( artifact.getDependencyConflictId(), artifact );
136            }
137            for ( Artifact artifact : project.getDependencyArtifacts() )
138            {
139                if ( artifact.getFile() == null )
140                {
141                    Artifact resolved = map.get( artifact.getDependencyConflictId() );
142                    if ( resolved != null )
143                    {
144                        artifact.setFile( resolved.getFile() );
145                        artifact.setDependencyTrail( resolved.getDependencyTrail() );
146                        artifact.setResolvedVersion( resolved.getVersion() );
147                        artifact.setResolved( true );
148                    }
149                }
150            }
151        }
152        finally
153        {
154            Thread.currentThread().setContextClassLoader( tccl );
155        }
156    }
157
158    private Set<Artifact> getDependencies( MavenProject project, Collection<String> scopesToCollect,
159                                           Collection<String> scopesToResolve, MavenSession session,
160                                           boolean aggregating, Set<Artifact> projectArtifacts )
161        throws LifecycleExecutionException
162    {
163        if ( scopesToCollect == null )
164        {
165            scopesToCollect = Collections.emptySet();
166        }
167        if ( scopesToResolve == null )
168        {
169            scopesToResolve = Collections.emptySet();
170        }
171
172        if ( scopesToCollect.isEmpty() && scopesToResolve.isEmpty() )
173        {
174            return new LinkedHashSet<Artifact>();
175        }
176
177        scopesToCollect = new HashSet<String>( scopesToCollect );
178        scopesToCollect.addAll( scopesToResolve );
179
180        DependencyFilter collectionFilter = new ScopeDependencyFilter( null, negate( scopesToCollect ) );
181        DependencyFilter resolutionFilter = new ScopeDependencyFilter( null, negate( scopesToResolve ) );
182        resolutionFilter = AndDependencyFilter.newInstance( collectionFilter, resolutionFilter );
183        resolutionFilter =
184            AndDependencyFilter.newInstance( resolutionFilter, new ReactorDependencyFilter( projectArtifacts ) );
185
186        DependencyResolutionResult result;
187        try
188        {
189            DefaultDependencyResolutionRequest request =
190                new DefaultDependencyResolutionRequest( project, session.getRepositorySession() );
191            request.setResolutionFilter( resolutionFilter );
192
193            eventSpyDispatcher.onEvent( request );
194
195            result = dependenciesResolver.resolve( request );
196        }
197        catch ( DependencyResolutionException e )
198        {
199            result = e.getResult();
200
201            /*
202             * MNG-2277, the check below compensates for our bad plugin support where we ended up with aggregator
203             * plugins that require dependency resolution although they usually run in phases of the build where project
204             * artifacts haven't been assembled yet. The prime example of this is "mvn release:prepare".
205             */
206            if ( aggregating && areAllDependenciesInReactor( session.getProjects(), result.getUnresolvedDependencies() ) )
207            {
208                logger.warn( "The following dependencies could not be resolved at this point of the build"
209                    + " but seem to be part of the reactor:" );
210
211                for ( Dependency dependency : result.getUnresolvedDependencies() )
212                {
213                    logger.warn( "o " + dependency );
214                }
215
216                logger.warn( "Try running the build up to the lifecycle phase \"package\"" );
217            }
218            else
219            {
220                throw new LifecycleExecutionException( null, project, e );
221            }
222        }
223
224        eventSpyDispatcher.onEvent( result );
225
226        Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
227        if ( result.getDependencyGraph() != null && !result.getDependencyGraph().getChildren().isEmpty() )
228        {
229            RepositoryUtils.toArtifacts( artifacts, result.getDependencyGraph().getChildren(),
230                                         Collections.singletonList( project.getArtifact().getId() ), collectionFilter );
231        }
232        return artifacts;
233    }
234
235    private boolean areAllDependenciesInReactor( Collection<MavenProject> projects, Collection<Dependency> dependencies )
236    {
237        Set<String> projectKeys = getReactorProjectKeys( projects );
238
239        for ( Dependency dependency : dependencies )
240        {
241            org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
242            String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
243            if ( !projectKeys.contains( key ) )
244            {
245                return false;
246            }
247        }
248
249        return true;
250    }
251
252    private Set<String> getReactorProjectKeys( Collection<MavenProject> projects )
253    {
254        Set<String> projectKeys = new HashSet<String>( projects.size() * 2 );
255        for ( MavenProject project : projects )
256        {
257            String key = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
258            projectKeys.add( key );
259        }
260        return projectKeys;
261    }
262
263    private Collection<String> negate( Collection<String> scopes )
264    {
265        Collection<String> result = new HashSet<String>();
266        Collections.addAll( result, "system", "compile", "provided", "runtime", "test" );
267
268        for ( String scope : scopes )
269        {
270            if ( "compile".equals( scope ) )
271            {
272                result.remove( "compile" );
273                result.remove( "system" );
274                result.remove( "provided" );
275            }
276            else if ( "runtime".equals( scope ) )
277            {
278                result.remove( "compile" );
279                result.remove( "runtime" );
280            }
281            else if ( "compile+runtime".equals( scope ) )
282            {
283                result.remove( "compile" );
284                result.remove( "system" );
285                result.remove( "provided" );
286                result.remove( "runtime" );
287            }
288            else if ( "runtime+system".equals( scope ) )
289            {
290                result.remove( "compile" );
291                result.remove( "system" );
292                result.remove( "runtime" );
293            }
294            else if ( "test".equals( scope ) )
295            {
296                result.clear();
297            }
298        }
299
300        return result;
301    }
302
303    private static class ReactorDependencyFilter
304        implements DependencyFilter
305    {
306
307        private Set<String> keys = new HashSet<String>();
308
309        public ReactorDependencyFilter( Collection<Artifact> artifacts )
310        {
311            for ( Artifact artifact : artifacts )
312            {
313                String key = ArtifactUtils.key( artifact );
314                keys.add( key );
315            }
316        }
317
318        public boolean accept( DependencyNode node, List<DependencyNode> parents )
319        {
320            Dependency dependency = node.getDependency();
321            if ( dependency != null )
322            {
323                org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
324                String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
325                return !keys.contains( key );
326            }
327            return false;
328        }
329
330    }
331
332}