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