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 org.apache.commons.lang3.Validate;
023import org.apache.maven.RepositoryUtils;
024import org.apache.maven.artifact.Artifact;
025import org.apache.maven.classrealm.ClassRealmManager;
026import org.apache.maven.execution.MavenSession;
027import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
028import org.apache.maven.model.Plugin;
029import org.apache.maven.monitor.logging.DefaultLog;
030import org.apache.maven.plugin.ContextEnabled;
031import org.apache.maven.plugin.DebugConfigurationListener;
032import org.apache.maven.plugin.ExtensionRealmCache;
033import org.apache.maven.plugin.InvalidPluginDescriptorException;
034import org.apache.maven.plugin.MavenPluginManager;
035import org.apache.maven.plugin.MavenPluginValidator;
036import org.apache.maven.plugin.Mojo;
037import org.apache.maven.plugin.MojoExecution;
038import org.apache.maven.plugin.MojoNotFoundException;
039import org.apache.maven.plugin.PluginArtifactsCache;
040import org.apache.maven.plugin.PluginConfigurationException;
041import org.apache.maven.plugin.PluginContainerException;
042import org.apache.maven.plugin.PluginDescriptorCache;
043import org.apache.maven.plugin.PluginDescriptorParsingException;
044import org.apache.maven.plugin.PluginIncompatibleException;
045import org.apache.maven.plugin.PluginManagerException;
046import org.apache.maven.plugin.PluginParameterException;
047import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
048import org.apache.maven.plugin.PluginRealmCache;
049import org.apache.maven.plugin.PluginResolutionException;
050import org.apache.maven.plugin.descriptor.MojoDescriptor;
051import org.apache.maven.plugin.descriptor.Parameter;
052import org.apache.maven.plugin.descriptor.PluginDescriptor;
053import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
054import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
055import org.apache.maven.plugin.version.PluginVersionRequest;
056import org.apache.maven.plugin.version.PluginVersionResolutionException;
057import org.apache.maven.plugin.version.PluginVersionResolver;
058import org.apache.maven.project.ExtensionDescriptor;
059import org.apache.maven.project.ExtensionDescriptorBuilder;
060import org.apache.maven.project.MavenProject;
061import org.apache.maven.rtinfo.RuntimeInformation;
062import org.apache.maven.session.scope.internal.SessionScopeModule;
063import org.codehaus.plexus.DefaultPlexusContainer;
064import org.codehaus.plexus.PlexusContainer;
065import org.codehaus.plexus.classworlds.realm.ClassRealm;
066import org.codehaus.plexus.component.annotations.Component;
067import org.codehaus.plexus.component.annotations.Requirement;
068import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
069import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
070import org.codehaus.plexus.component.configurator.ComponentConfigurator;
071import org.codehaus.plexus.component.configurator.ConfigurationListener;
072import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
073import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
074import org.codehaus.plexus.component.repository.ComponentDescriptor;
075import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
076import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
077import org.codehaus.plexus.configuration.PlexusConfiguration;
078import org.codehaus.plexus.configuration.PlexusConfigurationException;
079import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
080import org.codehaus.plexus.logging.Logger;
081import org.codehaus.plexus.logging.LoggerManager;
082import org.codehaus.plexus.util.ReaderFactory;
083import org.codehaus.plexus.util.StringUtils;
084import org.codehaus.plexus.util.xml.Xpp3Dom;
085import org.eclipse.aether.RepositorySystemSession;
086import org.eclipse.aether.graph.DependencyFilter;
087import org.eclipse.aether.graph.DependencyNode;
088import org.eclipse.aether.repository.RemoteRepository;
089import org.eclipse.aether.util.filter.AndDependencyFilter;
090import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
091
092import java.io.BufferedInputStream;
093import java.io.ByteArrayOutputStream;
094import java.io.File;
095import java.io.FileInputStream;
096import java.io.IOException;
097import java.io.InputStream;
098import java.io.PrintStream;
099import java.io.Reader;
100import java.util.ArrayList;
101import java.util.Collection;
102import java.util.Collections;
103import java.util.HashMap;
104import java.util.Iterator;
105import java.util.List;
106import java.util.Map;
107import java.util.jar.JarFile;
108import java.util.zip.ZipEntry;
109
110/**
111 * Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
112 * that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
113 * plugins as well as special purpose plugins like reports.
114 *
115 * @author Benjamin Bentmann
116 * @since 3.0
117 */
118@Component( role = MavenPluginManager.class )
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                try ( JarFile pluginJar = new JarFile( pluginFile, false ) )
208                {
209                    ZipEntry pluginDescriptorEntry = pluginJar.getEntry( getPluginDescriptorLocation() );
210
211                    if ( pluginDescriptorEntry != null )
212                    {
213                        InputStream is = pluginJar.getInputStream( pluginDescriptorEntry );
214
215                        pluginDescriptor = parsePluginDescriptor( is, plugin, pluginFile.getAbsolutePath() );
216                    }
217                }
218            }
219            else
220            {
221                File pluginXml = new File( pluginFile, getPluginDescriptorLocation() );
222
223                if ( pluginXml.isFile() )
224                {
225                    try ( InputStream is = new BufferedInputStream( new FileInputStream( pluginXml ) ) )
226                    {
227                        pluginDescriptor = parsePluginDescriptor( is, plugin, pluginXml.getAbsolutePath() );
228                    }
229                }
230            }
231
232            if ( pluginDescriptor == null )
233            {
234                throw new IOException( "No plugin descriptor found at " + getPluginDescriptorLocation() );
235            }
236        }
237        catch ( IOException e )
238        {
239            throw new PluginDescriptorParsingException( plugin, pluginFile.getAbsolutePath(), e );
240        }
241
242        MavenPluginValidator validator = new MavenPluginValidator( pluginArtifact );
243
244        validator.validate( pluginDescriptor );
245
246        if ( validator.hasErrors() )
247        {
248            throw new InvalidPluginDescriptorException(
249                "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors() );
250        }
251
252        pluginDescriptor.setPluginArtifact( pluginArtifact );
253
254        return pluginDescriptor;
255    }
256
257    private String getPluginDescriptorLocation()
258    {
259        return "META-INF/maven/plugin.xml";
260    }
261
262    private PluginDescriptor parsePluginDescriptor( InputStream is, Plugin plugin, String descriptorLocation )
263        throws PluginDescriptorParsingException
264    {
265        try
266        {
267            Reader reader = ReaderFactory.newXmlReader( is );
268
269            PluginDescriptor pluginDescriptor = builder.build( reader, descriptorLocation );
270
271            return pluginDescriptor;
272        }
273        catch ( IOException | PlexusConfigurationException e )
274        {
275            throw new PluginDescriptorParsingException( plugin, descriptorLocation, e );
276        }
277    }
278
279    public MojoDescriptor getMojoDescriptor( Plugin plugin, String goal, List<RemoteRepository> repositories,
280                                             RepositorySystemSession session )
281        throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
282        InvalidPluginDescriptorException
283    {
284        PluginDescriptor pluginDescriptor = getPluginDescriptor( plugin, repositories, session );
285
286        MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( goal );
287
288        if ( mojoDescriptor == null )
289        {
290            throw new MojoNotFoundException( goal, pluginDescriptor );
291        }
292
293        return mojoDescriptor;
294    }
295
296    public void checkRequiredMavenVersion( PluginDescriptor pluginDescriptor )
297        throws PluginIncompatibleException
298    {
299        String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion();
300        if ( StringUtils.isNotBlank( requiredMavenVersion ) )
301        {
302            try
303            {
304                if ( !runtimeInformation.isMavenVersion( requiredMavenVersion ) )
305                {
306                    throw new PluginIncompatibleException( pluginDescriptor.getPlugin(),
307                                                           "The plugin " + pluginDescriptor.getId()
308                                                               + " requires Maven version " + requiredMavenVersion );
309                }
310            }
311            catch ( RuntimeException e )
312            {
313                logger.warn( "Could not verify plugin's Maven prerequisite: " + e.getMessage() );
314            }
315        }
316    }
317
318    public synchronized void setupPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session,
319                                               ClassLoader parent, List<String> imports, DependencyFilter filter )
320        throws PluginResolutionException, PluginContainerException
321    {
322        Plugin plugin = pluginDescriptor.getPlugin();
323        MavenProject project = session.getCurrentProject();
324
325        if ( plugin.isExtensions() )
326        {
327            ExtensionRealmCache.CacheRecord extensionRecord;
328            try
329            {
330                RepositorySystemSession repositorySession = session.getRepositorySession();
331                extensionRecord = setupExtensionsRealm( project, plugin, repositorySession );
332            }
333            catch ( PluginManagerException e )
334            {
335                // extensions realm is expected to be fully setup at this point
336                // any exception means a problem in maven code, not a user error
337                throw new IllegalStateException( e );
338            }
339
340            ClassRealm pluginRealm = extensionRecord.realm;
341            List<Artifact> pluginArtifacts = extensionRecord.artifacts;
342
343            for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
344            {
345                componentDescriptor.setRealm( pluginRealm );
346            }
347
348            pluginDescriptor.setClassRealm( pluginRealm );
349            pluginDescriptor.setArtifacts( pluginArtifacts );
350        }
351        else
352        {
353            Map<String, ClassLoader> foreignImports = calcImports( project, parent, imports );
354
355            PluginRealmCache.Key cacheKey = pluginRealmCache.createKey( plugin, parent, foreignImports, filter,
356                                                                        project.getRemotePluginRepositories(),
357                                                                        session.getRepositorySession() );
358
359            PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get( cacheKey );
360
361            if ( cacheRecord != null )
362            {
363                pluginDescriptor.setClassRealm( cacheRecord.realm );
364                pluginDescriptor.setArtifacts( new ArrayList<>( cacheRecord.artifacts ) );
365                for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
366                {
367                    componentDescriptor.setRealm( cacheRecord.realm );
368                }
369            }
370            else
371            {
372                createPluginRealm( pluginDescriptor, session, parent, foreignImports, filter );
373
374                cacheRecord =
375                    pluginRealmCache.put( cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts() );
376            }
377
378            pluginRealmCache.register( project, cacheKey, cacheRecord );
379        }
380    }
381
382    private void createPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent,
383                                    Map<String, ClassLoader> foreignImports, DependencyFilter filter )
384        throws PluginResolutionException, PluginContainerException
385    {
386        Plugin plugin = Validate.notNull( pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null" );
387
388        Artifact pluginArtifact =
389            Validate.notNull( pluginDescriptor.getPluginArtifact(), "pluginDescriptor.pluginArtifact cannot be null" );
390
391        MavenProject project = session.getCurrentProject();
392
393        final ClassRealm pluginRealm;
394        final List<Artifact> pluginArtifacts;
395
396        RepositorySystemSession repositorySession = session.getRepositorySession();
397        DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
398        dependencyFilter = AndDependencyFilter.newInstance( dependencyFilter, filter );
399
400        DependencyNode root =
401            pluginDependenciesResolver.resolve( plugin, RepositoryUtils.toArtifact( pluginArtifact ), dependencyFilter,
402                                                project.getRemotePluginRepositories(), repositorySession );
403
404        PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
405        root.accept( nlg );
406
407        pluginArtifacts = toMavenArtifacts( root, nlg );
408
409        pluginRealm = classRealmManager.createPluginRealm( plugin, parent, null, foreignImports,
410                                                           toAetherArtifacts( pluginArtifacts ) );
411
412        discoverPluginComponents( pluginRealm, plugin, pluginDescriptor );
413
414        pluginDescriptor.setClassRealm( pluginRealm );
415        pluginDescriptor.setArtifacts( pluginArtifacts );
416    }
417
418    private void discoverPluginComponents( final ClassRealm pluginRealm, Plugin plugin,
419                                           PluginDescriptor pluginDescriptor )
420        throws PluginContainerException
421    {
422        try
423        {
424            if ( pluginDescriptor != null )
425            {
426                for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
427                {
428                    componentDescriptor.setRealm( pluginRealm );
429                    container.addComponentDescriptor( componentDescriptor );
430                }
431            }
432
433            ( (DefaultPlexusContainer) container ).discoverComponents( pluginRealm, new SessionScopeModule( container ),
434                                                                       new MojoExecutionScopeModule( container ) );
435        }
436        catch ( ComponentLookupException | CycleDetectedInComponentGraphException e )
437        {
438            throw new PluginContainerException( plugin, pluginRealm,
439                                                "Error in component graph of plugin " + plugin.getId() + ": "
440                                                    + e.getMessage(), e );
441        }
442    }
443
444    private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts( final List<Artifact> pluginArtifacts )
445    {
446        return new ArrayList<>( RepositoryUtils.toArtifacts( pluginArtifacts ) );
447    }
448
449    private List<Artifact> toMavenArtifacts( DependencyNode root, PreorderNodeListGenerator nlg )
450    {
451        List<Artifact> artifacts = new ArrayList<>( nlg.getNodes().size() );
452        RepositoryUtils.toArtifacts( artifacts, Collections.singleton( root ), Collections.<String>emptyList(), null );
453        for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
454        {
455            Artifact artifact = it.next();
456            if ( artifact.getFile() == null )
457            {
458                it.remove();
459            }
460        }
461        return artifacts;
462    }
463
464    private Map<String, ClassLoader> calcImports( MavenProject project, ClassLoader parent, List<String> imports )
465    {
466        Map<String, ClassLoader> foreignImports = new HashMap<>();
467
468        ClassLoader projectRealm = project.getClassRealm();
469        if ( projectRealm != null )
470        {
471            foreignImports.put( "", projectRealm );
472        }
473        else
474        {
475            foreignImports.put( "", classRealmManager.getMavenApiRealm() );
476        }
477
478        if ( parent != null && imports != null )
479        {
480            for ( String parentImport : imports )
481            {
482                foreignImports.put( parentImport, parent );
483            }
484        }
485
486        return foreignImports;
487    }
488
489    public <T> T getConfiguredMojo( Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution )
490        throws PluginConfigurationException, PluginContainerException
491    {
492        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
493
494        PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
495
496        ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
497
498        if ( logger.isDebugEnabled() )
499        {
500            logger.debug( "Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm );
501        }
502
503        // We are forcing the use of the plugin realm for all lookups that might occur during
504        // the lifecycle that is part of the lookup. Here we are specifically trying to keep
505        // lookups that occur in contextualize calls in line with the right realm.
506        ClassRealm oldLookupRealm = container.setLookupRealm( pluginRealm );
507
508        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
509        Thread.currentThread().setContextClassLoader( pluginRealm );
510
511        try
512        {
513            T mojo;
514
515            try
516            {
517                mojo = container.lookup( mojoInterface, mojoDescriptor.getRoleHint() );
518            }
519            catch ( ComponentLookupException e )
520            {
521                Throwable cause = e.getCause();
522                while ( cause != null && !( cause instanceof LinkageError )
523                    && !( cause instanceof ClassNotFoundException ) )
524                {
525                    cause = cause.getCause();
526                }
527
528                if ( ( cause instanceof NoClassDefFoundError ) || ( cause instanceof ClassNotFoundException ) )
529                {
530                    ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
531                    PrintStream ps = new PrintStream( os );
532                    ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
533                                    + pluginDescriptor.getId() + "'. A required class is missing: "
534                                    + cause.getMessage() );
535                    pluginRealm.display( ps );
536
537                    throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
538                }
539                else if ( cause instanceof LinkageError )
540                {
541                    ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
542                    PrintStream ps = new PrintStream( os );
543                    ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
544                                    + pluginDescriptor.getId() + "' due to an API incompatibility: "
545                                    + e.getClass().getName() + ": " + cause.getMessage() );
546                    pluginRealm.display( ps );
547
548                    throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
549                }
550
551                throw new PluginContainerException( mojoDescriptor, pluginRealm,
552                                                    "Unable to load the mojo '" + mojoDescriptor.getGoal()
553                                                        + "' (or one of its required components) from the plugin '"
554                                                        + pluginDescriptor.getId() + "'", e );
555            }
556
557            if ( mojo instanceof ContextEnabled )
558            {
559                MavenProject project = session.getCurrentProject();
560
561                Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, project );
562
563                if ( pluginContext != null )
564                {
565                    pluginContext.put( "project", project );
566
567                    pluginContext.put( "pluginDescriptor", pluginDescriptor );
568
569                    ( (ContextEnabled) mojo ).setPluginContext( pluginContext );
570                }
571            }
572
573            if ( mojo instanceof Mojo )
574            {
575                Logger mojoLogger = loggerManager.getLoggerForComponent( mojoDescriptor.getImplementation() );
576                ( (Mojo) mojo ).setLog( new DefaultLog( mojoLogger ) );
577            }
578
579            Xpp3Dom dom = mojoExecution.getConfiguration();
580
581            PlexusConfiguration pomConfiguration;
582
583            if ( dom == null )
584            {
585                pomConfiguration = new XmlPlexusConfiguration( "configuration" );
586            }
587            else
588            {
589                pomConfiguration = new XmlPlexusConfiguration( dom );
590            }
591
592            ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution );
593
594            populatePluginFields( mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator );
595
596            return mojo;
597        }
598        finally
599        {
600            Thread.currentThread().setContextClassLoader( oldClassLoader );
601            container.setLookupRealm( oldLookupRealm );
602        }
603    }
604
605    private void populatePluginFields( Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm,
606                                       PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator )
607        throws PluginConfigurationException
608    {
609        ComponentConfigurator configurator = null;
610
611        String configuratorId = mojoDescriptor.getComponentConfigurator();
612
613        if ( StringUtils.isEmpty( configuratorId ) )
614        {
615            configuratorId = "basic";
616        }
617
618        try
619        {
620            // TODO: could the configuration be passed to lookup and the configurator known to plexus via the descriptor
621            // so that this method could entirely be handled by a plexus lookup?
622            configurator = container.lookup( ComponentConfigurator.class, configuratorId );
623
624            ConfigurationListener listener = new DebugConfigurationListener( logger );
625
626            ValidatingConfigurationListener validator =
627                new ValidatingConfigurationListener( mojo, mojoDescriptor, listener );
628
629            logger.debug(
630                "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->" );
631
632            configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator );
633
634            logger.debug( "-- end configuration --" );
635
636            Collection<Parameter> missingParameters = validator.getMissingParameters();
637            if ( !missingParameters.isEmpty() )
638            {
639                if ( "basic".equals( configuratorId ) )
640                {
641                    throw new PluginParameterException( mojoDescriptor, new ArrayList<>( missingParameters ) );
642                }
643                else
644                {
645                    /*
646                     * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
647                     * hard way.
648                     */
649                    validateParameters( mojoDescriptor, configuration, expressionEvaluator );
650                }
651            }
652        }
653        catch ( ComponentConfigurationException e )
654        {
655            String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
656            if ( e.getFailedConfiguration() != null )
657            {
658                message += " for parameter " + e.getFailedConfiguration().getName();
659            }
660            message += ": " + e.getMessage();
661
662            throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), message, e );
663        }
664        catch ( ComponentLookupException e )
665        {
666            throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(),
667                                                    "Unable to retrieve component configurator " + configuratorId
668                                                        + " for configuration of mojo " + mojoDescriptor.getId(), e );
669        }
670        catch ( NoClassDefFoundError e )
671        {
672            ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
673            PrintStream ps = new PrintStream( os );
674            ps.println( "A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
675                            + e.getMessage() );
676            pluginRealm.display( ps );
677
678            throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
679        }
680        catch ( LinkageError e )
681        {
682            ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
683            PrintStream ps = new PrintStream( os );
684            ps.println(
685                "An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId() + ": "
686                    + e.getClass().getName() + ": " + e.getMessage() );
687            pluginRealm.display( ps );
688
689            throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
690        }
691        finally
692        {
693            if ( configurator != null )
694            {
695                try
696                {
697                    container.release( configurator );
698                }
699                catch ( ComponentLifecycleException e )
700                {
701                    logger.debug( "Failed to release mojo configurator - ignoring." );
702                }
703            }
704        }
705    }
706
707    private void validateParameters( MojoDescriptor mojoDescriptor, PlexusConfiguration configuration,
708                                     ExpressionEvaluator expressionEvaluator )
709        throws ComponentConfigurationException, PluginParameterException
710    {
711        if ( mojoDescriptor.getParameters() == null )
712        {
713            return;
714        }
715
716        List<Parameter> invalidParameters = new ArrayList<>();
717
718        for ( Parameter parameter : mojoDescriptor.getParameters() )
719        {
720            if ( !parameter.isRequired() )
721            {
722                continue;
723            }
724
725            Object value = null;
726
727            PlexusConfiguration config = configuration.getChild( parameter.getName(), false );
728            if ( config != null )
729            {
730                String expression = config.getValue( null );
731
732                try
733                {
734                    value = expressionEvaluator.evaluate( expression );
735
736                    if ( value == null )
737                    {
738                        value = config.getAttribute( "default-value", null );
739                    }
740                }
741                catch ( ExpressionEvaluationException e )
742                {
743                    String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
744                        + configuration.getName() + "'";
745                    throw new ComponentConfigurationException( configuration, msg, e );
746                }
747            }
748
749            if ( value == null && ( config == null || config.getChildCount() <= 0 ) )
750            {
751                invalidParameters.add( parameter );
752            }
753        }
754
755        if ( !invalidParameters.isEmpty() )
756        {
757            throw new PluginParameterException( mojoDescriptor, invalidParameters );
758        }
759    }
760
761    public void releaseMojo( Object mojo, MojoExecution mojoExecution )
762    {
763        if ( mojo != null )
764        {
765            try
766            {
767                container.release( mojo );
768            }
769            catch ( ComponentLifecycleException e )
770            {
771                String goalExecId = mojoExecution.getGoal();
772
773                if ( mojoExecution.getExecutionId() != null )
774                {
775                    goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
776                }
777
778                logger.debug( "Error releasing mojo for " + goalExecId, e );
779            }
780        }
781    }
782
783    public ExtensionRealmCache.CacheRecord setupExtensionsRealm( MavenProject project, Plugin plugin,
784                                                                 RepositorySystemSession session )
785        throws PluginManagerException
786    {
787        @SuppressWarnings( "unchecked" ) Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
788            (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue( KEY_EXTENSIONS_REALMS );
789        if ( pluginRealms == null )
790        {
791            pluginRealms = new HashMap<>();
792            project.setContextValue( KEY_EXTENSIONS_REALMS, pluginRealms );
793        }
794
795        final String pluginKey = plugin.getId();
796
797        ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get( pluginKey );
798        if ( extensionRecord != null )
799        {
800            return extensionRecord;
801        }
802
803        final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
804
805        // resolve plugin version as necessary
806        if ( plugin.getVersion() == null )
807        {
808            PluginVersionRequest versionRequest = new DefaultPluginVersionRequest( plugin, session, repositories );
809            try
810            {
811                plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
812            }
813            catch ( PluginVersionResolutionException e )
814            {
815                throw new PluginManagerException( plugin, e.getMessage(), e );
816            }
817        }
818
819        // resolve plugin artifacts
820        List<Artifact> artifacts;
821        PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey( plugin, null, repositories, session );
822        PluginArtifactsCache.CacheRecord recordArtifacts;
823        try
824        {
825            recordArtifacts = pluginArtifactsCache.get( cacheKey );
826        }
827        catch ( PluginResolutionException e )
828        {
829            throw new PluginManagerException( plugin, e.getMessage(), e );
830        }
831        if ( recordArtifacts != null )
832        {
833            artifacts = recordArtifacts.artifacts;
834        }
835        else
836        {
837            try
838            {
839                artifacts = resolveExtensionArtifacts( plugin, repositories, session );
840                recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts );
841            }
842            catch ( PluginResolutionException e )
843            {
844                pluginArtifactsCache.put( cacheKey, e );
845                pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
846                throw new PluginManagerException( plugin, e.getMessage(), e );
847            }
848        }
849        pluginArtifactsCache.register( project, cacheKey, recordArtifacts );
850
851        // create and cache extensions realms
852        final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey( artifacts );
853        extensionRecord = extensionRealmCache.get( extensionKey );
854        if ( extensionRecord == null )
855        {
856            ClassRealm extensionRealm =
857                classRealmManager.createExtensionRealm( plugin, toAetherArtifacts( artifacts ) );
858
859            // TODO figure out how to use the same PluginDescriptor when running mojos
860
861            PluginDescriptor pluginDescriptor = null;
862            if ( plugin.isExtensions() && !artifacts.isEmpty() )
863            {
864                // ignore plugin descriptor parsing errors at this point
865                // these errors will reported during calculation of project build execution plan
866                try
867                {
868                    pluginDescriptor = extractPluginDescriptor( artifacts.get( 0 ), plugin );
869                }
870                catch ( PluginDescriptorParsingException | InvalidPluginDescriptorException e )
871                {
872                    // ignore, see above
873                }
874            }
875
876            discoverPluginComponents( extensionRealm, plugin, pluginDescriptor );
877
878            ExtensionDescriptor extensionDescriptor = null;
879            Artifact extensionArtifact = artifacts.get( 0 );
880            try
881            {
882                extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() );
883            }
884            catch ( IOException e )
885            {
886                String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
887                if ( logger.isDebugEnabled() )
888                {
889                    logger.error( message, e );
890                }
891                else
892                {
893                    logger.error( message );
894                }
895            }
896            extensionRecord = extensionRealmCache.put( extensionKey, extensionRealm, extensionDescriptor, artifacts );
897        }
898        extensionRealmCache.register( project, extensionKey, extensionRecord );
899        pluginRealms.put( pluginKey, extensionRecord );
900
901        return extensionRecord;
902    }
903
904    private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories,
905                                                      RepositorySystemSession session )
906        throws PluginResolutionException
907    {
908        DependencyNode root = pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories, session );
909        PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
910        root.accept( nlg );
911        return toMavenArtifacts( root, nlg );
912    }
913
914}