| /* |
| * 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.mvndaemon.mvnd.plugin; |
| |
| import java.io.BufferedInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintStream; |
| import java.io.Reader; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.jar.JarFile; |
| import java.util.zip.ZipEntry; |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| import org.apache.maven.RepositoryUtils; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.classrealm.ClassRealmManager; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule; |
| import org.apache.maven.model.Plugin; |
| import org.apache.maven.monitor.logging.DefaultLog; |
| 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.MavenPluginValidator; |
| 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.PluginRealmCache; |
| import org.apache.maven.plugin.PluginResolutionException; |
| 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.internal.PluginDependenciesResolver; |
| 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.rtinfo.RuntimeInformation; |
| 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.PlexusConfiguration; |
| import org.codehaus.plexus.configuration.PlexusConfigurationException; |
| import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; |
| import org.codehaus.plexus.logging.Logger; |
| import org.codehaus.plexus.logging.LoggerManager; |
| import org.codehaus.plexus.util.ReaderFactory; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.codehaus.plexus.util.xml.Xpp3Dom; |
| import org.eclipse.aether.RepositorySystemSession; |
| import org.eclipse.aether.graph.DependencyFilter; |
| import org.eclipse.aether.graph.DependencyNode; |
| import org.eclipse.aether.repository.RemoteRepository; |
| import org.eclipse.aether.util.filter.AndDependencyFilter; |
| import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; |
| import org.eclipse.sisu.Priority; |
| import org.eclipse.sisu.Typed; |
| import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginDescriptorCache; |
| import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginRealmCache; |
| |
| /* |
| * gnodet: This file is based on maven DefaultMavenPluginManager and changed in order |
| * to better support parallel builds. See https://github.com/apache/maven-mvnd/issues/310 |
| */ |
| /** |
| * 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. |
| * |
| * @author Benjamin Bentmann |
| * @since 3.0 |
| */ |
| @Singleton |
| @Named |
| @Priority(10) |
| @Typed(MavenPluginManager.class) |
| public class CliMavenPluginManager |
| 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 = CliMavenPluginManager.class.getName() + "/extensionsRealms"; |
| |
| @Inject |
| private Logger logger; |
| |
| @Inject |
| private LoggerManager loggerManager; |
| |
| @Inject |
| private PlexusContainer container; |
| |
| @Inject |
| private ClassRealmManager classRealmManager; |
| |
| @Inject |
| private InvalidatingPluginDescriptorCache pluginDescriptorCache; |
| |
| @Inject |
| private InvalidatingPluginRealmCache pluginRealmCache; |
| |
| @Inject |
| private PluginDependenciesResolver pluginDependenciesResolver; |
| |
| @Inject |
| private RuntimeInformation runtimeInformation; |
| |
| @Inject |
| private ExtensionRealmCache extensionRealmCache; |
| |
| @Inject |
| private PluginVersionResolver pluginVersionResolver; |
| |
| @Inject |
| private PluginArtifactsCache pluginArtifactsCache; |
| |
| private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder(); |
| |
| private PluginDescriptorBuilder builder = new PluginDescriptorBuilder(); |
| |
| 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); |
| |
| 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) { |
| InputStream is = pluginJar.getInputStream(pluginDescriptorEntry); |
| |
| pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath()); |
| } |
| } |
| } else { |
| File pluginXml = new File(pluginFile, getPluginDescriptorLocation()); |
| |
| if (pluginXml.isFile()) { |
| try (InputStream is = new BufferedInputStream(new FileInputStream(pluginXml))) { |
| pluginDescriptor = parsePluginDescriptor(is, 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); |
| } |
| |
| MavenPluginValidator validator = new MavenPluginValidator(pluginArtifact); |
| |
| validator.validate(pluginDescriptor); |
| |
| if (validator.hasErrors()) { |
| throw new InvalidPluginDescriptorException( |
| "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors()); |
| } |
| |
| pluginDescriptor.setPluginArtifact(pluginArtifact); |
| |
| return pluginDescriptor; |
| } |
| |
| private String getPluginDescriptorLocation() { |
| return "META-INF/maven/plugin.xml"; |
| } |
| |
| private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation) |
| throws PluginDescriptorParsingException { |
| try { |
| Reader reader = ReaderFactory.newXmlReader(is); |
| |
| PluginDescriptor pluginDescriptor = builder.build(reader, descriptorLocation); |
| |
| return pluginDescriptor; |
| } catch (IOException | 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; |
| } |
| |
| public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor) |
| throws PluginIncompatibleException { |
| String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion(); |
| if (StringUtils.isNotBlank(requiredMavenVersion)) { |
| try { |
| if (!runtimeInformation.isMavenVersion(requiredMavenVersion)) { |
| throw new PluginIncompatibleException(pluginDescriptor.getPlugin(), |
| "The plugin " + pluginDescriptor.getId() |
| + " requires Maven version " + requiredMavenVersion); |
| } |
| } catch (RuntimeException e) { |
| logger.warn("Could not verify plugin's Maven prerequisite: " + e.getMessage()); |
| } |
| } |
| } |
| |
| 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()); |
| }); |
| |
| if (cacheRecord != null) { |
| 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); |
| |
| DependencyNode root = pluginDependenciesResolver.resolve(plugin, RepositoryUtils.toArtifact(pluginArtifact), |
| dependencyFilter, |
| project.getRemotePluginRepositories(), repositorySession); |
| |
| PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); |
| root.accept(nlg); |
| |
| pluginArtifacts = toMavenArtifacts(root, nlg); |
| |
| if (parent == null) { |
| parent = new URLClassLoader(new URL[0]); |
| } |
| pluginRealm = classRealmManager.createPluginRealm(plugin, parent, null, foreignImports, |
| toAetherArtifacts(pluginArtifacts)); |
| |
| discoverPluginComponents(pluginRealm, plugin, pluginDescriptor); |
| |
| pluginDescriptor.setClassRealm(pluginRealm); |
| pluginDescriptor.setArtifacts(pluginArtifacts); |
| } |
| |
| private void discoverPluginComponents(final ClassRealm pluginRealm, Plugin plugin, |
| PluginDescriptor pluginDescriptor) |
| throws PluginContainerException { |
| try { |
| if (pluginDescriptor != null) { |
| for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) { |
| componentDescriptor.setRealm(pluginRealm); |
| container.addComponentDescriptor(componentDescriptor); |
| } |
| } |
| |
| ((DefaultPlexusContainer) container).discoverComponents(pluginRealm, new SessionScopeModule(container), |
| new MojoExecutionScopeModule(container)); |
| } 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(DependencyNode root, PreorderNodeListGenerator nlg) { |
| List<Artifact> artifacts = new ArrayList<>(nlg.getNodes().size()); |
| RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), Collections.<String> emptyList(), null); |
| for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext();) { |
| Artifact artifact = it.next(); |
| if (artifact.getFile() == null) { |
| it.remove(); |
| } |
| } |
| 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 (logger.isDebugEnabled()) { |
| logger.debug("Configuring 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 { |
| 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 = loggerManager.getLoggerForComponent(mojoDescriptor.getImplementation()); |
| ((Mojo) mojo).setLog(new DefaultLog(mojoLogger)); |
| } |
| |
| Xpp3Dom dom = mojoExecution.getConfiguration(); |
| |
| PlexusConfiguration pomConfiguration; |
| |
| if (dom == null) { |
| pomConfiguration = new XmlPlexusConfiguration("configuration"); |
| } else { |
| pomConfiguration = new XmlPlexusConfiguration(dom); |
| } |
| |
| ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); |
| |
| populatePluginFields(mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator); |
| |
| return mojo; |
| } finally { |
| Thread.currentThread().setContextClassLoader(oldClassLoader); |
| container.setLookupRealm(oldLookupRealm); |
| } |
| } |
| |
| private void populatePluginFields(Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm, |
| PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator) |
| throws PluginConfigurationException { |
| ComponentConfigurator configurator = null; |
| |
| String configuratorId = mojoDescriptor.getComponentConfigurator(); |
| |
| if (StringUtils.isEmpty(configuratorId)) { |
| configuratorId = "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); |
| |
| logger.debug( |
| "Configuring mojo '" + mojoDescriptor.getId() + "' 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) { |
| goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}"; |
| } |
| |
| 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); |
| } |
| } |
| |
| // 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 { |
| DependencyNode root = pluginDependenciesResolver.resolve(extensionPlugin, null, null, repositories, session); |
| PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); |
| root.accept(nlg); |
| return toMavenArtifacts(root, nlg); |
| } |
| |
| } |