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