View Javadoc
1   package org.apache.maven.project;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.maven.artifact.InvalidRepositoryException;
34  import org.apache.maven.artifact.repository.ArtifactRepository;
35  import org.apache.maven.classrealm.ClassRealmManager;
36  import org.apache.maven.execution.scope.internal.MojoExecutionScope;
37  import org.apache.maven.model.Build;
38  import org.apache.maven.model.Extension;
39  import org.apache.maven.model.Model;
40  import org.apache.maven.model.Plugin;
41  import org.apache.maven.model.Repository;
42  import org.apache.maven.plugin.ExtensionRealmCache;
43  import org.apache.maven.plugin.PluginArtifactsCache;
44  import org.apache.maven.plugin.PluginResolutionException;
45  import org.apache.maven.plugin.internal.PluginDependenciesResolver;
46  import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
47  import org.apache.maven.plugin.version.PluginVersionRequest;
48  import org.apache.maven.plugin.version.PluginVersionResolutionException;
49  import org.apache.maven.plugin.version.PluginVersionResolver;
50  import org.apache.maven.repository.RepositorySystem;
51  import org.codehaus.plexus.DefaultPlexusContainer;
52  import org.codehaus.plexus.PlexusContainer;
53  import org.codehaus.plexus.classworlds.realm.ClassRealm;
54  import org.codehaus.plexus.component.annotations.Component;
55  import org.codehaus.plexus.component.annotations.Requirement;
56  import org.codehaus.plexus.logging.Logger;
57  import org.eclipse.aether.artifact.Artifact;
58  import org.eclipse.aether.graph.DependencyFilter;
59  import org.eclipse.aether.graph.DependencyNode;
60  import org.eclipse.aether.repository.RemoteRepository;
61  import org.eclipse.aether.util.filter.ExclusionsDependencyFilter;
62  import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
63  
64  /**
65   * Assists the project builder. <strong>Warning:</strong> This is an internal utility class that is only public for
66   * technical reasons, it is not part of the public API. In particular, this class can be changed or deleted without
67   * prior notice.
68   * 
69   * @author Benjamin Bentmann
70   */
71  @Component( role = ProjectBuildingHelper.class )
72  public class DefaultProjectBuildingHelper
73      implements ProjectBuildingHelper
74  {
75  
76      @Requirement
77      private Logger logger;
78  
79      @Requirement
80      private PlexusContainer container;
81  
82      @Requirement
83      private ClassRealmManager classRealmManager;
84  
85      @Requirement
86      private PluginArtifactsCache pluginArtifactsCache;
87  
88      @Requirement
89      private ExtensionRealmCache extensionRealmCache;
90  
91      @Requirement
92      private ProjectRealmCache projectRealmCache;
93  
94      @Requirement
95      private RepositorySystem repositorySystem;
96  
97      @Requirement
98      private PluginVersionResolver pluginVersionResolver;
99  
100     @Requirement
101     private PluginDependenciesResolver pluginDependenciesResolver;
102 
103     private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
104 
105     public List<ArtifactRepository> createArtifactRepositories( List<Repository> pomRepositories,
106                                                                 List<ArtifactRepository> externalRepositories,
107                                                                 ProjectBuildingRequest request )
108         throws InvalidRepositoryException
109     {
110         List<ArtifactRepository> internalRepositories = new ArrayList<ArtifactRepository>();
111 
112         for ( Repository repository : pomRepositories )
113         {
114             internalRepositories.add( repositorySystem.buildArtifactRepository( repository ) );
115         }
116 
117         repositorySystem.injectMirror( request.getRepositorySession(), internalRepositories );
118 
119         repositorySystem.injectProxy( request.getRepositorySession(), internalRepositories );
120 
121         repositorySystem.injectAuthentication( request.getRepositorySession(), internalRepositories );
122 
123         List<ArtifactRepository> dominantRepositories;
124         List<ArtifactRepository> recessiveRepositories;
125 
126         if ( ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT.equals( request.getRepositoryMerging() ) )
127         {
128             dominantRepositories = externalRepositories;
129             recessiveRepositories = internalRepositories;
130         }
131         else
132         {
133             dominantRepositories = internalRepositories;
134             recessiveRepositories = externalRepositories;
135         }
136 
137         List<ArtifactRepository> artifactRepositories = new ArrayList<ArtifactRepository>();
138         Collection<String> repoIds = new HashSet<String>();
139 
140         if ( dominantRepositories != null )
141         {
142             for ( ArtifactRepository repository : dominantRepositories )
143             {
144                 repoIds.add( repository.getId() );
145                 artifactRepositories.add( repository );
146             }
147         }
148 
149         if ( recessiveRepositories != null )
150         {
151             for ( ArtifactRepository repository : recessiveRepositories )
152             {
153                 if ( repoIds.add( repository.getId() ) )
154                 {
155                     artifactRepositories.add( repository );
156                 }
157             }
158         }
159 
160         artifactRepositories = repositorySystem.getEffectiveRepositories( artifactRepositories );
161 
162         return artifactRepositories;
163     }
164 
165     public synchronized ProjectRealmCache.CacheRecord createProjectRealm( MavenProject project, Model model,
166                                                                           ProjectBuildingRequest request )
167         throws PluginResolutionException, PluginVersionResolutionException
168     {
169         ClassRealm projectRealm;
170 
171         List<Plugin> extensionPlugins = new ArrayList<Plugin>();
172 
173         Build build = model.getBuild();
174 
175         if ( build != null )
176         {
177             for ( Extension extension : build.getExtensions() )
178             {
179                 Plugin plugin = new Plugin();
180                 plugin.setGroupId( extension.getGroupId() );
181                 plugin.setArtifactId( extension.getArtifactId() );
182                 plugin.setVersion( extension.getVersion() );
183                 extensionPlugins.add( plugin );
184             }
185 
186             for ( Plugin plugin : build.getPlugins() )
187             {
188                 if ( plugin.isExtensions() )
189                 {
190                     extensionPlugins.add( plugin );
191                 }
192             }
193         }
194 
195         if ( extensionPlugins.isEmpty() )
196         {
197             if ( logger.isDebugEnabled() )
198             {
199                 logger.debug( "Extension realms for project " + model.getId() + ": (none)" );
200             }
201 
202             return new ProjectRealmCache.CacheRecord( null, null );
203         }
204 
205         List<ClassRealm> extensionRealms = new ArrayList<ClassRealm>();
206 
207         Map<ClassRealm, List<String>> exportedPackages = new HashMap<ClassRealm, List<String>>();
208 
209         Map<ClassRealm, List<String>> exportedArtifacts = new HashMap<ClassRealm, List<String>>();
210 
211         List<Artifact> publicArtifacts = new ArrayList<Artifact>();
212 
213         for ( Plugin plugin : extensionPlugins )
214         {
215             if ( plugin.getVersion() == null )
216             {
217                 PluginVersionRequest versionRequest =
218                     new DefaultPluginVersionRequest( plugin, request.getRepositorySession(),
219                                                      project.getRemotePluginRepositories() );
220                 plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
221             }
222 
223             List<Artifact> artifacts;
224 
225             PluginArtifactsCache.Key cacheKey =
226                 pluginArtifactsCache.createKey( plugin, null, project.getRemotePluginRepositories(),
227                                                 request.getRepositorySession() );
228 
229             PluginArtifactsCache.CacheRecord recordArtifacts = pluginArtifactsCache.get( cacheKey );
230 
231             if ( recordArtifacts != null )
232             {
233                 artifacts = recordArtifacts.artifacts;
234             }
235             else
236             {
237                 try
238                 {
239                     artifacts = resolveExtensionArtifacts( plugin, project.getRemotePluginRepositories(), request );
240 
241                     recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts );
242                 }
243                 catch ( PluginResolutionException e )
244                 {
245                     pluginArtifactsCache.put( cacheKey, e );
246 
247                     pluginArtifactsCache.register( project, recordArtifacts );
248 
249                     throw e;
250                 }
251             }
252 
253             pluginArtifactsCache.register( project, recordArtifacts );
254 
255             ClassRealm extensionRealm;
256             ExtensionDescriptor extensionDescriptor = null;
257 
258             ExtensionRealmCache.CacheRecord recordRealm = extensionRealmCache.get( artifacts );
259 
260             if ( recordRealm != null )
261             {
262                 extensionRealm = recordRealm.realm;
263                 extensionDescriptor = recordRealm.desciptor;
264             }
265             else
266             {
267                 extensionRealm = classRealmManager.createExtensionRealm( plugin, artifacts );
268 
269                 try
270                 {
271                     ( (DefaultPlexusContainer) container ).discoverComponents( extensionRealm,
272                                                                                MojoExecutionScope.getScopeModule( container ) );
273                 }
274                 catch ( Exception e )
275                 {
276                     throw new IllegalStateException( "Failed to discover components in extension realm "
277                         + extensionRealm.getId(), e );
278                 }
279 
280                 Artifact extensionArtifact = artifacts.get( 0 );
281                 try
282                 {
283                     extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() );
284                 }
285                 catch ( IOException e )
286                 {
287                     String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
288                     if ( logger.isDebugEnabled() )
289                     {
290                         logger.error( message, e );
291                     }
292                     else
293                     {
294                         logger.error( message );
295                     }
296                 }
297 
298                 recordRealm = extensionRealmCache.put( artifacts, extensionRealm, extensionDescriptor );
299             }
300 
301             extensionRealmCache.register( project, recordRealm );
302 
303             extensionRealms.add( extensionRealm );
304             if ( extensionDescriptor != null )
305             {
306                 exportedPackages.put( extensionRealm, extensionDescriptor.getExportedPackages() );
307                 exportedArtifacts.put( extensionRealm, extensionDescriptor.getExportedArtifacts() );
308             }
309 
310             if ( !plugin.isExtensions() && artifacts.size() == 2 && artifacts.get( 0 ).getFile() != null
311                 && "plexus-utils".equals( artifacts.get( 1 ).getArtifactId() ) )
312             {
313                 /*
314                  * This is purely for backward-compat with 2.x where <extensions> consisting of a single artifact where
315                  * loaded into the core and hence available to plugins, in contrast to bigger extensions that were
316                  * loaded into a dedicated realm which is invisible to plugins (MNG-2749).
317                  */
318                 publicArtifacts.add( artifacts.get( 0 ) );
319             }
320         }
321 
322         if ( logger.isDebugEnabled() )
323         {
324             logger.debug( "Extension realms for project " + model.getId() + ": " + extensionRealms );
325         }
326 
327         ProjectRealmCache.CacheRecord record = projectRealmCache.get( extensionRealms );
328 
329         if ( record == null )
330         {
331             projectRealm = classRealmManager.createProjectRealm( model, publicArtifacts );
332 
333             Set<String> exclusions = new LinkedHashSet<String>();
334 
335             for ( ClassRealm extensionRealm : extensionRealms )
336             {
337                 List<String> excludes = exportedArtifacts.get( extensionRealm );
338 
339                 if ( excludes != null )
340                 {
341                     exclusions.addAll( excludes );
342                 }
343 
344                 List<String> exports = exportedPackages.get( extensionRealm );
345 
346                 if ( exports == null || exports.isEmpty() )
347                 {
348                     /*
349                      * Most existing extensions don't define exported packages, i.e. no classes are to be exposed to
350                      * plugins, yet the components provided by the extension (e.g. artifact handlers) must be
351                      * accessible, i.e. we still must import the extension realm into the project realm.
352                      */
353                     exports = Arrays.asList( extensionRealm.getId() );
354                 }
355 
356                 for ( String export : exports )
357                 {
358                     projectRealm.importFrom( extensionRealm, export );
359                 }
360             }
361 
362             DependencyFilter extensionArtifactFilter = null;
363             if ( !exclusions.isEmpty() )
364             {
365                 extensionArtifactFilter = new ExclusionsDependencyFilter( exclusions );
366             }
367 
368             record = projectRealmCache.put( extensionRealms, projectRealm, extensionArtifactFilter );
369         }
370 
371         projectRealmCache.register( project, record );
372 
373         return record;
374     }
375 
376     private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories,
377                                                       ProjectBuildingRequest request )
378         throws PluginResolutionException
379     {
380         DependencyNode root =
381             pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories,
382                                                 request.getRepositorySession() );
383 
384         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
385         root.accept( nlg );
386         return nlg.getArtifacts( false );
387     }
388 
389     public void selectProjectRealm( MavenProject project )
390     {
391         ClassLoader projectRealm = project.getClassRealm();
392 
393         if ( projectRealm == null )
394         {
395             projectRealm = classRealmManager.getCoreRealm();
396         }
397 
398         Thread.currentThread().setContextClassLoader( projectRealm );
399     }
400 
401 }