001    package org.apache.maven.project;
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    
022    import java.io.IOException;
023    import java.util.ArrayList;
024    import java.util.Arrays;
025    import java.util.Collection;
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.LinkedHashSet;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import org.apache.maven.artifact.InvalidRepositoryException;
034    import org.apache.maven.artifact.repository.ArtifactRepository;
035    import org.apache.maven.classrealm.ClassRealmManager;
036    import org.apache.maven.model.Build;
037    import org.apache.maven.model.Extension;
038    import org.apache.maven.model.Model;
039    import org.apache.maven.model.Plugin;
040    import org.apache.maven.model.Repository;
041    import org.apache.maven.plugin.ExtensionRealmCache;
042    import org.apache.maven.plugin.PluginArtifactsCache;
043    import org.apache.maven.plugin.PluginResolutionException;
044    import org.apache.maven.plugin.internal.PluginDependenciesResolver;
045    import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
046    import org.apache.maven.plugin.version.PluginVersionRequest;
047    import org.apache.maven.plugin.version.PluginVersionResolutionException;
048    import org.apache.maven.plugin.version.PluginVersionResolver;
049    import org.apache.maven.repository.RepositorySystem;
050    import org.codehaus.plexus.PlexusContainer;
051    import org.codehaus.plexus.classworlds.realm.ClassRealm;
052    import org.codehaus.plexus.component.annotations.Component;
053    import org.codehaus.plexus.component.annotations.Requirement;
054    import org.codehaus.plexus.logging.Logger;
055    import org.sonatype.aether.artifact.Artifact;
056    import org.sonatype.aether.graph.DependencyFilter;
057    import org.sonatype.aether.graph.DependencyNode;
058    import org.sonatype.aether.repository.RemoteRepository;
059    import org.sonatype.aether.util.filter.ExclusionsDependencyFilter;
060    import org.sonatype.aether.util.graph.PreorderNodeListGenerator;
061    
062    /**
063     * Assists the project builder. <strong>Warning:</strong> This is an internal utility class that is only public for
064     * technical reasons, it is not part of the public API. In particular, this class can be changed or deleted without
065     * prior notice.
066     * 
067     * @author Benjamin Bentmann
068     */
069    @Component( role = ProjectBuildingHelper.class )
070    public class DefaultProjectBuildingHelper
071        implements ProjectBuildingHelper
072    {
073    
074        @Requirement
075        private Logger logger;
076    
077        @Requirement
078        private PlexusContainer container;
079    
080        @Requirement
081        private ClassRealmManager classRealmManager;
082    
083        @Requirement
084        private PluginArtifactsCache pluginArtifactsCache;
085    
086        @Requirement
087        private ExtensionRealmCache extensionRealmCache;
088    
089        @Requirement
090        private ProjectRealmCache projectRealmCache;
091    
092        @Requirement
093        private RepositorySystem repositorySystem;
094    
095        @Requirement
096        private PluginVersionResolver pluginVersionResolver;
097    
098        @Requirement
099        private PluginDependenciesResolver pluginDependenciesResolver;
100    
101        private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
102    
103        public List<ArtifactRepository> createArtifactRepositories( List<Repository> pomRepositories,
104                                                                    List<ArtifactRepository> externalRepositories,
105                                                                    ProjectBuildingRequest request )
106            throws InvalidRepositoryException
107        {
108            List<ArtifactRepository> internalRepositories = new ArrayList<ArtifactRepository>();
109    
110            for ( Repository repository : pomRepositories )
111            {
112                internalRepositories.add( repositorySystem.buildArtifactRepository( repository ) );
113            }
114    
115            repositorySystem.injectMirror( request.getRepositorySession(), internalRepositories );
116    
117            repositorySystem.injectProxy( request.getRepositorySession(), internalRepositories );
118    
119            repositorySystem.injectAuthentication( request.getRepositorySession(), internalRepositories );
120    
121            List<ArtifactRepository> dominantRepositories;
122            List<ArtifactRepository> recessiveRepositories;
123    
124            if ( ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT.equals( request.getRepositoryMerging() ) )
125            {
126                dominantRepositories = externalRepositories;
127                recessiveRepositories = internalRepositories;
128            }
129            else
130            {
131                dominantRepositories = internalRepositories;
132                recessiveRepositories = externalRepositories;
133            }
134    
135            List<ArtifactRepository> artifactRepositories = new ArrayList<ArtifactRepository>();
136            Collection<String> repoIds = new HashSet<String>();
137    
138            if ( dominantRepositories != null )
139            {
140                for ( ArtifactRepository repository : dominantRepositories )
141                {
142                    repoIds.add( repository.getId() );
143                    artifactRepositories.add( repository );
144                }
145            }
146    
147            if ( recessiveRepositories != null )
148            {
149                for ( ArtifactRepository repository : recessiveRepositories )
150                {
151                    if ( repoIds.add( repository.getId() ) )
152                    {
153                        artifactRepositories.add( repository );
154                    }
155                }
156            }
157    
158            artifactRepositories = repositorySystem.getEffectiveRepositories( artifactRepositories );
159    
160            return artifactRepositories;
161        }
162    
163        public synchronized ProjectRealmCache.CacheRecord createProjectRealm( MavenProject project, Model model,
164                                                                              ProjectBuildingRequest request )
165            throws PluginResolutionException, PluginVersionResolutionException
166        {
167            ClassRealm projectRealm = null;
168    
169            List<Plugin> extensionPlugins = new ArrayList<Plugin>();
170    
171            Build build = model.getBuild();
172    
173            if ( build != null )
174            {
175                for ( Extension extension : build.getExtensions() )
176                {
177                    Plugin plugin = new Plugin();
178                    plugin.setGroupId( extension.getGroupId() );
179                    plugin.setArtifactId( extension.getArtifactId() );
180                    plugin.setVersion( extension.getVersion() );
181                    extensionPlugins.add( plugin );
182                }
183    
184                for ( Plugin plugin : build.getPlugins() )
185                {
186                    if ( plugin.isExtensions() )
187                    {
188                        extensionPlugins.add( plugin );
189                    }
190                }
191            }
192    
193            if ( extensionPlugins.isEmpty() )
194            {
195                if ( logger.isDebugEnabled() )
196                {
197                    logger.debug( "Extension realms for project " + model.getId() + ": (none)" );
198                }
199    
200                return new ProjectRealmCache.CacheRecord( null, null );
201            }
202    
203            List<ClassRealm> extensionRealms = new ArrayList<ClassRealm>();
204    
205            Map<ClassRealm, List<String>> exportedPackages = new HashMap<ClassRealm, List<String>>();
206    
207            Map<ClassRealm, List<String>> exportedArtifacts = new HashMap<ClassRealm, List<String>>();
208    
209            List<Artifact> publicArtifacts = new ArrayList<Artifact>();
210    
211            for ( Plugin plugin : extensionPlugins )
212            {
213                if ( plugin.getVersion() == null )
214                {
215                    PluginVersionRequest versionRequest =
216                        new DefaultPluginVersionRequest( plugin, request.getRepositorySession(),
217                                                         project.getRemotePluginRepositories() );
218                    plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
219                }
220    
221                List<Artifact> artifacts;
222    
223                PluginArtifactsCache.Key cacheKey =
224                    pluginArtifactsCache.createKey( plugin, null, project.getRemotePluginRepositories(),
225                                                    request.getRepositorySession() );
226    
227                PluginArtifactsCache.CacheRecord recordArtifacts = pluginArtifactsCache.get( cacheKey );
228    
229                if ( recordArtifacts != null )
230                {
231                    artifacts = recordArtifacts.artifacts;
232                }
233                else
234                {
235                    try
236                    {
237                        artifacts = resolveExtensionArtifacts( plugin, project.getRemotePluginRepositories(), request );
238    
239                        recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts );
240                    }
241                    catch ( PluginResolutionException e )
242                    {
243                        pluginArtifactsCache.put( cacheKey, e );
244    
245                        pluginArtifactsCache.register( project, recordArtifacts );
246    
247                        throw e;
248                    }
249                }
250    
251                pluginArtifactsCache.register( project, recordArtifacts );
252    
253                ClassRealm extensionRealm;
254                ExtensionDescriptor extensionDescriptor = null;
255    
256                ExtensionRealmCache.CacheRecord recordRealm = extensionRealmCache.get( artifacts );
257    
258                if ( recordRealm != null )
259                {
260                    extensionRealm = recordRealm.realm;
261                    extensionDescriptor = recordRealm.desciptor;
262                }
263                else
264                {
265                    extensionRealm = classRealmManager.createExtensionRealm( plugin, artifacts );
266    
267                    try
268                    {
269                        container.discoverComponents( extensionRealm );
270                    }
271                    catch ( Exception e )
272                    {
273                        throw new IllegalStateException( "Failed to discover components in extension realm "
274                            + extensionRealm.getId(), e );
275                    }
276    
277                    Artifact extensionArtifact = artifacts.get( 0 );
278                    try
279                    {
280                        extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() );
281                    }
282                    catch ( IOException e )
283                    {
284                        String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
285                        if ( logger.isDebugEnabled() )
286                        {
287                            logger.error( message, e );
288                        }
289                        else
290                        {
291                            logger.error( message );
292                        }
293                    }
294    
295                    recordRealm = extensionRealmCache.put( artifacts, extensionRealm, extensionDescriptor );
296                }
297    
298                extensionRealmCache.register( project, recordRealm );
299    
300                extensionRealms.add( extensionRealm );
301                if ( extensionDescriptor != null )
302                {
303                    exportedPackages.put( extensionRealm, extensionDescriptor.getExportedPackages() );
304                    exportedArtifacts.put( extensionRealm, extensionDescriptor.getExportedArtifacts() );
305                }
306    
307                if ( !plugin.isExtensions() && artifacts.size() == 2 && artifacts.get( 0 ).getFile() != null
308                    && "plexus-utils".equals( artifacts.get( 1 ).getArtifactId() ) )
309                {
310                    /*
311                     * This is purely for backward-compat with 2.x where <extensions> consisting of a single artifact where
312                     * loaded into the core and hence available to plugins, in contrast to bigger extensions that were
313                     * loaded into a dedicated realm which is invisible to plugins (MNG-2749).
314                     */
315                    publicArtifacts.add( artifacts.get( 0 ) );
316                }
317            }
318    
319            if ( logger.isDebugEnabled() )
320            {
321                logger.debug( "Extension realms for project " + model.getId() + ": " + extensionRealms );
322            }
323    
324            ProjectRealmCache.CacheRecord record = projectRealmCache.get( extensionRealms );
325    
326            if ( record == null )
327            {
328                projectRealm = classRealmManager.createProjectRealm( model, publicArtifacts );
329    
330                Set<String> exclusions = new LinkedHashSet<String>();
331    
332                for ( ClassRealm extensionRealm : extensionRealms )
333                {
334                    List<String> excludes = exportedArtifacts.get( extensionRealm );
335    
336                    if ( excludes != null )
337                    {
338                        exclusions.addAll( excludes );
339                    }
340    
341                    List<String> exports = exportedPackages.get( extensionRealm );
342    
343                    if ( exports == null || exports.isEmpty() )
344                    {
345                        /*
346                         * Most existing extensions don't define exported packages, i.e. no classes are to be exposed to
347                         * plugins, yet the components provided by the extension (e.g. artifact handlers) must be
348                         * accessible, i.e. we still must import the extension realm into the project realm.
349                         */
350                        exports = Arrays.asList( extensionRealm.getId() );
351                    }
352    
353                    for ( String export : exports )
354                    {
355                        projectRealm.importFrom( extensionRealm, export );
356                    }
357                }
358    
359                DependencyFilter extensionArtifactFilter = null;
360                if ( !exclusions.isEmpty() )
361                {
362                    extensionArtifactFilter = new ExclusionsDependencyFilter( exclusions );
363                }
364    
365                record = projectRealmCache.put( extensionRealms, projectRealm, extensionArtifactFilter );
366            }
367    
368            projectRealmCache.register( project, record );
369    
370            return record;
371        }
372    
373        private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories,
374                                                          ProjectBuildingRequest request )
375            throws PluginResolutionException
376        {
377            DependencyNode root =
378                pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories,
379                                                    request.getRepositorySession() );
380    
381            PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
382            root.accept( nlg );
383            return nlg.getArtifacts( false );
384        }
385    
386        public void selectProjectRealm( MavenProject project )
387        {
388            ClassLoader projectRealm = project.getClassRealm();
389    
390            if ( projectRealm == null )
391            {
392                projectRealm = classRealmManager.getCoreRealm();
393            }
394    
395            Thread.currentThread().setContextClassLoader( projectRealm );
396        }
397    
398    }