001    package 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    
022    import java.util.Collection;
023    import java.util.Collections;
024    import java.util.HashMap;
025    import java.util.HashSet;
026    import java.util.LinkedHashSet;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    
031    import org.apache.maven.RepositoryUtils;
032    import org.apache.maven.artifact.Artifact;
033    import org.apache.maven.artifact.ArtifactUtils;
034    import org.apache.maven.artifact.factory.ArtifactFactory;
035    import org.apache.maven.eventspy.internal.EventSpyDispatcher;
036    import org.apache.maven.execution.MavenSession;
037    import org.apache.maven.lifecycle.LifecycleExecutionException;
038    import org.apache.maven.project.DefaultDependencyResolutionRequest;
039    import org.apache.maven.project.DependencyResolutionException;
040    import org.apache.maven.project.DependencyResolutionResult;
041    import org.apache.maven.project.MavenProject;
042    import org.apache.maven.project.ProjectDependenciesResolver;
043    import org.apache.maven.project.artifact.InvalidDependencyVersionException;
044    import org.codehaus.plexus.component.annotations.Component;
045    import org.codehaus.plexus.component.annotations.Requirement;
046    import org.codehaus.plexus.logging.Logger;
047    import org.eclipse.aether.graph.Dependency;
048    import org.eclipse.aether.graph.DependencyFilter;
049    import org.eclipse.aether.graph.DependencyNode;
050    import org.eclipse.aether.util.filter.AndDependencyFilter;
051    import 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 )
064    public 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    }