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