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.io.IOException;
024import java.io.InputStream;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.LinkedHashMap;
033import java.util.LinkedHashSet;
034import java.util.List;
035import java.util.Map;
036import java.util.Properties;
037
038import org.apache.maven.artifact.ArtifactUtils;
039import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
040import org.apache.maven.eventspy.internal.EventSpyDispatcher;
041import org.apache.maven.execution.DefaultMavenExecutionResult;
042import org.apache.maven.execution.ExecutionEvent;
043import org.apache.maven.execution.MavenExecutionRequest;
044import org.apache.maven.execution.MavenExecutionRequestPopulationException;
045import org.apache.maven.execution.MavenExecutionRequestPopulator;
046import org.apache.maven.execution.MavenExecutionResult;
047import org.apache.maven.execution.MavenSession;
048import org.apache.maven.execution.ProjectDependencyGraph;
049import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
050import org.apache.maven.lifecycle.internal.LifecycleStarter;
051import org.apache.maven.model.Plugin;
052import org.apache.maven.model.building.ModelProblem;
053import org.apache.maven.model.building.ModelProblemUtils;
054import org.apache.maven.model.building.ModelSource;
055import org.apache.maven.model.building.UrlModelSource;
056import org.apache.maven.plugin.LegacySupport;
057import org.apache.maven.project.MavenProject;
058import org.apache.maven.project.ProjectBuilder;
059import org.apache.maven.project.ProjectBuildingException;
060import org.apache.maven.project.ProjectBuildingRequest;
061import org.apache.maven.project.ProjectBuildingResult;
062import org.apache.maven.repository.LocalRepositoryNotAccessibleException;
063import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
064import org.apache.maven.settings.Mirror;
065import org.apache.maven.settings.Proxy;
066import org.apache.maven.settings.Server;
067import org.apache.maven.settings.building.SettingsProblem;
068import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
069import org.apache.maven.settings.crypto.SettingsDecrypter;
070import org.apache.maven.settings.crypto.SettingsDecryptionResult;
071import org.codehaus.plexus.PlexusContainer;
072import org.codehaus.plexus.component.annotations.Component;
073import org.codehaus.plexus.component.annotations.Requirement;
074import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
075import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
076import org.codehaus.plexus.logging.Logger;
077import org.codehaus.plexus.util.IOUtil;
078import org.codehaus.plexus.util.StringUtils;
079import org.codehaus.plexus.util.dag.CycleDetectedException;
080import org.codehaus.plexus.util.xml.Xpp3Dom;
081import org.eclipse.aether.ConfigurationProperties;
082import org.eclipse.aether.DefaultRepositorySystemSession;
083import org.eclipse.aether.RepositorySystem;
084import org.eclipse.aether.RepositorySystemSession;
085import org.eclipse.aether.repository.LocalRepository;
086import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
087import org.eclipse.aether.repository.RepositoryPolicy;
088import org.eclipse.aether.repository.WorkspaceReader;
089import org.eclipse.aether.resolution.ResolutionErrorPolicy;
090import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
091import org.eclipse.aether.util.repository.AuthenticationBuilder;
092import org.eclipse.aether.util.repository.ChainedWorkspaceReader;
093import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
094import org.eclipse.aether.util.repository.DefaultMirrorSelector;
095import org.eclipse.aether.util.repository.DefaultProxySelector;
096import org.eclipse.aether.util.repository.SimpleResolutionErrorPolicy;
097
098/**
099 * @author Jason van Zyl
100 */
101@Component( role = Maven.class )
102public class DefaultMaven
103    implements Maven
104{
105
106    @Requirement
107    private Logger logger;
108
109    @Requirement
110    protected ProjectBuilder projectBuilder;
111
112    @Requirement
113    private LifecycleStarter lifecycleStarter;
114
115    @Requirement
116    protected PlexusContainer container;
117
118    @Requirement
119    MavenExecutionRequestPopulator populator;
120
121    @Requirement
122    private ExecutionEventCatapult eventCatapult;
123
124    @Requirement
125    private ArtifactHandlerManager artifactHandlerManager;
126
127    @Requirement( optional = true, hint = "ide" )
128    private WorkspaceReader workspaceRepository;
129
130    @Requirement
131    private RepositorySystem repoSystem;
132
133    @Requirement( optional = true, hint = "simple" )
134    private LocalRepositoryManagerFactory simpleLocalRepositoryManagerFactory;
135
136    @Requirement
137    private SettingsDecrypter settingsDecrypter;
138
139    @Requirement
140    private LegacySupport legacySupport;
141
142    @Requirement
143    private EventSpyDispatcher eventSpyDispatcher;
144
145    @Requirement
146    private SessionScope sessionScope;
147        
148    public MavenExecutionResult execute( MavenExecutionRequest request )
149    {
150        MavenExecutionResult result;
151
152        try
153        {
154            result = doExecute( populator.populateDefaults( request ) );
155        }
156        catch ( OutOfMemoryError e )
157        {
158            result = addExceptionToResult( new DefaultMavenExecutionResult(), e );
159        }
160        catch ( MavenExecutionRequestPopulationException e )
161        {
162            result = addExceptionToResult( new DefaultMavenExecutionResult(), e );
163        }
164        catch ( RuntimeException e )
165        {
166            result =
167                addExceptionToResult( new DefaultMavenExecutionResult(), new InternalErrorException( "Internal error: "
168                    + e, e ) );
169        }
170        finally
171        {
172            legacySupport.setSession( null );
173        }
174
175        return result;
176    }
177
178    // 
179    // 1) Setup initial properties.
180    //
181    // 2) Validate local repository directory is accessible.
182    //
183    // 3) Create RepositorySystemSession.
184    //
185    // 4) Create MavenSession.
186    //
187    // 5) Execute AbstractLifecycleParticipant.afterSessionStart(session)
188    //
189    // 6) Get reactor projects looking for general POM errors
190    //
191    // 7) Create ProjectDependencyGraph using trimming which takes into account --projects and reactor mode. This ensures
192    //    that the projects passed into the ReactorReader are only those specified.
193    //
194    // 8) Create ReactorReader with the getProjectMap( projects ). NOTE that getProjectMap(projects) is the code that
195    //    checks for duplicate projects definitions in the build. Ideally this type of duplicate checking should be part of
196    //    getting the reactor projects in 6). The duplicate checking is conflated with getProjectMap(projects).
197    //
198    // 9) Execute AbstractLifecycleParticipant.afterProjectsRead(session)
199    //
200    // 10) Create ProjectDependencyGraph without trimming (as trimming was done in 7). A new topological sort is required after
201    //     the execution of 9) as the AbstractLifecycleParticipants are free to mutate the MavenProject instances, which may change
202    //     dependencies which can, in turn, affect the build order.
203    // 
204    // 11) Execute LifecycleStarter.start()
205    //    
206    private MavenExecutionResult doExecute( MavenExecutionRequest request )
207    {
208        request.setStartTime( new Date() );
209        
210        MavenExecutionResult result = new DefaultMavenExecutionResult();
211
212        try
213        {
214            validateLocalRepository( request );
215        }
216        catch ( LocalRepositoryNotAccessibleException e )
217        {
218            return addExceptionToResult( result, e );
219        }
220
221        DefaultRepositorySystemSession repoSession = (DefaultRepositorySystemSession) newRepositorySession( request );
222
223        MavenSession session = new MavenSession( container, repoSession, request, result );
224        legacySupport.setSession( session );
225
226        try
227        {
228            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( Collections.<MavenProject> emptyList() ) )
229            {
230                listener.afterSessionStart( session );
231            }
232        }
233        catch ( MavenExecutionException e )
234        {
235            return addExceptionToResult( result, e );
236        }
237
238        eventCatapult.fire( ExecutionEvent.Type.ProjectDiscoveryStarted, session, null );
239
240        List<MavenProject> projects;
241        try
242        {
243            projects = getProjectsForMavenReactor( session );
244            //
245            // Capture the full set of projects before any potential constraining is performed by --projects
246            //
247            session.setAllProjects( projects );
248        }
249        catch ( ProjectBuildingException e )
250        {
251            return addExceptionToResult( result, e );
252        }
253
254        validateProjects( projects );
255
256        //
257        // This creates the graph and trims the projects down based on the user request using something like:
258        //
259        // -pl project0,project2 eclipse:eclipse
260        //
261        ProjectDependencyGraph projectDependencyGraph = createProjectDependencyGraph( projects, request, result, true );
262
263        if ( result.hasExceptions() )
264        {
265            return result;
266        }
267
268        session.setProjects( projectDependencyGraph.getSortedProjects() );
269
270        try
271        {
272            session.setProjectMap( getProjectMap( session.getProjects() ) );
273        }
274        catch ( DuplicateProjectException e )
275        {
276            return addExceptionToResult( result, e );
277        }
278        
279        WorkspaceReader reactorWorkspace;
280        sessionScope.enter();
281        sessionScope.seed( MavenSession.class, session );
282        try
283        {
284            reactorWorkspace = container.lookup( WorkspaceReader.class, ReactorReader.HINT );
285        }
286        catch ( ComponentLookupException e )
287        {
288            return addExceptionToResult( result, e );
289        }
290        
291        //
292        // Desired order of precedence for local artifact repositories
293        //
294        // Reactor
295        // Workspace
296        // User Local Repository
297        //        
298        repoSession.setWorkspaceReader( ChainedWorkspaceReader.newInstance( reactorWorkspace,
299                                                                            repoSession.getWorkspaceReader() ) );
300
301        repoSession.setReadOnly();
302
303        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
304        try
305        {
306            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( projects ) )
307            {
308                Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() );
309
310                listener.afterProjectsRead( session );
311            }
312        }
313        catch ( MavenExecutionException e )
314        {
315            return addExceptionToResult( result, e );
316        }
317        finally
318        {
319            Thread.currentThread().setContextClassLoader( originalClassLoader );
320        }
321
322        //
323        // The projects need to be topologically after the participants have run their afterProjectsRead(session)
324        // because the participant is free to change the dependencies of a project which can potentially change the
325        // topological order of the projects, and therefore can potentially change the build order.
326        //
327        // Note that participants may affect the topological order of the projects but it is
328        // not expected that a participant will add or remove projects from the session.
329        //
330        projectDependencyGraph = createProjectDependencyGraph( session.getProjects(), request, result, false );
331
332        try
333        {
334            if ( result.hasExceptions() )
335            {
336                return result;
337            }
338
339            session.setProjects( projectDependencyGraph.getSortedProjects() );
340
341            session.setProjectDependencyGraph( projectDependencyGraph );
342
343            result.setTopologicallySortedProjects( session.getProjects() );
344
345            result.setProject( session.getTopLevelProject() );
346
347            lifecycleStarter.execute( session );
348
349            validateActivatedProfiles( session.getProjects(), request.getActiveProfiles() );
350
351            if ( session.getResult().hasExceptions() )
352            {
353                return addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) );
354            }
355        }
356        finally
357        {
358            try
359            {
360                afterSessionEnd( projects, session );
361            }
362            catch ( MavenExecutionException e )
363            {
364                return addExceptionToResult( result, e );
365            }
366            finally
367            {
368                sessionScope.exit();
369            }
370        }
371
372        return result;
373    }
374
375    private void afterSessionEnd( Collection<MavenProject> projects, MavenSession session ) 
376        throws MavenExecutionException
377    {
378        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
379        try
380        {
381            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( projects ) )
382            {
383                Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() );
384
385                listener.afterSessionEnd( session );
386            }
387        }
388        finally
389        {
390            Thread.currentThread().setContextClassLoader( originalClassLoader );
391        }
392    }
393
394    public RepositorySystemSession newRepositorySession( MavenExecutionRequest request )
395    {
396        DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
397
398        session.setCache( request.getRepositoryCache() );
399
400        Map<Object, Object> configProps = new LinkedHashMap<Object, Object>();
401        configProps.put( ConfigurationProperties.USER_AGENT, getUserAgent() );
402        configProps.put( ConfigurationProperties.INTERACTIVE, request.isInteractiveMode() );
403        configProps.putAll( request.getSystemProperties() );
404        configProps.putAll( request.getUserProperties() );
405
406        session.setOffline( request.isOffline() );
407        session.setChecksumPolicy( request.getGlobalChecksumPolicy() );
408        if ( request.isNoSnapshotUpdates() )
409        {
410            session.setUpdatePolicy( RepositoryPolicy.UPDATE_POLICY_NEVER );
411        }
412        else if ( request.isUpdateSnapshots() )
413        {
414            session.setUpdatePolicy( RepositoryPolicy.UPDATE_POLICY_ALWAYS );
415        }
416        else
417        {
418            session.setUpdatePolicy( null );
419        }
420
421        int errorPolicy = 0;
422        errorPolicy |= request.isCacheNotFound() ? ResolutionErrorPolicy.CACHE_NOT_FOUND : 0;
423        errorPolicy |= request.isCacheTransferError() ? ResolutionErrorPolicy.CACHE_TRANSFER_ERROR : 0;
424        session.setResolutionErrorPolicy( new SimpleResolutionErrorPolicy( errorPolicy, errorPolicy
425            | ResolutionErrorPolicy.CACHE_NOT_FOUND ) );
426
427        session.setArtifactTypeRegistry( RepositoryUtils.newArtifactTypeRegistry( artifactHandlerManager ) );
428
429        LocalRepository localRepo = new LocalRepository( request.getLocalRepository().getBasedir() );
430
431        if ( request.isUseLegacyLocalRepository() )
432        {
433            logger.warn( "Disabling enhanced local repository: using legacy is strongly discouraged to ensure build reproducibility." );
434            try
435            {
436                session.setLocalRepositoryManager( simpleLocalRepositoryManagerFactory.newInstance( session, localRepo ) );
437            }
438            catch ( NoLocalRepositoryManagerException e )
439            {
440
441                logger.warn( "Failed to configure legacy local repository: back to default" );
442                session.setLocalRepositoryManager( repoSystem.newLocalRepositoryManager( session, localRepo ) );
443            }
444        }
445        else
446        {
447            session.setLocalRepositoryManager( repoSystem.newLocalRepositoryManager( session, localRepo ) );
448        }
449
450        if ( request.getWorkspaceReader() != null )
451        {
452            session.setWorkspaceReader( request.getWorkspaceReader() );
453        }
454        else
455        {
456            session.setWorkspaceReader( workspaceRepository );
457        }
458
459        DefaultSettingsDecryptionRequest decrypt = new DefaultSettingsDecryptionRequest();
460        decrypt.setProxies( request.getProxies() );
461        decrypt.setServers( request.getServers() );
462        SettingsDecryptionResult decrypted = settingsDecrypter.decrypt( decrypt );
463
464        if ( logger.isDebugEnabled() )
465        {
466            for ( SettingsProblem problem : decrypted.getProblems() )
467            {
468                logger.debug( problem.getMessage(), problem.getException() );
469            }
470        }
471
472        DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector();
473        for ( Mirror mirror : request.getMirrors() )
474        {
475            mirrorSelector.add( mirror.getId(), mirror.getUrl(), mirror.getLayout(), false, mirror.getMirrorOf(),
476                                mirror.getMirrorOfLayouts() );
477        }
478        session.setMirrorSelector( mirrorSelector );
479
480        DefaultProxySelector proxySelector = new DefaultProxySelector();
481        for ( Proxy proxy : decrypted.getProxies() )
482        {
483            AuthenticationBuilder authBuilder = new AuthenticationBuilder();
484            authBuilder.addUsername( proxy.getUsername() ).addPassword( proxy.getPassword() );
485            proxySelector.add( new org.eclipse.aether.repository.Proxy( proxy.getProtocol(), proxy.getHost(),
486                                                                        proxy.getPort(), authBuilder.build() ),
487                               proxy.getNonProxyHosts() );
488        }
489        session.setProxySelector( proxySelector );
490
491        DefaultAuthenticationSelector authSelector = new DefaultAuthenticationSelector();
492        for ( Server server : decrypted.getServers() )
493        {
494            AuthenticationBuilder authBuilder = new AuthenticationBuilder();
495            authBuilder.addUsername( server.getUsername() ).addPassword( server.getPassword() );
496            authBuilder.addPrivateKey( server.getPrivateKey(), server.getPassphrase() );
497            authSelector.add( server.getId(), authBuilder.build() );
498
499            if ( server.getConfiguration() != null )
500            {
501                Xpp3Dom dom = (Xpp3Dom) server.getConfiguration();
502                for ( int i = dom.getChildCount() - 1; i >= 0; i-- )
503                {
504                    Xpp3Dom child = dom.getChild( i );
505                    if ( "wagonProvider".equals( child.getName() ) )
506                    {
507                        dom.removeChild( i );
508                    }
509                }
510
511                XmlPlexusConfiguration config = new XmlPlexusConfiguration( dom );
512                configProps.put( "aether.connector.wagon.config." + server.getId(), config );
513            }
514
515            configProps.put( "aether.connector.perms.fileMode." + server.getId(), server.getFilePermissions() );
516            configProps.put( "aether.connector.perms.dirMode." + server.getId(), server.getDirectoryPermissions() );
517        }
518        session.setAuthenticationSelector( authSelector );
519
520        session.setTransferListener( request.getTransferListener() );
521
522        session.setRepositoryListener( eventSpyDispatcher.chainListener( new LoggingRepositoryListener( logger ) ) );
523
524        session.setUserProperties( request.getUserProperties() );
525        session.setSystemProperties( request.getSystemProperties() );
526        session.setConfigProperties( configProps );
527
528        return session;
529    }
530
531    private String getUserAgent()
532    {
533        return "Apache-Maven/" + getMavenVersion() + " (Java " + System.getProperty( "java.version" ) + "; "
534            + System.getProperty( "os.name" ) + " " + System.getProperty( "os.version" ) + ")";
535    }
536
537    private String getMavenVersion()
538    {
539        Properties props = new Properties();
540
541        InputStream is = getClass().getResourceAsStream( "/META-INF/maven/org.apache.maven/maven-core/pom.properties" );
542        if ( is != null )
543        {
544            try
545            {
546                props.load( is );
547            }
548            catch ( IOException e )
549            {
550                logger.debug( "Failed to read Maven version", e );
551            }
552            IOUtil.close( is );
553        }
554
555        return props.getProperty( "version", "unknown-version" );
556    }
557
558    private void validateLocalRepository( MavenExecutionRequest request )
559        throws LocalRepositoryNotAccessibleException
560    {
561        File localRepoDir = request.getLocalRepositoryPath();
562
563        logger.debug( "Using local repository at " + localRepoDir );
564
565        localRepoDir.mkdirs();
566
567        if ( !localRepoDir.isDirectory() )
568        {
569            throw new LocalRepositoryNotAccessibleException( "Could not create local repository at " + localRepoDir );
570        }
571    }
572
573    private Collection<AbstractMavenLifecycleParticipant> getLifecycleParticipants( Collection<MavenProject> projects )
574    {
575        Collection<AbstractMavenLifecycleParticipant> lifecycleListeners =
576            new LinkedHashSet<AbstractMavenLifecycleParticipant>();
577
578        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
579        try
580        {
581            try
582            {
583                lifecycleListeners.addAll( container.lookupList( AbstractMavenLifecycleParticipant.class ) );
584            }
585            catch ( ComponentLookupException e )
586            {
587                // this is just silly, lookupList should return an empty list!
588                logger.warn( "Failed to lookup lifecycle participants: " + e.getMessage() );
589            }
590
591            Collection<ClassLoader> scannedRealms = new HashSet<ClassLoader>();
592
593            for ( MavenProject project : projects )
594            {
595                ClassLoader projectRealm = project.getClassRealm();
596
597                if ( projectRealm != null && scannedRealms.add( projectRealm ) )
598                {
599                    Thread.currentThread().setContextClassLoader( projectRealm );
600
601                    try
602                    {
603                        lifecycleListeners.addAll( container.lookupList( AbstractMavenLifecycleParticipant.class ) );
604                    }
605                    catch ( ComponentLookupException e )
606                    {
607                        // this is just silly, lookupList should return an empty list!
608                        logger.warn( "Failed to lookup lifecycle participants: " + e.getMessage() );
609                    }
610                }
611            }
612        }
613        finally
614        {
615            Thread.currentThread().setContextClassLoader( originalClassLoader );
616        }
617
618        return lifecycleListeners;
619    }
620
621    private MavenExecutionResult addExceptionToResult( MavenExecutionResult result, Throwable e )
622    {
623        if ( !result.getExceptions().contains( e ) )
624        {
625            result.addException( e );
626        }
627
628        return result;
629    }
630
631    private List<MavenProject> getProjectsForMavenReactor( MavenSession session )
632        throws ProjectBuildingException
633    {
634        MavenExecutionRequest request = session.getRequest();
635        
636        request.getProjectBuildingRequest().setRepositorySession( session.getRepositorySession() );
637
638        List<MavenProject> projects = new ArrayList<MavenProject>();
639
640        // We have no POM file.
641        //
642        if ( request.getPom() == null )
643        {
644            ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) );
645            MavenProject project =
646                projectBuilder.build( modelSource, request.getProjectBuildingRequest() ).getProject();
647            project.setExecutionRoot( true );
648            projects.add( project );
649            request.setProjectPresent( false );
650            return projects;
651        }
652
653        List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() );
654        collectProjects( projects, files, request );
655        return projects;
656    }
657
658    private void collectProjects( List<MavenProject> projects, List<File> files, MavenExecutionRequest request )
659        throws ProjectBuildingException
660    {
661        ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest();
662
663        List<ProjectBuildingResult> results =
664            projectBuilder.build( files, request.isRecursive(), projectBuildingRequest );
665
666        boolean problems = false;
667
668        for ( ProjectBuildingResult result : results )
669        {
670            projects.add( result.getProject() );
671
672            if ( !result.getProblems().isEmpty() && logger.isWarnEnabled() )
673            {
674                logger.warn( "" );
675                logger.warn( "Some problems were encountered while building the effective model for "
676                    + result.getProject().getId() );
677
678                for ( ModelProblem problem : result.getProblems() )
679                {
680                    String location = ModelProblemUtils.formatLocation( problem, result.getProjectId() );
681                    logger.warn( problem.getMessage() + ( StringUtils.isNotEmpty( location ) ? " @ " + location : "" ) );
682                }
683
684                problems = true;
685            }
686        }
687
688        if ( problems )
689        {
690            logger.warn( "" );
691            logger.warn( "It is highly recommended to fix these problems"
692                + " because they threaten the stability of your build." );
693            logger.warn( "" );
694            logger.warn( "For this reason, future Maven versions might no"
695                + " longer support building such malformed projects." );
696            logger.warn( "" );
697        }
698    }
699
700    private Map<String, MavenProject> getProjectMap( Collection<MavenProject> projects )
701        throws DuplicateProjectException
702    {
703        Map<String, MavenProject> index = new LinkedHashMap<String, MavenProject>();
704        Map<String, List<File>> collisions = new LinkedHashMap<String, List<File>>();
705
706        for ( MavenProject project : projects )
707        {
708            String projectId = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
709
710            MavenProject collision = index.get( projectId );
711
712            if ( collision == null )
713            {
714                index.put( projectId, project );
715            }
716            else
717            {
718                List<File> pomFiles = collisions.get( projectId );
719
720                if ( pomFiles == null )
721                {
722                    pomFiles = new ArrayList<File>( Arrays.asList( collision.getFile(), project.getFile() ) );
723                    collisions.put( projectId, pomFiles );
724                }
725                else
726                {
727                    pomFiles.add( project.getFile() );
728                }
729            }
730        }
731
732        if ( !collisions.isEmpty() )
733        {
734            throw new DuplicateProjectException( "Two or more projects in the reactor"
735                + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>"
736                + " is unique for each project: " + collisions, collisions );
737        }
738
739        return index;
740    }
741
742    private void validateProjects( List<MavenProject> projects )
743    {
744        Map<String, MavenProject> projectsMap = new HashMap<String, MavenProject>();
745
746        for ( MavenProject project : projects )
747        {
748            String projectKey = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
749
750            projectsMap.put( projectKey, project );
751        }
752
753        for ( MavenProject project : projects )
754        {
755            // MNG-1911 / MNG-5572: Building plugins with extensions cannot be part of reactor 
756            for ( Plugin plugin : project.getBuildPlugins() )
757            {
758                if ( plugin.isExtensions() )
759                {
760                    String pluginKey =
761                        ArtifactUtils.key( plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion() );
762
763                    if ( projectsMap.containsKey( pluginKey ) )
764                    {
765                        logger.warn( project.getName() + " uses " + plugin.getKey()
766                            + " as extensions, which is not possible within the same reactor build. This plugin was pulled from the local repository!" );
767                    }
768                }
769            }
770        }
771    }
772
773    private void validateActivatedProfiles( List<MavenProject> projects, List<String> activeProfileIds )
774    {
775        Collection<String> notActivatedProfileIds = new LinkedHashSet<String>( activeProfileIds );
776
777        for ( MavenProject project : projects )
778        {
779            for ( List<String> profileIds : project.getInjectedProfileIds().values() )
780            {
781                notActivatedProfileIds.removeAll( profileIds );
782            }
783        }
784
785        for ( String notActivatedProfileId : notActivatedProfileIds )
786        {
787            logger.warn( "The requested profile \"" + notActivatedProfileId
788                + "\" could not be activated because it does not exist." );
789        }
790    }
791
792    @Deprecated // 5 January 2014
793    protected Logger getLogger()
794    {
795        return logger;
796    }
797
798    private ProjectDependencyGraph createProjectDependencyGraph( Collection<MavenProject> projects, MavenExecutionRequest request,
799                                                                 MavenExecutionResult result, boolean trimming )
800    {
801        ProjectDependencyGraph projectDependencyGraph = null;
802
803        try
804        {
805            projectDependencyGraph = new DefaultProjectDependencyGraph( projects );
806
807            if ( trimming )
808            {
809                List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
810
811                activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, request );
812                activeProjects = trimExcludedProjects( activeProjects,  request );
813                activeProjects = trimResumedProjects( activeProjects, request );
814
815                if ( activeProjects.size() != projectDependencyGraph.getSortedProjects().size() )
816                {
817                    projectDependencyGraph =
818                        new FilteredProjectDependencyGraph( projectDependencyGraph, activeProjects );
819                }
820            }
821        }
822        catch ( CycleDetectedException e )
823        {
824            String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage();
825
826            ProjectCycleException error = new ProjectCycleException( message, e );
827
828            addExceptionToResult( result, error );
829        }
830        catch ( org.apache.maven.project.DuplicateProjectException e )
831        {
832            addExceptionToResult( result, e );
833        }
834        catch ( MavenExecutionException e )
835        {
836            addExceptionToResult( result, e );
837        }
838
839        return projectDependencyGraph;
840    }
841
842    private List<MavenProject> trimSelectedProjects( List<MavenProject> projects, ProjectDependencyGraph graph,
843                                                     MavenExecutionRequest request )
844        throws MavenExecutionException
845    {
846        List<MavenProject> result = projects;
847
848        if ( !request.getSelectedProjects().isEmpty() )
849        {
850            File reactorDirectory = null;
851            if ( request.getBaseDirectory() != null )
852            {
853                reactorDirectory = new File( request.getBaseDirectory() );
854            }
855
856            Collection<MavenProject> selectedProjects = new LinkedHashSet<MavenProject>( projects.size() );
857
858            for ( String selector : request.getSelectedProjects() )
859            {
860                MavenProject selectedProject = null;
861
862                for ( MavenProject project : projects )
863                {
864                    if ( isMatchingProject( project, selector, reactorDirectory ) )
865                    {
866                        selectedProject = project;
867                        break;
868                    }
869                }
870
871                if ( selectedProject != null )
872                {
873                    selectedProjects.add( selectedProject );
874                }
875                else
876                {
877                    throw new MavenExecutionException( "Could not find the selected project in the reactor: "
878                        + selector, request.getPom() );
879                }
880            }
881
882            boolean makeUpstream = false;
883            boolean makeDownstream = false;
884
885            if ( MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals( request.getMakeBehavior() ) )
886            {
887                makeUpstream = true;
888            }
889            else if ( MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals( request.getMakeBehavior() ) )
890            {
891                makeDownstream = true;
892            }
893            else if ( MavenExecutionRequest.REACTOR_MAKE_BOTH.equals( request.getMakeBehavior() ) )
894            {
895                makeUpstream = true;
896                makeDownstream = true;
897            }
898            else if ( StringUtils.isNotEmpty( request.getMakeBehavior() ) )
899            {
900                throw new MavenExecutionException( "Invalid reactor make behavior: " + request.getMakeBehavior(),
901                                                   request.getPom() );
902            }
903
904            if ( makeUpstream || makeDownstream )
905            {
906                for ( MavenProject selectedProject : new ArrayList<MavenProject>( selectedProjects ) )
907                {
908                    if ( makeUpstream )
909                    {
910                        selectedProjects.addAll( graph.getUpstreamProjects( selectedProject, true ) );
911                    }
912                    if ( makeDownstream )
913                    {
914                        selectedProjects.addAll( graph.getDownstreamProjects( selectedProject, true ) );
915                    }
916                }
917            }
918
919            result = new ArrayList<MavenProject>( selectedProjects.size() );
920
921            for ( MavenProject project : projects )
922            {
923                if ( selectedProjects.contains( project ) )
924                {
925                    result.add( project );
926                }
927            }
928        }
929
930        return result;
931    }
932    
933    private List<MavenProject> trimExcludedProjects( List<MavenProject> projects, MavenExecutionRequest request )
934        throws MavenExecutionException
935    {
936        List<MavenProject> result = projects;
937
938        if ( !request.getExcludedProjects().isEmpty() )
939        {
940            File reactorDirectory = null;
941
942            if ( request.getBaseDirectory() != null )
943            {
944                reactorDirectory = new File( request.getBaseDirectory() );
945            }
946
947            Collection<MavenProject> excludedProjects = new LinkedHashSet<MavenProject>( projects.size() );
948
949            for ( String selector : request.getExcludedProjects() )
950            {
951                MavenProject excludedProject = null;
952
953                for ( MavenProject project : projects )
954                {
955                    if ( isMatchingProject( project, selector, reactorDirectory ) )
956                    {
957                        excludedProject = project;
958                        break;
959                    }
960                }
961
962                if ( excludedProject != null )
963                {
964                    excludedProjects.add( excludedProject );
965                }
966                else
967                {
968                    throw new MavenExecutionException( "Could not find the selected project in the reactor: "
969                        + selector, request.getPom() );
970                }
971            }
972
973            result = new ArrayList<MavenProject>( projects.size() );
974            for ( MavenProject project : projects )
975            {
976                if ( !excludedProjects.contains( project ) )
977                {
978                    result.add( project );
979                }
980            }
981        }
982
983        return result;
984    }
985
986    private List<MavenProject> trimResumedProjects( List<MavenProject> projects, MavenExecutionRequest request )
987        throws MavenExecutionException
988    {
989        List<MavenProject> result = projects;
990
991        if ( StringUtils.isNotEmpty( request.getResumeFrom() ) )
992        {
993            File reactorDirectory = null;
994            if ( request.getBaseDirectory() != null )
995            {
996                reactorDirectory = new File( request.getBaseDirectory() );
997            }
998
999            String selector = request.getResumeFrom();
1000
1001            result = new ArrayList<MavenProject>( projects.size() );
1002
1003            boolean resumed = false;
1004
1005            for ( MavenProject project : projects )
1006            {
1007                if ( !resumed && isMatchingProject( project, selector, reactorDirectory ) )
1008                {
1009                    resumed = true;
1010                }
1011
1012                if ( resumed )
1013                {
1014                    result.add( project );
1015                }
1016            }
1017
1018            if ( !resumed )
1019            {
1020                throw new MavenExecutionException( "Could not find project to resume reactor build from: " + selector
1021                    + " vs " + projects, request.getPom() );
1022            }
1023        }
1024
1025        return result;
1026    }
1027
1028    private boolean isMatchingProject( MavenProject project, String selector, File reactorDirectory )
1029    {
1030        // [groupId]:artifactId
1031        if ( selector.indexOf( ':' ) >= 0 )
1032        {
1033            String id = ':' + project.getArtifactId();
1034
1035            if ( id.equals( selector ) )
1036            {
1037                return true;
1038            }
1039
1040            id = project.getGroupId() + id;
1041
1042            if ( id.equals( selector ) )
1043            {
1044                return true;
1045            }
1046        }
1047
1048        // relative path, e.g. "sub", "../sub" or "."
1049        else if ( reactorDirectory != null )
1050        {
1051            File selectedProject = new File( new File( reactorDirectory, selector ).toURI().normalize() );
1052
1053            if ( selectedProject.isFile() )
1054            {
1055                return selectedProject.equals( project.getFile() );
1056            }
1057            else if ( selectedProject.isDirectory() )
1058            {
1059                return selectedProject.equals( project.getBasedir() );
1060            }
1061        }
1062
1063        return false;
1064    }
1065
1066}