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