View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.internal;
20  
21  import java.io.BufferedInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.PrintStream;
28  import java.io.Reader;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Objects;
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.ReaderFactory;
100 import org.codehaus.plexus.util.StringUtils;
101 import org.codehaus.plexus.util.xml.Xpp3Dom;
102 import org.eclipse.aether.RepositorySystemSession;
103 import org.eclipse.aether.graph.DependencyFilter;
104 import org.eclipse.aether.graph.DependencyNode;
105 import org.eclipse.aether.repository.RemoteRepository;
106 import org.eclipse.aether.util.filter.AndDependencyFilter;
107 import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
108 
109 /**
110  * Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
111  * that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
112  * plugins as well as special purpose plugins like reports.
113  *
114  * @author Benjamin Bentmann
115  * @since 3.0
116  */
117 @Component(role = MavenPluginManager.class)
118 public class DefaultMavenPluginManager implements MavenPluginManager {
119 
120     /**
121      * <p>
122      * PluginId =&gt; ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the
123      * same class realm is used to load build extensions and load mojos for extensions=true plugins.
124      * </p>
125      * <strong>Note:</strong> This is part of internal implementation and may be changed or removed without notice
126      *
127      * @since 3.3.0
128      */
129     public static final String KEY_EXTENSIONS_REALMS = DefaultMavenPluginManager.class.getName() + "/extensionsRealms";
130 
131     @Requirement
132     private Logger logger;
133 
134     @Requirement
135     private LoggerManager loggerManager;
136 
137     @Requirement
138     private PlexusContainer container;
139 
140     @Requirement
141     private ClassRealmManager classRealmManager;
142 
143     @Requirement
144     private PluginDescriptorCache pluginDescriptorCache;
145 
146     @Requirement
147     private PluginRealmCache pluginRealmCache;
148 
149     @Requirement
150     private PluginDependenciesResolver pluginDependenciesResolver;
151 
152     @Requirement
153     private RuntimeInformation runtimeInformation;
154 
155     @Requirement
156     private ExtensionRealmCache extensionRealmCache;
157 
158     @Requirement
159     private PluginVersionResolver pluginVersionResolver;
160 
161     @Requirement
162     private PluginArtifactsCache pluginArtifactsCache;
163 
164     @Requirement
165     private List<MavenPluginConfigurationValidator> configurationValidators;
166 
167     private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
168 
169     private PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
170 
171     public synchronized PluginDescriptor getPluginDescriptor(
172             Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
173             throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
174         PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
175 
176         PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey);
177 
178         if (pluginDescriptor == null) {
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         PluginDescriptor pluginDescriptor = null;
199 
200         File pluginFile = pluginArtifact.getFile();
201 
202         try {
203             if (pluginFile.isFile()) {
204                 try (JarFile pluginJar = new JarFile(pluginFile, false)) {
205                     ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
206 
207                     if (pluginDescriptorEntry != null) {
208                         InputStream is = pluginJar.getInputStream(pluginDescriptorEntry);
209 
210                         pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath());
211                     }
212                 }
213             } else {
214                 File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
215 
216                 if (pluginXml.isFile()) {
217                     try (InputStream is = new BufferedInputStream(new FileInputStream(pluginXml))) {
218                         pluginDescriptor = parsePluginDescriptor(is, plugin, pluginXml.getAbsolutePath());
219                     }
220                 }
221             }
222 
223             if (pluginDescriptor == null) {
224                 throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
225             }
226         } catch (IOException e) {
227             throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
228         }
229 
230         MavenPluginValidator validator = new MavenPluginValidator(pluginArtifact);
231 
232         validator.validate(pluginDescriptor);
233 
234         if (validator.hasErrors()) {
235             throw new InvalidPluginDescriptorException(
236                     "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors());
237         }
238 
239         pluginDescriptor.setPluginArtifact(pluginArtifact);
240 
241         return pluginDescriptor;
242     }
243 
244     private String getPluginDescriptorLocation() {
245         return "META-INF/maven/plugin.xml";
246     }
247 
248     private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation)
249             throws PluginDescriptorParsingException {
250         try {
251             Reader reader = ReaderFactory.newXmlReader(is);
252 
253             PluginDescriptor pluginDescriptor = builder.build(reader, descriptorLocation);
254 
255             return pluginDescriptor;
256         } catch (IOException | PlexusConfigurationException e) {
257             throw new PluginDescriptorParsingException(plugin, descriptorLocation, e);
258         }
259     }
260 
261     public MojoDescriptor getMojoDescriptor(
262             Plugin plugin, String goal, List<RemoteRepository> repositories, RepositorySystemSession session)
263             throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
264                     InvalidPluginDescriptorException {
265         PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session);
266 
267         MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal);
268 
269         if (mojoDescriptor == null) {
270             throw new MojoNotFoundException(goal, pluginDescriptor);
271         }
272 
273         return mojoDescriptor;
274     }
275 
276     public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor) throws PluginIncompatibleException {
277         String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion();
278         if (StringUtils.isNotBlank(requiredMavenVersion)) {
279             try {
280                 if (!runtimeInformation.isMavenVersion(requiredMavenVersion)) {
281                     throw new PluginIncompatibleException(
282                             pluginDescriptor.getPlugin(),
283                             "The plugin " + pluginDescriptor.getId() + " requires Maven version "
284                                     + requiredMavenVersion);
285                 }
286             } catch (RuntimeException e) {
287                 logger.warn("Could not verify plugin's Maven prerequisite: " + e.getMessage());
288             }
289         }
290     }
291 
292     public synchronized void setupPluginRealm(
293             PluginDescriptor pluginDescriptor,
294             MavenSession session,
295             ClassLoader parent,
296             List<String> imports,
297             DependencyFilter filter)
298             throws PluginResolutionException, PluginContainerException {
299         Plugin plugin = pluginDescriptor.getPlugin();
300         MavenProject project = session.getCurrentProject();
301 
302         if (plugin.isExtensions()) {
303             ExtensionRealmCache.CacheRecord extensionRecord;
304             try {
305                 RepositorySystemSession repositorySession = session.getRepositorySession();
306                 extensionRecord = setupExtensionsRealm(project, plugin, repositorySession);
307             } catch (PluginManagerException e) {
308                 // extensions realm is expected to be fully setup at this point
309                 // any exception means a problem in maven code, not a user error
310                 throw new IllegalStateException(e);
311             }
312 
313             ClassRealm pluginRealm = extensionRecord.getRealm();
314             List<Artifact> pluginArtifacts = extensionRecord.getArtifacts();
315 
316             for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
317                 componentDescriptor.setRealm(pluginRealm);
318             }
319 
320             pluginDescriptor.setClassRealm(pluginRealm);
321             pluginDescriptor.setArtifacts(pluginArtifacts);
322         } else {
323             Map<String, ClassLoader> foreignImports = calcImports(project, parent, imports);
324 
325             PluginRealmCache.Key cacheKey = pluginRealmCache.createKey(
326                     plugin,
327                     parent,
328                     foreignImports,
329                     filter,
330                     project.getRemotePluginRepositories(),
331                     session.getRepositorySession());
332 
333             PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey);
334 
335             if (cacheRecord != null) {
336                 pluginDescriptor.setClassRealm(cacheRecord.getRealm());
337                 pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts()));
338                 for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
339                     componentDescriptor.setRealm(cacheRecord.getRealm());
340                 }
341             } else {
342                 createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter);
343 
344                 cacheRecord = pluginRealmCache.put(
345                         cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts());
346             }
347 
348             pluginRealmCache.register(project, cacheKey, cacheRecord);
349         }
350     }
351 
352     private void createPluginRealm(
353             PluginDescriptor pluginDescriptor,
354             MavenSession session,
355             ClassLoader parent,
356             Map<String, ClassLoader> foreignImports,
357             DependencyFilter filter)
358             throws PluginResolutionException, PluginContainerException {
359         Plugin plugin = Objects.requireNonNull(pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null");
360 
361         Artifact pluginArtifact = Objects.requireNonNull(
362                 pluginDescriptor.getPluginArtifact(), "pluginDescriptor.pluginArtifact cannot be null");
363 
364         MavenProject project = session.getCurrentProject();
365 
366         final ClassRealm pluginRealm;
367         final List<Artifact> pluginArtifacts;
368 
369         RepositorySystemSession repositorySession = session.getRepositorySession();
370         DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
371         dependencyFilter = AndDependencyFilter.newInstance(dependencyFilter, filter);
372 
373         DependencyNode root = pluginDependenciesResolver.resolve(
374                 plugin,
375                 RepositoryUtils.toArtifact(pluginArtifact),
376                 dependencyFilter,
377                 project.getRemotePluginRepositories(),
378                 repositorySession);
379 
380         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
381         root.accept(nlg);
382 
383         pluginArtifacts = toMavenArtifacts(root, nlg);
384 
385         pluginRealm = classRealmManager.createPluginRealm(
386                 plugin, parent, null, foreignImports, toAetherArtifacts(pluginArtifacts));
387 
388         discoverPluginComponents(pluginRealm, plugin, pluginDescriptor);
389 
390         pluginDescriptor.setClassRealm(pluginRealm);
391         pluginDescriptor.setArtifacts(pluginArtifacts);
392     }
393 
394     private void discoverPluginComponents(
395             final ClassRealm pluginRealm, Plugin plugin, PluginDescriptor pluginDescriptor)
396             throws PluginContainerException {
397         try {
398             if (pluginDescriptor != null) {
399                 for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
400                     componentDescriptor.setRealm(pluginRealm);
401                     container.addComponentDescriptor(componentDescriptor);
402                 }
403             }
404 
405             ((DefaultPlexusContainer) container)
406                     .discoverComponents(
407                             pluginRealm, new SessionScopeModule(container), new MojoExecutionScopeModule(container));
408         } catch (ComponentLookupException | CycleDetectedInComponentGraphException e) {
409             throw new PluginContainerException(
410                     plugin,
411                     pluginRealm,
412                     "Error in component graph of plugin " + plugin.getId() + ": " + e.getMessage(),
413                     e);
414         }
415     }
416 
417     private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts(final List<Artifact> pluginArtifacts) {
418         return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
419     }
420 
421     private List<Artifact> toMavenArtifacts(DependencyNode root, PreorderNodeListGenerator nlg) {
422         List<Artifact> artifacts = new ArrayList<>(nlg.getNodes().size());
423         RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), Collections.<String>emptyList(), null);
424         for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) {
425             Artifact artifact = it.next();
426             if (artifact.getFile() == null) {
427                 it.remove();
428             }
429         }
430         return Collections.unmodifiableList(artifacts);
431     }
432 
433     private Map<String, ClassLoader> calcImports(MavenProject project, ClassLoader parent, List<String> imports) {
434         Map<String, ClassLoader> foreignImports = new HashMap<>();
435 
436         ClassLoader projectRealm = project.getClassRealm();
437         if (projectRealm != null) {
438             foreignImports.put("", projectRealm);
439         } else {
440             foreignImports.put("", classRealmManager.getMavenApiRealm());
441         }
442 
443         if (parent != null && imports != null) {
444             for (String parentImport : imports) {
445                 foreignImports.put(parentImport, parent);
446             }
447         }
448 
449         return foreignImports;
450     }
451 
452     public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution)
453             throws PluginConfigurationException, PluginContainerException {
454         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
455 
456         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
457 
458         ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
459 
460         if (pluginRealm == null) {
461             try {
462                 setupPluginRealm(pluginDescriptor, session, null, null, null);
463             } catch (PluginResolutionException e) {
464                 String msg = "Cannot setup plugin realm [mojoDescriptor=" + mojoDescriptor.getId()
465                         + ", pluginDescriptor=" + pluginDescriptor.getId() + "]";
466                 throw new PluginConfigurationException(pluginDescriptor, msg, e);
467             }
468             pluginRealm = pluginDescriptor.getClassRealm();
469         }
470 
471         if (logger.isDebugEnabled()) {
472             logger.debug("Loading mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm);
473         }
474 
475         // We are forcing the use of the plugin realm for all lookups that might occur during
476         // the lifecycle that is part of the lookup. Here we are specifically trying to keep
477         // lookups that occur in contextualize calls in line with the right realm.
478         ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
479 
480         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
481         Thread.currentThread().setContextClassLoader(pluginRealm);
482 
483         try {
484             T mojo;
485 
486             try {
487                 mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
488             } catch (ComponentLookupException e) {
489                 Throwable cause = e.getCause();
490                 while (cause != null
491                         && !(cause instanceof LinkageError)
492                         && !(cause instanceof ClassNotFoundException)) {
493                     cause = cause.getCause();
494                 }
495 
496                 if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
497                     ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
498                     PrintStream ps = new PrintStream(os);
499                     ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
500                             + pluginDescriptor.getId() + "'. A required class is missing: "
501                             + cause.getMessage());
502                     pluginRealm.display(ps);
503 
504                     throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
505                 } else if (cause instanceof LinkageError) {
506                     ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
507                     PrintStream ps = new PrintStream(os);
508                     ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
509                             + pluginDescriptor.getId() + "' due to an API incompatibility: "
510                             + e.getClass().getName() + ": " + cause.getMessage());
511                     pluginRealm.display(ps);
512 
513                     throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
514                 }
515 
516                 throw new PluginContainerException(
517                         mojoDescriptor,
518                         pluginRealm,
519                         "Unable to load the mojo '" + mojoDescriptor.getGoal()
520                                 + "' (or one of its required components) from the plugin '"
521                                 + pluginDescriptor.getId() + "'",
522                         e);
523             }
524 
525             if (mojo instanceof ContextEnabled) {
526                 MavenProject project = session.getCurrentProject();
527 
528                 Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
529 
530                 if (pluginContext != null) {
531                     pluginContext.put("project", project);
532 
533                     pluginContext.put("pluginDescriptor", pluginDescriptor);
534 
535                     ((ContextEnabled) mojo).setPluginContext(pluginContext);
536                 }
537             }
538 
539             if (mojo instanceof Mojo) {
540                 Logger mojoLogger = loggerManager.getLoggerForComponent(mojoDescriptor.getImplementation());
541                 ((Mojo) mojo).setLog(new DefaultLog(mojoLogger));
542             }
543 
544             Xpp3Dom dom = mojoExecution.getConfiguration();
545 
546             PlexusConfiguration pomConfiguration;
547 
548             if (dom == null) {
549                 pomConfiguration = new XmlPlexusConfiguration("configuration");
550             } else {
551                 pomConfiguration = new XmlPlexusConfiguration(dom);
552             }
553 
554             ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
555 
556             for (MavenPluginConfigurationValidator validator : configurationValidators) {
557                 validator.validate(mojoDescriptor, pomConfiguration, expressionEvaluator);
558             }
559 
560             populateMojoExecutionFields(
561                     mojo,
562                     mojoExecution.getExecutionId(),
563                     mojoDescriptor,
564                     pluginRealm,
565                     pomConfiguration,
566                     expressionEvaluator);
567 
568             return mojo;
569         } finally {
570             Thread.currentThread().setContextClassLoader(oldClassLoader);
571             container.setLookupRealm(oldLookupRealm);
572         }
573     }
574 
575     private void populateMojoExecutionFields(
576             Object mojo,
577             String executionId,
578             MojoDescriptor mojoDescriptor,
579             ClassRealm pluginRealm,
580             PlexusConfiguration configuration,
581             ExpressionEvaluator expressionEvaluator)
582             throws PluginConfigurationException {
583         ComponentConfigurator configurator = null;
584 
585         String configuratorId = mojoDescriptor.getComponentConfigurator();
586 
587         if (StringUtils.isEmpty(configuratorId)) {
588             configuratorId = "basic";
589         }
590 
591         try {
592             // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
593             // so that this method could entirely be handled by a plexus lookup?
594             configurator = container.lookup(ComponentConfigurator.class, configuratorId);
595 
596             ConfigurationListener listener = new DebugConfigurationListener(logger);
597 
598             ValidatingConfigurationListener validator =
599                     new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
600 
601             logger.debug("Configuring mojo execution '" + mojoDescriptor.getId() + ':' + executionId + "' with "
602                     + configuratorId + " configurator -->");
603 
604             configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
605 
606             logger.debug("-- end configuration --");
607 
608             Collection<Parameter> missingParameters = validator.getMissingParameters();
609             if (!missingParameters.isEmpty()) {
610                 if ("basic".equals(configuratorId)) {
611                     throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
612                 } else {
613                     /*
614                      * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
615                      * hard way.
616                      */
617                     validateParameters(mojoDescriptor, configuration, expressionEvaluator);
618                 }
619             }
620         } catch (ComponentConfigurationException e) {
621             String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
622             if (e.getFailedConfiguration() != null) {
623                 message += " for parameter " + e.getFailedConfiguration().getName();
624             }
625             message += ": " + e.getMessage();
626 
627             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
628         } catch (ComponentLookupException e) {
629             throw new PluginConfigurationException(
630                     mojoDescriptor.getPluginDescriptor(),
631                     "Unable to retrieve component configurator " + configuratorId + " for configuration of mojo "
632                             + mojoDescriptor.getId(),
633                     e);
634         } catch (NoClassDefFoundError e) {
635             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
636             PrintStream ps = new PrintStream(os);
637             ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
638                     + e.getMessage());
639             pluginRealm.display(ps);
640 
641             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
642         } catch (LinkageError e) {
643             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
644             PrintStream ps = new PrintStream(os);
645             ps.println("An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
646                     + ": " + e.getClass().getName() + ": " + e.getMessage());
647             pluginRealm.display(ps);
648 
649             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
650         } finally {
651             if (configurator != null) {
652                 try {
653                     container.release(configurator);
654                 } catch (ComponentLifecycleException e) {
655                     logger.debug("Failed to release mojo configurator - ignoring.");
656                 }
657             }
658         }
659     }
660 
661     private void validateParameters(
662             MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
663             throws ComponentConfigurationException, PluginParameterException {
664         if (mojoDescriptor.getParameters() == null) {
665             return;
666         }
667 
668         List<Parameter> invalidParameters = new ArrayList<>();
669 
670         for (Parameter parameter : mojoDescriptor.getParameters()) {
671             if (!parameter.isRequired()) {
672                 continue;
673             }
674 
675             Object value = null;
676 
677             PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
678             if (config != null) {
679                 String expression = config.getValue(null);
680 
681                 try {
682                     value = expressionEvaluator.evaluate(expression);
683 
684                     if (value == null) {
685                         value = config.getAttribute("default-value", null);
686                     }
687                 } catch (ExpressionEvaluationException e) {
688                     String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
689                             + configuration.getName() + "'";
690                     throw new ComponentConfigurationException(configuration, msg, e);
691                 }
692             }
693 
694             if (value == null && (config == null || config.getChildCount() <= 0)) {
695                 invalidParameters.add(parameter);
696             }
697         }
698 
699         if (!invalidParameters.isEmpty()) {
700             throw new PluginParameterException(mojoDescriptor, invalidParameters);
701         }
702     }
703 
704     public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
705         if (mojo != null) {
706             try {
707                 container.release(mojo);
708             } catch (ComponentLifecycleException e) {
709                 String goalExecId = mojoExecution.getGoal();
710 
711                 if (mojoExecution.getExecutionId() != null) {
712                     goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
713                 }
714 
715                 logger.debug("Error releasing mojo for " + goalExecId, e);
716             }
717         }
718     }
719 
720     public ExtensionRealmCache.CacheRecord setupExtensionsRealm(
721             MavenProject project, Plugin plugin, RepositorySystemSession session) throws PluginManagerException {
722         @SuppressWarnings("unchecked")
723         Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
724                 (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue(KEY_EXTENSIONS_REALMS);
725         if (pluginRealms == null) {
726             pluginRealms = new HashMap<>();
727             project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
728         }
729 
730         final String pluginKey = plugin.getId();
731 
732         ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
733         if (extensionRecord != null) {
734             return extensionRecord;
735         }
736 
737         final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
738 
739         // resolve plugin version as necessary
740         if (plugin.getVersion() == null) {
741             PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
742             try {
743                 plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
744             } catch (PluginVersionResolutionException e) {
745                 throw new PluginManagerException(plugin, e.getMessage(), e);
746             }
747         }
748 
749         // resolve plugin artifacts
750         List<Artifact> artifacts;
751         PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
752         PluginArtifactsCache.CacheRecord recordArtifacts;
753         try {
754             recordArtifacts = pluginArtifactsCache.get(cacheKey);
755         } catch (PluginResolutionException e) {
756             throw new PluginManagerException(plugin, e.getMessage(), e);
757         }
758         if (recordArtifacts != null) {
759             artifacts = recordArtifacts.getArtifacts();
760         } else {
761             try {
762                 artifacts = resolveExtensionArtifacts(plugin, repositories, session);
763                 recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
764             } catch (PluginResolutionException e) {
765                 pluginArtifactsCache.put(cacheKey, e);
766                 pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
767                 throw new PluginManagerException(plugin, e.getMessage(), e);
768             }
769         }
770         pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
771 
772         // create and cache extensions realms
773         final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
774         extensionRecord = extensionRealmCache.get(extensionKey);
775         if (extensionRecord == null) {
776             ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
777 
778             // TODO figure out how to use the same PluginDescriptor when running mojos
779 
780             PluginDescriptor pluginDescriptor = null;
781             if (plugin.isExtensions() && !artifacts.isEmpty()) {
782                 // ignore plugin descriptor parsing errors at this point
783                 // these errors will reported during calculation of project build execution plan
784                 try {
785                     pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
786                 } catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
787                     // ignore, see above
788                 }
789             }
790 
791             discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
792 
793             ExtensionDescriptor extensionDescriptor = null;
794             Artifact extensionArtifact = artifacts.get(0);
795             try {
796                 extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
797             } catch (IOException e) {
798                 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
799                 if (logger.isDebugEnabled()) {
800                     logger.error(message, e);
801                 } else {
802                     logger.error(message);
803                 }
804             }
805             extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
806         }
807         extensionRealmCache.register(project, extensionKey, extensionRecord);
808         pluginRealms.put(pluginKey, extensionRecord);
809 
810         return extensionRecord;
811     }
812 
813     private List<Artifact> resolveExtensionArtifacts(
814             Plugin extensionPlugin, List<RemoteRepository> repositories, RepositorySystemSession session)
815             throws PluginResolutionException {
816         DependencyNode root = pluginDependenciesResolver.resolve(extensionPlugin, null, null, repositories, session);
817         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
818         root.accept(nlg);
819         return toMavenArtifacts(root, nlg);
820     }
821 }