001package org.apache.maven.plugin.internal;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.BufferedInputStream;
023import java.io.ByteArrayOutputStream;
024import java.io.File;
025import java.io.FileInputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.PrintStream;
029import java.io.Reader;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Map;
037import java.util.jar.JarFile;
038import java.util.zip.ZipEntry;
039
040import org.apache.maven.RepositoryUtils;
041import org.apache.maven.artifact.Artifact;
042import org.apache.maven.classrealm.ClassRealmManager;
043import org.apache.maven.execution.MavenSession;
044import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
045import org.apache.maven.model.Plugin;
046import org.apache.maven.monitor.logging.DefaultLog;
047import org.apache.maven.plugin.ContextEnabled;
048import org.apache.maven.plugin.DebugConfigurationListener;
049import org.apache.maven.plugin.ExtensionRealmCache;
050import org.apache.maven.plugin.InvalidPluginDescriptorException;
051import org.apache.maven.plugin.MavenPluginManager;
052import org.apache.maven.plugin.MavenPluginValidator;
053import org.apache.maven.plugin.Mojo;
054import org.apache.maven.plugin.MojoExecution;
055import org.apache.maven.plugin.MojoNotFoundException;
056import org.apache.maven.plugin.PluginArtifactsCache;
057import org.apache.maven.plugin.PluginConfigurationException;
058import org.apache.maven.plugin.PluginContainerException;
059import org.apache.maven.plugin.PluginDescriptorCache;
060import org.apache.maven.plugin.PluginDescriptorParsingException;
061import org.apache.maven.plugin.PluginIncompatibleException;
062import org.apache.maven.plugin.PluginManagerException;
063import org.apache.maven.plugin.PluginParameterException;
064import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
065import org.apache.maven.plugin.PluginRealmCache;
066import org.apache.maven.plugin.PluginResolutionException;
067import org.apache.maven.plugin.descriptor.MojoDescriptor;
068import org.apache.maven.plugin.descriptor.Parameter;
069import org.apache.maven.plugin.descriptor.PluginDescriptor;
070import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
071import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
072import org.apache.maven.plugin.version.PluginVersionRequest;
073import org.apache.maven.plugin.version.PluginVersionResolutionException;
074import org.apache.maven.plugin.version.PluginVersionResolver;
075import org.apache.maven.project.ExtensionDescriptor;
076import org.apache.maven.project.ExtensionDescriptorBuilder;
077import org.apache.maven.project.MavenProject;
078import org.apache.maven.rtinfo.RuntimeInformation;
079import org.apache.maven.session.scope.internal.SessionScopeModule;
080import org.codehaus.plexus.DefaultPlexusContainer;
081import org.codehaus.plexus.PlexusContainer;
082import org.codehaus.plexus.classworlds.realm.ClassRealm;
083import org.codehaus.plexus.component.annotations.Component;
084import org.codehaus.plexus.component.annotations.Requirement;
085import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
086import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
087import org.codehaus.plexus.component.configurator.ComponentConfigurator;
088import org.codehaus.plexus.component.configurator.ConfigurationListener;
089import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
090import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
091import org.codehaus.plexus.component.repository.ComponentDescriptor;
092import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
093import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
094import org.codehaus.plexus.configuration.PlexusConfiguration;
095import org.codehaus.plexus.configuration.PlexusConfigurationException;
096import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
097import org.codehaus.plexus.logging.Logger;
098import org.codehaus.plexus.logging.LoggerManager;
099import org.codehaus.plexus.util.IOUtil;
100import org.codehaus.plexus.util.ReaderFactory;
101import org.codehaus.plexus.util.StringUtils;
102import org.codehaus.plexus.util.xml.Xpp3Dom;
103import org.eclipse.aether.RepositorySystemSession;
104import org.eclipse.aether.graph.DependencyFilter;
105import org.eclipse.aether.graph.DependencyNode;
106import org.eclipse.aether.repository.RemoteRepository;
107import org.eclipse.aether.util.filter.AndDependencyFilter;
108import 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 )
119public 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}