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