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