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