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.ByteArrayOutputStream;
26  import java.io.File;
27  import java.io.IOException;
28  import java.io.PrintStream;
29  import java.lang.annotation.Annotation;
30  import java.lang.reflect.Field;
31  import java.lang.reflect.ParameterizedType;
32  import java.lang.reflect.Type;
33  import java.nio.file.Files;
34  import java.nio.file.Path;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.HashMap;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Objects;
42  import java.util.jar.JarFile;
43  import java.util.stream.Collectors;
44  import java.util.zip.ZipEntry;
45  
46  import org.apache.maven.RepositoryUtils;
47  import org.apache.maven.api.Dependency;
48  import org.apache.maven.api.Node;
49  import org.apache.maven.api.PathScope;
50  import org.apache.maven.api.PathType;
51  import org.apache.maven.api.Project;
52  import org.apache.maven.api.Session;
53  import org.apache.maven.api.plugin.descriptor.Resolution;
54  import org.apache.maven.api.services.DependencyResolver;
55  import org.apache.maven.api.services.DependencyResolverResult;
56  import org.apache.maven.api.services.PathScopeRegistry;
57  import org.apache.maven.api.xml.XmlNode;
58  import org.apache.maven.artifact.Artifact;
59  import org.apache.maven.classrealm.ClassRealmManager;
60  import org.apache.maven.di.Injector;
61  import org.apache.maven.di.Key;
62  import org.apache.maven.execution.MavenSession;
63  import org.apache.maven.execution.scope.internal.MojoExecutionScope;
64  import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
65  import org.apache.maven.internal.impl.DefaultLog;
66  import org.apache.maven.internal.impl.DefaultMojoExecution;
67  import org.apache.maven.internal.impl.InternalMavenSession;
68  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
69  import org.apache.maven.model.Plugin;
70  import org.apache.maven.plugin.ContextEnabled;
71  import org.apache.maven.plugin.DebugConfigurationListener;
72  import org.apache.maven.plugin.ExtensionRealmCache;
73  import org.apache.maven.plugin.InvalidPluginDescriptorException;
74  import org.apache.maven.plugin.MavenPluginManager;
75  import org.apache.maven.plugin.MavenPluginPrerequisitesChecker;
76  import org.apache.maven.plugin.Mojo;
77  import org.apache.maven.plugin.MojoExecution;
78  import org.apache.maven.plugin.MojoNotFoundException;
79  import org.apache.maven.plugin.PluginArtifactsCache;
80  import org.apache.maven.plugin.PluginConfigurationException;
81  import org.apache.maven.plugin.PluginContainerException;
82  import org.apache.maven.plugin.PluginDescriptorCache;
83  import org.apache.maven.plugin.PluginDescriptorParsingException;
84  import org.apache.maven.plugin.PluginIncompatibleException;
85  import org.apache.maven.plugin.PluginManagerException;
86  import org.apache.maven.plugin.PluginParameterException;
87  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
88  import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
89  import org.apache.maven.plugin.PluginRealmCache;
90  import org.apache.maven.plugin.PluginResolutionException;
91  import org.apache.maven.plugin.PluginValidationManager;
92  import org.apache.maven.plugin.descriptor.MojoDescriptor;
93  import org.apache.maven.plugin.descriptor.Parameter;
94  import org.apache.maven.plugin.descriptor.PluginDescriptor;
95  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
96  import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
97  import org.apache.maven.plugin.version.PluginVersionRequest;
98  import org.apache.maven.plugin.version.PluginVersionResolutionException;
99  import org.apache.maven.plugin.version.PluginVersionResolver;
100 import org.apache.maven.project.ExtensionDescriptor;
101 import org.apache.maven.project.ExtensionDescriptorBuilder;
102 import org.apache.maven.project.MavenProject;
103 import org.apache.maven.session.scope.internal.SessionScope;
104 import org.apache.maven.session.scope.internal.SessionScopeModule;
105 import org.codehaus.plexus.DefaultPlexusContainer;
106 import org.codehaus.plexus.PlexusContainer;
107 import org.codehaus.plexus.classworlds.realm.ClassRealm;
108 import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
109 import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
110 import org.codehaus.plexus.component.configurator.ComponentConfigurator;
111 import org.codehaus.plexus.component.configurator.ConfigurationListener;
112 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
113 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
114 import org.codehaus.plexus.component.repository.ComponentDescriptor;
115 import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
116 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
117 import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
118 import org.codehaus.plexus.configuration.PlexusConfiguration;
119 import org.codehaus.plexus.configuration.PlexusConfigurationException;
120 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
121 import org.eclipse.aether.RepositorySystemSession;
122 import org.eclipse.aether.graph.DependencyFilter;
123 import org.eclipse.aether.repository.RemoteRepository;
124 import org.eclipse.aether.resolution.ArtifactResult;
125 import org.eclipse.aether.resolution.DependencyResult;
126 import org.eclipse.aether.util.filter.AndDependencyFilter;
127 import org.slf4j.Logger;
128 import org.slf4j.LoggerFactory;
129 
130 /**
131  * Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
132  * that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
133  * plugins as well as special purpose plugins like reports.
134  *
135  * @since 3.0
136  */
137 @Named
138 @Singleton
139 public class DefaultMavenPluginManager implements MavenPluginManager {
140 
141     /**
142      * <p>
143      * PluginId =&gt; ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the
144      * same class realm is used to load build extensions and load mojos for extensions=true plugins.
145      * </p>
146      * <strong>Note:</strong> This is part of internal implementation and may be changed or removed without notice
147      *
148      * @since 3.3.0
149      */
150     public static final String KEY_EXTENSIONS_REALMS = DefaultMavenPluginManager.class.getName() + "/extensionsRealms";
151 
152     private final Logger logger = LoggerFactory.getLogger(getClass());
153 
154     private final PlexusContainer container;
155     private final ClassRealmManager classRealmManager;
156     private final PluginDescriptorCache pluginDescriptorCache;
157     private final PluginRealmCache pluginRealmCache;
158     private final PluginDependenciesResolver pluginDependenciesResolver;
159     private final ExtensionRealmCache extensionRealmCache;
160     private final PluginVersionResolver pluginVersionResolver;
161     private final PluginArtifactsCache pluginArtifactsCache;
162     private final MavenPluginValidator pluginValidator;
163     private final List<MavenPluginConfigurationValidator> configurationValidators;
164     private final PluginValidationManager pluginValidationManager;
165     private final List<MavenPluginPrerequisitesChecker> prerequisitesCheckers;
166     private final ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
167     private final PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
168 
169     @Inject
170     @SuppressWarnings("checkstyle:ParameterNumber")
171     public DefaultMavenPluginManager(
172             PlexusContainer container,
173             ClassRealmManager classRealmManager,
174             PluginDescriptorCache pluginDescriptorCache,
175             PluginRealmCache pluginRealmCache,
176             PluginDependenciesResolver pluginDependenciesResolver,
177             ExtensionRealmCache extensionRealmCache,
178             PluginVersionResolver pluginVersionResolver,
179             PluginArtifactsCache pluginArtifactsCache,
180             MavenPluginValidator pluginValidator,
181             List<MavenPluginConfigurationValidator> configurationValidators,
182             PluginValidationManager pluginValidationManager,
183             List<MavenPluginPrerequisitesChecker> prerequisitesCheckers) {
184         this.container = container;
185         this.classRealmManager = classRealmManager;
186         this.pluginDescriptorCache = pluginDescriptorCache;
187         this.pluginRealmCache = pluginRealmCache;
188         this.pluginDependenciesResolver = pluginDependenciesResolver;
189         this.extensionRealmCache = extensionRealmCache;
190         this.pluginVersionResolver = pluginVersionResolver;
191         this.pluginArtifactsCache = pluginArtifactsCache;
192         this.pluginValidator = pluginValidator;
193         this.configurationValidators = configurationValidators;
194         this.pluginValidationManager = pluginValidationManager;
195         this.prerequisitesCheckers = prerequisitesCheckers;
196     }
197 
198     public PluginDescriptor getPluginDescriptor(
199             Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
200             throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
201         PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
202 
203         PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
204             org.eclipse.aether.artifact.Artifact artifact =
205                     pluginDependenciesResolver.resolve(plugin, repositories, session);
206 
207             Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
208 
209             PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
210 
211             boolean isBlankVersion = descriptor.getRequiredMavenVersion() == null
212                     || descriptor.getRequiredMavenVersion().trim().isEmpty();
213 
214             if (isBlankVersion) {
215                 // only take value from underlying POM if plugin descriptor has no explicit Maven requirement
216                 descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null));
217             }
218 
219             return descriptor;
220         });
221 
222         pluginDescriptor.setPlugin(plugin);
223 
224         return pluginDescriptor;
225     }
226 
227     private PluginDescriptor extractPluginDescriptor(Artifact pluginArtifact, Plugin plugin)
228             throws PluginDescriptorParsingException, InvalidPluginDescriptorException {
229         PluginDescriptor pluginDescriptor = null;
230 
231         File pluginFile = pluginArtifact.getFile();
232 
233         try {
234             if (pluginFile.isFile()) {
235                 try (JarFile pluginJar = new JarFile(pluginFile, false)) {
236                     ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
237 
238                     if (pluginDescriptorEntry != null) {
239                         pluginDescriptor = parsePluginDescriptor(
240                                 () -> pluginJar.getInputStream(pluginDescriptorEntry),
241                                 plugin,
242                                 pluginFile.getAbsolutePath());
243                     }
244                 }
245             } else {
246                 File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
247 
248                 if (pluginXml.isFile()) {
249                     pluginDescriptor = parsePluginDescriptor(
250                             () -> Files.newInputStream(pluginXml.toPath()), plugin, pluginXml.getAbsolutePath());
251                 }
252             }
253 
254             if (pluginDescriptor == null) {
255                 throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
256             }
257         } catch (IOException e) {
258             throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
259         }
260 
261         List<String> errors = new ArrayList<>();
262         pluginValidator.validate(pluginArtifact, pluginDescriptor, errors);
263 
264         if (!errors.isEmpty()) {
265             throw new InvalidPluginDescriptorException(
266                     "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", errors);
267         }
268 
269         pluginDescriptor.setPluginArtifact(pluginArtifact);
270 
271         return pluginDescriptor;
272     }
273 
274     private String getPluginDescriptorLocation() {
275         return "META-INF/maven/plugin.xml";
276     }
277 
278     private PluginDescriptor parsePluginDescriptor(
279             PluginDescriptorBuilder.StreamSupplier is, Plugin plugin, String descriptorLocation)
280             throws PluginDescriptorParsingException {
281         try {
282             return builder.build(is, descriptorLocation);
283         } catch (PlexusConfigurationException e) {
284             throw new PluginDescriptorParsingException(plugin, descriptorLocation, e);
285         }
286     }
287 
288     public MojoDescriptor getMojoDescriptor(
289             Plugin plugin, String goal, List<RemoteRepository> repositories, RepositorySystemSession session)
290             throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
291                     InvalidPluginDescriptorException {
292         PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session);
293 
294         MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal);
295 
296         if (mojoDescriptor == null) {
297             throw new MojoNotFoundException(goal, pluginDescriptor);
298         }
299 
300         return mojoDescriptor;
301     }
302 
303     @Override
304     public void checkPrerequisites(PluginDescriptor pluginDescriptor) throws PluginIncompatibleException {
305         List<IllegalStateException> prerequisiteExceptions = new ArrayList<>();
306         prerequisitesCheckers.forEach(c -> {
307             try {
308                 c.accept(pluginDescriptor);
309             } catch (IllegalStateException e) {
310                 prerequisiteExceptions.add(e);
311             }
312         });
313         // aggregate all exceptions
314         if (!prerequisiteExceptions.isEmpty()) {
315             String messages = prerequisiteExceptions.stream()
316                     .map(IllegalStateException::getMessage)
317                     .collect(Collectors.joining(", "));
318             PluginIncompatibleException pie = new PluginIncompatibleException(
319                     pluginDescriptor.getPlugin(),
320                     "The plugin " + pluginDescriptor.getId() + " has unmet prerequisites: " + messages,
321                     prerequisiteExceptions.get(0));
322             // the first exception is added as cause, all other ones as suppressed exceptions
323             prerequisiteExceptions.stream().skip(1).forEach(pie::addSuppressed);
324             throw pie;
325         }
326     }
327 
328     @Override
329     @Deprecated
330     public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor) throws PluginIncompatibleException {
331         checkPrerequisites(pluginDescriptor);
332     }
333 
334     public void setupPluginRealm(
335             PluginDescriptor pluginDescriptor,
336             MavenSession session,
337             ClassLoader parent,
338             List<String> imports,
339             DependencyFilter filter)
340             throws PluginResolutionException, PluginContainerException {
341         Plugin plugin = pluginDescriptor.getPlugin();
342         MavenProject project = session.getCurrentProject();
343 
344         if (plugin.isExtensions()) {
345             ExtensionRealmCache.CacheRecord extensionRecord;
346             try {
347                 RepositorySystemSession repositorySession = session.getRepositorySession();
348                 extensionRecord = setupExtensionsRealm(project, plugin, repositorySession);
349             } catch (PluginManagerException e) {
350                 // extensions realm is expected to be fully setup at this point
351                 // any exception means a problem in maven code, not a user error
352                 throw new IllegalStateException(e);
353             }
354 
355             ClassRealm pluginRealm = extensionRecord.getRealm();
356             List<Artifact> pluginArtifacts = extensionRecord.getArtifacts();
357 
358             for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
359                 componentDescriptor.setRealm(pluginRealm);
360             }
361 
362             pluginDescriptor.setClassRealm(pluginRealm);
363             pluginDescriptor.setArtifacts(pluginArtifacts);
364         } else {
365             boolean v4api = pluginDescriptor.getMojos().stream().anyMatch(MojoDescriptor::isV4Api);
366             Map<String, ClassLoader> foreignImports = calcImports(project, parent, imports, v4api);
367 
368             PluginRealmCache.Key cacheKey = pluginRealmCache.createKey(
369                     plugin,
370                     parent,
371                     foreignImports,
372                     filter,
373                     project.getRemotePluginRepositories(),
374                     session.getRepositorySession());
375 
376             PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey, () -> {
377                 createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter);
378 
379                 return new PluginRealmCache.CacheRecord(
380                         pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts());
381             });
382 
383             pluginDescriptor.setClassRealm(cacheRecord.getRealm());
384             pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts()));
385             for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
386                 componentDescriptor.setRealm(cacheRecord.getRealm());
387             }
388 
389             pluginRealmCache.register(project, cacheKey, cacheRecord);
390         }
391     }
392 
393     private void createPluginRealm(
394             PluginDescriptor pluginDescriptor,
395             MavenSession session,
396             ClassLoader parent,
397             Map<String, ClassLoader> foreignImports,
398             DependencyFilter filter)
399             throws PluginResolutionException, PluginContainerException {
400         Plugin plugin = Objects.requireNonNull(pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null");
401 
402         Artifact pluginArtifact = Objects.requireNonNull(
403                 pluginDescriptor.getPluginArtifact(), "pluginDescriptor.pluginArtifact cannot be null");
404 
405         MavenProject project = session.getCurrentProject();
406 
407         final ClassRealm pluginRealm;
408         final List<Artifact> pluginArtifacts;
409 
410         RepositorySystemSession repositorySession = session.getRepositorySession();
411         DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
412         dependencyFilter = AndDependencyFilter.newInstance(dependencyFilter, filter);
413 
414         DependencyResult result = pluginDependenciesResolver.resolvePlugin(
415                 plugin,
416                 RepositoryUtils.toArtifact(pluginArtifact),
417                 dependencyFilter,
418                 project.getRemotePluginRepositories(),
419                 repositorySession);
420 
421         pluginArtifacts = toMavenArtifacts(result);
422 
423         pluginRealm = classRealmManager.createPluginRealm(
424                 plugin, parent, null, foreignImports, toAetherArtifacts(pluginArtifacts));
425 
426         discoverPluginComponents(pluginRealm, plugin, pluginDescriptor);
427 
428         pluginDescriptor.setDependencyNode(result.getRoot());
429         pluginDescriptor.setClassRealm(pluginRealm);
430         pluginDescriptor.setArtifacts(pluginArtifacts);
431     }
432 
433     private void discoverPluginComponents(
434             final ClassRealm pluginRealm, Plugin plugin, PluginDescriptor pluginDescriptor)
435             throws PluginContainerException {
436         try {
437             if (pluginDescriptor != null) {
438                 for (MojoDescriptor mojo : pluginDescriptor.getMojos()) {
439                     if (!mojo.isV4Api()) {
440                         mojo.setRealm(pluginRealm);
441                         container.addComponentDescriptor(mojo);
442                     }
443                 }
444             }
445 
446             ((DefaultPlexusContainer) container)
447                     .discoverComponents(
448                             pluginRealm,
449                             new SessionScopeModule(container.lookup(SessionScope.class)),
450                             new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)),
451                             new PluginConfigurationModule(plugin.getDelegate()));
452         } catch (ComponentLookupException | CycleDetectedInComponentGraphException e) {
453             throw new PluginContainerException(
454                     plugin,
455                     pluginRealm,
456                     "Error in component graph of plugin " + plugin.getId() + ": " + e.getMessage(),
457                     e);
458         }
459     }
460 
461     private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts(final List<Artifact> pluginArtifacts) {
462         return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
463     }
464 
465     private List<Artifact> toMavenArtifacts(DependencyResult dependencyResult) {
466         List<Artifact> artifacts =
467                 new ArrayList<>(dependencyResult.getArtifactResults().size());
468         dependencyResult.getArtifactResults().stream()
469                 .filter(ArtifactResult::isResolved)
470                 .forEach(a -> artifacts.add(RepositoryUtils.toArtifact(a.getArtifact())));
471         return Collections.unmodifiableList(artifacts);
472     }
473 
474     private Map<String, ClassLoader> calcImports(
475             MavenProject project, ClassLoader parent, List<String> imports, boolean v4api) {
476         Map<String, ClassLoader> foreignImports = new HashMap<>();
477 
478         ClassLoader projectRealm = project.getClassRealm();
479         if (projectRealm != null) {
480             foreignImports.put("", projectRealm);
481         } else {
482             foreignImports.put(
483                     "", v4api ? classRealmManager.getMaven4ApiRealm() : classRealmManager.getMavenApiRealm());
484         }
485 
486         if (parent != null && imports != null) {
487             for (String parentImport : imports) {
488                 foreignImports.put(parentImport, parent);
489             }
490         }
491 
492         return foreignImports;
493     }
494 
495     public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution)
496             throws PluginConfigurationException, PluginContainerException {
497         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
498 
499         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
500 
501         ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
502 
503         if (pluginRealm == null) {
504             try {
505                 setupPluginRealm(pluginDescriptor, session, null, null, null);
506             } catch (PluginResolutionException e) {
507                 String msg = "Cannot setup plugin realm [mojoDescriptor=" + mojoDescriptor.getId()
508                         + ", pluginDescriptor=" + pluginDescriptor.getId() + "]";
509                 throw new PluginConfigurationException(pluginDescriptor, msg, e);
510             }
511             pluginRealm = pluginDescriptor.getClassRealm();
512         }
513 
514         if (logger.isDebugEnabled()) {
515             logger.debug("Loading mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm);
516         }
517 
518         // We are forcing the use of the plugin realm for all lookups that might occur during
519         // the lifecycle that is part of the lookup. Here we are specifically trying to keep
520         // lookups that occur in contextualize calls in line with the right realm.
521         ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
522 
523         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
524         Thread.currentThread().setContextClassLoader(pluginRealm);
525 
526         try {
527             if (mojoDescriptor.isV4Api()) {
528                 return loadV4Mojo(mojoInterface, session, mojoExecution, mojoDescriptor, pluginDescriptor, pluginRealm);
529             } else {
530                 return loadV3Mojo(mojoInterface, session, mojoExecution, mojoDescriptor, pluginDescriptor, pluginRealm);
531             }
532         } finally {
533             Thread.currentThread().setContextClassLoader(oldClassLoader);
534             container.setLookupRealm(oldLookupRealm);
535         }
536     }
537 
538     private <T> T loadV4Mojo(
539             Class<T> mojoInterface,
540             MavenSession session,
541             MojoExecution mojoExecution,
542             MojoDescriptor mojoDescriptor,
543             PluginDescriptor pluginDescriptor,
544             ClassRealm pluginRealm)
545             throws PluginContainerException, PluginConfigurationException {
546         T mojo;
547 
548         InternalMavenSession sessionV4 = InternalMavenSession.from(session.getSession());
549         Project project = sessionV4.getProject(session.getCurrentProject());
550 
551         org.apache.maven.api.MojoExecution execution = new DefaultMojoExecution(sessionV4, mojoExecution);
552         org.apache.maven.api.plugin.Log log = new DefaultLog(
553                 LoggerFactory.getLogger(mojoExecution.getMojoDescriptor().getFullGoalName()));
554         try {
555             Injector injector = Injector.create();
556             injector.discover(pluginRealm);
557             // Add known classes
558             // TODO: get those from the existing plexus scopes ?
559             injector.bindInstance(Session.class, sessionV4);
560             injector.bindInstance(Project.class, project);
561             injector.bindInstance(org.apache.maven.api.MojoExecution.class, execution);
562             injector.bindInstance(org.apache.maven.api.plugin.Log.class, log);
563             mojo = mojoInterface.cast(injector.getInstance(
564                     Key.of(mojoDescriptor.getImplementationClass(), mojoDescriptor.getRoleHint())));
565 
566         } catch (Exception e) {
567             throw new PluginContainerException(mojoDescriptor, pluginRealm, "Unable to lookup Mojo", e);
568         }
569 
570         XmlNode dom = mojoExecution.getConfiguration() != null
571                 ? mojoExecution.getConfiguration().getDom()
572                 : null;
573 
574         PlexusConfiguration pomConfiguration;
575 
576         if (dom == null) {
577             pomConfiguration = new DefaultPlexusConfiguration("configuration");
578         } else {
579             pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
580         }
581 
582         ExpressionEvaluator expressionEvaluator =
583                 new PluginParameterExpressionEvaluatorV4(sessionV4, project, execution);
584 
585         for (MavenPluginConfigurationValidator validator : configurationValidators) {
586             validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
587         }
588 
589         populateMojoExecutionFields(
590                 mojo,
591                 mojoExecution.getExecutionId(),
592                 mojoDescriptor,
593                 pluginRealm,
594                 pomConfiguration,
595                 expressionEvaluator);
596 
597         for (Resolution resolution : mojoDescriptor.getMojoDescriptorV4().getResolutions()) {
598             Field field = null;
599             for (Class<?> clazz = mojo.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
600                 try {
601                     field = clazz.getDeclaredField(resolution.getField());
602                     break;
603                 } catch (NoSuchFieldException e) {
604                     // continue
605                 }
606             }
607             if (field == null) {
608                 throw new PluginConfigurationException(
609                         pluginDescriptor,
610                         "Unable to find field '" + resolution.getField() + "' annotated with @Resolution");
611             }
612             field.setAccessible(true);
613             String pathScope = resolution.getPathScope();
614             Object result = null;
615             if (pathScope != null && !pathScope.isEmpty()) {
616                 // resolution
617                 PathScope ps = sessionV4.getService(PathScopeRegistry.class).require(pathScope);
618                 DependencyResolverResult res =
619                         sessionV4.getService(DependencyResolver.class).resolve(sessionV4, project, ps);
620                 if (field.getType() == DependencyResolverResult.class) {
621                     result = res;
622                 } else if (field.getType() == Node.class) {
623                     result = res.getRoot();
624                 } else if (field.getType() == List.class && field.getGenericType() instanceof ParameterizedType pt) {
625                     Type t = pt.getActualTypeArguments()[0];
626                     if (t == Node.class) {
627                         result = res.getNodes();
628                     } else if (t == Path.class) {
629                         result = res.getPaths();
630                     }
631                 } else if (field.getType() == Map.class && field.getGenericType() instanceof ParameterizedType pt) {
632                     Type k = pt.getActualTypeArguments()[0];
633                     Type v = pt.getActualTypeArguments()[1];
634                     if (k == PathType.class
635                             && v instanceof ParameterizedType ptv
636                             && ptv.getRawType() == List.class
637                             && ptv.getActualTypeArguments()[0] == Path.class) {
638                         result = res.getDispatchedPaths();
639                     } else if (k == Dependency.class && v == Path.class) {
640                         result = res.getDependencies();
641                     }
642                 }
643             } else {
644                 // collection
645                 DependencyResolverResult res = sessionV4
646                         .getService(DependencyResolver.class)
647                         .collect(sessionV4, project, PathScope.MAIN_RUNTIME);
648                 if (field.getType() == DependencyResolverResult.class) {
649                     result = res;
650                 } else if (field.getType() == Node.class) {
651                     result = res.getRoot();
652                 }
653             }
654             if (result == null) {
655                 throw new PluginConfigurationException(
656                         pluginDescriptor,
657                         "Unable to inject field '" + resolution.getField()
658                                 + "' annotated with @Dependencies. Unsupported type " + field.getGenericType());
659             }
660             try {
661                 field.set(mojo, result);
662             } catch (IllegalAccessException e) {
663                 throw new PluginConfigurationException(
664                         pluginDescriptor,
665                         "Unable to inject field '" + resolution.getField() + "' annotated with @Dependencies",
666                         e);
667             }
668         }
669 
670         return mojo;
671     }
672 
673     private <T> T loadV3Mojo(
674             Class<T> mojoInterface,
675             MavenSession session,
676             MojoExecution mojoExecution,
677             MojoDescriptor mojoDescriptor,
678             PluginDescriptor pluginDescriptor,
679             ClassRealm pluginRealm)
680             throws PluginContainerException, PluginConfigurationException {
681         T mojo;
682 
683         try {
684             mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
685         } catch (ComponentLookupException e) {
686             Throwable cause = e.getCause();
687             while (cause != null && !(cause instanceof LinkageError) && !(cause instanceof ClassNotFoundException)) {
688                 cause = cause.getCause();
689             }
690 
691             if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
692                 ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
693                 PrintStream ps = new PrintStream(os);
694                 ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
695                         + pluginDescriptor.getId() + "'. A required class is missing: "
696                         + cause.getMessage());
697                 pluginRealm.display(ps);
698 
699                 throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
700             } else if (cause instanceof LinkageError) {
701                 ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
702                 PrintStream ps = new PrintStream(os);
703                 ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
704                         + pluginDescriptor.getId() + "' due to an API incompatibility: "
705                         + e.getClass().getName() + ": " + cause.getMessage());
706                 pluginRealm.display(ps);
707 
708                 throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
709             }
710 
711             throw new PluginContainerException(
712                     mojoDescriptor,
713                     pluginRealm,
714                     "Unable to load the mojo '" + mojoDescriptor.getGoal()
715                             + "' (or one of its required components) from the plugin '"
716                             + pluginDescriptor.getId() + "'",
717                     e);
718         }
719 
720         if (mojo instanceof ContextEnabled) {
721             MavenProject project = session.getCurrentProject();
722 
723             Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
724 
725             if (pluginContext != null) {
726                 pluginContext.put("project", project);
727 
728                 pluginContext.put("pluginDescriptor", pluginDescriptor);
729 
730                 ((ContextEnabled) mojo).setPluginContext(pluginContext);
731             }
732         }
733 
734         if (mojo instanceof Mojo) {
735             Logger mojoLogger = LoggerFactory.getLogger(mojoDescriptor.getImplementation());
736             ((Mojo) mojo).setLog(new MojoLogWrapper(mojoLogger));
737         }
738 
739         if (mojo instanceof Contextualizable) {
740             pluginValidationManager.reportPluginMojoValidationIssue(
741                     PluginValidationManager.IssueLocality.EXTERNAL,
742                     session,
743                     mojoDescriptor,
744                     mojo.getClass(),
745                     "Mojo implements `Contextualizable` interface from Plexus Container, which is EOL.");
746         }
747 
748         XmlNode dom = mojoExecution.getConfiguration() != null
749                 ? mojoExecution.getConfiguration().getDom()
750                 : null;
751 
752         PlexusConfiguration pomConfiguration;
753 
754         if (dom == null) {
755             pomConfiguration = new DefaultPlexusConfiguration("configuration");
756         } else {
757             pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
758         }
759 
760         ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
761 
762         for (MavenPluginConfigurationValidator validator : configurationValidators) {
763             validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
764         }
765 
766         populateMojoExecutionFields(
767                 mojo,
768                 mojoExecution.getExecutionId(),
769                 mojoDescriptor,
770                 pluginRealm,
771                 pomConfiguration,
772                 expressionEvaluator);
773 
774         return mojo;
775     }
776 
777     private void populateMojoExecutionFields(
778             Object mojo,
779             String executionId,
780             MojoDescriptor mojoDescriptor,
781             ClassRealm pluginRealm,
782             PlexusConfiguration configuration,
783             ExpressionEvaluator expressionEvaluator)
784             throws PluginConfigurationException {
785         ComponentConfigurator configurator = null;
786 
787         String configuratorId = mojoDescriptor.getComponentConfigurator();
788 
789         if (configuratorId == null || configuratorId.isEmpty()) {
790             configuratorId = mojoDescriptor.isV4Api() ? "enhanced" : "basic";
791         }
792 
793         try {
794             // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
795             // so that this method could entirely be handled by a plexus lookup?
796             configurator = container.lookup(ComponentConfigurator.class, configuratorId);
797 
798             ConfigurationListener listener = new DebugConfigurationListener(logger);
799 
800             ValidatingConfigurationListener validator =
801                     new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
802 
803             if (logger.isDebugEnabled()) {
804                 logger.debug("Configuring mojo execution '" + mojoDescriptor.getId() + ':' + executionId + "' with "
805                         + configuratorId + " configurator -->");
806             }
807 
808             configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
809 
810             logger.debug("-- end configuration --");
811 
812             Collection<Parameter> missingParameters = validator.getMissingParameters();
813             if (!missingParameters.isEmpty()) {
814                 if ("basic".equals(configuratorId)) {
815                     throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
816                 } else {
817                     /*
818                      * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
819                      * hard way.
820                      */
821                     validateParameters(mojoDescriptor, configuration, expressionEvaluator);
822                 }
823             }
824 
825         } catch (ComponentConfigurationException e) {
826             String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
827             if (e.getFailedConfiguration() != null) {
828                 message += " for parameter " + e.getFailedConfiguration().getName();
829             }
830             message += ": " + e.getMessage();
831 
832             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
833         } catch (ComponentLookupException e) {
834             throw new PluginConfigurationException(
835                     mojoDescriptor.getPluginDescriptor(),
836                     "Unable to retrieve component configurator " + configuratorId + " for configuration of mojo "
837                             + mojoDescriptor.getId(),
838                     e);
839         } catch (NoClassDefFoundError e) {
840             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
841             PrintStream ps = new PrintStream(os);
842             ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
843                     + e.getMessage());
844             pluginRealm.display(ps);
845 
846             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
847         } catch (LinkageError e) {
848             ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
849             PrintStream ps = new PrintStream(os);
850             ps.println("An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
851                     + ": " + e.getClass().getName() + ": " + e.getMessage());
852             pluginRealm.display(ps);
853 
854             throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
855         } finally {
856             if (configurator != null) {
857                 try {
858                     container.release(configurator);
859                 } catch (ComponentLifecycleException e) {
860                     logger.debug("Failed to release mojo configurator - ignoring.");
861                 }
862             }
863         }
864     }
865 
866     private void validateParameters(
867             MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
868             throws ComponentConfigurationException, PluginParameterException {
869         if (mojoDescriptor.getParameters() == null) {
870             return;
871         }
872 
873         List<Parameter> invalidParameters = new ArrayList<>();
874 
875         for (Parameter parameter : mojoDescriptor.getParameters()) {
876             if (!parameter.isRequired()) {
877                 continue;
878             }
879 
880             Object value = null;
881 
882             PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
883             if (config != null) {
884                 String expression = config.getValue(null);
885 
886                 try {
887                     value = expressionEvaluator.evaluate(expression);
888 
889                     if (value == null) {
890                         value = config.getAttribute("default-value", null);
891                     }
892                 } catch (ExpressionEvaluationException e) {
893                     String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
894                             + configuration.getName() + "'";
895                     throw new ComponentConfigurationException(configuration, msg, e);
896                 }
897             }
898 
899             if (value == null && (config == null || config.getChildCount() <= 0)) {
900                 invalidParameters.add(parameter);
901             }
902         }
903 
904         if (!invalidParameters.isEmpty()) {
905             throw new PluginParameterException(mojoDescriptor, invalidParameters);
906         }
907     }
908 
909     public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
910         if (mojo != null) {
911             try {
912                 container.release(mojo);
913             } catch (ComponentLifecycleException e) {
914                 String goalExecId = mojoExecution.getGoal();
915 
916                 if (mojoExecution.getExecutionId() != null) {
917                     logger.debug(
918                             "Error releasing mojo for {} {execution: {}}",
919                             goalExecId,
920                             mojoExecution.getExecutionId(),
921                             e);
922                 } else {
923                     logger.debug("Error releasing mojo for {}", goalExecId, e);
924                 }
925             }
926         }
927     }
928 
929     public ExtensionRealmCache.CacheRecord setupExtensionsRealm(
930             MavenProject project, Plugin plugin, RepositorySystemSession session) throws PluginManagerException {
931         @SuppressWarnings("unchecked")
932         Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
933                 (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue(KEY_EXTENSIONS_REALMS);
934         if (pluginRealms == null) {
935             pluginRealms = new HashMap<>();
936             project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
937         }
938 
939         final String pluginKey = plugin.getId();
940 
941         ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
942         if (extensionRecord != null) {
943             return extensionRecord;
944         }
945 
946         final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
947 
948         // resolve plugin version as necessary
949         if (plugin.getVersion() == null) {
950             PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
951             try {
952                 plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
953             } catch (PluginVersionResolutionException e) {
954                 throw new PluginManagerException(plugin, e.getMessage(), e);
955             }
956         }
957 
958         // TODO: store plugin version
959 
960         // resolve plugin artifacts
961         List<Artifact> artifacts;
962         PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
963         PluginArtifactsCache.CacheRecord recordArtifacts;
964         try {
965             recordArtifacts = pluginArtifactsCache.get(cacheKey);
966         } catch (PluginResolutionException e) {
967             throw new PluginManagerException(plugin, e.getMessage(), e);
968         }
969         if (recordArtifacts != null) {
970             artifacts = recordArtifacts.getArtifacts();
971         } else {
972             try {
973                 artifacts = resolveExtensionArtifacts(plugin, repositories, session);
974                 recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
975             } catch (PluginResolutionException e) {
976                 pluginArtifactsCache.put(cacheKey, e);
977                 pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
978                 throw new PluginManagerException(plugin, e.getMessage(), e);
979             }
980         }
981         pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
982 
983         // create and cache extensions realms
984         final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
985         extensionRecord = extensionRealmCache.get(extensionKey);
986         if (extensionRecord == null) {
987             ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
988 
989             // TODO figure out how to use the same PluginDescriptor when running mojos
990 
991             PluginDescriptor pluginDescriptor = null;
992             if (plugin.isExtensions() && !artifacts.isEmpty()) {
993                 // ignore plugin descriptor parsing errors at this point
994                 // these errors will reported during calculation of project build execution plan
995                 try {
996                     pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
997                 } catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
998                     // ignore, see above
999                 }
1000             }
1001 
1002             discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
1003 
1004             ExtensionDescriptor extensionDescriptor = null;
1005             Artifact extensionArtifact = artifacts.get(0);
1006             try {
1007                 extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
1008             } catch (IOException e) {
1009                 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
1010                 if (logger.isDebugEnabled()) {
1011                     logger.error(message, e);
1012                 } else {
1013                     logger.error(message);
1014                 }
1015             }
1016             extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
1017         }
1018         extensionRealmCache.register(project, extensionKey, extensionRecord);
1019         pluginRealms.put(pluginKey, extensionRecord);
1020 
1021         return extensionRecord;
1022     }
1023 
1024     private List<Artifact> resolveExtensionArtifacts(
1025             Plugin extensionPlugin, List<RemoteRepository> repositories, RepositorySystemSession session)
1026             throws PluginResolutionException {
1027         DependencyResult root =
1028                 pluginDependenciesResolver.resolvePlugin(extensionPlugin, null, null, repositories, session);
1029         return toMavenArtifacts(root);
1030     }
1031 
1032     static class NamedImpl implements Named {
1033         private final String value;
1034 
1035         NamedImpl(String value) {
1036             this.value = value;
1037         }
1038 
1039         public String value() {
1040             return this.value;
1041         }
1042 
1043         public int hashCode() {
1044             return 127 * "value".hashCode() ^ this.value.hashCode();
1045         }
1046 
1047         public boolean equals(Object o) {
1048             return o instanceof Named && this.value.equals(((Named) o).value());
1049         }
1050 
1051         public Class<? extends Annotation> annotationType() {
1052             return Named.class;
1053         }
1054     }
1055 }