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