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.io.Reader;
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Objects;
40  import java.util.jar.JarFile;
41  import java.util.stream.Collectors;
42  import java.util.zip.ZipEntry;
43  
44  import org.apache.maven.RepositoryUtils;
45  import org.apache.maven.api.xml.XmlNode;
46  import org.apache.maven.artifact.Artifact;
47  import org.apache.maven.classrealm.ClassRealmManager;
48  import org.apache.maven.execution.MavenSession;
49  import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
50  import org.apache.maven.internal.impl.DefaultSession;
51  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
52  import org.apache.maven.model.Plugin;
53  import org.apache.maven.plugin.ContextEnabled;
54  import org.apache.maven.plugin.DebugConfigurationListener;
55  import org.apache.maven.plugin.ExtensionRealmCache;
56  import org.apache.maven.plugin.InvalidPluginDescriptorException;
57  import org.apache.maven.plugin.MavenPluginManager;
58  import org.apache.maven.plugin.MavenPluginPrerequisitesChecker;
59  import org.apache.maven.plugin.Mojo;
60  import org.apache.maven.plugin.MojoExecution;
61  import org.apache.maven.plugin.MojoNotFoundException;
62  import org.apache.maven.plugin.PluginArtifactsCache;
63  import org.apache.maven.plugin.PluginConfigurationException;
64  import org.apache.maven.plugin.PluginContainerException;
65  import org.apache.maven.plugin.PluginDescriptorCache;
66  import org.apache.maven.plugin.PluginDescriptorParsingException;
67  import org.apache.maven.plugin.PluginIncompatibleException;
68  import org.apache.maven.plugin.PluginManagerException;
69  import org.apache.maven.plugin.PluginParameterException;
70  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
71  import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
72  import org.apache.maven.plugin.PluginRealmCache;
73  import org.apache.maven.plugin.PluginResolutionException;
74  import org.apache.maven.plugin.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.util.ReaderFactory;
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 List<MavenPluginPrerequisitesChecker> prerequisitesCheckers;
150 
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             List<MavenPluginPrerequisitesChecker> prerequisitesCheckers) {
169         this.container = container;
170         this.classRealmManager = classRealmManager;
171         this.pluginDescriptorCache = pluginDescriptorCache;
172         this.pluginRealmCache = pluginRealmCache;
173         this.pluginDependenciesResolver = pluginDependenciesResolver;
174         this.runtimeInformation = runtimeInformation;
175         this.extensionRealmCache = extensionRealmCache;
176         this.pluginVersionResolver = pluginVersionResolver;
177         this.pluginArtifactsCache = pluginArtifactsCache;
178         this.pluginValidator = pluginValidator;
179         this.configurationValidators = configurationValidators;
180         this.prerequisitesCheckers = prerequisitesCheckers;
181     }
182 
183     public PluginDescriptor getPluginDescriptor(
184             Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
185             throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
186         PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
187 
188         PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
189             org.eclipse.aether.artifact.Artifact artifact =
190                     pluginDependenciesResolver.resolve(plugin, repositories, session);
191 
192             Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
193 
194             PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
195 
196             if (StringUtils.isBlank(descriptor.getRequiredMavenVersion())) {
197                 // only take value from underlying POM if plugin descriptor has no explicit Maven requirement
198                 descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null));
199             }
200 
201             return descriptor;
202         });
203 
204         pluginDescriptor.setPlugin(plugin);
205 
206         return pluginDescriptor;
207     }
208 
209     private PluginDescriptor extractPluginDescriptor(Artifact pluginArtifact, Plugin plugin)
210             throws PluginDescriptorParsingException, InvalidPluginDescriptorException {
211         PluginDescriptor pluginDescriptor = null;
212 
213         File pluginFile = pluginArtifact.getFile();
214 
215         try {
216             if (pluginFile.isFile()) {
217                 try (JarFile pluginJar = new JarFile(pluginFile, false)) {
218                     ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
219 
220                     if (pluginDescriptorEntry != null) {
221                         InputStream is = pluginJar.getInputStream(pluginDescriptorEntry);
222 
223                         pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath());
224                     }
225                 }
226             } else {
227                 File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
228 
229                 if (pluginXml.isFile()) {
230                     try (InputStream is = new BufferedInputStream(new FileInputStream(pluginXml))) {
231                         pluginDescriptor = parsePluginDescriptor(is, plugin, pluginXml.getAbsolutePath());
232                     }
233                 }
234             }
235 
236             if (pluginDescriptor == null) {
237                 throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
238             }
239         } catch (IOException e) {
240             throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
241         }
242 
243         List<String> errors = new ArrayList<>();
244         pluginValidator.validate(pluginArtifact, pluginDescriptor, errors);
245 
246         if (!errors.isEmpty()) {
247             throw new InvalidPluginDescriptorException(
248                     "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", errors);
249         }
250 
251         pluginDescriptor.setPluginArtifact(pluginArtifact);
252 
253         return pluginDescriptor;
254     }
255 
256     private String getPluginDescriptorLocation() {
257         return "META-INF/maven/plugin.xml";
258     }
259 
260     private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation)
261             throws PluginDescriptorParsingException {
262         try {
263             Reader reader = ReaderFactory.newXmlReader(is);
264 
265             return builder.build(reader, descriptorLocation);
266         } catch (IOException | 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             XmlNode dom = mojoExecution.getConfiguration() != null
563                     ? mojoExecution.getConfiguration().getDom()
564                     : null;
565 
566             PlexusConfiguration pomConfiguration;
567 
568             if (dom == null) {
569                 pomConfiguration = new DefaultPlexusConfiguration("configuration");
570             } else {
571                 pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
572             }
573 
574             ExpressionEvaluator expressionEvaluator;
575             if (mojoDescriptor.isV4Api()) {
576                 expressionEvaluator = new PluginParameterExpressionEvaluatorV4(
577                         session.getSession(),
578                         ((DefaultSession) session.getSession()).getProject(session.getCurrentProject()),
579                         mojoExecution);
580             } else {
581                 expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
582             }
583 
584             for (MavenPluginConfigurationValidator validator : configurationValidators) {
585                 validator.validate(mojoDescriptor, pomConfiguration, expressionEvaluator);
586             }
587 
588             populateMojoExecutionFields(
589                     mojo,
590                     mojoExecution.getExecutionId(),
591                     mojoDescriptor,
592                     pluginRealm,
593                     pomConfiguration,
594                     expressionEvaluator);
595 
596             return mojo;
597         } finally {
598             Thread.currentThread().setContextClassLoader(oldClassLoader);
599             container.setLookupRealm(oldLookupRealm);
600         }
601     }
602 
603     private void populateMojoExecutionFields(
604             Object mojo,
605             String executionId,
606             MojoDescriptor mojoDescriptor,
607             ClassRealm pluginRealm,
608             PlexusConfiguration configuration,
609             ExpressionEvaluator expressionEvaluator)
610             throws PluginConfigurationException {
611         ComponentConfigurator configurator = null;
612 
613         String configuratorId = mojoDescriptor.getComponentConfigurator();
614 
615         if (StringUtils.isEmpty(configuratorId)) {
616             configuratorId = mojoDescriptor.isV4Api() ? "enhanced" : "basic";
617         }
618 
619         try {
620             // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
621             // so that this method could entirely be handled by a plexus lookup?
622             configurator = container.lookup(ComponentConfigurator.class, configuratorId);
623 
624             ConfigurationListener listener = new DebugConfigurationListener(logger);
625 
626             ValidatingConfigurationListener validator =
627                     new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
628 
629             logger.debug("Configuring mojo execution '" + mojoDescriptor.getId() + ':' + executionId + "' with "
630                     + configuratorId + " configurator -->");
631 
632             configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
633 
634             logger.debug("-- end configuration --");
635 
636             Collection<Parameter> missingParameters = validator.getMissingParameters();
637             if (!missingParameters.isEmpty()) {
638                 if ("basic".equals(configuratorId)) {
639                     throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
640                 } else {
641                     /*
642                      * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
643                      * hard way.
644                      */
645                     validateParameters(mojoDescriptor, configuration, expressionEvaluator);
646                 }
647             }
648         } catch (ComponentConfigurationException e) {
649             String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
650             if (e.getFailedConfiguration() != null) {
651                 message += " for parameter " + e.getFailedConfiguration().getName();
652             }
653             message += ": " + e.getMessage();
654 
655             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
656         } catch (ComponentLookupException e) {
657             throw new PluginConfigurationException(
658                     mojoDescriptor.getPluginDescriptor(),
659                     "Unable to retrieve component configurator " + configuratorId + " for configuration of mojo "
660                             + mojoDescriptor.getId(),
661                     e);
662         } catch (NoClassDefFoundError e) {
663             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
664             PrintStream ps = new PrintStream(os);
665             ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
666                     + e.getMessage());
667             pluginRealm.display(ps);
668 
669             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
670         } catch (LinkageError e) {
671             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
672             PrintStream ps = new PrintStream(os);
673             ps.println("An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
674                     + ": " + e.getClass().getName() + ": " + e.getMessage());
675             pluginRealm.display(ps);
676 
677             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
678         } finally {
679             if (configurator != null) {
680                 try {
681                     container.release(configurator);
682                 } catch (ComponentLifecycleException e) {
683                     logger.debug("Failed to release mojo configurator - ignoring.");
684                 }
685             }
686         }
687     }
688 
689     private void validateParameters(
690             MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
691             throws ComponentConfigurationException, PluginParameterException {
692         if (mojoDescriptor.getParameters() == null) {
693             return;
694         }
695 
696         List<Parameter> invalidParameters = new ArrayList<>();
697 
698         for (Parameter parameter : mojoDescriptor.getParameters()) {
699             if (!parameter.isRequired()) {
700                 continue;
701             }
702 
703             Object value = null;
704 
705             PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
706             if (config != null) {
707                 String expression = config.getValue(null);
708 
709                 try {
710                     value = expressionEvaluator.evaluate(expression);
711 
712                     if (value == null) {
713                         value = config.getAttribute("default-value", null);
714                     }
715                 } catch (ExpressionEvaluationException e) {
716                     String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
717                             + configuration.getName() + "'";
718                     throw new ComponentConfigurationException(configuration, msg, e);
719                 }
720             }
721 
722             if (value == null && (config == null || config.getChildCount() <= 0)) {
723                 invalidParameters.add(parameter);
724             }
725         }
726 
727         if (!invalidParameters.isEmpty()) {
728             throw new PluginParameterException(mojoDescriptor, invalidParameters);
729         }
730     }
731 
732     public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
733         if (mojo != null) {
734             try {
735                 container.release(mojo);
736             } catch (ComponentLifecycleException e) {
737                 String goalExecId = mojoExecution.getGoal();
738 
739                 if (mojoExecution.getExecutionId() != null) {
740                     goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
741                 }
742 
743                 logger.debug("Error releasing mojo for " + goalExecId, e);
744             }
745         }
746     }
747 
748     public ExtensionRealmCache.CacheRecord setupExtensionsRealm(
749             MavenProject project, Plugin plugin, RepositorySystemSession session) throws PluginManagerException {
750         @SuppressWarnings("unchecked")
751         Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
752                 (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue(KEY_EXTENSIONS_REALMS);
753         if (pluginRealms == null) {
754             pluginRealms = new HashMap<>();
755             project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
756         }
757 
758         final String pluginKey = plugin.getId();
759 
760         ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
761         if (extensionRecord != null) {
762             return extensionRecord;
763         }
764 
765         final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
766 
767         // resolve plugin version as necessary
768         if (plugin.getVersion() == null) {
769             PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
770             try {
771                 plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
772             } catch (PluginVersionResolutionException e) {
773                 throw new PluginManagerException(plugin, e.getMessage(), e);
774             }
775         }
776 
777         // TODO: store plugin version
778 
779         // resolve plugin artifacts
780         List<Artifact> artifacts;
781         PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
782         PluginArtifactsCache.CacheRecord recordArtifacts;
783         try {
784             recordArtifacts = pluginArtifactsCache.get(cacheKey);
785         } catch (PluginResolutionException e) {
786             throw new PluginManagerException(plugin, e.getMessage(), e);
787         }
788         if (recordArtifacts != null) {
789             artifacts = recordArtifacts.getArtifacts();
790         } else {
791             try {
792                 artifacts = resolveExtensionArtifacts(plugin, repositories, session);
793                 recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
794             } catch (PluginResolutionException e) {
795                 pluginArtifactsCache.put(cacheKey, e);
796                 pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
797                 throw new PluginManagerException(plugin, e.getMessage(), e);
798             }
799         }
800         pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
801 
802         // create and cache extensions realms
803         final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
804         extensionRecord = extensionRealmCache.get(extensionKey);
805         if (extensionRecord == null) {
806             ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
807 
808             // TODO figure out how to use the same PluginDescriptor when running mojos
809 
810             PluginDescriptor pluginDescriptor = null;
811             if (plugin.isExtensions() && !artifacts.isEmpty()) {
812                 // ignore plugin descriptor parsing errors at this point
813                 // these errors will reported during calculation of project build execution plan
814                 try {
815                     pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
816                 } catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
817                     // ignore, see above
818                 }
819             }
820 
821             discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
822 
823             ExtensionDescriptor extensionDescriptor = null;
824             Artifact extensionArtifact = artifacts.get(0);
825             try {
826                 extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
827             } catch (IOException e) {
828                 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
829                 if (logger.isDebugEnabled()) {
830                     logger.error(message, e);
831                 } else {
832                     logger.error(message);
833                 }
834             }
835             extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
836         }
837         extensionRealmCache.register(project, extensionKey, extensionRecord);
838         pluginRealms.put(pluginKey, extensionRecord);
839 
840         return extensionRecord;
841     }
842 
843     private List<Artifact> resolveExtensionArtifacts(
844             Plugin extensionPlugin, List<RemoteRepository> repositories, RepositorySystemSession session)
845             throws PluginResolutionException {
846         DependencyNode root = pluginDependenciesResolver.resolve(extensionPlugin, null, null, repositories, session);
847         PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
848         root.accept(nlg);
849         return toMavenArtifacts(root, nlg);
850     }
851 }