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