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