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