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.BufferedInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.PrintStream;
32  import java.util.ArrayList;
33  import java.util.Collection;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Objects;
39  import java.util.jar.JarFile;
40  import java.util.stream.Collectors;
41  import java.util.zip.ZipEntry;
42  
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.DefaultSession;
50  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
51  import org.apache.maven.model.Plugin;
52  import org.apache.maven.plugin.ContextEnabled;
53  import org.apache.maven.plugin.DebugConfigurationListener;
54  import org.apache.maven.plugin.ExtensionRealmCache;
55  import org.apache.maven.plugin.InvalidPluginDescriptorException;
56  import org.apache.maven.plugin.MavenPluginManager;
57  import org.apache.maven.plugin.MavenPluginPrerequisitesChecker;
58  import org.apache.maven.plugin.Mojo;
59  import org.apache.maven.plugin.MojoExecution;
60  import org.apache.maven.plugin.MojoNotFoundException;
61  import org.apache.maven.plugin.PluginArtifactsCache;
62  import org.apache.maven.plugin.PluginConfigurationException;
63  import org.apache.maven.plugin.PluginContainerException;
64  import org.apache.maven.plugin.PluginDescriptorCache;
65  import org.apache.maven.plugin.PluginDescriptorParsingException;
66  import org.apache.maven.plugin.PluginIncompatibleException;
67  import org.apache.maven.plugin.PluginManagerException;
68  import org.apache.maven.plugin.PluginParameterException;
69  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
70  import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
71  import org.apache.maven.plugin.PluginRealmCache;
72  import org.apache.maven.plugin.PluginResolutionException;
73  import org.apache.maven.plugin.PluginValidationManager;
74  import org.apache.maven.plugin.descriptor.MojoDescriptor;
75  import org.apache.maven.plugin.descriptor.Parameter;
76  import org.apache.maven.plugin.descriptor.PluginDescriptor;
77  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
78  import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
79  import org.apache.maven.plugin.version.PluginVersionRequest;
80  import org.apache.maven.plugin.version.PluginVersionResolutionException;
81  import org.apache.maven.plugin.version.PluginVersionResolver;
82  import org.apache.maven.project.ExtensionDescriptor;
83  import org.apache.maven.project.ExtensionDescriptorBuilder;
84  import org.apache.maven.project.MavenProject;
85  import org.apache.maven.rtinfo.RuntimeInformation;
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.codehaus.plexus.util.StringUtils;
104 import org.eclipse.aether.RepositorySystemSession;
105 import org.eclipse.aether.graph.DependencyFilter;
106 import org.eclipse.aether.graph.DependencyNode;
107 import org.eclipse.aether.repository.RemoteRepository;
108 import org.eclipse.aether.util.filter.AndDependencyFilter;
109 import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
110 import org.slf4j.Logger;
111 import org.slf4j.LoggerFactory;
112 
113 /**
114  * Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
115  * that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
116  * plugins as well as special purpose plugins like reports.
117  *
118  * @author Benjamin Bentmann
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 PlexusContainer container;
139     private ClassRealmManager classRealmManager;
140     private PluginDescriptorCache pluginDescriptorCache;
141     private PluginRealmCache pluginRealmCache;
142     private PluginDependenciesResolver pluginDependenciesResolver;
143     private RuntimeInformation runtimeInformation;
144     private ExtensionRealmCache extensionRealmCache;
145     private PluginVersionResolver pluginVersionResolver;
146     private PluginArtifactsCache pluginArtifactsCache;
147     private MavenPluginValidator pluginValidator;
148     private List<MavenPluginConfigurationValidator> configurationValidators;
149     private PluginValidationManager pluginValidationManager;
150     private List<MavenPluginPrerequisitesChecker> prerequisitesCheckers;
151     private final ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
152     private final PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
153 
154     @Inject
155     @SuppressWarnings("checkstyle:ParameterNumber")
156     public DefaultMavenPluginManager(
157             PlexusContainer container,
158             ClassRealmManager classRealmManager,
159             PluginDescriptorCache pluginDescriptorCache,
160             PluginRealmCache pluginRealmCache,
161             PluginDependenciesResolver pluginDependenciesResolver,
162             RuntimeInformation runtimeInformation,
163             ExtensionRealmCache extensionRealmCache,
164             PluginVersionResolver pluginVersionResolver,
165             PluginArtifactsCache pluginArtifactsCache,
166             MavenPluginValidator pluginValidator,
167             List<MavenPluginConfigurationValidator> configurationValidators,
168             PluginValidationManager pluginValidationManager,
169             List<MavenPluginPrerequisitesChecker> prerequisitesCheckers) {
170         this.container = container;
171         this.classRealmManager = classRealmManager;
172         this.pluginDescriptorCache = pluginDescriptorCache;
173         this.pluginRealmCache = pluginRealmCache;
174         this.pluginDependenciesResolver = pluginDependenciesResolver;
175         this.runtimeInformation = runtimeInformation;
176         this.extensionRealmCache = extensionRealmCache;
177         this.pluginVersionResolver = pluginVersionResolver;
178         this.pluginArtifactsCache = pluginArtifactsCache;
179         this.pluginValidator = pluginValidator;
180         this.configurationValidators = configurationValidators;
181         this.pluginValidationManager = pluginValidationManager;
182         this.prerequisitesCheckers = prerequisitesCheckers;
183     }
184 
185     public PluginDescriptor getPluginDescriptor(
186             Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
187             throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
188         PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
189 
190         PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
191             org.eclipse.aether.artifact.Artifact artifact =
192                     pluginDependenciesResolver.resolve(plugin, repositories, session);
193 
194             Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
195 
196             PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
197 
198             if (StringUtils.isBlank(descriptor.getRequiredMavenVersion())) {
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 = new BufferedInputStream(new FileInputStream(pluginXml))) {
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, new SessionScopeModule(container), new MojoExecutionScopeModule(container));
431         } catch (ComponentLookupException | CycleDetectedInComponentGraphException e) {
432             throw new PluginContainerException(
433                     plugin,
434                     pluginRealm,
435                     "Error in component graph of plugin " + plugin.getId() + ": " + e.getMessage(),
436                     e);
437         }
438     }
439 
440     private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts(final List<Artifact> pluginArtifacts) {
441         return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
442     }
443 
444     private List<Artifact> toMavenArtifacts(DependencyNode root, PreorderNodeListGenerator nlg) {
445         List<Artifact> artifacts = new ArrayList<>(nlg.getNodes().size());
446         RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), Collections.emptyList(), null);
447         artifacts.removeIf(artifact -> artifact.getFile() == null);
448         return Collections.unmodifiableList(artifacts);
449     }
450 
451     private Map<String, ClassLoader> calcImports(MavenProject project, ClassLoader parent, List<String> imports) {
452         Map<String, ClassLoader> foreignImports = new HashMap<>();
453 
454         ClassLoader projectRealm = project.getClassRealm();
455         if (projectRealm != null) {
456             foreignImports.put("", projectRealm);
457         } else {
458             foreignImports.put("", classRealmManager.getMavenApiRealm());
459         }
460 
461         if (parent != null && imports != null) {
462             for (String parentImport : imports) {
463                 foreignImports.put(parentImport, parent);
464             }
465         }
466 
467         return foreignImports;
468     }
469 
470     public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution)
471             throws PluginConfigurationException, PluginContainerException {
472         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
473 
474         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
475 
476         ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
477 
478         if (pluginRealm == null) {
479             try {
480                 setupPluginRealm(pluginDescriptor, session, null, null, null);
481             } catch (PluginResolutionException e) {
482                 String msg = "Cannot setup plugin realm [mojoDescriptor=" + mojoDescriptor.getId()
483                         + ", pluginDescriptor=" + pluginDescriptor.getId() + "]";
484                 throw new PluginConfigurationException(pluginDescriptor, msg, e);
485             }
486             pluginRealm = pluginDescriptor.getClassRealm();
487         }
488 
489         if (logger.isDebugEnabled()) {
490             logger.debug("Loading mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm);
491         }
492 
493         // We are forcing the use of the plugin realm for all lookups that might occur during
494         // the lifecycle that is part of the lookup. Here we are specifically trying to keep
495         // lookups that occur in contextualize calls in line with the right realm.
496         ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
497 
498         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
499         Thread.currentThread().setContextClassLoader(pluginRealm);
500 
501         try {
502             T mojo;
503 
504             try {
505                 mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
506             } catch (ComponentLookupException e) {
507                 Throwable cause = e.getCause();
508                 while (cause != null
509                         && !(cause instanceof LinkageError)
510                         && !(cause instanceof ClassNotFoundException)) {
511                     cause = cause.getCause();
512                 }
513 
514                 if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
515                     ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
516                     PrintStream ps = new PrintStream(os);
517                     ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
518                             + pluginDescriptor.getId() + "'. A required class is missing: "
519                             + cause.getMessage());
520                     pluginRealm.display(ps);
521 
522                     throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
523                 } else if (cause instanceof LinkageError) {
524                     ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
525                     PrintStream ps = new PrintStream(os);
526                     ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
527                             + pluginDescriptor.getId() + "' due to an API incompatibility: "
528                             + e.getClass().getName() + ": " + cause.getMessage());
529                     pluginRealm.display(ps);
530 
531                     throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
532                 }
533 
534                 throw new PluginContainerException(
535                         mojoDescriptor,
536                         pluginRealm,
537                         "Unable to load the mojo '" + mojoDescriptor.getGoal()
538                                 + "' (or one of its required components) from the plugin '"
539                                 + pluginDescriptor.getId() + "'",
540                         e);
541             }
542 
543             if (mojo instanceof ContextEnabled) {
544                 MavenProject project = session.getCurrentProject();
545 
546                 Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
547 
548                 if (pluginContext != null) {
549                     pluginContext.put("project", project);
550 
551                     pluginContext.put("pluginDescriptor", pluginDescriptor);
552 
553                     ((ContextEnabled) mojo).setPluginContext(pluginContext);
554                 }
555             }
556 
557             if (mojo instanceof Mojo) {
558                 Logger mojoLogger = LoggerFactory.getLogger(mojoDescriptor.getImplementation());
559                 ((Mojo) mojo).setLog(new MojoLogWrapper(mojoLogger));
560             }
561 
562             if (mojo instanceof Contextualizable) {
563                 pluginValidationManager.reportPluginMojoValidationIssue(
564                         PluginValidationManager.IssueLocality.EXTERNAL,
565                         session,
566                         mojoDescriptor,
567                         mojo.getClass(),
568                         "Mojo implements `Contextualizable` interface from Plexus Container, which is EOL.");
569             }
570 
571             XmlNode dom = mojoExecution.getConfiguration() != null
572                     ? mojoExecution.getConfiguration().getDom()
573                     : null;
574 
575             PlexusConfiguration pomConfiguration;
576 
577             if (dom == null) {
578                 pomConfiguration = new DefaultPlexusConfiguration("configuration");
579             } else {
580                 pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
581             }
582 
583             ExpressionEvaluator expressionEvaluator;
584             if (mojoDescriptor.isV4Api()) {
585                 expressionEvaluator = new PluginParameterExpressionEvaluatorV4(
586                         session.getSession(),
587                         ((DefaultSession) session.getSession()).getProject(session.getCurrentProject()),
588                         mojoExecution);
589             } else {
590                 expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
591             }
592 
593             for (MavenPluginConfigurationValidator validator : configurationValidators) {
594                 validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
595             }
596 
597             populateMojoExecutionFields(
598                     mojo,
599                     mojoExecution.getExecutionId(),
600                     mojoDescriptor,
601                     pluginRealm,
602                     pomConfiguration,
603                     expressionEvaluator);
604 
605             return mojo;
606         } finally {
607             Thread.currentThread().setContextClassLoader(oldClassLoader);
608             container.setLookupRealm(oldLookupRealm);
609         }
610     }
611 
612     private void populateMojoExecutionFields(
613             Object mojo,
614             String executionId,
615             MojoDescriptor mojoDescriptor,
616             ClassRealm pluginRealm,
617             PlexusConfiguration configuration,
618             ExpressionEvaluator expressionEvaluator)
619             throws PluginConfigurationException {
620         ComponentConfigurator configurator = null;
621 
622         String configuratorId = mojoDescriptor.getComponentConfigurator();
623 
624         if (configuratorId == null || configuratorId.isEmpty()) {
625             configuratorId = mojoDescriptor.isV4Api() ? "enhanced" : "basic";
626         }
627 
628         try {
629             // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
630             // so that this method could entirely be handled by a plexus lookup?
631             configurator = container.lookup(ComponentConfigurator.class, configuratorId);
632 
633             ConfigurationListener listener = new DebugConfigurationListener(logger);
634 
635             ValidatingConfigurationListener validator =
636                     new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
637 
638             logger.debug("Configuring mojo execution '" + mojoDescriptor.getId() + ':' + executionId + "' with "
639                     + configuratorId + " configurator -->");
640 
641             configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
642 
643             logger.debug("-- end configuration --");
644 
645             Collection<Parameter> missingParameters = validator.getMissingParameters();
646             if (!missingParameters.isEmpty()) {
647                 if ("basic".equals(configuratorId)) {
648                     throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
649                 } else {
650                     /*
651                      * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
652                      * hard way.
653                      */
654                     validateParameters(mojoDescriptor, configuration, expressionEvaluator);
655                 }
656             }
657         } catch (ComponentConfigurationException e) {
658             String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
659             if (e.getFailedConfiguration() != null) {
660                 message += " for parameter " + e.getFailedConfiguration().getName();
661             }
662             message += ": " + e.getMessage();
663 
664             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
665         } catch (ComponentLookupException e) {
666             throw new PluginConfigurationException(
667                     mojoDescriptor.getPluginDescriptor(),
668                     "Unable to retrieve component configurator " + configuratorId + " for configuration of mojo "
669                             + mojoDescriptor.getId(),
670                     e);
671         } catch (NoClassDefFoundError e) {
672             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
673             PrintStream ps = new PrintStream(os);
674             ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
675                     + e.getMessage());
676             pluginRealm.display(ps);
677 
678             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
679         } catch (LinkageError e) {
680             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
681             PrintStream ps = new PrintStream(os);
682             ps.println("An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
683                     + ": " + e.getClass().getName() + ": " + e.getMessage());
684             pluginRealm.display(ps);
685 
686             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
687         } finally {
688             if (configurator != null) {
689                 try {
690                     container.release(configurator);
691                 } catch (ComponentLifecycleException e) {
692                     logger.debug("Failed to release mojo configurator - ignoring.");
693                 }
694             }
695         }
696     }
697 
698     private void validateParameters(
699             MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
700             throws ComponentConfigurationException, PluginParameterException {
701         if (mojoDescriptor.getParameters() == null) {
702             return;
703         }
704 
705         List<Parameter> invalidParameters = new ArrayList<>();
706 
707         for (Parameter parameter : mojoDescriptor.getParameters()) {
708             if (!parameter.isRequired()) {
709                 continue;
710             }
711 
712             Object value = null;
713 
714             PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
715             if (config != null) {
716                 String expression = config.getValue(null);
717 
718                 try {
719                     value = expressionEvaluator.evaluate(expression);
720 
721                     if (value == null) {
722                         value = config.getAttribute("default-value", null);
723                     }
724                 } catch (ExpressionEvaluationException e) {
725                     String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
726                             + configuration.getName() + "'";
727                     throw new ComponentConfigurationException(configuration, msg, e);
728                 }
729             }
730 
731             if (value == null && (config == null || config.getChildCount() <= 0)) {
732                 invalidParameters.add(parameter);
733             }
734         }
735 
736         if (!invalidParameters.isEmpty()) {
737             throw new PluginParameterException(mojoDescriptor, invalidParameters);
738         }
739     }
740 
741     public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
742         if (mojo != null) {
743             try {
744                 container.release(mojo);
745             } catch (ComponentLifecycleException e) {
746                 String goalExecId = mojoExecution.getGoal();
747 
748                 if (mojoExecution.getExecutionId() != null) {
749                     goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
750                 }
751 
752                 logger.debug("Error releasing mojo for " + goalExecId, e);
753             }
754         }
755     }
756 
757     public ExtensionRealmCache.CacheRecord setupExtensionsRealm(
758             MavenProject project, Plugin plugin, RepositorySystemSession session) throws PluginManagerException {
759         @SuppressWarnings("unchecked")
760         Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
761                 (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue(KEY_EXTENSIONS_REALMS);
762         if (pluginRealms == null) {
763             pluginRealms = new HashMap<>();
764             project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
765         }
766 
767         final String pluginKey = plugin.getId();
768 
769         ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
770         if (extensionRecord != null) {
771             return extensionRecord;
772         }
773 
774         final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
775 
776         // resolve plugin version as necessary
777         if (plugin.getVersion() == null) {
778             PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
779             try {
780                 plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
781             } catch (PluginVersionResolutionException e) {
782                 throw new PluginManagerException(plugin, e.getMessage(), e);
783             }
784         }
785 
786         // TODO: store plugin version
787 
788         // resolve plugin artifacts
789         List<Artifact> artifacts;
790         PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
791         PluginArtifactsCache.CacheRecord recordArtifacts;
792         try {
793             recordArtifacts = pluginArtifactsCache.get(cacheKey);
794         } catch (PluginResolutionException e) {
795             throw new PluginManagerException(plugin, e.getMessage(), e);
796         }
797         if (recordArtifacts != null) {
798             artifacts = recordArtifacts.getArtifacts();
799         } else {
800             try {
801                 artifacts = resolveExtensionArtifacts(plugin, repositories, session);
802                 recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
803             } catch (PluginResolutionException e) {
804                 pluginArtifactsCache.put(cacheKey, e);
805                 pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
806                 throw new PluginManagerException(plugin, e.getMessage(), e);
807             }
808         }
809         pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
810 
811         // create and cache extensions realms
812         final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
813         extensionRecord = extensionRealmCache.get(extensionKey);
814         if (extensionRecord == null) {
815             ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
816 
817             // TODO figure out how to use the same PluginDescriptor when running mojos
818 
819             PluginDescriptor pluginDescriptor = null;
820             if (plugin.isExtensions() && !artifacts.isEmpty()) {
821                 // ignore plugin descriptor parsing errors at this point
822                 // these errors will reported during calculation of project build execution plan
823                 try {
824                     pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
825                 } catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
826                     // ignore, see above
827                 }
828             }
829 
830             discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
831 
832             ExtensionDescriptor extensionDescriptor = null;
833             Artifact extensionArtifact = artifacts.get(0);
834             try {
835                 extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
836             } catch (IOException e) {
837                 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
838                 if (logger.isDebugEnabled()) {
839                     logger.error(message, e);
840                 } else {
841                     logger.error(message);
842                 }
843             }
844             extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
845         }
846         extensionRealmCache.register(project, extensionKey, extensionRecord);
847         pluginRealms.put(pluginKey, extensionRecord);
848 
849         return extensionRecord;
850     }
851 
852     private List<Artifact> resolveExtensionArtifacts(
853             Plugin extensionPlugin, List<RemoteRepository> repositories, RepositorySystemSession session)
854             throws PluginResolutionException {
855         DependencyNode root = pluginDependenciesResolver.resolve(extensionPlugin, null, null, repositories, session);
856         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
857         root.accept(nlg);
858         return toMavenArtifacts(root, nlg);
859     }
860 }