| /* |
| * 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 => 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; |
| } |
| } |
| } |