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