001    package org.apache.maven.project;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
005     * agreements. See the NOTICE file distributed with this work for additional information regarding
006     * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance with the License. You may obtain a
008     * copy of the License at
009     * 
010     * http://www.apache.org/licenses/LICENSE-2.0
011     * 
012     * Unless required by applicable law or agreed to in writing, software distributed under the License
013     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
014     * or implied. See the License for the specific language governing permissions and limitations under
015     * the License.
016     */
017    
018    import java.io.File;
019    import java.io.IOException;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.Collections;
023    import java.util.HashMap;
024    import java.util.LinkedHashSet;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Set;
028    
029    import org.apache.maven.RepositoryUtils;
030    import org.apache.maven.artifact.Artifact;
031    import org.apache.maven.artifact.repository.LegacyLocalRepositoryManager;
032    import org.apache.maven.model.Build;
033    import org.apache.maven.model.Model;
034    import org.apache.maven.model.Profile;
035    import org.apache.maven.model.building.DefaultModelBuildingRequest;
036    import org.apache.maven.model.building.DefaultModelProblem;
037    import org.apache.maven.model.building.FileModelSource;
038    import org.apache.maven.model.building.ModelBuilder;
039    import org.apache.maven.model.building.ModelBuildingException;
040    import org.apache.maven.model.building.ModelBuildingRequest;
041    import org.apache.maven.model.building.ModelBuildingResult;
042    import org.apache.maven.model.building.ModelProblem;
043    import org.apache.maven.model.building.ModelProcessor;
044    import org.apache.maven.model.building.ModelSource;
045    import org.apache.maven.model.building.StringModelSource;
046    import org.apache.maven.model.resolution.ModelResolver;
047    import org.apache.maven.repository.RepositorySystem;
048    import org.apache.maven.repository.internal.ArtifactDescriptorUtils;
049    import org.codehaus.plexus.component.annotations.Component;
050    import org.codehaus.plexus.component.annotations.Requirement;
051    import org.codehaus.plexus.logging.Logger;
052    import org.codehaus.plexus.util.Os;
053    import org.codehaus.plexus.util.StringUtils;
054    import org.sonatype.aether.RepositorySystemSession;
055    import org.sonatype.aether.RequestTrace;
056    import org.sonatype.aether.impl.RemoteRepositoryManager;
057    import org.sonatype.aether.repository.LocalRepositoryManager;
058    import org.sonatype.aether.repository.RemoteRepository;
059    import org.sonatype.aether.repository.WorkspaceRepository;
060    import org.sonatype.aether.resolution.ArtifactRequest;
061    import org.sonatype.aether.resolution.ArtifactResult;
062    import org.sonatype.aether.util.DefaultRequestTrace;
063    
064    /**
065     */
066    @Component( role = ProjectBuilder.class )
067    public class DefaultProjectBuilder
068        implements ProjectBuilder
069    {
070    
071        @Requirement
072        private Logger logger;
073    
074        @Requirement
075        private ModelBuilder modelBuilder;
076    
077        @Requirement
078        private ModelProcessor modelProcessor;
079    
080        @Requirement
081        private ProjectBuildingHelper projectBuildingHelper;
082    
083        @Requirement
084        private RepositorySystem repositorySystem;
085    
086        @Requirement
087        private org.sonatype.aether.RepositorySystem repoSystem;
088    
089        @Requirement
090        private RemoteRepositoryManager repositoryManager;
091    
092        @Requirement
093        private ProjectDependenciesResolver dependencyResolver;
094    
095        // ----------------------------------------------------------------------
096        // MavenProjectBuilder Implementation
097        // ----------------------------------------------------------------------
098    
099        public ProjectBuildingResult build( File pomFile, ProjectBuildingRequest request )
100            throws ProjectBuildingException
101        {
102            return build( pomFile, new FileModelSource( pomFile ), new InternalConfig( request, null, null ) );
103        }
104    
105        public ProjectBuildingResult build( ModelSource modelSource, ProjectBuildingRequest request )
106            throws ProjectBuildingException
107        {
108            return build( null, modelSource, new InternalConfig( request, null, null ) );
109        }
110    
111        private ProjectBuildingResult build( File pomFile, ModelSource modelSource, InternalConfig config )
112            throws ProjectBuildingException
113        {
114            ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
115    
116            try
117            {
118                ProjectBuildingRequest configuration = config.request;
119    
120                MavenProject project = configuration.getProject();
121    
122                List<ModelProblem> modelProblems = null;
123                Throwable error = null;
124    
125                if ( project == null )
126                {
127                    ModelBuildingRequest request = getModelBuildingRequest( config );
128    
129                    project = new MavenProject( repositorySystem, this, configuration, logger );
130    
131                    DefaultModelBuildingListener listener =
132                        new DefaultModelBuildingListener( project, projectBuildingHelper, configuration );
133                    request.setModelBuildingListener( listener );
134    
135                    request.setPomFile( pomFile );
136                    request.setModelSource( modelSource );
137                    request.setLocationTracking( true );
138    
139                    ModelBuildingResult result;
140                    try
141                    {
142                        result = modelBuilder.build( request );
143                    }
144                    catch ( ModelBuildingException e )
145                    {
146                        result = e.getResult();
147                        if ( result == null || result.getEffectiveModel() == null )
148                        {
149                            throw new ProjectBuildingException( e.getModelId(), e.getMessage(), pomFile, e );
150                        }
151                        // validation error, continue project building and delay failing to help IDEs
152                        error = e;
153                    }
154    
155                    modelProblems = result.getProblems();
156    
157                    initProject( project, Collections.<String, MavenProject> emptyMap(), result,
158                                 new HashMap<File, Boolean>() );
159                }
160                else if ( configuration.isResolveDependencies() )
161                {
162                    projectBuildingHelper.selectProjectRealm( project );
163                }
164    
165                DependencyResolutionResult resolutionResult = null;
166    
167                if ( configuration.isResolveDependencies() )
168                {
169                    resolutionResult = resolveDependencies( project, config.session );
170                }
171    
172                ProjectBuildingResult result = new DefaultProjectBuildingResult( project, modelProblems, resolutionResult );
173    
174                if ( error != null )
175                {
176                    ProjectBuildingException e = new ProjectBuildingException( Arrays.asList( result ) );
177                    e.initCause( error );
178                    throw e;
179                }
180    
181                return result;
182            }
183            finally
184            {
185                Thread.currentThread().setContextClassLoader( oldContextClassLoader );
186            }
187        }
188    
189        private DependencyResolutionResult resolveDependencies( MavenProject project, RepositorySystemSession session )
190        {
191            DependencyResolutionResult resolutionResult = null;
192    
193            try
194            {
195                DefaultDependencyResolutionRequest resolution = new DefaultDependencyResolutionRequest( project, session );
196                resolutionResult = dependencyResolver.resolve( resolution );
197            }
198            catch ( DependencyResolutionException e )
199            {
200                resolutionResult = e.getResult();
201            }
202    
203            Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
204            if ( resolutionResult.getDependencyGraph() != null )
205            {
206                RepositoryUtils.toArtifacts( artifacts, resolutionResult.getDependencyGraph().getChildren(),
207                                             Collections.singletonList( project.getArtifact().getId() ), null );
208    
209                // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not
210                LocalRepositoryManager lrm = session.getLocalRepositoryManager();
211                for ( Artifact artifact : artifacts )
212                {
213                    if ( !artifact.isResolved() )
214                    {
215                        String path = lrm.getPathForLocalArtifact( RepositoryUtils.toArtifact( artifact ) );
216                        artifact.setFile( new File( lrm.getRepository().getBasedir(), path ) );
217                    }
218                }
219            }
220            project.setResolvedArtifacts( artifacts );
221            project.setArtifacts( artifacts );
222    
223            return resolutionResult;
224        }
225    
226        private List<String> getProfileIds( List<Profile> profiles )
227        {
228            List<String> ids = new ArrayList<String>( profiles.size() );
229    
230            for ( Profile profile : profiles )
231            {
232                ids.add( profile.getId() );
233            }
234    
235            return ids;
236        }
237    
238        private ModelBuildingRequest getModelBuildingRequest( InternalConfig config )
239        {
240            ProjectBuildingRequest configuration = config.request;
241    
242            ModelBuildingRequest request = new DefaultModelBuildingRequest();
243    
244            RequestTrace trace = DefaultRequestTrace.newChild( null, configuration ).newChild( request );
245    
246            ModelResolver resolver =
247                new ProjectModelResolver( config.session, trace, repoSystem, repositoryManager, config.repositories,
248                                          configuration.getRepositoryMerging(), config.modelPool );
249    
250            request.setValidationLevel( configuration.getValidationLevel() );
251            request.setProcessPlugins( configuration.isProcessPlugins() );
252            request.setProfiles( configuration.getProfiles() );
253            request.setActiveProfileIds( configuration.getActiveProfileIds() );
254            request.setInactiveProfileIds( configuration.getInactiveProfileIds() );
255            request.setSystemProperties( configuration.getSystemProperties() );
256            request.setUserProperties( configuration.getUserProperties() );
257            request.setBuildStartTime( configuration.getBuildStartTime() );
258            request.setModelResolver( resolver );
259            request.setModelCache( config.modelCache );
260    
261            return request;
262        }
263    
264        public ProjectBuildingResult build( Artifact artifact, ProjectBuildingRequest request )
265            throws ProjectBuildingException
266        {
267            return build( artifact, false, request );
268        }
269    
270        public ProjectBuildingResult build( Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request )
271            throws ProjectBuildingException
272        {
273            org.sonatype.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact( artifact );
274            pomArtifact = ArtifactDescriptorUtils.toPomArtifact( pomArtifact );
275    
276            InternalConfig config = new InternalConfig( request, null, null );
277    
278            boolean localProject;
279    
280            try
281            {
282                ArtifactRequest pomRequest = new ArtifactRequest();
283                pomRequest.setArtifact( pomArtifact );
284                pomRequest.setRepositories( config.repositories );
285                ArtifactResult pomResult = repoSystem.resolveArtifact( config.session, pomRequest );
286    
287                pomArtifact = pomResult.getArtifact();
288                localProject = pomResult.getRepository() instanceof WorkspaceRepository;
289            }
290            catch ( org.sonatype.aether.resolution.ArtifactResolutionException e )
291            {
292                if ( e.getResults().get( 0 ).isMissing() && allowStubModel )
293                {
294                    return build( null, createStubModelSource( artifact ), config );
295                }
296                throw new ProjectBuildingException( artifact.getId(),
297                                                    "Error resolving project artifact: " + e.getMessage(), e );
298            }
299    
300            File pomFile = pomArtifact.getFile();
301    
302            if ( "pom".equals( artifact.getType() ) )
303            {
304                artifact.selectVersion( pomArtifact.getVersion() );
305                artifact.setFile( pomFile );
306                artifact.setResolved( true );
307            }
308    
309            return build( localProject ? pomFile : null, new FileModelSource( pomFile ), config );
310        }
311    
312        private ModelSource createStubModelSource( Artifact artifact )
313        {
314            StringBuilder buffer = new StringBuilder( 1024 );
315    
316            buffer.append( "<?xml version='1.0'?>" );
317            buffer.append( "<project>" );
318            buffer.append( "<modelVersion>4.0.0</modelVersion>" );
319            buffer.append( "<groupId>" ).append( artifact.getGroupId() ).append( "</groupId>" );
320            buffer.append( "<artifactId>" ).append( artifact.getArtifactId() ).append( "</artifactId>" );
321            buffer.append( "<version>" ).append( artifact.getBaseVersion() ).append( "</version>" );
322            buffer.append( "<packaging>" ).append( artifact.getType() ).append( "</packaging>" );
323            buffer.append( "</project>" );
324    
325            return new StringModelSource( buffer, artifact.getId() );
326        }
327    
328        public List<ProjectBuildingResult> build( List<File> pomFiles, boolean recursive, ProjectBuildingRequest request )
329            throws ProjectBuildingException
330        {
331            List<ProjectBuildingResult> results = new ArrayList<ProjectBuildingResult>();
332    
333            List<InterimResult> interimResults = new ArrayList<InterimResult>();
334    
335            ReactorModelPool modelPool = new ReactorModelPool();
336    
337            ReactorModelCache modelCache = new ReactorModelCache();
338    
339            InternalConfig config = new InternalConfig( request, modelPool, modelCache );
340    
341            Map<String, MavenProject> projectIndex = new HashMap<String, MavenProject>( 256 );
342    
343            boolean noErrors =
344                build( results, interimResults, projectIndex, pomFiles, new LinkedHashSet<File>(), true, recursive, config );
345    
346            populateReactorModelPool( modelPool, interimResults );
347    
348            ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
349    
350            try
351            {
352                noErrors =
353                    build( results, new ArrayList<MavenProject>(), projectIndex, interimResults, request,
354                           new HashMap<File, Boolean>() ) && noErrors;
355            }
356            finally
357            {
358                Thread.currentThread().setContextClassLoader( oldContextClassLoader );
359            }
360    
361            if ( !noErrors )
362            {
363                throw new ProjectBuildingException( results );
364            }
365    
366            return results;
367        }
368    
369        private boolean build( List<ProjectBuildingResult> results, List<InterimResult> interimResults,
370                               Map<String, MavenProject> projectIndex, List<File> pomFiles, Set<File> aggregatorFiles,
371                               boolean isRoot, boolean recursive, InternalConfig config )
372        {
373            boolean noErrors = true;
374    
375            for ( File pomFile : pomFiles )
376            {
377                aggregatorFiles.add( pomFile );
378    
379                if ( !build( results, interimResults, projectIndex, pomFile, aggregatorFiles, isRoot, recursive, config ) )
380                {
381                    noErrors = false;
382                }
383    
384                aggregatorFiles.remove( pomFile );
385            }
386    
387            return noErrors;
388        }
389    
390        private boolean build( List<ProjectBuildingResult> results, List<InterimResult> interimResults,
391                               Map<String, MavenProject> projectIndex, File pomFile, Set<File> aggregatorFiles,
392                               boolean isRoot, boolean recursive, InternalConfig config )
393        {
394            boolean noErrors = true;
395    
396            ModelBuildingRequest request = getModelBuildingRequest( config );
397    
398            MavenProject project = new MavenProject( repositorySystem, this, config.request, logger );
399    
400            request.setPomFile( pomFile );
401            request.setTwoPhaseBuilding( true );
402            request.setLocationTracking( true );
403    
404            DefaultModelBuildingListener listener =
405                new DefaultModelBuildingListener( project, projectBuildingHelper, config.request );
406            request.setModelBuildingListener( listener );
407    
408            try
409            {
410                ModelBuildingResult result = modelBuilder.build( request );
411    
412                Model model = result.getEffectiveModel();
413    
414                projectIndex.put( result.getModelIds().get( 0 ), project );
415    
416                InterimResult interimResult = new InterimResult( pomFile, request, result, listener, isRoot );
417                interimResults.add( interimResult );
418    
419                if ( recursive && !model.getModules().isEmpty() )
420                {
421                    File basedir = pomFile.getParentFile();
422    
423                    List<File> moduleFiles = new ArrayList<File>();
424    
425                    for ( String module : model.getModules() )
426                    {
427                        if ( StringUtils.isEmpty( module ) )
428                        {
429                            continue;
430                        }
431    
432                        module = module.replace( '\\', File.separatorChar ).replace( '/', File.separatorChar );
433    
434                        File moduleFile = new File( basedir, module );
435    
436                        if ( moduleFile.isDirectory() )
437                        {
438                            moduleFile = modelProcessor.locatePom( moduleFile );
439                        }
440    
441                        if ( !moduleFile.isFile() )
442                        {
443                            ModelProblem problem =
444                                new DefaultModelProblem( "Child module " + moduleFile + " of " + pomFile
445                                    + " does not exist", ModelProblem.Severity.ERROR, model, -1, -1, null );
446                            result.getProblems().add( problem );
447    
448                            noErrors = false;
449    
450                            continue;
451                        }
452    
453                        if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
454                        {
455                            // we don't canonicalize on unix to avoid interfering with symlinks
456                            try
457                            {
458                                moduleFile = moduleFile.getCanonicalFile();
459                            }
460                            catch ( IOException e )
461                            {
462                                moduleFile = moduleFile.getAbsoluteFile();
463                            }
464                        }
465                        else
466                        {
467                            moduleFile = new File( moduleFile.toURI().normalize() );
468                        }
469    
470                        if ( aggregatorFiles.contains( moduleFile ) )
471                        {
472                            StringBuilder buffer = new StringBuilder( 256 );
473                            for ( File aggregatorFile : aggregatorFiles )
474                            {
475                                buffer.append( aggregatorFile ).append( " -> " );
476                            }
477                            buffer.append( moduleFile );
478    
479                            ModelProblem problem =
480                                new DefaultModelProblem( "Child module " + moduleFile + " of " + pomFile
481                                    + " forms aggregation cycle " + buffer, ModelProblem.Severity.ERROR, model, -1, -1,
482                                                         null );
483                            result.getProblems().add( problem );
484    
485                            noErrors = false;
486    
487                            continue;
488                        }
489    
490                        moduleFiles.add( moduleFile );
491                    }
492    
493                    interimResult.modules = new ArrayList<InterimResult>();
494    
495                    if ( !build( results, interimResult.modules, projectIndex, moduleFiles, aggregatorFiles, false,
496                                 recursive, config ) )
497                    {
498                        noErrors = false;
499                    }
500                }
501            }
502            catch ( ModelBuildingException e )
503            {
504                results.add( new DefaultProjectBuildingResult( e.getModelId(), pomFile, e.getProblems() ) );
505    
506                noErrors = false;
507            }
508    
509            return noErrors;
510        }
511    
512        static class InterimResult
513        {
514    
515            File pomFile;
516    
517            ModelBuildingRequest request;
518    
519            ModelBuildingResult result;
520    
521            DefaultModelBuildingListener listener;
522    
523            boolean root;
524    
525            List<InterimResult> modules = Collections.emptyList();
526    
527            InterimResult( File pomFile, ModelBuildingRequest request, ModelBuildingResult result,
528                           DefaultModelBuildingListener listener, boolean root )
529            {
530                this.pomFile = pomFile;
531                this.request = request;
532                this.result = result;
533                this.listener = listener;
534                this.root = root;
535            }
536    
537        }
538    
539        private void populateReactorModelPool( ReactorModelPool reactorModelPool, List<InterimResult> interimResults )
540        {
541            for ( InterimResult interimResult : interimResults )
542            {
543                Model model = interimResult.result.getEffectiveModel();
544                reactorModelPool.put( model.getGroupId(), model.getArtifactId(), model.getVersion(), model.getPomFile() );
545    
546                populateReactorModelPool( reactorModelPool, interimResult.modules );
547            }
548        }
549    
550        private boolean build( List<ProjectBuildingResult> results, List<MavenProject> projects,
551                               Map<String, MavenProject> projectIndex, List<InterimResult> interimResults,
552                               ProjectBuildingRequest request, Map<File, Boolean> profilesXmls )
553        {
554            boolean noErrors = true;
555    
556            for ( InterimResult interimResult : interimResults )
557            {
558                try
559                {
560                    ModelBuildingResult result = modelBuilder.build( interimResult.request, interimResult.result );
561    
562                    MavenProject project = interimResult.listener.getProject();
563                    initProject( project, projectIndex, result, profilesXmls );
564    
565                    List<MavenProject> modules = new ArrayList<MavenProject>();
566                    noErrors =
567                        build( results, modules, projectIndex, interimResult.modules, request, profilesXmls ) && noErrors;
568    
569                    projects.addAll( modules );
570                    projects.add( project );
571    
572                    project.setExecutionRoot( interimResult.root );
573                    project.setCollectedProjects( modules );
574    
575                    results.add( new DefaultProjectBuildingResult( project, result.getProblems(), null ) );
576                }
577                catch ( ModelBuildingException e )
578                {
579                    results.add( new DefaultProjectBuildingResult( e.getModelId(), interimResult.pomFile, e.getProblems() ) );
580    
581                    noErrors = false;
582                }
583            }
584    
585            return noErrors;
586        }
587    
588        private void initProject( MavenProject project, Map<String, MavenProject> projects, ModelBuildingResult result,
589                                  Map<File, Boolean> profilesXmls )
590        {
591            Model model = result.getEffectiveModel();
592    
593            project.setModel( model );
594            project.setOriginalModel( result.getRawModel() );
595    
596            project.setFile( model.getPomFile() );
597    
598            File parentPomFile = result.getRawModel( result.getModelIds().get( 1 ) ).getPomFile();
599            project.setParentFile( parentPomFile );
600    
601            project.setParent( projects.get( result.getModelIds().get( 1 ) ) );
602    
603            Artifact projectArtifact =
604                repositorySystem.createArtifact( project.getGroupId(), project.getArtifactId(), project.getVersion(), null,
605                                                 project.getPackaging() );
606            project.setArtifact( projectArtifact );
607    
608            if ( project.getFile() != null )
609            {
610                Build build = project.getBuild();
611                project.addScriptSourceRoot( build.getScriptSourceDirectory() );
612                project.addCompileSourceRoot( build.getSourceDirectory() );
613                project.addTestCompileSourceRoot( build.getTestSourceDirectory() );
614            }
615    
616            List<Profile> activeProfiles = new ArrayList<Profile>();
617            activeProfiles.addAll( result.getActivePomProfiles( result.getModelIds().get( 0 ) ) );
618            activeProfiles.addAll( result.getActiveExternalProfiles() );
619            project.setActiveProfiles( activeProfiles );
620    
621            project.setInjectedProfileIds( "external", getProfileIds( result.getActiveExternalProfiles() ) );
622            for ( String modelId : result.getModelIds() )
623            {
624                project.setInjectedProfileIds( modelId, getProfileIds( result.getActivePomProfiles( modelId ) ) );
625            }
626    
627            String modelId = findProfilesXml( result, profilesXmls );
628            if ( modelId != null )
629            {
630                ModelProblem problem =
631                    new DefaultModelProblem( "Detected profiles.xml alongside " + modelId
632                        + ", this file is no longer supported and was ignored" + ", please use the settings.xml instead",
633                                             ModelProblem.Severity.WARNING, model, -1, -1, null );
634                result.getProblems().add( problem );
635            }
636        }
637    
638        private String findProfilesXml( ModelBuildingResult result, Map<File, Boolean> profilesXmls )
639        {
640            for ( String modelId : result.getModelIds() )
641            {
642                Model model = result.getRawModel( modelId );
643    
644                File basedir = model.getProjectDirectory();
645                if ( basedir == null )
646                {
647                    break;
648                }
649    
650                Boolean profilesXml = profilesXmls.get( basedir );
651                if ( profilesXml == null )
652                {
653                    profilesXml = Boolean.valueOf( new File( basedir, "profiles.xml" ).exists() );
654                    profilesXmls.put( basedir, profilesXml );
655                }
656                if ( profilesXml.booleanValue() )
657                {
658                    return modelId;
659                }
660            }
661    
662            return null;
663        }
664    
665        class InternalConfig
666        {
667    
668            public final ProjectBuildingRequest request;
669    
670            public final RepositorySystemSession session;
671    
672            public final List<RemoteRepository> repositories;
673    
674            public final ReactorModelPool modelPool;
675    
676            public final ReactorModelCache modelCache;
677    
678            public InternalConfig( ProjectBuildingRequest request, ReactorModelPool modelPool, ReactorModelCache modelCache )
679            {
680                this.request = request;
681                this.modelPool = modelPool;
682                this.modelCache = modelCache;
683                session =
684                    LegacyLocalRepositoryManager.overlay( request.getLocalRepository(), request.getRepositorySession(),
685                                                          repoSystem );
686                repositories = RepositoryUtils.toRepos( request.getRemoteRepositories() );
687            }
688    
689        }
690    
691    }