001package org.apache.maven;
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.io.File;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Date;
028import java.util.HashSet;
029import java.util.LinkedHashMap;
030import java.util.LinkedHashSet;
031import java.util.List;
032import java.util.Map;
033
034import org.apache.maven.artifact.ArtifactUtils;
035import org.apache.maven.execution.DefaultMavenExecutionResult;
036import org.apache.maven.execution.ExecutionEvent;
037import org.apache.maven.execution.MavenExecutionRequest;
038import org.apache.maven.execution.MavenExecutionResult;
039import org.apache.maven.execution.MavenSession;
040import org.apache.maven.execution.ProjectDependencyGraph;
041import org.apache.maven.graph.GraphBuilder;
042import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
043import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
044import org.apache.maven.lifecycle.internal.LifecycleStarter;
045import org.apache.maven.model.building.ModelProblem;
046import org.apache.maven.model.building.Result;
047import org.apache.maven.plugin.LegacySupport;
048import org.apache.maven.project.MavenProject;
049import org.apache.maven.project.ProjectBuilder;
050import org.apache.maven.repository.LocalRepositoryNotAccessibleException;
051import org.apache.maven.session.scope.internal.SessionScope;
052import org.codehaus.plexus.PlexusContainer;
053import org.codehaus.plexus.component.annotations.Component;
054import org.codehaus.plexus.component.annotations.Requirement;
055import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
056import org.codehaus.plexus.logging.Logger;
057import org.eclipse.aether.DefaultRepositorySystemSession;
058import org.eclipse.aether.RepositorySystemSession;
059import org.eclipse.aether.repository.WorkspaceReader;
060import org.eclipse.aether.util.repository.ChainedWorkspaceReader;
061
062import com.google.common.collect.Iterables;
063
064/**
065 * @author Jason van Zyl
066 */
067@Component( role = Maven.class )
068public class DefaultMaven
069    implements Maven
070{
071
072    @Requirement
073    private Logger logger;
074
075    @Requirement
076    protected ProjectBuilder projectBuilder;
077
078    @Requirement
079    private LifecycleStarter lifecycleStarter;
080
081    @Requirement
082    protected PlexusContainer container;
083
084    @Requirement
085    private ExecutionEventCatapult eventCatapult;
086
087    @Requirement
088    private LegacySupport legacySupport;
089
090    @Requirement
091    private SessionScope sessionScope;
092
093    @Requirement
094    private DefaultRepositorySystemSessionFactory repositorySessionFactory;
095
096    @Requirement( hint = GraphBuilder.HINT )
097    private GraphBuilder graphBuilder;
098
099    @Override
100    public MavenExecutionResult execute( MavenExecutionRequest request )
101    {
102        MavenExecutionResult result;
103
104        try
105        {
106            result = doExecute( request );
107        }
108        catch ( OutOfMemoryError e )
109        {
110            result = addExceptionToResult( new DefaultMavenExecutionResult(), e );
111        }
112        catch ( RuntimeException e )
113        {
114            //TODO Hack to make the cycle detection the same for the new graph builder
115            if ( e.getCause() instanceof ProjectCycleException )
116            {
117                result = addExceptionToResult( new DefaultMavenExecutionResult(), e.getCause() );
118            }
119            else
120            {
121                result = addExceptionToResult( new DefaultMavenExecutionResult(),
122                                               new InternalErrorException( "Internal error: " + e, e ) );
123            }
124        }
125        finally
126        {
127            legacySupport.setSession( null );
128        }
129
130        return result;
131    }
132
133    //
134    // 1) Setup initial properties.
135    //
136    // 2) Validate local repository directory is accessible.
137    //
138    // 3) Create RepositorySystemSession.
139    //
140    // 4) Create MavenSession.
141    //
142    // 5) Execute AbstractLifecycleParticipant.afterSessionStart(session)
143    //
144    // 6) Get reactor projects looking for general POM errors
145    //
146    // 7) Create ProjectDependencyGraph using trimming which takes into account --projects and reactor mode.
147    // This ensures that the projects passed into the ReactorReader are only those specified.
148    //
149    // 8) Create ReactorReader with the getProjectMap( projects ). NOTE that getProjectMap(projects) is the code that
150    // checks for duplicate projects definitions in the build. Ideally this type of duplicate checking should be
151    // part of getting the reactor projects in 6). The duplicate checking is conflated with getProjectMap(projects).
152    //
153    // 9) Execute AbstractLifecycleParticipant.afterProjectsRead(session)
154    //
155    // 10) Create ProjectDependencyGraph without trimming (as trimming was done in 7). A new topological sort is
156    // required after the execution of 9) as the AbstractLifecycleParticipants are free to mutate the MavenProject
157    // instances, which may change dependencies which can, in turn, affect the build order.
158    //
159    // 11) Execute LifecycleStarter.start()
160    //
161    @SuppressWarnings( "checkstyle:methodlength" )
162    private MavenExecutionResult doExecute( MavenExecutionRequest request )
163    {        
164        request.setStartTime( new Date() );
165
166        MavenExecutionResult result = new DefaultMavenExecutionResult();
167
168        try
169        {
170            validateLocalRepository( request );
171        }
172        catch ( LocalRepositoryNotAccessibleException e )
173        {
174            return addExceptionToResult( result, e );
175        }
176
177        //
178        // We enter the session scope right after the MavenSession creation and before any of the
179        // AbstractLifecycleParticipant lookups
180        // so that @SessionScoped components can be @Injected into AbstractLifecycleParticipants.
181        //
182        sessionScope.enter();
183        try
184        {
185            DefaultRepositorySystemSession repoSession =
186                (DefaultRepositorySystemSession) newRepositorySession( request );
187            MavenSession session = new MavenSession( container, repoSession, request, result );
188
189            sessionScope.seed( MavenSession.class, session );
190
191            legacySupport.setSession( session );
192
193            return doExecute( request, session, result, repoSession );
194        }
195        finally
196        {
197            sessionScope.exit();
198        }
199    }
200
201    private MavenExecutionResult doExecute( MavenExecutionRequest request, MavenSession session,
202                                            MavenExecutionResult result, DefaultRepositorySystemSession repoSession )
203    {
204        try
205        {
206            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( Collections
207                .<MavenProject>emptyList() ) )
208            {
209                listener.afterSessionStart( session );
210            }
211        }
212        catch ( MavenExecutionException e )
213        {
214            return addExceptionToResult( result, e );
215        }
216
217        eventCatapult.fire( ExecutionEvent.Type.ProjectDiscoveryStarted, session, null );
218
219        Result<? extends ProjectDependencyGraph> graphResult = buildGraph( session, result );
220        
221        if ( graphResult.hasErrors() )
222        {
223            return addExceptionToResult( result,
224                                         Iterables.toArray( graphResult.getProblems(), ModelProblem.class )[0]
225                                             .getException() );
226        }
227
228        try
229        {
230            session.setProjectMap( getProjectMap( session.getProjects() ) );
231        }
232        catch ( DuplicateProjectException e )
233        {
234            return addExceptionToResult( result, e );
235        }
236
237        WorkspaceReader reactorWorkspace;
238        try
239        {
240            reactorWorkspace = container.lookup( WorkspaceReader.class, ReactorReader.HINT );
241        }
242        catch ( ComponentLookupException e )
243        {
244            return addExceptionToResult( result, e );
245        }
246
247        //
248        // Desired order of precedence for local artifact repositories
249        //
250        // Reactor
251        // Workspace
252        // User Local Repository
253        //
254        repoSession.setWorkspaceReader( ChainedWorkspaceReader.newInstance( reactorWorkspace,
255                                                                            repoSession.getWorkspaceReader() ) );
256
257        repoSession.setReadOnly();
258
259        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
260        try
261        {
262            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( session.getProjects() ) )
263            {
264                Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() );
265
266                listener.afterProjectsRead( session );
267            }
268        }
269        catch ( MavenExecutionException e )
270        {
271            return addExceptionToResult( result, e );
272        }
273        finally
274        {
275            Thread.currentThread().setContextClassLoader( originalClassLoader );
276        }
277
278        //
279        // The projects need to be topologically after the participants have run their afterProjectsRead(session)
280        // because the participant is free to change the dependencies of a project which can potentially change the
281        // topological order of the projects, and therefore can potentially change the build order.
282        //
283        // Note that participants may affect the topological order of the projects but it is
284        // not expected that a participant will add or remove projects from the session.
285        //
286        
287        graphResult = buildGraph( session, result );
288        
289        if ( graphResult.hasErrors() )
290        {
291            return addExceptionToResult( result,
292                                         Iterables.toArray( graphResult.getProblems(), ModelProblem.class )[0]
293                                             .getException() );
294        }
295
296        try
297        {
298            if ( result.hasExceptions() )
299            {
300                return result;
301            }
302
303            result.setTopologicallySortedProjects( session.getProjects() );
304
305            result.setProject( session.getTopLevelProject() );
306
307            lifecycleStarter.execute( session );
308
309            validateActivatedProfiles( session.getProjects(), request.getActiveProfiles() );
310
311            if ( session.getResult().hasExceptions() )
312            {
313                return addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) );
314            }
315        }
316        finally
317        {
318            try
319            {
320                afterSessionEnd( session.getProjects(), session );
321            }
322            catch ( MavenExecutionException e )
323            {
324                return addExceptionToResult( result, e );
325            }
326        }
327
328        return result;
329    }
330
331    private void afterSessionEnd( Collection<MavenProject> projects, MavenSession session )
332        throws MavenExecutionException
333    {
334        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
335        try
336        {
337            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( projects ) )
338            {
339                Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() );
340
341                listener.afterSessionEnd( session );
342            }
343        }
344        finally
345        {
346            Thread.currentThread().setContextClassLoader( originalClassLoader );
347        }
348    }
349    
350    public RepositorySystemSession newRepositorySession( MavenExecutionRequest request )
351    {
352        return repositorySessionFactory.newRepositorySession( request );
353    }
354
355    private void validateLocalRepository( MavenExecutionRequest request )
356        throws LocalRepositoryNotAccessibleException
357    {
358        File localRepoDir = request.getLocalRepositoryPath();
359
360        logger.debug( "Using local repository at " + localRepoDir );
361
362        localRepoDir.mkdirs();
363
364        if ( !localRepoDir.isDirectory() )
365        {
366            throw new LocalRepositoryNotAccessibleException( "Could not create local repository at " + localRepoDir );
367        }
368    }
369
370    private Collection<AbstractMavenLifecycleParticipant> getLifecycleParticipants( Collection<MavenProject> projects )
371    {
372        Collection<AbstractMavenLifecycleParticipant> lifecycleListeners =
373            new LinkedHashSet<>();
374
375        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
376        try
377        {
378            try
379            {
380                lifecycleListeners.addAll( container.lookupList( AbstractMavenLifecycleParticipant.class ) );
381            }
382            catch ( ComponentLookupException e )
383            {
384                // this is just silly, lookupList should return an empty list!
385                logger.warn( "Failed to lookup lifecycle participants: " + e.getMessage() );
386            }
387
388            Collection<ClassLoader> scannedRealms = new HashSet<>();
389
390            for ( MavenProject project : projects )
391            {
392                ClassLoader projectRealm = project.getClassRealm();
393
394                if ( projectRealm != null && scannedRealms.add( projectRealm ) )
395                {
396                    Thread.currentThread().setContextClassLoader( projectRealm );
397
398                    try
399                    {
400                        lifecycleListeners.addAll( container.lookupList( AbstractMavenLifecycleParticipant.class ) );
401                    }
402                    catch ( ComponentLookupException e )
403                    {
404                        // this is just silly, lookupList should return an empty list!
405                        logger.warn( "Failed to lookup lifecycle participants: " + e.getMessage() );
406                    }
407                }
408            }
409        }
410        finally
411        {
412            Thread.currentThread().setContextClassLoader( originalClassLoader );
413        }
414
415        return lifecycleListeners;
416    }
417
418    private MavenExecutionResult addExceptionToResult( MavenExecutionResult result, Throwable e )
419    {
420        if ( !result.getExceptions().contains( e ) )
421        {
422            result.addException( e );
423        }
424
425        return result;
426    }
427
428    private void validateActivatedProfiles( List<MavenProject> projects, List<String> activeProfileIds )
429    {
430        Collection<String> notActivatedProfileIds = new LinkedHashSet<>( activeProfileIds );
431
432        for ( MavenProject project : projects )
433        {
434            for ( List<String> profileIds : project.getInjectedProfileIds().values() )
435            {
436                notActivatedProfileIds.removeAll( profileIds );
437            }
438        }
439
440        for ( String notActivatedProfileId : notActivatedProfileIds )
441        {
442            logger.warn( "The requested profile \"" + notActivatedProfileId
443                + "\" could not be activated because it does not exist." );
444        }
445    }
446
447    private Map<String, MavenProject> getProjectMap( Collection<MavenProject> projects )
448        throws DuplicateProjectException
449    {
450        Map<String, MavenProject> index = new LinkedHashMap<>();
451        Map<String, List<File>> collisions = new LinkedHashMap<>();
452
453        for ( MavenProject project : projects )
454        {
455            String projectId = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
456
457            MavenProject collision = index.get( projectId );
458
459            if ( collision == null )
460            {
461                index.put( projectId, project );
462            }
463            else
464            {
465                List<File> pomFiles = collisions.get( projectId );
466
467                if ( pomFiles == null )
468                {
469                    pomFiles = new ArrayList<>( Arrays.asList( collision.getFile(), project.getFile() ) );
470                    collisions.put( projectId, pomFiles );
471                }
472                else
473                {
474                    pomFiles.add( project.getFile() );
475                }
476            }
477        }
478
479        if ( !collisions.isEmpty() )
480        {
481            throw new DuplicateProjectException( "Two or more projects in the reactor"
482                + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>"
483                + " is unique for each project: " + collisions, collisions );
484        }
485
486        return index;
487    }
488
489    private Result<? extends ProjectDependencyGraph> buildGraph( MavenSession session, MavenExecutionResult result ) 
490    {
491        Result<? extends ProjectDependencyGraph> graphResult = graphBuilder.build( session );
492        for ( ModelProblem problem : graphResult.getProblems() )
493        {
494            if ( problem.getSeverity() == ModelProblem.Severity.WARNING )
495            {
496                logger.warn( problem.toString() );
497            }
498            else
499            {
500                logger.error( problem.toString() );
501            }
502        }
503
504        if ( !graphResult.hasErrors() )
505        {
506            ProjectDependencyGraph projectDependencyGraph = graphResult.get();
507            session.setProjects( projectDependencyGraph.getSortedProjects() );
508            session.setAllProjects( projectDependencyGraph.getSortedProjects() );
509            session.setProjectDependencyGraph( projectDependencyGraph );                
510        }
511        
512        return graphResult;        
513    }
514    
515    @Deprecated
516    // 5 January 2014
517    protected Logger getLogger()
518    {
519        return logger;
520    }
521}