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( "Loading 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             populateMojoExecutionFields( mojo, mojoExecution.getExecutionId(), mojoDescriptor, pluginRealm,
598                                          pomConfiguration, expressionEvaluator );
599 
600             return mojo;
601         }
602         finally
603         {
604             Thread.currentThread().setContextClassLoader( oldClassLoader );
605             container.setLookupRealm( oldLookupRealm );
606         }
607     }
608 
609     private void populateMojoExecutionFields( Object mojo, String executionId, MojoDescriptor mojoDescriptor,
610                                               ClassRealm pluginRealm, PlexusConfiguration configuration,
611                                               ExpressionEvaluator expressionEvaluator )
612         throws PluginConfigurationException
613     {
614         ComponentConfigurator configurator = null;
615 
616         String configuratorId = mojoDescriptor.getComponentConfigurator();
617 
618         if ( StringUtils.isEmpty( configuratorId ) )
619         {
620             configuratorId = "basic";
621         }
622 
623         try
624         {
625             // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
626             // so that this method could entirely be handled by a plexus lookup?
627             configurator = container.lookup( ComponentConfigurator.class, configuratorId );
628 
629             ConfigurationListener listener = new DebugConfigurationListener( logger );
630 
631             ValidatingConfigurationListener validator =
632                 new ValidatingConfigurationListener( mojo, mojoDescriptor, listener );
633 
634             logger.debug( "Configuring mojo execution '" + mojoDescriptor.getId() + ':' + executionId + "' with "
635                 + configuratorId + " configurator -->" );
636 
637             configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator );
638 
639             logger.debug( "-- end configuration --" );
640 
641             Collection<Parameter> missingParameters = validator.getMissingParameters();
642             if ( !missingParameters.isEmpty() )
643             {
644                 if ( "basic".equals( configuratorId ) )
645                 {
646                     throw new PluginParameterException( mojoDescriptor, new ArrayList<>( missingParameters ) );
647                 }
648                 else
649                 {
650                     /*
651                      * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
652                      * hard way.
653                      */
654                     validateParameters( mojoDescriptor, configuration, expressionEvaluator );
655                 }
656             }
657         }
658         catch ( ComponentConfigurationException e )
659         {
660             String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
661             if ( e.getFailedConfiguration() != null )
662             {
663                 message += " for parameter " + e.getFailedConfiguration().getName();
664             }
665             message += ": " + e.getMessage();
666 
667             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), message, e );
668         }
669         catch ( ComponentLookupException e )
670         {
671             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(),
672                                                     "Unable to retrieve component configurator " + configuratorId
673                                                         + " for configuration of mojo " + mojoDescriptor.getId(), e );
674         }
675         catch ( NoClassDefFoundError e )
676         {
677             ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
678             PrintStream ps = new PrintStream( os );
679             ps.println( "A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
680                             + e.getMessage() );
681             pluginRealm.display( ps );
682 
683             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
684         }
685         catch ( LinkageError e )
686         {
687             ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
688             PrintStream ps = new PrintStream( os );
689             ps.println(
690                 "An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId() + ": "
691                     + e.getClass().getName() + ": " + e.getMessage() );
692             pluginRealm.display( ps );
693 
694             throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
695         }
696         finally
697         {
698             if ( configurator != null )
699             {
700                 try
701                 {
702                     container.release( configurator );
703                 }
704                 catch ( ComponentLifecycleException e )
705                 {
706                     logger.debug( "Failed to release mojo configurator - ignoring." );
707                 }
708             }
709         }
710     }
711 
712     private void validateParameters( MojoDescriptor mojoDescriptor, PlexusConfiguration configuration,
713                                      ExpressionEvaluator expressionEvaluator )
714         throws ComponentConfigurationException, PluginParameterException
715     {
716         if ( mojoDescriptor.getParameters() == null )
717         {
718             return;
719         }
720 
721         List<Parameter> invalidParameters = new ArrayList<>();
722 
723         for ( Parameter parameter : mojoDescriptor.getParameters() )
724         {
725             if ( !parameter.isRequired() )
726             {
727                 continue;
728             }
729 
730             Object value = null;
731 
732             PlexusConfiguration config = configuration.getChild( parameter.getName(), false );
733             if ( config != null )
734             {
735                 String expression = config.getValue( null );
736 
737                 try
738                 {
739                     value = expressionEvaluator.evaluate( expression );
740 
741                     if ( value == null )
742                     {
743                         value = config.getAttribute( "default-value", null );
744                     }
745                 }
746                 catch ( ExpressionEvaluationException e )
747                 {
748                     String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
749                         + configuration.getName() + "'";
750                     throw new ComponentConfigurationException( configuration, msg, e );
751                 }
752             }
753 
754             if ( value == null && ( config == null || config.getChildCount() <= 0 ) )
755             {
756                 invalidParameters.add( parameter );
757             }
758         }
759 
760         if ( !invalidParameters.isEmpty() )
761         {
762             throw new PluginParameterException( mojoDescriptor, invalidParameters );
763         }
764     }
765 
766     public void releaseMojo( Object mojo, MojoExecution mojoExecution )
767     {
768         if ( mojo != null )
769         {
770             try
771             {
772                 container.release( mojo );
773             }
774             catch ( ComponentLifecycleException e )
775             {
776                 String goalExecId = mojoExecution.getGoal();
777 
778                 if ( mojoExecution.getExecutionId() != null )
779                 {
780                     goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
781                 }
782 
783                 logger.debug( "Error releasing mojo for " + goalExecId, e );
784             }
785         }
786     }
787 
788     public ExtensionRealmCache.CacheRecord setupExtensionsRealm( MavenProject project, Plugin plugin,
789                                                                  RepositorySystemSession session )
790         throws PluginManagerException
791     {
792         @SuppressWarnings( "unchecked" ) Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
793             (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue( KEY_EXTENSIONS_REALMS );
794         if ( pluginRealms == null )
795         {
796             pluginRealms = new HashMap<>();
797             project.setContextValue( KEY_EXTENSIONS_REALMS, pluginRealms );
798         }
799 
800         final String pluginKey = plugin.getId();
801 
802         ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get( pluginKey );
803         if ( extensionRecord != null )
804         {
805             return extensionRecord;
806         }
807 
808         final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
809 
810         // resolve plugin version as necessary
811         if ( plugin.getVersion() == null )
812         {
813             PluginVersionRequest versionRequest = new DefaultPluginVersionRequest( plugin, session, repositories );
814             try
815             {
816                 plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
817             }
818             catch ( PluginVersionResolutionException e )
819             {
820                 throw new PluginManagerException( plugin, e.getMessage(), e );
821             }
822         }
823 
824         // resolve plugin artifacts
825         List<Artifact> artifacts;
826         PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey( plugin, null, repositories, session );
827         PluginArtifactsCache.CacheRecord recordArtifacts;
828         try
829         {
830             recordArtifacts = pluginArtifactsCache.get( cacheKey );
831         }
832         catch ( PluginResolutionException e )
833         {
834             throw new PluginManagerException( plugin, e.getMessage(), e );
835         }
836         if ( recordArtifacts != null )
837         {
838             artifacts = recordArtifacts.getArtifacts();
839         }
840         else
841         {
842             try
843             {
844                 artifacts = resolveExtensionArtifacts( plugin, repositories, session );
845                 recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts );
846             }
847             catch ( PluginResolutionException e )
848             {
849                 pluginArtifactsCache.put( cacheKey, e );
850                 pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
851                 throw new PluginManagerException( plugin, e.getMessage(), e );
852             }
853         }
854         pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
855 
856         // create and cache extensions realms
857         final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey( artifacts );
858         extensionRecord = extensionRealmCache.get( extensionKey );
859         if ( extensionRecord == null )
860         {
861             ClassRealm extensionRealm =
862                 classRealmManager.createExtensionRealm( plugin, toAetherArtifacts( artifacts ) );
863 
864             // TODO figure out how to use the same PluginDescriptor when running mojos
865 
866             PluginDescriptor pluginDescriptor = null;
867             if ( plugin.isExtensions() && !artifacts.isEmpty() )
868             {
869                 // ignore plugin descriptor parsing errors at this point
870                 // these errors will reported during calculation of project build execution plan
871                 try
872                 {
873                     pluginDescriptor = extractPluginDescriptor( artifacts.get( 0 ), plugin );
874                 }
875                 catch ( PluginDescriptorParsingException | InvalidPluginDescriptorException e )
876                 {
877                     // ignore, see above
878                 }
879             }
880 
881             discoverPluginComponents( extensionRealm, plugin, pluginDescriptor );
882 
883             ExtensionDescriptor extensionDescriptor = null;
884             Artifact extensionArtifact = artifacts.get( 0 );
885             try
886             {
887                 extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() );
888             }
889             catch ( IOException e )
890             {
891                 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
892                 if ( logger.isDebugEnabled() )
893                 {
894                     logger.error( message, e );
895                 }
896                 else
897                 {
898                     logger.error( message );
899                 }
900             }
901             extensionRecord = extensionRealmCache.put( extensionKey, extensionRealm, extensionDescriptor, artifacts );
902         }
903         extensionRealmCache.register( project, extensionKey, extensionRecord );
904         pluginRealms.put( pluginKey, extensionRecord );
905 
906         return extensionRecord;
907     }
908 
909     private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories,
910                                                       RepositorySystemSession session )
911         throws PluginResolutionException
912     {
913         DependencyNode root = pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories, session );
914         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
915         root.accept( nlg );
916         return toMavenArtifacts( root, nlg );
917     }
918 
919 }