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