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