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