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