View Javadoc
1   package org.apache.maven.plugin.internal;
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 org.apache.commons.lang3.Validate;
23  import org.apache.maven.RepositoryUtils;
24  import org.apache.maven.artifact.Artifact;
25  import org.apache.maven.classrealm.ClassRealmManager;
26  import org.apache.maven.execution.MavenSession;
27  import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
28  import org.apache.maven.model.Plugin;
29  import org.apache.maven.monitor.logging.DefaultLog;
30  import org.apache.maven.plugin.ContextEnabled;
31  import org.apache.maven.plugin.DebugConfigurationListener;
32  import org.apache.maven.plugin.ExtensionRealmCache;
33  import org.apache.maven.plugin.InvalidPluginDescriptorException;
34  import org.apache.maven.plugin.MavenPluginManager;
35  import org.apache.maven.plugin.MavenPluginValidator;
36  import org.apache.maven.plugin.Mojo;
37  import org.apache.maven.plugin.MojoExecution;
38  import org.apache.maven.plugin.MojoNotFoundException;
39  import org.apache.maven.plugin.PluginArtifactsCache;
40  import org.apache.maven.plugin.PluginConfigurationException;
41  import org.apache.maven.plugin.PluginContainerException;
42  import org.apache.maven.plugin.PluginDescriptorCache;
43  import org.apache.maven.plugin.PluginDescriptorParsingException;
44  import org.apache.maven.plugin.PluginIncompatibleException;
45  import org.apache.maven.plugin.PluginManagerException;
46  import org.apache.maven.plugin.PluginParameterException;
47  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
48  import org.apache.maven.plugin.PluginRealmCache;
49  import org.apache.maven.plugin.PluginResolutionException;
50  import org.apache.maven.plugin.descriptor.MojoDescriptor;
51  import org.apache.maven.plugin.descriptor.Parameter;
52  import org.apache.maven.plugin.descriptor.PluginDescriptor;
53  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
54  import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
55  import org.apache.maven.plugin.version.PluginVersionRequest;
56  import org.apache.maven.plugin.version.PluginVersionResolutionException;
57  import org.apache.maven.plugin.version.PluginVersionResolver;
58  import org.apache.maven.project.ExtensionDescriptor;
59  import org.apache.maven.project.ExtensionDescriptorBuilder;
60  import org.apache.maven.project.MavenProject;
61  import org.apache.maven.rtinfo.RuntimeInformation;
62  import org.apache.maven.session.scope.internal.SessionScopeModule;
63  import org.codehaus.plexus.DefaultPlexusContainer;
64  import org.codehaus.plexus.PlexusContainer;
65  import org.codehaus.plexus.classworlds.realm.ClassRealm;
66  import org.codehaus.plexus.component.annotations.Component;
67  import org.codehaus.plexus.component.annotations.Requirement;
68  import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
69  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
70  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
71  import org.codehaus.plexus.component.configurator.ConfigurationListener;
72  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
73  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
74  import org.codehaus.plexus.component.repository.ComponentDescriptor;
75  import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
76  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
77  import org.codehaus.plexus.configuration.PlexusConfiguration;
78  import org.codehaus.plexus.configuration.PlexusConfigurationException;
79  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
80  import org.codehaus.plexus.logging.Logger;
81  import org.codehaus.plexus.logging.LoggerManager;
82  import org.codehaus.plexus.util.ReaderFactory;
83  import org.codehaus.plexus.util.StringUtils;
84  import org.codehaus.plexus.util.xml.Xpp3Dom;
85  import org.eclipse.aether.RepositorySystemSession;
86  import org.eclipse.aether.graph.DependencyFilter;
87  import org.eclipse.aether.graph.DependencyNode;
88  import org.eclipse.aether.repository.RemoteRepository;
89  import org.eclipse.aether.util.filter.AndDependencyFilter;
90  import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
91  
92  import java.io.BufferedInputStream;
93  import java.io.ByteArrayOutputStream;
94  import java.io.File;
95  import java.io.FileInputStream;
96  import java.io.IOException;
97  import java.io.InputStream;
98  import java.io.PrintStream;
99  import java.io.Reader;
100 import java.util.ArrayList;
101 import java.util.Collection;
102 import java.util.Collections;
103 import java.util.HashMap;
104 import java.util.Iterator;
105 import java.util.List;
106 import java.util.Map;
107 import java.util.jar.JarFile;
108 import java.util.zip.ZipEntry;
109 
110 /**
111  * Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
112  * that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
113  * plugins as well as special purpose plugins like reports.
114  *
115  * @author Benjamin Bentmann
116  * @since 3.0
117  */
118 @Component( role = MavenPluginManager.class )
119 public class DefaultMavenPluginManager
120     implements MavenPluginManager
121 {
122 
123     /**
124      * PluginId=>ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the same
125      * class realm is used to load build extensions and load mojos for extensions=true plugins.
126      *
127      * @provisional This is part of internal implementation and may be changed or removed without notice
128      * @since 3.3.0
129      */
130     public static final String KEY_EXTENSIONS_REALMS = DefaultMavenPluginManager.class.getName() + "/extensionsRealms";
131 
132     @Requirement
133     private Logger logger;
134 
135     @Requirement
136     private LoggerManager loggerManager;
137 
138     @Requirement
139     private PlexusContainer container;
140 
141     @Requirement
142     private ClassRealmManager classRealmManager;
143 
144     @Requirement
145     private PluginDescriptorCache pluginDescriptorCache;
146 
147     @Requirement
148     private PluginRealmCache pluginRealmCache;
149 
150     @Requirement
151     private PluginDependenciesResolver pluginDependenciesResolver;
152 
153     @Requirement
154     private RuntimeInformation runtimeInformation;
155 
156     @Requirement
157     private ExtensionRealmCache extensionRealmCache;
158 
159     @Requirement
160     private PluginVersionResolver pluginVersionResolver;
161 
162     @Requirement
163     private PluginArtifactsCache pluginArtifactsCache;
164 
165     private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
166 
167     private PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
168 
169     public synchronized PluginDescriptor getPluginDescriptor( Plugin plugin, List<RemoteRepository> repositories,
170                                                               RepositorySystemSession session )
171         throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException
172     {
173         PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey( plugin, repositories, session );
174 
175         PluginDescriptor pluginDescriptor = pluginDescriptorCache.get( cacheKey );
176 
177         if ( pluginDescriptor == null )
178         {
179             org.eclipse.aether.artifact.Artifact artifact =
180                 pluginDependenciesResolver.resolve( plugin, repositories, session );
181 
182             Artifact pluginArtifact = RepositoryUtils.toArtifact( artifact );
183 
184             pluginDescriptor = extractPluginDescriptor( pluginArtifact, plugin );
185 
186             pluginDescriptor.setRequiredMavenVersion( artifact.getProperty( "requiredMavenVersion", null ) );
187 
188             pluginDescriptorCache.put( cacheKey, pluginDescriptor );
189         }
190 
191         pluginDescriptor.setPlugin( plugin );
192 
193         return pluginDescriptor;
194     }
195 
196     private PluginDescriptor extractPluginDescriptor( Artifact pluginArtifact, Plugin plugin )
197         throws PluginDescriptorParsingException, InvalidPluginDescriptorException
198     {
199         PluginDescriptor pluginDescriptor = null;
200 
201         File pluginFile = pluginArtifact.getFile();
202 
203         try
204         {
205             if ( pluginFile.isFile() )
206             {
207                 try ( JarFile pluginJar = new JarFile( pluginFile, false ) )
208                 {
209                     ZipEntry pluginDescriptorEntry = pluginJar.getEntry( getPluginDescriptorLocation() );
210 
211                     if ( pluginDescriptorEntry != null )
212                     {
213                         InputStream is = pluginJar.getInputStream( pluginDescriptorEntry );
214 
215                         pluginDescriptor = parsePluginDescriptor( is, plugin, pluginFile.getAbsolutePath() );
216                     }
217                 }
218             }
219             else
220             {
221                 File pluginXml = new File( pluginFile, getPluginDescriptorLocation() );
222 
223                 if ( pluginXml.isFile() )
224                 {
225                     try ( InputStream is = new BufferedInputStream( new FileInputStream( pluginXml ) ) )
226                     {
227                         pluginDescriptor = parsePluginDescriptor( is, plugin, pluginXml.getAbsolutePath() );
228                     }
229                 }
230             }
231 
232             if ( pluginDescriptor == null )
233             {
234                 throw new IOException( "No plugin descriptor found at " + getPluginDescriptorLocation() );
235             }
236         }
237         catch ( IOException e )
238         {
239             throw new PluginDescriptorParsingException( plugin, pluginFile.getAbsolutePath(), e );
240         }
241 
242         MavenPluginValidator validator = new MavenPluginValidator( pluginArtifact );
243 
244         validator.validate( pluginDescriptor );
245 
246         if ( validator.hasErrors() )
247         {
248             throw new InvalidPluginDescriptorException(
249                 "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors() );
250         }
251 
252         pluginDescriptor.setPluginArtifact( pluginArtifact );
253 
254         return pluginDescriptor;
255     }
256 
257     private String getPluginDescriptorLocation()
258     {
259         return "META-INF/maven/plugin.xml";
260     }
261 
262     private PluginDescriptor parsePluginDescriptor( InputStream is, Plugin plugin, String descriptorLocation )
263         throws PluginDescriptorParsingException
264     {
265         try
266         {
267             Reader reader = ReaderFactory.newXmlReader( is );
268 
269             PluginDescriptor pluginDescriptor = builder.build( reader, descriptorLocation );
270 
271             return pluginDescriptor;
272         }
273         catch ( IOException | PlexusConfigurationException e )
274         {
275             throw new PluginDescriptorParsingException( plugin, descriptorLocation, e );
276         }
277     }
278 
279     public MojoDescriptor getMojoDescriptor( Plugin plugin, String goal, List<RemoteRepository> repositories,
280                                              RepositorySystemSession session )
281         throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
282         InvalidPluginDescriptorException
283     {
284         PluginDescriptor pluginDescriptor = getPluginDescriptor( plugin, repositories, session );
285 
286         MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( goal );
287 
288         if ( mojoDescriptor == null )
289         {
290             throw new MojoNotFoundException( goal, pluginDescriptor );
291         }
292 
293         return mojoDescriptor;
294     }
295 
296     public void checkRequiredMavenVersion( PluginDescriptor pluginDescriptor )
297         throws PluginIncompatibleException
298     {
299         String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion();
300         if ( StringUtils.isNotBlank( requiredMavenVersion ) )
301         {
302             try
303             {
304                 if ( !runtimeInformation.isMavenVersion( requiredMavenVersion ) )
305                 {
306                     throw new PluginIncompatibleException( pluginDescriptor.getPlugin(),
307                                                            "The plugin " + pluginDescriptor.getId()
308                                                                + " requires Maven version " + requiredMavenVersion );
309                 }
310             }
311             catch ( RuntimeException e )
312             {
313                 logger.warn( "Could not verify plugin's Maven prerequisite: " + e.getMessage() );
314             }
315         }
316     }
317 
318     public synchronized void setupPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session,
319                                                ClassLoader parent, List<String> imports, DependencyFilter filter )
320         throws PluginResolutionException, PluginContainerException
321     {
322         Plugin plugin = pluginDescriptor.getPlugin();
323         MavenProject project = session.getCurrentProject();
324 
325         if ( plugin.isExtensions() )
326         {
327             ExtensionRealmCache.CacheRecord extensionRecord;
328             try
329             {
330                 RepositorySystemSession repositorySession = session.getRepositorySession();
331                 extensionRecord = setupExtensionsRealm( project, plugin, repositorySession );
332             }
333             catch ( PluginManagerException e )
334             {
335                 // extensions realm is expected to be fully setup at this point
336                 // any exception means a problem in maven code, not a user error
337                 throw new IllegalStateException( e );
338             }
339 
340             ClassRealm pluginRealm = extensionRecord.realm;
341             List<Artifact> pluginArtifacts = extensionRecord.artifacts;
342 
343             for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
344             {
345                 componentDescriptor.setRealm( pluginRealm );
346             }
347 
348             pluginDescriptor.setClassRealm( pluginRealm );
349             pluginDescriptor.setArtifacts( pluginArtifacts );
350         }
351         else
352         {
353             Map<String, ClassLoader> foreignImports = calcImports( project, parent, imports );
354 
355             PluginRealmCache.Key cacheKey = pluginRealmCache.createKey( plugin, parent, foreignImports, filter,
356                                                                         project.getRemotePluginRepositories(),
357                                                                         session.getRepositorySession() );
358 
359             PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get( cacheKey );
360 
361             if ( cacheRecord != null )
362             {
363                 pluginDescriptor.setClassRealm( cacheRecord.realm );
364                 pluginDescriptor.setArtifacts( new ArrayList<>( cacheRecord.artifacts ) );
365                 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
366                 {
367                     componentDescriptor.setRealm( cacheRecord.realm );
368                 }
369             }
370             else
371             {
372                 createPluginRealm( pluginDescriptor, session, parent, foreignImports, filter );
373 
374                 cacheRecord =
375                     pluginRealmCache.put( cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts() );
376             }
377 
378             pluginRealmCache.register( project, cacheKey, cacheRecord );
379         }
380     }
381 
382     private void createPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent,
383                                     Map<String, ClassLoader> foreignImports, DependencyFilter filter )
384         throws PluginResolutionException, PluginContainerException
385     {
386         Plugin plugin = Validate.notNull( pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null" );
387 
388         Artifact pluginArtifact =
389             Validate.notNull( pluginDescriptor.getPluginArtifact(), "pluginDescriptor.pluginArtifact cannot be null" );
390 
391         MavenProject project = session.getCurrentProject();
392 
393         final ClassRealm pluginRealm;
394         final List<Artifact> pluginArtifacts;
395 
396         RepositorySystemSession repositorySession = session.getRepositorySession();
397         DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
398         dependencyFilter = AndDependencyFilter.newInstance( dependencyFilter, filter );
399 
400         DependencyNode root =
401             pluginDependenciesResolver.resolve( plugin, RepositoryUtils.toArtifact( pluginArtifact ), dependencyFilter,
402                                                 project.getRemotePluginRepositories(), repositorySession );
403 
404         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
405         root.accept( nlg );
406 
407         pluginArtifacts = toMavenArtifacts( root, nlg );
408 
409         pluginRealm = classRealmManager.createPluginRealm( plugin, parent, null, foreignImports,
410                                                            toAetherArtifacts( pluginArtifacts ) );
411 
412         discoverPluginComponents( pluginRealm, plugin, pluginDescriptor );
413 
414         pluginDescriptor.setClassRealm( pluginRealm );
415         pluginDescriptor.setArtifacts( pluginArtifacts );
416     }
417 
418     private void discoverPluginComponents( final ClassRealm pluginRealm, Plugin plugin,
419                                            PluginDescriptor pluginDescriptor )
420         throws PluginContainerException
421     {
422         try
423         {
424             if ( pluginDescriptor != null )
425             {
426                 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
427                 {
428                     componentDescriptor.setRealm( pluginRealm );
429                     container.addComponentDescriptor( componentDescriptor );
430                 }
431             }
432 
433             ( (DefaultPlexusContainer) container ).discoverComponents( pluginRealm, new SessionScopeModule( container ),
434                                                                        new MojoExecutionScopeModule( container ) );
435         }
436         catch ( ComponentLookupException | CycleDetectedInComponentGraphException e )
437         {
438             throw new PluginContainerException( plugin, pluginRealm,
439                                                 "Error in component graph of plugin " + plugin.getId() + ": "
440                                                     + e.getMessage(), e );
441         }
442     }
443 
444     private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts( final List<Artifact> pluginArtifacts )
445     {
446         return new ArrayList<>( RepositoryUtils.toArtifacts( pluginArtifacts ) );
447     }
448 
449     private List<Artifact> toMavenArtifacts( DependencyNode root, PreorderNodeListGenerator nlg )
450     {
451         List<Artifact> artifacts = new ArrayList<>( nlg.getNodes().size() );
452         RepositoryUtils.toArtifacts( artifacts, Collections.singleton( root ), Collections.<String>emptyList(), null );
453         for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
454         {
455             Artifact artifact = it.next();
456             if ( artifact.getFile() == null )
457             {
458                 it.remove();
459             }
460         }
461         return artifacts;
462     }
463 
464     private Map<String, ClassLoader> calcImports( MavenProject project, ClassLoader parent, List<String> imports )
465     {
466         Map<String, ClassLoader> foreignImports = new HashMap<>();
467 
468         ClassLoader projectRealm = project.getClassRealm();
469         if ( projectRealm != null )
470         {
471             foreignImports.put( "", projectRealm );
472         }
473         else
474         {
475             foreignImports.put( "", classRealmManager.getMavenApiRealm() );
476         }
477 
478         if ( parent != null && imports != null )
479         {
480             for ( String parentImport : imports )
481             {
482                 foreignImports.put( parentImport, parent );
483             }
484         }
485 
486         return foreignImports;
487     }
488 
489     public <T> T getConfiguredMojo( Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution )
490         throws PluginConfigurationException, PluginContainerException
491     {
492         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
493 
494         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
495 
496         ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
497 
498         if ( logger.isDebugEnabled() )
499         {
500             logger.debug( "Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm );
501         }
502 
503         // We are forcing the use of the plugin realm for all lookups that might occur during
504         // the lifecycle that is part of the lookup. Here we are specifically trying to keep
505         // lookups that occur in contextualize calls in line with the right realm.
506         ClassRealm oldLookupRealm = container.setLookupRealm( pluginRealm );
507 
508         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
509         Thread.currentThread().setContextClassLoader( pluginRealm );
510 
511         try
512         {
513             T mojo;
514 
515             try
516             {
517                 mojo = container.lookup( mojoInterface, mojoDescriptor.getRoleHint() );
518             }
519             catch ( ComponentLookupException e )
520             {
521                 Throwable cause = e.getCause();
522                 while ( cause != null && !( cause instanceof LinkageError )
523                     && !( cause instanceof ClassNotFoundException ) )
524                 {
525                     cause = cause.getCause();
526                 }
527 
528                 if ( ( cause instanceof NoClassDefFoundError ) || ( cause instanceof ClassNotFoundException ) )
529                 {
530                     ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
531                     PrintStream ps = new PrintStream( os );
532                     ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
533                                     + pluginDescriptor.getId() + "'. A required class is missing: "
534                                     + cause.getMessage() );
535                     pluginRealm.display( ps );
536 
537                     throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
538                 }
539                 else if ( cause instanceof LinkageError )
540                 {
541                     ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
542                     PrintStream ps = new PrintStream( os );
543                     ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
544                                     + pluginDescriptor.getId() + "' due to an API incompatibility: "
545                                     + e.getClass().getName() + ": " + cause.getMessage() );
546                     pluginRealm.display( ps );
547 
548                     throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
549                 }
550 
551                 throw new PluginContainerException( mojoDescriptor, pluginRealm,
552                                                     "Unable to load the mojo '" + mojoDescriptor.getGoal()
553                                                         + "' (or one of its required components) from the plugin '"
554                                                         + pluginDescriptor.getId() + "'", e );
555             }
556 
557             if ( mojo instanceof ContextEnabled )
558             {
559                 MavenProject project = session.getCurrentProject();
560 
561                 Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, project );
562 
563                 if ( pluginContext != null )
564                 {
565                     pluginContext.put( "project", project );
566 
567                     pluginContext.put( "pluginDescriptor", pluginDescriptor );
568 
569                     ( (ContextEnabled) mojo ).setPluginContext( pluginContext );
570                 }
571             }
572 
573             if ( mojo instanceof Mojo )
574             {
575                 Logger mojoLogger = loggerManager.getLoggerForComponent( mojoDescriptor.getImplementation() );
576                 ( (Mojo) mojo ).setLog( new DefaultLog( mojoLogger ) );
577             }
578 
579             Xpp3Dom dom = mojoExecution.getConfiguration();
580 
581             PlexusConfiguration pomConfiguration;
582 
583             if ( dom == null )
584             {
585                 pomConfiguration = new XmlPlexusConfiguration( "configuration" );
586             }
587             else
588             {
589                 pomConfiguration = new XmlPlexusConfiguration( dom );
590             }
591 
592             ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution );
593 
594             populatePluginFields( mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator );
595 
596             return mojo;
597         }
598         finally
599         {
600             Thread.currentThread().setContextClassLoader( oldClassLoader );
601             container.setLookupRealm( oldLookupRealm );
602         }
603     }
604 
605     private void populatePluginFields( Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm,
606                                        PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator )
607         throws PluginConfigurationException
608     {
609         ComponentConfigurator configurator = null;
610 
611         String configuratorId = mojoDescriptor.getComponentConfigurator();
612 
613         if ( StringUtils.isEmpty( configuratorId ) )
614         {
615             configuratorId = "basic";
616         }
617 
618         try
619         {
620             // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
621             // so that this method could entirely be handled by a plexus lookup?
622             configurator = container.lookup( ComponentConfigurator.class, configuratorId );
623 
624             ConfigurationListener listener = new DebugConfigurationListener( logger );
625 
626             ValidatingConfigurationListener validator =
627                 new ValidatingConfigurationListener( mojo, mojoDescriptor, listener );
628 
629             logger.debug(
630                 "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->" );
631 
632             configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator );
633 
634             logger.debug( "-- end configuration --" );
635 
636             Collection<Parameter> missingParameters = validator.getMissingParameters();
637             if ( !missingParameters.isEmpty() )
638             {
639                 if ( "basic".equals( configuratorId ) )
640                 {
641                     throw new PluginParameterException( mojoDescriptor, new ArrayList<>( missingParameters ) );
642                 }
643                 else
644                 {
645                     /*
646                      * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
647                      * hard way.
648                      */
649                     validateParameters( mojoDescriptor, configuration, expressionEvaluator );
650                 }
651             }
652         }
653         catch ( ComponentConfigurationException e )
654         {
655             String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
656             if ( e.getFailedConfiguration() != null )
657             {
658                 message += " for parameter " + e.getFailedConfiguration().getName();
659             }
660             message += ": " + e.getMessage();
661 
662             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), message, e );
663         }
664         catch ( ComponentLookupException e )
665         {
666             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(),
667                                                     "Unable to retrieve component configurator " + configuratorId
668                                                         + " for configuration of mojo " + mojoDescriptor.getId(), e );
669         }
670         catch ( NoClassDefFoundError e )
671         {
672             ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
673             PrintStream ps = new PrintStream( os );
674             ps.println( "A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
675                             + e.getMessage() );
676             pluginRealm.display( ps );
677 
678             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
679         }
680         catch ( LinkageError e )
681         {
682             ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
683             PrintStream ps = new PrintStream( os );
684             ps.println(
685                 "An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId() + ": "
686                     + e.getClass().getName() + ": " + e.getMessage() );
687             pluginRealm.display( ps );
688 
689             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
690         }
691         finally
692         {
693             if ( configurator != null )
694             {
695                 try
696                 {
697                     container.release( configurator );
698                 }
699                 catch ( ComponentLifecycleException e )
700                 {
701                     logger.debug( "Failed to release mojo configurator - ignoring." );
702                 }
703             }
704         }
705     }
706 
707     private void validateParameters( MojoDescriptor mojoDescriptor, PlexusConfiguration configuration,
708                                      ExpressionEvaluator expressionEvaluator )
709         throws ComponentConfigurationException, PluginParameterException
710     {
711         if ( mojoDescriptor.getParameters() == null )
712         {
713             return;
714         }
715 
716         List<Parameter> invalidParameters = new ArrayList<>();
717 
718         for ( Parameter parameter : mojoDescriptor.getParameters() )
719         {
720             if ( !parameter.isRequired() )
721             {
722                 continue;
723             }
724 
725             Object value = null;
726 
727             PlexusConfiguration config = configuration.getChild( parameter.getName(), false );
728             if ( config != null )
729             {
730                 String expression = config.getValue( null );
731 
732                 try
733                 {
734                     value = expressionEvaluator.evaluate( expression );
735 
736                     if ( value == null )
737                     {
738                         value = config.getAttribute( "default-value", null );
739                     }
740                 }
741                 catch ( ExpressionEvaluationException e )
742                 {
743                     String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
744                         + configuration.getName() + "'";
745                     throw new ComponentConfigurationException( configuration, msg, e );
746                 }
747             }
748 
749             if ( value == null && ( config == null || config.getChildCount() <= 0 ) )
750             {
751                 invalidParameters.add( parameter );
752             }
753         }
754 
755         if ( !invalidParameters.isEmpty() )
756         {
757             throw new PluginParameterException( mojoDescriptor, invalidParameters );
758         }
759     }
760 
761     public void releaseMojo( Object mojo, MojoExecution mojoExecution )
762     {
763         if ( mojo != null )
764         {
765             try
766             {
767                 container.release( mojo );
768             }
769             catch ( ComponentLifecycleException e )
770             {
771                 String goalExecId = mojoExecution.getGoal();
772 
773                 if ( mojoExecution.getExecutionId() != null )
774                 {
775                     goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
776                 }
777 
778                 logger.debug( "Error releasing mojo for " + goalExecId, e );
779             }
780         }
781     }
782 
783     public ExtensionRealmCache.CacheRecord setupExtensionsRealm( MavenProject project, Plugin plugin,
784                                                                  RepositorySystemSession session )
785         throws PluginManagerException
786     {
787         @SuppressWarnings( "unchecked" ) Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
788             (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue( KEY_EXTENSIONS_REALMS );
789         if ( pluginRealms == null )
790         {
791             pluginRealms = new HashMap<>();
792             project.setContextValue( KEY_EXTENSIONS_REALMS, pluginRealms );
793         }
794 
795         final String pluginKey = plugin.getId();
796 
797         ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get( pluginKey );
798         if ( extensionRecord != null )
799         {
800             return extensionRecord;
801         }
802 
803         final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
804 
805         // resolve plugin version as necessary
806         if ( plugin.getVersion() == null )
807         {
808             PluginVersionRequest versionRequest = new DefaultPluginVersionRequest( plugin, session, repositories );
809             try
810             {
811                 plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
812             }
813             catch ( PluginVersionResolutionException e )
814             {
815                 throw new PluginManagerException( plugin, e.getMessage(), e );
816             }
817         }
818 
819         // resolve plugin artifacts
820         List<Artifact> artifacts;
821         PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey( plugin, null, repositories, session );
822         PluginArtifactsCache.CacheRecord recordArtifacts;
823         try
824         {
825             recordArtifacts = pluginArtifactsCache.get( cacheKey );
826         }
827         catch ( PluginResolutionException e )
828         {
829             throw new PluginManagerException( plugin, e.getMessage(), e );
830         }
831         if ( recordArtifacts != null )
832         {
833             artifacts = recordArtifacts.artifacts;
834         }
835         else
836         {
837             try
838             {
839                 artifacts = resolveExtensionArtifacts( plugin, repositories, session );
840                 recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts );
841             }
842             catch ( PluginResolutionException e )
843             {
844                 pluginArtifactsCache.put( cacheKey, e );
845                 pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
846                 throw new PluginManagerException( plugin, e.getMessage(), e );
847             }
848         }
849         pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
850 
851         // create and cache extensions realms
852         final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey( artifacts );
853         extensionRecord = extensionRealmCache.get( extensionKey );
854         if ( extensionRecord == null )
855         {
856             ClassRealm extensionRealm =
857                 classRealmManager.createExtensionRealm( plugin, toAetherArtifacts( artifacts ) );
858 
859             // TODO figure out how to use the same PluginDescriptor when running mojos
860 
861             PluginDescriptor pluginDescriptor = null;
862             if ( plugin.isExtensions() && !artifacts.isEmpty() )
863             {
864                 // ignore plugin descriptor parsing errors at this point
865                 // these errors will reported during calculation of project build execution plan
866                 try
867                 {
868                     pluginDescriptor = extractPluginDescriptor( artifacts.get( 0 ), plugin );
869                 }
870                 catch ( PluginDescriptorParsingException | InvalidPluginDescriptorException e )
871                 {
872                     // ignore, see above
873                 }
874             }
875 
876             discoverPluginComponents( extensionRealm, plugin, pluginDescriptor );
877 
878             ExtensionDescriptor extensionDescriptor = null;
879             Artifact extensionArtifact = artifacts.get( 0 );
880             try
881             {
882                 extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() );
883             }
884             catch ( IOException e )
885             {
886                 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
887                 if ( logger.isDebugEnabled() )
888                 {
889                     logger.error( message, e );
890                 }
891                 else
892                 {
893                     logger.error( message );
894                 }
895             }
896             extensionRecord = extensionRealmCache.put( extensionKey, extensionRealm, extensionDescriptor, artifacts );
897         }
898         extensionRealmCache.register( project, extensionKey, extensionRecord );
899         pluginRealms.put( pluginKey, extensionRecord );
900 
901         return extensionRecord;
902     }
903 
904     private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories,
905                                                       RepositorySystemSession session )
906         throws PluginResolutionException
907     {
908         DependencyNode root = pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories, session );
909         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
910         root.accept( nlg );
911         return toMavenArtifacts( root, nlg );
912     }
913 
914 }