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