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.maven.RepositoryUtils;
23  import org.apache.maven.artifact.Artifact;
24  import org.apache.maven.classrealm.ClassRealmManager;
25  import org.apache.maven.execution.MavenSession;
26  import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
27  import org.apache.maven.model.Plugin;
28  import org.apache.maven.monitor.logging.DefaultLog;
29  import org.apache.maven.plugin.ContextEnabled;
30  import org.apache.maven.plugin.DebugConfigurationListener;
31  import org.apache.maven.plugin.ExtensionRealmCache;
32  import org.apache.maven.plugin.InvalidPluginDescriptorException;
33  import org.apache.maven.plugin.MavenPluginManager;
34  import org.apache.maven.plugin.MavenPluginValidator;
35  import org.apache.maven.plugin.Mojo;
36  import org.apache.maven.plugin.MojoExecution;
37  import org.apache.maven.plugin.MojoNotFoundException;
38  import org.apache.maven.plugin.PluginArtifactsCache;
39  import org.apache.maven.plugin.PluginConfigurationException;
40  import org.apache.maven.plugin.PluginContainerException;
41  import org.apache.maven.plugin.PluginDescriptorCache;
42  import org.apache.maven.plugin.PluginDescriptorParsingException;
43  import org.apache.maven.plugin.PluginIncompatibleException;
44  import org.apache.maven.plugin.PluginManagerException;
45  import org.apache.maven.plugin.PluginParameterException;
46  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
47  import org.apache.maven.plugin.PluginRealmCache;
48  import org.apache.maven.plugin.PluginResolutionException;
49  import org.apache.maven.plugin.descriptor.MojoDescriptor;
50  import org.apache.maven.plugin.descriptor.Parameter;
51  import org.apache.maven.plugin.descriptor.PluginDescriptor;
52  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
53  import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
54  import org.apache.maven.plugin.version.PluginVersionRequest;
55  import org.apache.maven.plugin.version.PluginVersionResolutionException;
56  import org.apache.maven.plugin.version.PluginVersionResolver;
57  import org.apache.maven.project.ExtensionDescriptor;
58  import org.apache.maven.project.ExtensionDescriptorBuilder;
59  import org.apache.maven.project.MavenProject;
60  import org.apache.maven.rtinfo.RuntimeInformation;
61  import org.apache.maven.session.scope.internal.SessionScopeModule;
62  import org.codehaus.plexus.DefaultPlexusContainer;
63  import org.codehaus.plexus.PlexusContainer;
64  import org.codehaus.plexus.classworlds.realm.ClassRealm;
65  import org.codehaus.plexus.component.annotations.Component;
66  import org.codehaus.plexus.component.annotations.Requirement;
67  import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
68  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
69  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
70  import org.codehaus.plexus.component.configurator.ConfigurationListener;
71  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
72  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
73  import org.codehaus.plexus.component.repository.ComponentDescriptor;
74  import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
75  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
76  import org.codehaus.plexus.configuration.PlexusConfiguration;
77  import org.codehaus.plexus.configuration.PlexusConfigurationException;
78  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
79  import org.codehaus.plexus.logging.Logger;
80  import org.codehaus.plexus.logging.LoggerManager;
81  import org.codehaus.plexus.util.ReaderFactory;
82  import org.codehaus.plexus.util.StringUtils;
83  import org.codehaus.plexus.util.xml.Xpp3Dom;
84  import org.eclipse.aether.RepositorySystemSession;
85  import org.eclipse.aether.graph.DependencyFilter;
86  import org.eclipse.aether.graph.DependencyNode;
87  import org.eclipse.aether.repository.RemoteRepository;
88  import org.eclipse.aether.util.filter.AndDependencyFilter;
89  import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
90  
91  import java.io.BufferedInputStream;
92  import java.io.ByteArrayOutputStream;
93  import java.io.File;
94  import java.io.FileInputStream;
95  import java.io.IOException;
96  import java.io.InputStream;
97  import java.io.PrintStream;
98  import java.io.Reader;
99  import java.util.ArrayList;
100 import java.util.Collection;
101 import java.util.Collections;
102 import java.util.HashMap;
103 import java.util.Iterator;
104 import java.util.List;
105 import java.util.Map;
106 import java.util.Objects;
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.getRealm();
343             List<Artifact> pluginArtifacts = extensionRecord.getArtifacts();
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.getRealm() );
366                 pluginDescriptor.setArtifacts( new ArrayList<>( cacheRecord.getArtifacts() ) );
367                 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
368                 {
369                     componentDescriptor.setRealm( cacheRecord.getRealm() );
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 =
389             Objects.requireNonNull( pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null" );
390 
391         Artifact pluginArtifact = Objects.requireNonNull( pluginDescriptor.getPluginArtifact(),
392                                                           "pluginDescriptor.pluginArtifact cannot be null" );
393 
394         MavenProject project = session.getCurrentProject();
395 
396         final ClassRealm pluginRealm;
397         final List<Artifact> pluginArtifacts;
398 
399         RepositorySystemSession repositorySession = session.getRepositorySession();
400         DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
401         dependencyFilter = AndDependencyFilter.newInstance( dependencyFilter, filter );
402 
403         DependencyNode root =
404             pluginDependenciesResolver.resolve( plugin, RepositoryUtils.toArtifact( pluginArtifact ), dependencyFilter,
405                                                 project.getRemotePluginRepositories(), repositorySession );
406 
407         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
408         root.accept( nlg );
409 
410         pluginArtifacts = toMavenArtifacts( root, nlg );
411 
412         pluginRealm = classRealmManager.createPluginRealm( plugin, parent, null, foreignImports,
413                                                            toAetherArtifacts( pluginArtifacts ) );
414 
415         discoverPluginComponents( pluginRealm, plugin, pluginDescriptor );
416 
417         pluginDescriptor.setClassRealm( pluginRealm );
418         pluginDescriptor.setArtifacts( pluginArtifacts );
419     }
420 
421     private void discoverPluginComponents( final ClassRealm pluginRealm, Plugin plugin,
422                                            PluginDescriptor pluginDescriptor )
423         throws PluginContainerException
424     {
425         try
426         {
427             if ( pluginDescriptor != null )
428             {
429                 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
430                 {
431                     componentDescriptor.setRealm( pluginRealm );
432                     container.addComponentDescriptor( componentDescriptor );
433                 }
434             }
435 
436             ( (DefaultPlexusContainer) container ).discoverComponents( pluginRealm, new SessionScopeModule( container ),
437                                                                        new MojoExecutionScopeModule( container ) );
438         }
439         catch ( ComponentLookupException | CycleDetectedInComponentGraphException e )
440         {
441             throw new PluginContainerException( plugin, pluginRealm,
442                                                 "Error in component graph of plugin " + plugin.getId() + ": "
443                                                     + e.getMessage(), e );
444         }
445     }
446 
447     private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts( final List<Artifact> pluginArtifacts )
448     {
449         return new ArrayList<>( RepositoryUtils.toArtifacts( pluginArtifacts ) );
450     }
451 
452     private List<Artifact> toMavenArtifacts( DependencyNode root, PreorderNodeListGenerator nlg )
453     {
454         List<Artifact> artifacts = new ArrayList<>( nlg.getNodes().size() );
455         RepositoryUtils.toArtifacts( artifacts, Collections.singleton( root ), Collections.<String>emptyList(), null );
456         for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
457         {
458             Artifact artifact = it.next();
459             if ( artifact.getFile() == null )
460             {
461                 it.remove();
462             }
463         }
464         return Collections.unmodifiableList( artifacts );
465     }
466 
467     private Map<String, ClassLoader> calcImports( MavenProject project, ClassLoader parent, List<String> imports )
468     {
469         Map<String, ClassLoader> foreignImports = new HashMap<>();
470 
471         ClassLoader projectRealm = project.getClassRealm();
472         if ( projectRealm != null )
473         {
474             foreignImports.put( "", projectRealm );
475         }
476         else
477         {
478             foreignImports.put( "", classRealmManager.getMavenApiRealm() );
479         }
480 
481         if ( parent != null && imports != null )
482         {
483             for ( String parentImport : imports )
484             {
485                 foreignImports.put( parentImport, parent );
486             }
487         }
488 
489         return foreignImports;
490     }
491 
492     public <T> T getConfiguredMojo( Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution )
493         throws PluginConfigurationException, PluginContainerException
494     {
495         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
496 
497         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
498 
499         ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
500 
501         if ( logger.isDebugEnabled() )
502         {
503             logger.debug( "Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm );
504         }
505 
506         // We are forcing the use of the plugin realm for all lookups that might occur during
507         // the lifecycle that is part of the lookup. Here we are specifically trying to keep
508         // lookups that occur in contextualize calls in line with the right realm.
509         ClassRealm oldLookupRealm = container.setLookupRealm( pluginRealm );
510 
511         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
512         Thread.currentThread().setContextClassLoader( pluginRealm );
513 
514         try
515         {
516             T mojo;
517 
518             try
519             {
520                 mojo = container.lookup( mojoInterface, mojoDescriptor.getRoleHint() );
521             }
522             catch ( ComponentLookupException e )
523             {
524                 Throwable cause = e.getCause();
525                 while ( cause != null && !( cause instanceof LinkageError )
526                     && !( cause instanceof ClassNotFoundException ) )
527                 {
528                     cause = cause.getCause();
529                 }
530 
531                 if ( ( cause instanceof NoClassDefFoundError ) || ( cause instanceof ClassNotFoundException ) )
532                 {
533                     ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
534                     PrintStream ps = new PrintStream( os );
535                     ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
536                                     + pluginDescriptor.getId() + "'. A required class is missing: "
537                                     + cause.getMessage() );
538                     pluginRealm.display( ps );
539 
540                     throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
541                 }
542                 else if ( cause instanceof LinkageError )
543                 {
544                     ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
545                     PrintStream ps = new PrintStream( os );
546                     ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
547                                     + pluginDescriptor.getId() + "' due to an API incompatibility: "
548                                     + e.getClass().getName() + ": " + cause.getMessage() );
549                     pluginRealm.display( ps );
550 
551                     throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
552                 }
553 
554                 throw new PluginContainerException( mojoDescriptor, pluginRealm,
555                                                     "Unable to load the mojo '" + mojoDescriptor.getGoal()
556                                                         + "' (or one of its required components) from the plugin '"
557                                                         + pluginDescriptor.getId() + "'", e );
558             }
559 
560             if ( mojo instanceof ContextEnabled )
561             {
562                 MavenProject project = session.getCurrentProject();
563 
564                 Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, project );
565 
566                 if ( pluginContext != null )
567                 {
568                     pluginContext.put( "project", project );
569 
570                     pluginContext.put( "pluginDescriptor", pluginDescriptor );
571 
572                     ( (ContextEnabled) mojo ).setPluginContext( pluginContext );
573                 }
574             }
575 
576             if ( mojo instanceof Mojo )
577             {
578                 Logger mojoLogger = loggerManager.getLoggerForComponent( mojoDescriptor.getImplementation() );
579                 ( (Mojo) mojo ).setLog( new DefaultLog( mojoLogger ) );
580             }
581 
582             Xpp3Dom dom = mojoExecution.getConfiguration();
583 
584             PlexusConfiguration pomConfiguration;
585 
586             if ( dom == null )
587             {
588                 pomConfiguration = new XmlPlexusConfiguration( "configuration" );
589             }
590             else
591             {
592                 pomConfiguration = new XmlPlexusConfiguration( dom );
593             }
594 
595             ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution );
596 
597             populatePluginFields( mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator );
598 
599             return mojo;
600         }
601         finally
602         {
603             Thread.currentThread().setContextClassLoader( oldClassLoader );
604             container.setLookupRealm( oldLookupRealm );
605         }
606     }
607 
608     private void populatePluginFields( Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm,
609                                        PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator )
610         throws PluginConfigurationException
611     {
612         ComponentConfigurator configurator = null;
613 
614         String configuratorId = mojoDescriptor.getComponentConfigurator();
615 
616         if ( StringUtils.isEmpty( configuratorId ) )
617         {
618             configuratorId = "basic";
619         }
620 
621         try
622         {
623             // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
624             // so that this method could entirely be handled by a plexus lookup?
625             configurator = container.lookup( ComponentConfigurator.class, configuratorId );
626 
627             ConfigurationListener listener = new DebugConfigurationListener( logger );
628 
629             ValidatingConfigurationListener validator =
630                 new ValidatingConfigurationListener( mojo, mojoDescriptor, listener );
631 
632             logger.debug(
633                 "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->" );
634 
635             configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator );
636 
637             logger.debug( "-- end configuration --" );
638 
639             Collection<Parameter> missingParameters = validator.getMissingParameters();
640             if ( !missingParameters.isEmpty() )
641             {
642                 if ( "basic".equals( configuratorId ) )
643                 {
644                     throw new PluginParameterException( mojoDescriptor, new ArrayList<>( missingParameters ) );
645                 }
646                 else
647                 {
648                     /*
649                      * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
650                      * hard way.
651                      */
652                     validateParameters( mojoDescriptor, configuration, expressionEvaluator );
653                 }
654             }
655         }
656         catch ( ComponentConfigurationException e )
657         {
658             String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
659             if ( e.getFailedConfiguration() != null )
660             {
661                 message += " for parameter " + e.getFailedConfiguration().getName();
662             }
663             message += ": " + e.getMessage();
664 
665             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), message, e );
666         }
667         catch ( ComponentLookupException e )
668         {
669             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(),
670                                                     "Unable to retrieve component configurator " + configuratorId
671                                                         + " for configuration of mojo " + mojoDescriptor.getId(), e );
672         }
673         catch ( NoClassDefFoundError e )
674         {
675             ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
676             PrintStream ps = new PrintStream( os );
677             ps.println( "A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
678                             + e.getMessage() );
679             pluginRealm.display( ps );
680 
681             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
682         }
683         catch ( LinkageError e )
684         {
685             ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
686             PrintStream ps = new PrintStream( os );
687             ps.println(
688                 "An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId() + ": "
689                     + e.getClass().getName() + ": " + e.getMessage() );
690             pluginRealm.display( ps );
691 
692             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
693         }
694         finally
695         {
696             if ( configurator != null )
697             {
698                 try
699                 {
700                     container.release( configurator );
701                 }
702                 catch ( ComponentLifecycleException e )
703                 {
704                     logger.debug( "Failed to release mojo configurator - ignoring." );
705                 }
706             }
707         }
708     }
709 
710     private void validateParameters( MojoDescriptor mojoDescriptor, PlexusConfiguration configuration,
711                                      ExpressionEvaluator expressionEvaluator )
712         throws ComponentConfigurationException, PluginParameterException
713     {
714         if ( mojoDescriptor.getParameters() == null )
715         {
716             return;
717         }
718 
719         List<Parameter> invalidParameters = new ArrayList<>();
720 
721         for ( Parameter parameter : mojoDescriptor.getParameters() )
722         {
723             if ( !parameter.isRequired() )
724             {
725                 continue;
726             }
727 
728             Object value = null;
729 
730             PlexusConfiguration config = configuration.getChild( parameter.getName(), false );
731             if ( config != null )
732             {
733                 String expression = config.getValue( null );
734 
735                 try
736                 {
737                     value = expressionEvaluator.evaluate( expression );
738 
739                     if ( value == null )
740                     {
741                         value = config.getAttribute( "default-value", null );
742                     }
743                 }
744                 catch ( ExpressionEvaluationException e )
745                 {
746                     String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
747                         + configuration.getName() + "'";
748                     throw new ComponentConfigurationException( configuration, msg, e );
749                 }
750             }
751 
752             if ( value == null && ( config == null || config.getChildCount() <= 0 ) )
753             {
754                 invalidParameters.add( parameter );
755             }
756         }
757 
758         if ( !invalidParameters.isEmpty() )
759         {
760             throw new PluginParameterException( mojoDescriptor, invalidParameters );
761         }
762     }
763 
764     public void releaseMojo( Object mojo, MojoExecution mojoExecution )
765     {
766         if ( mojo != null )
767         {
768             try
769             {
770                 container.release( mojo );
771             }
772             catch ( ComponentLifecycleException e )
773             {
774                 String goalExecId = mojoExecution.getGoal();
775 
776                 if ( mojoExecution.getExecutionId() != null )
777                 {
778                     goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
779                 }
780 
781                 logger.debug( "Error releasing mojo for " + goalExecId, e );
782             }
783         }
784     }
785 
786     public ExtensionRealmCache.CacheRecord setupExtensionsRealm( MavenProject project, Plugin plugin,
787                                                                  RepositorySystemSession session )
788         throws PluginManagerException
789     {
790         @SuppressWarnings( "unchecked" ) Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
791             (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue( KEY_EXTENSIONS_REALMS );
792         if ( pluginRealms == null )
793         {
794             pluginRealms = new HashMap<>();
795             project.setContextValue( KEY_EXTENSIONS_REALMS, pluginRealms );
796         }
797 
798         final String pluginKey = plugin.getId();
799 
800         ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get( pluginKey );
801         if ( extensionRecord != null )
802         {
803             return extensionRecord;
804         }
805 
806         final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
807 
808         // resolve plugin version as necessary
809         if ( plugin.getVersion() == null )
810         {
811             PluginVersionRequest versionRequest = new DefaultPluginVersionRequest( plugin, session, repositories );
812             try
813             {
814                 plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
815             }
816             catch ( PluginVersionResolutionException e )
817             {
818                 throw new PluginManagerException( plugin, e.getMessage(), e );
819             }
820         }
821 
822         // resolve plugin artifacts
823         List<Artifact> artifacts;
824         PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey( plugin, null, repositories, session );
825         PluginArtifactsCache.CacheRecord recordArtifacts;
826         try
827         {
828             recordArtifacts = pluginArtifactsCache.get( cacheKey );
829         }
830         catch ( PluginResolutionException e )
831         {
832             throw new PluginManagerException( plugin, e.getMessage(), e );
833         }
834         if ( recordArtifacts != null )
835         {
836             artifacts = recordArtifacts.getArtifacts();
837         }
838         else
839         {
840             try
841             {
842                 artifacts = resolveExtensionArtifacts( plugin, repositories, session );
843                 recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts );
844             }
845             catch ( PluginResolutionException e )
846             {
847                 pluginArtifactsCache.put( cacheKey, e );
848                 pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
849                 throw new PluginManagerException( plugin, e.getMessage(), e );
850             }
851         }
852         pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
853 
854         // create and cache extensions realms
855         final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey( artifacts );
856         extensionRecord = extensionRealmCache.get( extensionKey );
857         if ( extensionRecord == null )
858         {
859             ClassRealm extensionRealm =
860                 classRealmManager.createExtensionRealm( plugin, toAetherArtifacts( artifacts ) );
861 
862             // TODO figure out how to use the same PluginDescriptor when running mojos
863 
864             PluginDescriptor pluginDescriptor = null;
865             if ( plugin.isExtensions() && !artifacts.isEmpty() )
866             {
867                 // ignore plugin descriptor parsing errors at this point
868                 // these errors will reported during calculation of project build execution plan
869                 try
870                 {
871                     pluginDescriptor = extractPluginDescriptor( artifacts.get( 0 ), plugin );
872                 }
873                 catch ( PluginDescriptorParsingException | InvalidPluginDescriptorException e )
874                 {
875                     // ignore, see above
876                 }
877             }
878 
879             discoverPluginComponents( extensionRealm, plugin, pluginDescriptor );
880 
881             ExtensionDescriptor extensionDescriptor = null;
882             Artifact extensionArtifact = artifacts.get( 0 );
883             try
884             {
885                 extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() );
886             }
887             catch ( IOException e )
888             {
889                 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
890                 if ( logger.isDebugEnabled() )
891                 {
892                     logger.error( message, e );
893                 }
894                 else
895                 {
896                     logger.error( message );
897                 }
898             }
899             extensionRecord = extensionRealmCache.put( extensionKey, extensionRealm, extensionDescriptor, artifacts );
900         }
901         extensionRealmCache.register( project, extensionKey, extensionRecord );
902         pluginRealms.put( pluginKey, extensionRecord );
903 
904         return extensionRecord;
905     }
906 
907     private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories,
908                                                       RepositorySystemSession session )
909         throws PluginResolutionException
910     {
911         DependencyNode root = pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories, session );
912         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
913         root.accept( nlg );
914         return toMavenArtifacts( root, nlg );
915     }
916 
917 }