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