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