blob: 5c250082a1b9a19313c44fc0bfd5803c4bc1daa9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.*;
import java.lang.annotation.Annotation;
import java.nio.file.Files;
import java.util.*;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.api.Project;
import org.apache.maven.api.Session;
import org.apache.maven.api.services.ProjectManager;
import org.apache.maven.api.xml.XmlNode;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.classrealm.ClassRealmManager;
import org.apache.maven.di.Injector;
import org.apache.maven.di.Key;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
import org.apache.maven.internal.impl.DefaultLog;
import org.apache.maven.internal.impl.DefaultMojoExecution;
import org.apache.maven.internal.impl.InternalMavenSession;
import org.apache.maven.internal.xml.XmlPlexusConfiguration;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.ContextEnabled;
import org.apache.maven.plugin.DebugConfigurationListener;
import org.apache.maven.plugin.ExtensionRealmCache;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.MavenPluginPrerequisitesChecker;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoNotFoundException;
import org.apache.maven.plugin.PluginArtifactsCache;
import org.apache.maven.plugin.PluginConfigurationException;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.PluginDescriptorCache;
import org.apache.maven.plugin.PluginDescriptorParsingException;
import org.apache.maven.plugin.PluginIncompatibleException;
import org.apache.maven.plugin.PluginManagerException;
import org.apache.maven.plugin.PluginParameterException;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
import org.apache.maven.plugin.PluginRealmCache;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.project.ExtensionDescriptor;
import org.apache.maven.project.ExtensionDescriptorBuilder;
import org.apache.maven.project.MavenProject;
import org.apache.maven.session.scope.internal.SessionScope;
import org.apache.maven.session.scope.internal.SessionScopeModule;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ComponentConfigurator;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.component.repository.ComponentDescriptor;
import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.PlexusConfigurationException;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.util.filter.AndDependencyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
* that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
* plugins as well as special purpose plugins like reports.
*
* @since 3.0
*/
@Named
@Singleton
public class DefaultMavenPluginManager implements MavenPluginManager {
/**
* <p>
* PluginId =&gt; ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the
* same class realm is used to load build extensions and load mojos for extensions=true plugins.
* </p>
* <strong>Note:</strong> This is part of internal implementation and may be changed or removed without notice
*
* @since 3.3.0
*/
public static final String KEY_EXTENSIONS_REALMS = DefaultMavenPluginManager.class.getName() + "/extensionsRealms";
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PlexusContainer container;
private final ClassRealmManager classRealmManager;
private final PluginDescriptorCache pluginDescriptorCache;
private final PluginRealmCache pluginRealmCache;
private final PluginDependenciesResolver pluginDependenciesResolver;
private final ExtensionRealmCache extensionRealmCache;
private final PluginVersionResolver pluginVersionResolver;
private final PluginArtifactsCache pluginArtifactsCache;
private final MavenPluginValidator pluginValidator;
private final List<MavenPluginConfigurationValidator> configurationValidators;
private final PluginValidationManager pluginValidationManager;
private final List<MavenPluginPrerequisitesChecker> prerequisitesCheckers;
private final ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
private final PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
@Inject
@SuppressWarnings("checkstyle:ParameterNumber")
public DefaultMavenPluginManager(
PlexusContainer container,
ClassRealmManager classRealmManager,
PluginDescriptorCache pluginDescriptorCache,
PluginRealmCache pluginRealmCache,
PluginDependenciesResolver pluginDependenciesResolver,
ExtensionRealmCache extensionRealmCache,
PluginVersionResolver pluginVersionResolver,
PluginArtifactsCache pluginArtifactsCache,
MavenPluginValidator pluginValidator,
List<MavenPluginConfigurationValidator> configurationValidators,
PluginValidationManager pluginValidationManager,
List<MavenPluginPrerequisitesChecker> prerequisitesCheckers) {
this.container = container;
this.classRealmManager = classRealmManager;
this.pluginDescriptorCache = pluginDescriptorCache;
this.pluginRealmCache = pluginRealmCache;
this.pluginDependenciesResolver = pluginDependenciesResolver;
this.extensionRealmCache = extensionRealmCache;
this.pluginVersionResolver = pluginVersionResolver;
this.pluginArtifactsCache = pluginArtifactsCache;
this.pluginValidator = pluginValidator;
this.configurationValidators = configurationValidators;
this.pluginValidationManager = pluginValidationManager;
this.prerequisitesCheckers = prerequisitesCheckers;
}
public PluginDescriptor getPluginDescriptor(
Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
org.eclipse.aether.artifact.Artifact artifact =
pluginDependenciesResolver.resolve(plugin, repositories, session);
Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
boolean isBlankVersion = descriptor.getRequiredMavenVersion() == null
|| descriptor.getRequiredMavenVersion().trim().isEmpty();
if (isBlankVersion) {
// only take value from underlying POM if plugin descriptor has no explicit Maven requirement
descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null));
}
return descriptor;
});
pluginDescriptor.setPlugin(plugin);
return pluginDescriptor;
}
private PluginDescriptor extractPluginDescriptor(Artifact pluginArtifact, Plugin plugin)
throws PluginDescriptorParsingException, InvalidPluginDescriptorException {
PluginDescriptor pluginDescriptor = null;
File pluginFile = pluginArtifact.getFile();
try {
if (pluginFile.isFile()) {
try (JarFile pluginJar = new JarFile(pluginFile, false)) {
ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
if (pluginDescriptorEntry != null) {
pluginDescriptor = parsePluginDescriptor(
() -> pluginJar.getInputStream(pluginDescriptorEntry),
plugin,
pluginFile.getAbsolutePath());
}
}
} else {
File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
if (pluginXml.isFile()) {
pluginDescriptor = parsePluginDescriptor(
() -> Files.newInputStream(pluginXml.toPath()), plugin, pluginXml.getAbsolutePath());
}
}
if (pluginDescriptor == null) {
throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
}
} catch (IOException e) {
throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
}
List<String> errors = new ArrayList<>();
pluginValidator.validate(pluginArtifact, pluginDescriptor, errors);
if (!errors.isEmpty()) {
throw new InvalidPluginDescriptorException(
"Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", errors);
}
pluginDescriptor.setPluginArtifact(pluginArtifact);
return pluginDescriptor;
}
private String getPluginDescriptorLocation() {
return "META-INF/maven/plugin.xml";
}
private PluginDescriptor parsePluginDescriptor(
PluginDescriptorBuilder.StreamSupplier is, Plugin plugin, String descriptorLocation)
throws PluginDescriptorParsingException {
try {
return builder.build(is, descriptorLocation);
} catch (PlexusConfigurationException e) {
throw new PluginDescriptorParsingException(plugin, descriptorLocation, e);
}
}
public MojoDescriptor getMojoDescriptor(
Plugin plugin, String goal, List<RemoteRepository> repositories, RepositorySystemSession session)
throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
InvalidPluginDescriptorException {
PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session);
MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal);
if (mojoDescriptor == null) {
throw new MojoNotFoundException(goal, pluginDescriptor);
}
return mojoDescriptor;
}
@Override
public void checkPrerequisites(PluginDescriptor pluginDescriptor) throws PluginIncompatibleException {
List<IllegalStateException> prerequisiteExceptions = new ArrayList<>();
prerequisitesCheckers.forEach(c -> {
try {
c.accept(pluginDescriptor);
} catch (IllegalStateException e) {
prerequisiteExceptions.add(e);
}
});
// aggregate all exceptions
if (!prerequisiteExceptions.isEmpty()) {
String messages = prerequisiteExceptions.stream()
.map(IllegalStateException::getMessage)
.collect(Collectors.joining(", "));
PluginIncompatibleException pie = new PluginIncompatibleException(
pluginDescriptor.getPlugin(),
"The plugin " + pluginDescriptor.getId() + " has unmet prerequisites: " + messages,
prerequisiteExceptions.get(0));
// the first exception is added as cause, all other ones as suppressed exceptions
prerequisiteExceptions.stream().skip(1).forEach(pie::addSuppressed);
throw pie;
}
}
@Override
@Deprecated
public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor) throws PluginIncompatibleException {
checkPrerequisites(pluginDescriptor);
}
public void setupPluginRealm(
PluginDescriptor pluginDescriptor,
MavenSession session,
ClassLoader parent,
List<String> imports,
DependencyFilter filter)
throws PluginResolutionException, PluginContainerException {
Plugin plugin = pluginDescriptor.getPlugin();
MavenProject project = session.getCurrentProject();
if (plugin.isExtensions()) {
ExtensionRealmCache.CacheRecord extensionRecord;
try {
RepositorySystemSession repositorySession = session.getRepositorySession();
extensionRecord = setupExtensionsRealm(project, plugin, repositorySession);
} catch (PluginManagerException e) {
// extensions realm is expected to be fully setup at this point
// any exception means a problem in maven code, not a user error
throw new IllegalStateException(e);
}
ClassRealm pluginRealm = extensionRecord.getRealm();
List<Artifact> pluginArtifacts = extensionRecord.getArtifacts();
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(pluginRealm);
}
pluginDescriptor.setClassRealm(pluginRealm);
pluginDescriptor.setArtifacts(pluginArtifacts);
} else {
Map<String, ClassLoader> foreignImports = calcImports(project, parent, imports);
PluginRealmCache.Key cacheKey = pluginRealmCache.createKey(
plugin,
parent,
foreignImports,
filter,
project.getRemotePluginRepositories(),
session.getRepositorySession());
PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey, () -> {
createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter);
return new PluginRealmCache.CacheRecord(
pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts());
});
pluginDescriptor.setClassRealm(cacheRecord.getRealm());
pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts()));
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(cacheRecord.getRealm());
}
pluginRealmCache.register(project, cacheKey, cacheRecord);
}
}
private void createPluginRealm(
PluginDescriptor pluginDescriptor,
MavenSession session,
ClassLoader parent,
Map<String, ClassLoader> foreignImports,
DependencyFilter filter)
throws PluginResolutionException, PluginContainerException {
Plugin plugin = Objects.requireNonNull(pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null");
Artifact pluginArtifact = Objects.requireNonNull(
pluginDescriptor.getPluginArtifact(), "pluginDescriptor.pluginArtifact cannot be null");
MavenProject project = session.getCurrentProject();
final ClassRealm pluginRealm;
final List<Artifact> pluginArtifacts;
RepositorySystemSession repositorySession = session.getRepositorySession();
DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
dependencyFilter = AndDependencyFilter.newInstance(dependencyFilter, filter);
DependencyResult result = pluginDependenciesResolver.resolvePlugin(
plugin,
RepositoryUtils.toArtifact(pluginArtifact),
dependencyFilter,
project.getRemotePluginRepositories(),
repositorySession);
pluginArtifacts = toMavenArtifacts(result);
pluginRealm = classRealmManager.createPluginRealm(
plugin, parent, null, foreignImports, toAetherArtifacts(pluginArtifacts));
discoverPluginComponents(pluginRealm, plugin, pluginDescriptor);
pluginDescriptor.setDependencyNode(result.getRoot());
pluginDescriptor.setClassRealm(pluginRealm);
pluginDescriptor.setArtifacts(pluginArtifacts);
}
private void discoverPluginComponents(
final ClassRealm pluginRealm, Plugin plugin, PluginDescriptor pluginDescriptor)
throws PluginContainerException {
try {
if (pluginDescriptor != null) {
for (MojoDescriptor mojo : pluginDescriptor.getMojos()) {
if (!mojo.isV4Api()) {
mojo.setRealm(pluginRealm);
container.addComponentDescriptor(mojo);
}
}
}
((DefaultPlexusContainer) container)
.discoverComponents(
pluginRealm,
new SessionScopeModule(container.lookup(SessionScope.class)),
new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)),
new PluginConfigurationModule(plugin.getDelegate()));
} catch (ComponentLookupException | CycleDetectedInComponentGraphException e) {
throw new PluginContainerException(
plugin,
pluginRealm,
"Error in component graph of plugin " + plugin.getId() + ": " + e.getMessage(),
e);
}
}
private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts(final List<Artifact> pluginArtifacts) {
return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
}
private List<Artifact> toMavenArtifacts(DependencyResult dependencyResult) {
List<Artifact> artifacts =
new ArrayList<>(dependencyResult.getArtifactResults().size());
dependencyResult.getArtifactResults().stream()
.filter(ArtifactResult::isResolved)
.forEach(a -> artifacts.add(RepositoryUtils.toArtifact(a.getArtifact())));
return Collections.unmodifiableList(artifacts);
}
private Map<String, ClassLoader> calcImports(MavenProject project, ClassLoader parent, List<String> imports) {
Map<String, ClassLoader> foreignImports = new HashMap<>();
ClassLoader projectRealm = project.getClassRealm();
if (projectRealm != null) {
foreignImports.put("", projectRealm);
} else {
foreignImports.put("", classRealmManager.getMavenApiRealm());
}
if (parent != null && imports != null) {
for (String parentImport : imports) {
foreignImports.put(parentImport, parent);
}
}
return foreignImports;
}
public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution)
throws PluginConfigurationException, PluginContainerException {
MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
if (pluginRealm == null) {
try {
setupPluginRealm(pluginDescriptor, session, null, null, null);
} catch (PluginResolutionException e) {
String msg = "Cannot setup plugin realm [mojoDescriptor=" + mojoDescriptor.getId()
+ ", pluginDescriptor=" + pluginDescriptor.getId() + "]";
throw new PluginConfigurationException(pluginDescriptor, msg, e);
}
pluginRealm = pluginDescriptor.getClassRealm();
}
if (logger.isDebugEnabled()) {
logger.debug("Loading mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm);
}
// We are forcing the use of the plugin realm for all lookups that might occur during
// the lifecycle that is part of the lookup. Here we are specifically trying to keep
// lookups that occur in contextualize calls in line with the right realm.
ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(pluginRealm);
try {
if (mojoDescriptor.isV4Api()) {
return loadV4Mojo(mojoInterface, session, mojoExecution, mojoDescriptor, pluginDescriptor, pluginRealm);
} else {
return loadV3Mojo(mojoInterface, session, mojoExecution, mojoDescriptor, pluginDescriptor, pluginRealm);
}
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
container.setLookupRealm(oldLookupRealm);
}
}
private <T> T loadV4Mojo(
Class<T> mojoInterface,
MavenSession session,
MojoExecution mojoExecution,
MojoDescriptor mojoDescriptor,
PluginDescriptor pluginDescriptor,
ClassRealm pluginRealm)
throws PluginContainerException, PluginConfigurationException {
T mojo;
InternalMavenSession sessionV4 = InternalMavenSession.from(session.getSession());
Project project = sessionV4.getProject(session.getCurrentProject());
List<org.apache.maven.api.RemoteRepository> repos =
sessionV4.getService(ProjectManager.class).getRemoteProjectRepositories(project);
sessionV4 = InternalMavenSession.from(sessionV4.withRemoteRepositories(repos));
org.apache.maven.api.MojoExecution execution = new DefaultMojoExecution(sessionV4, mojoExecution);
org.apache.maven.api.plugin.Log log = new DefaultLog(
LoggerFactory.getLogger(mojoExecution.getMojoDescriptor().getFullGoalName()));
try {
Injector injector = Injector.create();
injector.discover(pluginRealm);
// Add known classes
// TODO: get those from the existing plexus scopes ?
injector.bindInstance(Session.class, sessionV4);
injector.bindInstance(Project.class, project);
injector.bindInstance(org.apache.maven.api.MojoExecution.class, execution);
injector.bindInstance(org.apache.maven.api.plugin.Log.class, log);
mojo = mojoInterface.cast(injector.getInstance(
Key.of(mojoDescriptor.getImplementationClass(), mojoDescriptor.getRoleHint())));
} catch (Exception e) {
throw new PluginContainerException(mojoDescriptor, pluginRealm, "Unable to lookup Mojo", e);
}
XmlNode dom = mojoExecution.getConfiguration() != null
? mojoExecution.getConfiguration().getDom()
: null;
PlexusConfiguration pomConfiguration;
if (dom == null) {
pomConfiguration = new DefaultPlexusConfiguration("configuration");
} else {
pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
}
ExpressionEvaluator expressionEvaluator =
new PluginParameterExpressionEvaluatorV4(sessionV4, project, execution);
for (MavenPluginConfigurationValidator validator : configurationValidators) {
validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
}
populateMojoExecutionFields(
mojo,
mojoExecution.getExecutionId(),
mojoDescriptor,
pluginRealm,
pomConfiguration,
expressionEvaluator);
return mojo;
}
private <T> T loadV3Mojo(
Class<T> mojoInterface,
MavenSession session,
MojoExecution mojoExecution,
MojoDescriptor mojoDescriptor,
PluginDescriptor pluginDescriptor,
ClassRealm pluginRealm)
throws PluginContainerException, PluginConfigurationException {
T mojo;
try {
mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
} catch (ComponentLookupException e) {
Throwable cause = e.getCause();
while (cause != null && !(cause instanceof LinkageError) && !(cause instanceof ClassNotFoundException)) {
cause = cause.getCause();
}
if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "'. A required class is missing: "
+ cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
} else if (cause instanceof LinkageError) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "' due to an API incompatibility: "
+ e.getClass().getName() + ": " + cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
}
throw new PluginContainerException(
mojoDescriptor,
pluginRealm,
"Unable to load the mojo '" + mojoDescriptor.getGoal()
+ "' (or one of its required components) from the plugin '"
+ pluginDescriptor.getId() + "'",
e);
}
if (mojo instanceof ContextEnabled) {
MavenProject project = session.getCurrentProject();
Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
if (pluginContext != null) {
pluginContext.put("project", project);
pluginContext.put("pluginDescriptor", pluginDescriptor);
((ContextEnabled) mojo).setPluginContext(pluginContext);
}
}
if (mojo instanceof Mojo) {
Logger mojoLogger = LoggerFactory.getLogger(mojoDescriptor.getImplementation());
((Mojo) mojo).setLog(new MojoLogWrapper(mojoLogger));
}
if (mojo instanceof Contextualizable) {
pluginValidationManager.reportPluginMojoValidationIssue(
PluginValidationManager.IssueLocality.EXTERNAL,
session,
mojoDescriptor,
mojo.getClass(),
"Mojo implements `Contextualizable` interface from Plexus Container, which is EOL.");
}
XmlNode dom = mojoExecution.getConfiguration() != null
? mojoExecution.getConfiguration().getDom()
: null;
PlexusConfiguration pomConfiguration;
if (dom == null) {
pomConfiguration = new DefaultPlexusConfiguration("configuration");
} else {
pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
}
InternalMavenSession sessionV4 = InternalMavenSession.from(session.getSession());
ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
for (MavenPluginConfigurationValidator validator : configurationValidators) {
validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
}
populateMojoExecutionFields(
mojo,
mojoExecution.getExecutionId(),
mojoDescriptor,
pluginRealm,
pomConfiguration,
expressionEvaluator);
return mojo;
}
private void populateMojoExecutionFields(
Object mojo,
String executionId,
MojoDescriptor mojoDescriptor,
ClassRealm pluginRealm,
PlexusConfiguration configuration,
ExpressionEvaluator expressionEvaluator)
throws PluginConfigurationException {
ComponentConfigurator configurator = null;
String configuratorId = mojoDescriptor.getComponentConfigurator();
if (configuratorId == null || configuratorId.isEmpty()) {
configuratorId = mojoDescriptor.isV4Api() ? "enhanced" : "basic";
}
try {
// TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
// so that this method could entirely be handled by a plexus lookup?
configurator = container.lookup(ComponentConfigurator.class, configuratorId);
ConfigurationListener listener = new DebugConfigurationListener(logger);
ValidatingConfigurationListener validator =
new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
if (logger.isDebugEnabled()) {
logger.debug("Configuring mojo execution '" + mojoDescriptor.getId() + ':' + executionId + "' with "
+ configuratorId + " configurator -->");
}
configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
logger.debug("-- end configuration --");
Collection<Parameter> missingParameters = validator.getMissingParameters();
if (!missingParameters.isEmpty()) {
if ("basic".equals(configuratorId)) {
throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
} else {
/*
* NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
* hard way.
*/
validateParameters(mojoDescriptor, configuration, expressionEvaluator);
}
}
} catch (ComponentConfigurationException e) {
String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
if (e.getFailedConfiguration() != null) {
message += " for parameter " + e.getFailedConfiguration().getName();
}
message += ": " + e.getMessage();
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
} catch (ComponentLookupException e) {
throw new PluginConfigurationException(
mojoDescriptor.getPluginDescriptor(),
"Unable to retrieve component configurator " + configuratorId + " for configuration of mojo "
+ mojoDescriptor.getId(),
e);
} catch (NoClassDefFoundError e) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
+ e.getMessage());
pluginRealm.display(ps);
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
} catch (LinkageError e) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
+ ": " + e.getClass().getName() + ": " + e.getMessage());
pluginRealm.display(ps);
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
} finally {
if (configurator != null) {
try {
container.release(configurator);
} catch (ComponentLifecycleException e) {
logger.debug("Failed to release mojo configurator - ignoring.");
}
}
}
}
private void validateParameters(
MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
throws ComponentConfigurationException, PluginParameterException {
if (mojoDescriptor.getParameters() == null) {
return;
}
List<Parameter> invalidParameters = new ArrayList<>();
for (Parameter parameter : mojoDescriptor.getParameters()) {
if (!parameter.isRequired()) {
continue;
}
Object value = null;
PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
if (config != null) {
String expression = config.getValue(null);
try {
value = expressionEvaluator.evaluate(expression);
if (value == null) {
value = config.getAttribute("default-value", null);
}
} catch (ExpressionEvaluationException e) {
String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
+ configuration.getName() + "'";
throw new ComponentConfigurationException(configuration, msg, e);
}
}
if (value == null && (config == null || config.getChildCount() <= 0)) {
invalidParameters.add(parameter);
}
}
if (!invalidParameters.isEmpty()) {
throw new PluginParameterException(mojoDescriptor, invalidParameters);
}
}
public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
if (mojo != null) {
try {
container.release(mojo);
} catch (ComponentLifecycleException e) {
String goalExecId = mojoExecution.getGoal();
if (mojoExecution.getExecutionId() != null) {
logger.debug(
"Error releasing mojo for {} {execution: {}}",
goalExecId,
mojoExecution.getExecutionId(),
e);
} else {
logger.debug("Error releasing mojo for {}", goalExecId, e);
}
}
}
}
public ExtensionRealmCache.CacheRecord setupExtensionsRealm(
MavenProject project, Plugin plugin, RepositorySystemSession session) throws PluginManagerException {
@SuppressWarnings("unchecked")
Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
(Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue(KEY_EXTENSIONS_REALMS);
if (pluginRealms == null) {
pluginRealms = new HashMap<>();
project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
}
final String pluginKey = plugin.getId();
ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
if (extensionRecord != null) {
return extensionRecord;
}
final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
// resolve plugin version as necessary
if (plugin.getVersion() == null) {
PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
try {
plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
} catch (PluginVersionResolutionException e) {
throw new PluginManagerException(plugin, e.getMessage(), e);
}
}
// TODO: store plugin version
// resolve plugin artifacts
List<Artifact> artifacts;
PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
PluginArtifactsCache.CacheRecord recordArtifacts;
try {
recordArtifacts = pluginArtifactsCache.get(cacheKey);
} catch (PluginResolutionException e) {
throw new PluginManagerException(plugin, e.getMessage(), e);
}
if (recordArtifacts != null) {
artifacts = recordArtifacts.getArtifacts();
} else {
try {
artifacts = resolveExtensionArtifacts(plugin, repositories, session);
recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
} catch (PluginResolutionException e) {
pluginArtifactsCache.put(cacheKey, e);
pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
throw new PluginManagerException(plugin, e.getMessage(), e);
}
}
pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
// create and cache extensions realms
final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
extensionRecord = extensionRealmCache.get(extensionKey);
if (extensionRecord == null) {
ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
// TODO figure out how to use the same PluginDescriptor when running mojos
PluginDescriptor pluginDescriptor = null;
if (plugin.isExtensions() && !artifacts.isEmpty()) {
// ignore plugin descriptor parsing errors at this point
// these errors will reported during calculation of project build execution plan
try {
pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
} catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
// ignore, see above
}
}
discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
ExtensionDescriptor extensionDescriptor = null;
Artifact extensionArtifact = artifacts.get(0);
try {
extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
} catch (IOException e) {
String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
if (logger.isDebugEnabled()) {
logger.error(message, e);
} else {
logger.error(message);
}
}
extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
}
extensionRealmCache.register(project, extensionKey, extensionRecord);
pluginRealms.put(pluginKey, extensionRecord);
return extensionRecord;
}
private List<Artifact> resolveExtensionArtifacts(
Plugin extensionPlugin, List<RemoteRepository> repositories, RepositorySystemSession session)
throws PluginResolutionException {
DependencyResult root =
pluginDependenciesResolver.resolvePlugin(extensionPlugin, null, null, repositories, session);
return toMavenArtifacts(root);
}
static class NamedImpl implements Named {
private final String value;
NamedImpl(String value) {
this.value = value;
}
public String value() {
return this.value;
}
@SuppressWarnings("checkstyle:MagicNumber")
public int hashCode() {
return 127 * "value".hashCode() ^ this.value.hashCode();
}
public boolean equals(Object o) {
return o instanceof Named && this.value.equals(((Named) o).value());
}
public Class<? extends Annotation> annotationType() {
return Named.class;
}
}
}