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