From 6c748d5ef12646e5b05a6dcf2b19f5be768739d1 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 13 Jan 2021 16:32:43 +0100 Subject: [PATCH 1/6] Copy unmodified DefaultMavenPluginManager and required class from maven 3.6.3 --- .../plugin/DefaultMavenPluginManager.java | 918 ++++++++++++++++++ .../ValidatingConfigurationListener.java | 97 ++ 2 files changed, 1015 insertions(+) create mode 100644 daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java create mode 100644 daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java new file mode 100644 index 00000000..2e63aa72 --- /dev/null +++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java @@ -0,0 +1,918 @@ +package org.mvndaemon.mvnd.plugin; + +/* + * 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. + */ + +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.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 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.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +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; + +/** + * 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 + */ +@Component( role = MavenPluginManager.class ) +public class DefaultMavenPluginManager + implements MavenPluginManager +{ + + /** + *

+ * 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. + *

+ * Note: 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"; + + @Requirement + private Logger logger; + + @Requirement + private LoggerManager loggerManager; + + @Requirement + private PlexusContainer container; + + @Requirement + private ClassRealmManager classRealmManager; + + @Requirement + private PluginDescriptorCache pluginDescriptorCache; + + @Requirement + private PluginRealmCache pluginRealmCache; + + @Requirement + private PluginDependenciesResolver pluginDependenciesResolver; + + @Requirement + private RuntimeInformation runtimeInformation; + + @Requirement + private ExtensionRealmCache extensionRealmCache; + + @Requirement + private PluginVersionResolver pluginVersionResolver; + + @Requirement + private PluginArtifactsCache pluginArtifactsCache; + + private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder(); + + private PluginDescriptorBuilder builder = new PluginDescriptorBuilder(); + + public synchronized PluginDescriptor getPluginDescriptor( Plugin plugin, List repositories, + RepositorySystemSession session ) + throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException + { + PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey( plugin, repositories, session ); + + PluginDescriptor pluginDescriptor = pluginDescriptorCache.get( cacheKey ); + + if ( pluginDescriptor == null ) + { + org.eclipse.aether.artifact.Artifact artifact = + pluginDependenciesResolver.resolve( plugin, repositories, session ); + + Artifact pluginArtifact = RepositoryUtils.toArtifact( artifact ); + + pluginDescriptor = extractPluginDescriptor( pluginArtifact, plugin ); + + pluginDescriptor.setRequiredMavenVersion( artifact.getProperty( "requiredMavenVersion", null ) ); + + pluginDescriptorCache.put( cacheKey, pluginDescriptor ); + } + + 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 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 synchronized void setupPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, + ClassLoader parent, List 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 pluginArtifacts = extensionRecord.getArtifacts(); + + for ( ComponentDescriptor componentDescriptor : pluginDescriptor.getComponents() ) + { + componentDescriptor.setRealm( pluginRealm ); + } + + pluginDescriptor.setClassRealm( pluginRealm ); + pluginDescriptor.setArtifacts( pluginArtifacts ); + } + else + { + Map foreignImports = calcImports( project, parent, imports ); + + PluginRealmCache.Key cacheKey = pluginRealmCache.createKey( plugin, parent, foreignImports, filter, + project.getRemotePluginRepositories(), + session.getRepositorySession() ); + + PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get( cacheKey ); + + if ( cacheRecord != null ) + { + pluginDescriptor.setClassRealm( cacheRecord.getRealm() ); + pluginDescriptor.setArtifacts( new ArrayList<>( cacheRecord.getArtifacts() ) ); + for ( ComponentDescriptor componentDescriptor : pluginDescriptor.getComponents() ) + { + componentDescriptor.setRealm( cacheRecord.getRealm() ); + } + } + else + { + createPluginRealm( pluginDescriptor, session, parent, foreignImports, filter ); + + cacheRecord = + pluginRealmCache.put( cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts() ); + } + + pluginRealmCache.register( project, cacheKey, cacheRecord ); + } + } + + private void createPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent, + Map 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 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 ); + + 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 toAetherArtifacts( final List pluginArtifacts ) + { + return new ArrayList<>( RepositoryUtils.toArtifacts( pluginArtifacts ) ); + } + + private List toMavenArtifacts( DependencyNode root, PreorderNodeListGenerator nlg ) + { + List artifacts = new ArrayList<>( nlg.getNodes().size() ); + RepositoryUtils.toArtifacts( artifacts, Collections.singleton( root ), Collections.emptyList(), null ); + for ( Iterator it = artifacts.iterator(); it.hasNext(); ) + { + Artifact artifact = it.next(); + if ( artifact.getFile() == null ) + { + it.remove(); + } + } + return Collections.unmodifiableList( artifacts ); + } + + private Map calcImports( MavenProject project, ClassLoader parent, List imports ) + { + Map 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 getConfiguredMojo( Class 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 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 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 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 pluginRealms = + (Map) 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 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 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 resolveExtensionArtifacts( Plugin extensionPlugin, List 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 ); + } + +} diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java new file mode 100644 index 00000000..bc386da9 --- /dev/null +++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java @@ -0,0 +1,97 @@ +package org.mvndaemon.mvnd.plugin; + +/* + * 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. + */ + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.descriptor.Parameter; +import org.codehaus.plexus.component.configurator.ConfigurationListener; + +/** + * A configuration listener to help validate the plugin configuration. For instance, check for required but missing + * parameters. + * + * @author Benjamin Bentmann + */ +class ValidatingConfigurationListener + implements ConfigurationListener +{ + + private final Object mojo; + + private final ConfigurationListener delegate; + + private final Map missingParameters; + + ValidatingConfigurationListener(Object mojo, MojoDescriptor mojoDescriptor, ConfigurationListener delegate ) + { + this.mojo = mojo; + this.delegate = delegate; + this.missingParameters = new HashMap<>(); + + if ( mojoDescriptor.getParameters() != null ) + { + for ( Parameter param : mojoDescriptor.getParameters() ) + { + if ( param.isRequired() ) + { + missingParameters.put( param.getName(), param ); + } + } + } + } + + public Collection getMissingParameters() + { + return missingParameters.values(); + } + + public void notifyFieldChangeUsingSetter( String fieldName, Object value, Object target ) + { + delegate.notifyFieldChangeUsingSetter( fieldName, value, target ); + + if ( mojo == target ) + { + notify( fieldName, value ); + } + } + + public void notifyFieldChangeUsingReflection( String fieldName, Object value, Object target ) + { + delegate.notifyFieldChangeUsingReflection( fieldName, value, target ); + + if ( mojo == target ) + { + notify( fieldName, value ); + } + } + + private void notify( String fieldName, Object value ) + { + if ( value != null ) + { + missingParameters.remove( fieldName ); + } + } + +} From 58d080484c6f83f4e17a79268275a7219d3321d0 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 13 Jan 2021 23:30:21 +0100 Subject: [PATCH 2/6] Reformat --- .../plugin/DefaultMavenPluginManager.java | 831 ++++++++---------- .../ValidatingConfigurationListener.java | 99 +-- 2 files changed, 400 insertions(+), 530 deletions(-) diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java index 2e63aa72..cb248e77 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java @@ -1,24 +1,38 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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; -/* - * 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. +/* + * 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. */ - import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -37,7 +51,6 @@ import java.util.Map; import java.util.Objects; import java.util.jar.JarFile; import java.util.zip.ZipEntry; - import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.classrealm.ClassRealmManager; @@ -114,12 +127,11 @@ import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; * plugins as well as special purpose plugins like reports. * * @author Benjamin Bentmann - * @since 3.0 + * @since 3.0 */ -@Component( role = MavenPluginManager.class ) +@Component(role = MavenPluginManager.class) public class DefaultMavenPluginManager - implements MavenPluginManager -{ + implements MavenPluginManager { /** *

@@ -169,228 +181,184 @@ public class DefaultMavenPluginManager private PluginDescriptorBuilder builder = new PluginDescriptorBuilder(); - public synchronized PluginDescriptor getPluginDescriptor( Plugin plugin, List repositories, - RepositorySystemSession session ) - throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException - { - PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey( plugin, repositories, session ); + public synchronized PluginDescriptor getPluginDescriptor(Plugin plugin, List repositories, + RepositorySystemSession session) + throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException { + PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session); - PluginDescriptor pluginDescriptor = pluginDescriptorCache.get( cacheKey ); + PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey); - if ( pluginDescriptor == null ) - { - org.eclipse.aether.artifact.Artifact artifact = - pluginDependenciesResolver.resolve( plugin, repositories, session ); + if (pluginDescriptor == null) { + org.eclipse.aether.artifact.Artifact artifact = pluginDependenciesResolver.resolve(plugin, repositories, session); - Artifact pluginArtifact = RepositoryUtils.toArtifact( artifact ); + Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact); - pluginDescriptor = extractPluginDescriptor( pluginArtifact, plugin ); + pluginDescriptor = extractPluginDescriptor(pluginArtifact, plugin); - pluginDescriptor.setRequiredMavenVersion( artifact.getProperty( "requiredMavenVersion", null ) ); + pluginDescriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null)); - pluginDescriptorCache.put( cacheKey, pluginDescriptor ); + pluginDescriptorCache.put(cacheKey, pluginDescriptor); } - pluginDescriptor.setPlugin( plugin ); + pluginDescriptor.setPlugin(plugin); return pluginDescriptor; } - private PluginDescriptor extractPluginDescriptor( Artifact pluginArtifact, Plugin plugin ) - throws PluginDescriptorParsingException, InvalidPluginDescriptorException - { + 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() ); + try { + if (pluginFile.isFile()) { + try (JarFile pluginJar = new JarFile(pluginFile, false)) { + ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation()); - if ( pluginDescriptorEntry != null ) - { - InputStream is = pluginJar.getInputStream( pluginDescriptorEntry ); + if (pluginDescriptorEntry != null) { + InputStream is = pluginJar.getInputStream(pluginDescriptorEntry); - pluginDescriptor = parsePluginDescriptor( is, plugin, pluginFile.getAbsolutePath() ); + pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath()); } } - } - else - { - File pluginXml = new File( pluginFile, getPluginDescriptorLocation() ); + } 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 (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() ); + if (pluginDescriptor == null) { + throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation()); } - } - catch ( IOException e ) - { - throw new PluginDescriptorParsingException( plugin, pluginFile.getAbsolutePath(), e ); + } catch (IOException e) { + throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e); } - MavenPluginValidator validator = new MavenPluginValidator( pluginArtifact ); + MavenPluginValidator validator = new MavenPluginValidator(pluginArtifact); - validator.validate( pluginDescriptor ); + validator.validate(pluginDescriptor); - if ( validator.hasErrors() ) - { + if (validator.hasErrors()) { throw new InvalidPluginDescriptorException( - "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors() ); + "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors()); } - pluginDescriptor.setPluginArtifact( pluginArtifact ); + pluginDescriptor.setPluginArtifact(pluginArtifact); return pluginDescriptor; } - private String getPluginDescriptorLocation() - { + 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 ); + private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation) + throws PluginDescriptorParsingException { + try { + Reader reader = ReaderFactory.newXmlReader(is); - PluginDescriptor pluginDescriptor = builder.build( reader, descriptorLocation ); + PluginDescriptor pluginDescriptor = builder.build(reader, descriptorLocation); return pluginDescriptor; - } - catch ( IOException | PlexusConfigurationException e ) - { - throw new PluginDescriptorParsingException( plugin, descriptorLocation, e ); + } catch (IOException | PlexusConfigurationException e) { + throw new PluginDescriptorParsingException(plugin, descriptorLocation, e); } } - public MojoDescriptor getMojoDescriptor( Plugin plugin, String goal, List repositories, - RepositorySystemSession session ) - throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException, - InvalidPluginDescriptorException - { - PluginDescriptor pluginDescriptor = getPluginDescriptor( plugin, repositories, session ); + public MojoDescriptor getMojoDescriptor(Plugin plugin, String goal, List repositories, + RepositorySystemSession session) + throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException, + InvalidPluginDescriptorException { + PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session); - MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( goal ); + MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal); - if ( mojoDescriptor == null ) - { - throw new MojoNotFoundException( goal, pluginDescriptor ); + if (mojoDescriptor == null) { + throw new MojoNotFoundException(goal, pluginDescriptor); } return mojoDescriptor; } - public void checkRequiredMavenVersion( PluginDescriptor pluginDescriptor ) - throws PluginIncompatibleException - { + 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 ); + 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() ); + } catch (RuntimeException e) { + logger.warn("Could not verify plugin's Maven prerequisite: " + e.getMessage()); } } } - public synchronized void setupPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, - ClassLoader parent, List imports, DependencyFilter filter ) - throws PluginResolutionException, PluginContainerException - { + public synchronized void setupPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session, + ClassLoader parent, List imports, DependencyFilter filter) + throws PluginResolutionException, PluginContainerException { Plugin plugin = pluginDescriptor.getPlugin(); MavenProject project = session.getCurrentProject(); - if ( plugin.isExtensions() ) - { + if (plugin.isExtensions()) { ExtensionRealmCache.CacheRecord extensionRecord; - try - { + try { RepositorySystemSession repositorySession = session.getRepositorySession(); - extensionRecord = setupExtensionsRealm( project, plugin, repositorySession ); - } - catch ( PluginManagerException e ) - { + 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 ); + throw new IllegalStateException(e); } ClassRealm pluginRealm = extensionRecord.getRealm(); List pluginArtifacts = extensionRecord.getArtifacts(); - for ( ComponentDescriptor componentDescriptor : pluginDescriptor.getComponents() ) - { - componentDescriptor.setRealm( pluginRealm ); + for (ComponentDescriptor componentDescriptor : pluginDescriptor.getComponents()) { + componentDescriptor.setRealm(pluginRealm); } - pluginDescriptor.setClassRealm( pluginRealm ); - pluginDescriptor.setArtifacts( pluginArtifacts ); - } - else - { - Map foreignImports = calcImports( project, parent, imports ); + pluginDescriptor.setClassRealm(pluginRealm); + pluginDescriptor.setArtifacts(pluginArtifacts); + } else { + Map foreignImports = calcImports(project, parent, imports); - PluginRealmCache.Key cacheKey = pluginRealmCache.createKey( plugin, parent, foreignImports, filter, - project.getRemotePluginRepositories(), - session.getRepositorySession() ); + PluginRealmCache.Key cacheKey = pluginRealmCache.createKey(plugin, parent, foreignImports, filter, + project.getRemotePluginRepositories(), + session.getRepositorySession()); - PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get( cacheKey ); + PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey); - if ( cacheRecord != null ) - { - pluginDescriptor.setClassRealm( cacheRecord.getRealm() ); - pluginDescriptor.setArtifacts( new ArrayList<>( cacheRecord.getArtifacts() ) ); - for ( ComponentDescriptor componentDescriptor : pluginDescriptor.getComponents() ) - { - componentDescriptor.setRealm( cacheRecord.getRealm() ); + if (cacheRecord != null) { + pluginDescriptor.setClassRealm(cacheRecord.getRealm()); + pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts())); + for (ComponentDescriptor componentDescriptor : pluginDescriptor.getComponents()) { + componentDescriptor.setRealm(cacheRecord.getRealm()); } - } - else - { - createPluginRealm( pluginDescriptor, session, parent, foreignImports, filter ); + } else { + createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter); - cacheRecord = - pluginRealmCache.put( cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts() ); + cacheRecord = pluginRealmCache.put(cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts()); } - pluginRealmCache.register( project, cacheKey, cacheRecord ); + pluginRealmCache.register(project, cacheKey, cacheRecord); } } - private void createPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent, - Map foreignImports, DependencyFilter filter ) - throws PluginResolutionException, PluginContainerException - { - Plugin plugin = - Objects.requireNonNull( pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null" ); + private void createPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent, + Map 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" ); + Artifact pluginArtifact = Objects.requireNonNull(pluginDescriptor.getPluginArtifact(), + "pluginDescriptor.pluginArtifact cannot be null"); MavenProject project = session.getCurrentProject(); @@ -399,520 +367,421 @@ public class DefaultMavenPluginManager RepositorySystemSession repositorySession = session.getRepositorySession(); DependencyFilter dependencyFilter = project.getExtensionDependencyFilter(); - dependencyFilter = AndDependencyFilter.newInstance( dependencyFilter, filter ); + dependencyFilter = AndDependencyFilter.newInstance(dependencyFilter, filter); - DependencyNode root = - pluginDependenciesResolver.resolve( plugin, RepositoryUtils.toArtifact( pluginArtifact ), dependencyFilter, - project.getRemotePluginRepositories(), repositorySession ); + DependencyNode root = pluginDependenciesResolver.resolve(plugin, RepositoryUtils.toArtifact(pluginArtifact), + dependencyFilter, + project.getRemotePluginRepositories(), repositorySession); PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); - root.accept( nlg ); + root.accept(nlg); - pluginArtifacts = toMavenArtifacts( root, nlg ); + pluginArtifacts = toMavenArtifacts(root, nlg); - pluginRealm = classRealmManager.createPluginRealm( plugin, parent, null, foreignImports, - toAetherArtifacts( pluginArtifacts ) ); + pluginRealm = classRealmManager.createPluginRealm(plugin, parent, null, foreignImports, + toAetherArtifacts(pluginArtifacts)); - discoverPluginComponents( pluginRealm, plugin, pluginDescriptor ); + discoverPluginComponents(pluginRealm, plugin, pluginDescriptor); - pluginDescriptor.setClassRealm( pluginRealm ); - pluginDescriptor.setArtifacts( pluginArtifacts ); + 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 ); + 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 ); + ((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 toAetherArtifacts( final List pluginArtifacts ) - { - return new ArrayList<>( RepositoryUtils.toArtifacts( pluginArtifacts ) ); + private List toAetherArtifacts(final List pluginArtifacts) { + return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts)); } - private List toMavenArtifacts( DependencyNode root, PreorderNodeListGenerator nlg ) - { - List artifacts = new ArrayList<>( nlg.getNodes().size() ); - RepositoryUtils.toArtifacts( artifacts, Collections.singleton( root ), Collections.emptyList(), null ); - for ( Iterator it = artifacts.iterator(); it.hasNext(); ) - { + private List toMavenArtifacts(DependencyNode root, PreorderNodeListGenerator nlg) { + List artifacts = new ArrayList<>(nlg.getNodes().size()); + RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), Collections. emptyList(), null); + for (Iterator it = artifacts.iterator(); it.hasNext();) { Artifact artifact = it.next(); - if ( artifact.getFile() == null ) - { + if (artifact.getFile() == null) { it.remove(); } } - return Collections.unmodifiableList( artifacts ); + return Collections.unmodifiableList(artifacts); } - private Map calcImports( MavenProject project, ClassLoader parent, List imports ) - { + private Map calcImports(MavenProject project, ClassLoader parent, List imports) { Map foreignImports = new HashMap<>(); ClassLoader projectRealm = project.getClassRealm(); - if ( projectRealm != null ) - { - foreignImports.put( "", projectRealm ); - } - else - { - foreignImports.put( "", classRealmManager.getMavenApiRealm() ); + if (projectRealm != null) { + foreignImports.put("", projectRealm); + } else { + foreignImports.put("", classRealmManager.getMavenApiRealm()); } - if ( parent != null && imports != null ) - { - for ( String parentImport : imports ) - { - foreignImports.put( parentImport, parent ); + if (parent != null && imports != null) { + for (String parentImport : imports) { + foreignImports.put(parentImport, parent); } } return foreignImports; } - public T getConfiguredMojo( Class mojoInterface, MavenSession session, MojoExecution mojoExecution ) - throws PluginConfigurationException, PluginContainerException - { + public T getConfiguredMojo(Class 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 ); + 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 ); + ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm); ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader( pluginRealm ); + Thread.currentThread().setContextClassLoader(pluginRealm); - try - { + try { T mojo; - try - { - mojo = container.lookup( mojoInterface, mojoDescriptor.getRoleHint() ); - } - catch ( ComponentLookupException e ) - { + try { + mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint()); + } catch (ComponentLookupException e) { Throwable cause = e.getCause(); - while ( cause != null && !( cause instanceof LinkageError ) - && !( cause instanceof ClassNotFoundException ) ) - { + 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 ); + 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); + } 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, 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 ); + 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 ) - { + if (mojo instanceof ContextEnabled) { MavenProject project = session.getCurrentProject(); - Map pluginContext = session.getPluginContext( pluginDescriptor, project ); + Map pluginContext = session.getPluginContext(pluginDescriptor, project); - if ( pluginContext != null ) - { - pluginContext.put( "project", project ); + if (pluginContext != null) { + pluginContext.put("project", project); - pluginContext.put( "pluginDescriptor", pluginDescriptor ); + pluginContext.put("pluginDescriptor", pluginDescriptor); - ( (ContextEnabled) mojo ).setPluginContext( pluginContext ); + ((ContextEnabled) mojo).setPluginContext(pluginContext); } } - if ( mojo instanceof Mojo ) - { - Logger mojoLogger = loggerManager.getLoggerForComponent( mojoDescriptor.getImplementation() ); - ( (Mojo) mojo ).setLog( new DefaultLog( mojoLogger ) ); + 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 ); + if (dom == null) { + pomConfiguration = new XmlPlexusConfiguration("configuration"); + } else { + pomConfiguration = new XmlPlexusConfiguration(dom); } - ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution ); + ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); - populatePluginFields( mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator ); + populatePluginFields(mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator); return mojo; - } - finally - { - Thread.currentThread().setContextClassLoader( oldClassLoader ); - container.setLookupRealm( oldLookupRealm ); + } finally { + Thread.currentThread().setContextClassLoader(oldClassLoader); + container.setLookupRealm(oldLookupRealm); } } - private void populatePluginFields( Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm, - PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator ) - throws PluginConfigurationException - { + 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 ) ) - { + if (StringUtils.isEmpty(configuratorId)) { configuratorId = "basic"; } - try - { + 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 ); + configurator = container.lookup(ComponentConfigurator.class, configuratorId); - ConfigurationListener listener = new DebugConfigurationListener( logger ); + ConfigurationListener listener = new DebugConfigurationListener(logger); - ValidatingConfigurationListener validator = - new ValidatingConfigurationListener( mojo, mojoDescriptor, listener ); + ValidatingConfigurationListener validator = new ValidatingConfigurationListener(mojo, mojoDescriptor, listener); logger.debug( - "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->" ); + "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->"); - configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator ); + configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator); - logger.debug( "-- end configuration --" ); + logger.debug("-- end configuration --"); Collection missingParameters = validator.getMissingParameters(); - if ( !missingParameters.isEmpty() ) - { - if ( "basic".equals( configuratorId ) ) - { - throw new PluginParameterException( mojoDescriptor, new ArrayList<>( missingParameters ) ); - } - else - { + 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 ); + validateParameters(mojoDescriptor, configuration, expressionEvaluator); } } - } - catch ( ComponentConfigurationException e ) - { + } catch (ComponentConfigurationException e) { String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId(); - if ( e.getFailedConfiguration() != null ) - { + 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(), 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 ); + 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 ); + "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." ); + 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 ) - { + private void validateParameters(MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, + ExpressionEvaluator expressionEvaluator) + throws ComponentConfigurationException, PluginParameterException { + if (mojoDescriptor.getParameters() == null) { return; } List invalidParameters = new ArrayList<>(); - for ( Parameter parameter : mojoDescriptor.getParameters() ) - { - if ( !parameter.isRequired() ) - { + 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 ); + PlexusConfiguration config = configuration.getChild(parameter.getName(), false); + if (config != null) { + String expression = config.getValue(null); - try - { - value = expressionEvaluator.evaluate( expression ); + try { + value = expressionEvaluator.evaluate(expression); - if ( value == null ) - { - value = config.getAttribute( "default-value", null ); + if (value == null) { + value = config.getAttribute("default-value", null); } - } - catch ( ExpressionEvaluationException e ) - { + } catch (ExpressionEvaluationException e) { String msg = "Error evaluating the expression '" + expression + "' for configuration value '" - + configuration.getName() + "'"; - throw new ComponentConfigurationException( configuration, msg, e ); + + configuration.getName() + "'"; + throw new ComponentConfigurationException(configuration, msg, e); } } - if ( value == null && ( config == null || config.getChildCount() <= 0 ) ) - { - invalidParameters.add( parameter ); + if (value == null && (config == null || config.getChildCount() <= 0)) { + invalidParameters.add(parameter); } } - if ( !invalidParameters.isEmpty() ) - { - throw new PluginParameterException( mojoDescriptor, invalidParameters ); + 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 ) - { + 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 ) - { + if (mojoExecution.getExecutionId() != null) { goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}"; } - logger.debug( "Error releasing mojo for " + goalExecId, e ); + logger.debug("Error releasing mojo for " + goalExecId, e); } } } - public ExtensionRealmCache.CacheRecord setupExtensionsRealm( MavenProject project, Plugin plugin, - RepositorySystemSession session ) - throws PluginManagerException - { - @SuppressWarnings( "unchecked" ) Map pluginRealms = - (Map) project.getContextValue( KEY_EXTENSIONS_REALMS ); - if ( pluginRealms == null ) - { + public ExtensionRealmCache.CacheRecord setupExtensionsRealm(MavenProject project, Plugin plugin, + RepositorySystemSession session) + throws PluginManagerException { + @SuppressWarnings("unchecked") + Map pluginRealms = (Map) project + .getContextValue(KEY_EXTENSIONS_REALMS); + if (pluginRealms == null) { pluginRealms = new HashMap<>(); - project.setContextValue( KEY_EXTENSIONS_REALMS, pluginRealms ); + project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms); } final String pluginKey = plugin.getId(); - ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get( pluginKey ); - if ( extensionRecord != null ) - { + ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey); + if (extensionRecord != null) { return extensionRecord; } final List 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 ); + 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 artifacts; - PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey( plugin, null, repositories, session ); + PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session); PluginArtifactsCache.CacheRecord recordArtifacts; - try - { - recordArtifacts = pluginArtifactsCache.get( cacheKey ); + try { + recordArtifacts = pluginArtifactsCache.get(cacheKey); + } catch (PluginResolutionException e) { + throw new PluginManagerException(plugin, e.getMessage(), e); } - catch ( PluginResolutionException e ) - { - throw new PluginManagerException( plugin, e.getMessage(), e ); - } - if ( recordArtifacts != null ) - { + 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 ); + } 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 ); + 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 ) ); + 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() ) - { + 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 ) - { + try { + pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin); + } catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) { // ignore, see above } } - discoverPluginComponents( extensionRealm, plugin, pluginDescriptor ); + discoverPluginComponents(extensionRealm, plugin, pluginDescriptor); ExtensionDescriptor extensionDescriptor = null; - Artifact extensionArtifact = artifacts.get( 0 ); - try - { - extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() ); - } - catch ( IOException e ) - { + 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 ); + if (logger.isDebugEnabled()) { + logger.error(message, e); + } else { + logger.error(message); } } - extensionRecord = extensionRealmCache.put( extensionKey, extensionRealm, extensionDescriptor, artifacts ); + extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts); } - extensionRealmCache.register( project, extensionKey, extensionRecord ); - pluginRealms.put( pluginKey, extensionRecord ); + extensionRealmCache.register(project, extensionKey, extensionRecord); + pluginRealms.put(pluginKey, extensionRecord); return extensionRecord; } - private List resolveExtensionArtifacts( Plugin extensionPlugin, List repositories, - RepositorySystemSession session ) - throws PluginResolutionException - { - DependencyNode root = pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories, session ); + private List resolveExtensionArtifacts(Plugin extensionPlugin, List 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 ); + root.accept(nlg); + return toMavenArtifacts(root, nlg); } } diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java index bc386da9..4aa7d5b0 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java @@ -1,28 +1,41 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed 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; -/* - * 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. +/* + * 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. */ - import java.util.Collection; import java.util.HashMap; import java.util.Map; - import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.Parameter; import org.codehaus.plexus.component.configurator.ConfigurationListener; @@ -34,8 +47,7 @@ import org.codehaus.plexus.component.configurator.ConfigurationListener; * @author Benjamin Bentmann */ class ValidatingConfigurationListener - implements ConfigurationListener -{ + implements ConfigurationListener { private final Object mojo; @@ -43,54 +55,43 @@ class ValidatingConfigurationListener private final Map missingParameters; - ValidatingConfigurationListener(Object mojo, MojoDescriptor mojoDescriptor, ConfigurationListener delegate ) - { + ValidatingConfigurationListener(Object mojo, MojoDescriptor mojoDescriptor, ConfigurationListener delegate) { this.mojo = mojo; this.delegate = delegate; this.missingParameters = new HashMap<>(); - if ( mojoDescriptor.getParameters() != null ) - { - for ( Parameter param : mojoDescriptor.getParameters() ) - { - if ( param.isRequired() ) - { - missingParameters.put( param.getName(), param ); + if (mojoDescriptor.getParameters() != null) { + for (Parameter param : mojoDescriptor.getParameters()) { + if (param.isRequired()) { + missingParameters.put(param.getName(), param); } } } } - public Collection getMissingParameters() - { + public Collection getMissingParameters() { return missingParameters.values(); } - public void notifyFieldChangeUsingSetter( String fieldName, Object value, Object target ) - { - delegate.notifyFieldChangeUsingSetter( fieldName, value, target ); + public void notifyFieldChangeUsingSetter(String fieldName, Object value, Object target) { + delegate.notifyFieldChangeUsingSetter(fieldName, value, target); - if ( mojo == target ) - { - notify( fieldName, value ); + if (mojo == target) { + notify(fieldName, value); } } - public void notifyFieldChangeUsingReflection( String fieldName, Object value, Object target ) - { - delegate.notifyFieldChangeUsingReflection( fieldName, value, target ); + public void notifyFieldChangeUsingReflection(String fieldName, Object value, Object target) { + delegate.notifyFieldChangeUsingReflection(fieldName, value, target); - if ( mojo == target ) - { - notify( fieldName, value ); + if (mojo == target) { + notify(fieldName, value); } } - private void notify( String fieldName, Object value ) - { - if ( value != null ) - { - missingParameters.remove( fieldName ); + private void notify(String fieldName, Object value) { + if (value != null) { + missingParameters.remove(fieldName); } } From a9b69c6dee507af72d4790a14aac5726227a202b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 13 Jan 2021 23:32:04 +0100 Subject: [PATCH 3/6] Add a computeIfAbsent method to the Cache --- .../java/org/mvndaemon/mvnd/cache/factory/Cache.java | 6 ++++++ .../mvnd/cache/factory/TimestampCacheFactory.java | 8 ++++++++ .../mvnd/cache/factory/WatchServiceCacheFactory.java | 10 ++++++++++ 3 files changed, 24 insertions(+) diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/Cache.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/Cache.java index f09fbc14..3f77ccec 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/Cache.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/Cache.java @@ -16,6 +16,7 @@ package org.mvndaemon.mvnd.cache.factory; import java.util.function.BiPredicate; +import java.util.function.Function; /** * Cache containing records that can be invalidated. @@ -53,4 +54,9 @@ public interface Cache { */ void removeIf(BiPredicate predicate); + /** + * Get or compute the cached value if absent and return it. + */ + V computeIfAbsent(K key, Function mappingFunction); + } diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java index 413e38c5..ba39d9a8 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java @@ -26,6 +26,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiPredicate; +import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Named; import javax.inject.Singleton; @@ -145,5 +146,12 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa } } + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return map.computeIfAbsent(key, k -> { + V v = mappingFunction.apply(k); + return new Record<>(v); + }).record; + } } } diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/WatchServiceCacheFactory.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/WatchServiceCacheFactory.java index 343d50f2..4d0c5376 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/WatchServiceCacheFactory.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/WatchServiceCacheFactory.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiPredicate; +import java.util.function.Function; import javax.inject.Named; import javax.inject.Singleton; import org.codehaus.plexus.logging.AbstractLogEnabled; @@ -241,5 +242,14 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach } } + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + validateRecords(); + return map.computeIfAbsent(key, k -> { + V v = mappingFunction.apply(k); + add(v); + return v; + }); + } } } From 66471b49062514344fc4ee1c472726055fb2d204 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 14 Jan 2021 13:30:39 +0100 Subject: [PATCH 4/6] Remove synchronization on the MavenPluginManager implementation --- .../cache/factory/TimestampCacheFactory.java | 16 +++- .../cache/impl/CliPluginDescriptorCache.java | 34 +++++++++ .../mvnd/cache/impl/CliPluginRealmCache.java | 29 +++++++ ...anager.java => CliMavenPluginManager.java} | 75 +++++++++++-------- 4 files changed, 118 insertions(+), 36 deletions(-) rename daemon/src/main/java/org/mvndaemon/mvnd/plugin/{DefaultMavenPluginManager.java => CliMavenPluginManager.java} (93%) diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java index ba39d9a8..53826709 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java @@ -148,9 +148,19 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa @Override public V computeIfAbsent(K key, Function mappingFunction) { - return map.computeIfAbsent(key, k -> { - V v = mappingFunction.apply(k); - return new Record<>(v); + return map.compute(key, (k, v) -> { + if (v != null) { + try { + if (Objects.equals(v.timestamp, v.current())) { + return v; + } + } catch (RuntimeException e) { + // ignore and invalidate the record + } + v.record.invalidate(); + v = null; + } + return new Record<>(mappingFunction.apply(k)); }).record; } } diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginDescriptorCache.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginDescriptorCache.java index c6b6df20..2d90f060 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginDescriptorCache.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginDescriptorCache.java @@ -25,6 +25,9 @@ import javax.inject.Named; import javax.inject.Singleton; import org.apache.maven.model.Plugin; import org.apache.maven.plugin.DefaultPluginDescriptorCache; +import org.apache.maven.plugin.InvalidPluginDescriptorException; +import org.apache.maven.plugin.PluginDescriptorParsingException; +import org.apache.maven.plugin.PluginResolutionException; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; @@ -38,6 +41,12 @@ import org.mvndaemon.mvnd.cache.factory.CacheRecord; @Named public class CliPluginDescriptorCache extends DefaultPluginDescriptorCache { + @FunctionalInterface + public interface PluginDescriptorSupplier { + PluginDescriptor load() + throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException; + } + protected static class Record implements CacheRecord { private final PluginDescriptor descriptor; @@ -81,6 +90,31 @@ public class CliPluginDescriptorCache extends DefaultPluginDescriptorCache { return r != null ? clone(r.descriptor) : null; } + public PluginDescriptor get(Key key, PluginDescriptorSupplier supplier) + throws PluginDescriptorParsingException, PluginResolutionException, InvalidPluginDescriptorException { + try { + Record r = cache.computeIfAbsent(key, k -> { + try { + return new Record(clone(supplier.load())); + } catch (PluginDescriptorParsingException | PluginResolutionException | InvalidPluginDescriptorException e) { + throw new RuntimeException(e); + } + }); + return clone(r.descriptor); + } catch (RuntimeException e) { + if (e.getCause() instanceof PluginDescriptorParsingException) { + throw (PluginDescriptorParsingException) e.getCause(); + } + if (e.getCause() instanceof PluginResolutionException) { + throw (PluginResolutionException) e.getCause(); + } + if (e.getCause() instanceof InvalidPluginDescriptorException) { + throw (InvalidPluginDescriptorException) e.getCause(); + } + throw e; + } + } + @Override public void put(Key key, PluginDescriptor descriptor) { cache.put(key, new Record(clone(descriptor))); diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginRealmCache.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginRealmCache.java index 2fbcf4a1..4cc7ead0 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginRealmCache.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginRealmCache.java @@ -23,7 +23,9 @@ import javax.inject.Named; import javax.inject.Singleton; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.DefaultPluginRealmCache; +import org.apache.maven.plugin.PluginContainerException; import org.apache.maven.plugin.PluginRealmCache; +import org.apache.maven.plugin.PluginResolutionException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; @@ -38,6 +40,11 @@ import org.mvndaemon.mvnd.cache.factory.CacheFactory; @Typed(PluginRealmCache.class) public class CliPluginRealmCache extends DefaultPluginRealmCache { + @FunctionalInterface + public interface PluginRealmSupplier { + CacheRecord load() throws PluginResolutionException, PluginContainerException; + } + protected static class Record implements org.mvndaemon.mvnd.cache.factory.CacheRecord { final CacheRecord record; @@ -75,6 +82,28 @@ public class CliPluginRealmCache extends DefaultPluginRealmCache { return r != null ? r.record : null; } + public CacheRecord get(Key key, PluginRealmSupplier supplier) + throws PluginResolutionException, PluginContainerException { + try { + Record r = cache.computeIfAbsent(key, k -> { + try { + return new Record(supplier.load()); + } catch (PluginResolutionException | PluginContainerException e) { + throw new RuntimeException(e); + } + }); + return r.record; + } catch (RuntimeException e) { + if (e.getCause() instanceof PluginResolutionException) { + throw (PluginResolutionException) e.getCause(); + } + if (e.getCause() instanceof PluginContainerException) { + throw (PluginContainerException) e.getCause(); + } + throw e; + } + } + @Override public CacheRecord put(Key key, ClassRealm pluginRealm, List pluginArtifacts) { CacheRecord record = super.put(key, pluginRealm, pluginArtifacts); diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java similarity index 93% rename from daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java rename to daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java index cb248e77..ac3f40f5 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/DefaultMavenPluginManager.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,9 @@ 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; @@ -95,8 +98,6 @@ 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.annotations.Component; -import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException; import org.codehaus.plexus.component.configurator.ComponentConfigurationException; import org.codehaus.plexus.component.configurator.ComponentConfigurator; @@ -120,7 +121,15 @@ 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.impl.CliPluginDescriptorCache; +import org.mvndaemon.mvnd.cache.impl.CliPluginRealmCache; +/* + * gnodet: This file is based on maven DefaultMavenPluginManager and changed in order + * to better support parallel builds. See https://github.com/mvndaemon/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 @@ -129,8 +138,11 @@ import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; * @author Benjamin Bentmann * @since 3.0 */ -@Component(role = MavenPluginManager.class) -public class DefaultMavenPluginManager +@Singleton +@Named +@Priority(10) +@Typed(MavenPluginManager.class) +public class CliMavenPluginManager implements MavenPluginManager { /** @@ -142,63 +154,61 @@ public class DefaultMavenPluginManager * * @since 3.3.0 */ - public static final String KEY_EXTENSIONS_REALMS = DefaultMavenPluginManager.class.getName() + "/extensionsRealms"; + public static final String KEY_EXTENSIONS_REALMS = CliMavenPluginManager.class.getName() + "/extensionsRealms"; - @Requirement + @Inject private Logger logger; - @Requirement + @Inject private LoggerManager loggerManager; - @Requirement + @Inject private PlexusContainer container; - @Requirement + @Inject private ClassRealmManager classRealmManager; - @Requirement - private PluginDescriptorCache pluginDescriptorCache; + @Inject + private CliPluginDescriptorCache pluginDescriptorCache; - @Requirement - private PluginRealmCache pluginRealmCache; + @Inject + private CliPluginRealmCache pluginRealmCache; - @Requirement + @Inject private PluginDependenciesResolver pluginDependenciesResolver; - @Requirement + @Inject private RuntimeInformation runtimeInformation; - @Requirement + @Inject private ExtensionRealmCache extensionRealmCache; - @Requirement + @Inject private PluginVersionResolver pluginVersionResolver; - @Requirement + @Inject private PluginArtifactsCache pluginArtifactsCache; private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder(); private PluginDescriptorBuilder builder = new PluginDescriptorBuilder(); - public synchronized PluginDescriptor getPluginDescriptor(Plugin plugin, List repositories, + public PluginDescriptor getPluginDescriptor(Plugin plugin, List repositories, RepositorySystemSession session) throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException { PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session); - PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey); - - if (pluginDescriptor == null) { + PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> { org.eclipse.aether.artifact.Artifact artifact = pluginDependenciesResolver.resolve(plugin, repositories, session); Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact); - pluginDescriptor = extractPluginDescriptor(pluginArtifact, plugin); + PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin); - pluginDescriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null)); + descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null)); - pluginDescriptorCache.put(cacheKey, pluginDescriptor); - } + return descriptor; + }); pluginDescriptor.setPlugin(plugin); @@ -301,7 +311,7 @@ public class DefaultMavenPluginManager } } - public synchronized void setupPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session, + public void setupPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent, List imports, DependencyFilter filter) throws PluginResolutionException, PluginContainerException { Plugin plugin = pluginDescriptor.getPlugin(); @@ -334,7 +344,10 @@ public class DefaultMavenPluginManager project.getRemotePluginRepositories(), session.getRepositorySession()); - PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey); + 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()); @@ -342,10 +355,6 @@ public class DefaultMavenPluginManager for (ComponentDescriptor componentDescriptor : pluginDescriptor.getComponents()) { componentDescriptor.setRealm(cacheRecord.getRealm()); } - } else { - createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter); - - cacheRecord = pluginRealmCache.put(cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts()); } pluginRealmCache.register(project, cacheKey, cacheRecord); From 3b2efbfead22237c52121184a372097d2eb4f0b4 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 14 Jan 2021 13:31:57 +0100 Subject: [PATCH 5/6] Add an integration test --- .../mvnd/it/ConcurrentDownloadsTest.java | 65 ++++++++++++++++ .../mvnd/junit/MvndTestExtension.java | 4 +- .../concurrent-downloads/.mvn/maven.config | 3 + .../concurrent-downloads/mod1/pom.xml | 53 +++++++++++++ .../concurrent/downloads/mod1/Greeting.java | 22 ++++++ .../concurrent-downloads/mod2/pom.xml | 53 +++++++++++++ .../test/concurrent/downloads/mod2/Hi.java | 24 ++++++ .../projects/concurrent-downloads/pom.xml | 76 +++++++++++++++++++ 8 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java create mode 100644 integration-tests/src/test/projects/concurrent-downloads/.mvn/maven.config create mode 100644 integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml create mode 100644 integration-tests/src/test/projects/concurrent-downloads/mod1/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod1/Greeting.java create mode 100644 integration-tests/src/test/projects/concurrent-downloads/mod2/pom.xml create mode 100644 integration-tests/src/test/projects/concurrent-downloads/mod2/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod2/Hi.java create mode 100644 integration-tests/src/test/projects/concurrent-downloads/pom.xml diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java new file mode 100644 index 00000000..f96cc1ee --- /dev/null +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.it; + +import java.io.IOException; +import java.nio.file.Path; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.mvndaemon.mvnd.assertj.TestClientOutput; +import org.mvndaemon.mvnd.client.Client; +import org.mvndaemon.mvnd.client.DaemonParameters; +import org.mvndaemon.mvnd.common.Message; +import org.mvndaemon.mvnd.junit.MvndTest; +import org.mvndaemon.mvnd.junit.TestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@MvndTest(projectDir = "src/test/projects/concurrent-downloads") +public class ConcurrentDownloadsTest { + + @Inject + Client client; + + @Inject + DaemonParameters parameters; + + @Test + void build() throws IOException, InterruptedException { + final Path localMavenRepo = parameters.mavenRepoLocal(); + TestUtils.deleteDir(localMavenRepo); + + final TestClientOutput o = new TestClientOutput(); + client.execute(o, "clean", "install", "-e", "-B").assertSuccess(); + + int maxCur = 0; + int cur = 0; + for (Message m : o.getMessages()) { + if (m.getType() == Message.TRANSFER_STARTED) { + if (((Message.TransferEvent) m).getResourceName().contains("apache-camel")) { + cur++; + maxCur = Math.max(cur, maxCur); + } + } else if (m.getType() == Message.TRANSFER_SUCCEEDED) { + if (((Message.TransferEvent) m).getResourceName().contains("apache-camel")) { + cur--; + } + } + } + assertEquals(2, maxCur); + } + +} diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java index c0718689..543e8b6a 100644 --- a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java @@ -220,8 +220,8 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback, localMavenRepository, settingsPath, logback, TimeUtils.toDuration(Environment.MVND_IDLE_TIMEOUT.getDefault()), - TimeUtils.toDuration(Environment.MVND_KEEP_ALIVE.getDefault()), - Integer.parseInt(Environment.MVND_MAX_LOST_KEEP_ALIVE.getDefault())); + TimeUtils.toDuration(Environment.MVND_KEEP_ALIVE.getDefault()).multipliedBy(10), + Integer.parseInt(Environment.MVND_MAX_LOST_KEEP_ALIVE.getDefault()) * 10); final TestRegistry registry = new TestRegistry(parameters.registry()); return new MvndResource(parameters, registry, isNative, timeoutMs); diff --git a/integration-tests/src/test/projects/concurrent-downloads/.mvn/maven.config b/integration-tests/src/test/projects/concurrent-downloads/.mvn/maven.config new file mode 100644 index 00000000..4230c241 --- /dev/null +++ b/integration-tests/src/test/projects/concurrent-downloads/.mvn/maven.config @@ -0,0 +1,3 @@ +-Dmaven.wagon.httpconnectionManager.ttlSeconds=120 +-Dmaven.wagon.http.retryHandler.requestSentEnabled=true +-Dmaven.wagon.http.retryHandler.count=10 diff --git a/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml b/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml new file mode 100644 index 00000000..b741e7c6 --- /dev/null +++ b/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + org.mvndaemon.mvnd.test.concurrent-downloads + concurrent-downloads + 0.0.1-SNAPSHOT + ../pom.xml + + + concurrent-downloads-mod1 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.camel + apache-camel + 3.7.0 + tar.gz + + + * + * + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/src/test/projects/concurrent-downloads/mod1/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod1/Greeting.java b/integration-tests/src/test/projects/concurrent-downloads/mod1/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod1/Greeting.java new file mode 100644 index 00000000..5c5863cd --- /dev/null +++ b/integration-tests/src/test/projects/concurrent-downloads/mod1/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod1/Greeting.java @@ -0,0 +1,22 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.test.concurrent.downloads.mod1; + +public interface Greeting { + + public String greet(); + +} diff --git a/integration-tests/src/test/projects/concurrent-downloads/mod2/pom.xml b/integration-tests/src/test/projects/concurrent-downloads/mod2/pom.xml new file mode 100644 index 00000000..f628d667 --- /dev/null +++ b/integration-tests/src/test/projects/concurrent-downloads/mod2/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + org.mvndaemon.mvnd.test.concurrent-downloads + concurrent-downloads + 0.0.1-SNAPSHOT + ../pom.xml + + + concurrent-downloads-mod2 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.camel + apache-camel + 3.7.0 + zip + + + * + * + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/src/test/projects/concurrent-downloads/mod2/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod2/Hi.java b/integration-tests/src/test/projects/concurrent-downloads/mod2/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod2/Hi.java new file mode 100644 index 00000000..dbf90e32 --- /dev/null +++ b/integration-tests/src/test/projects/concurrent-downloads/mod2/src/main/java/org/mvndaemon/mvnd/test/concurrent/downloads/mod2/Hi.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.test.concurrent.downloads.mod2; + +public class Hi { + + public String greet() { + return "Hi"; + } + +} diff --git a/integration-tests/src/test/projects/concurrent-downloads/pom.xml b/integration-tests/src/test/projects/concurrent-downloads/pom.xml new file mode 100644 index 00000000..d2cdfbbb --- /dev/null +++ b/integration-tests/src/test/projects/concurrent-downloads/pom.xml @@ -0,0 +1,76 @@ + + + + 4.0.0 + org.mvndaemon.mvnd.test.concurrent-downloads + concurrent-downloads + 0.0.1-SNAPSHOT + pom + + + UTF-8 + 1.8 + 1.8 + + 2.5 + 3.8.0 + 2.4 + 2.6 + 2.22.2 + + + + mod1 + mod2 + + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + + + \ No newline at end of file From a846ccb96dc36a0399c9b437dc0dfe7e4349cc8e Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 14 Jan 2021 14:08:16 +0100 Subject: [PATCH 6/6] Fix the integration test --- .../org/apache/maven/cli/DaemonMavenCli.java | 3 +++ .../mvnd/it/ConcurrentDownloadsTest.java | 18 ++++++++++-------- .../projects/concurrent-downloads/mod1/pom.xml | 7 ++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java index 6a52ddca..e8606040 100644 --- a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java +++ b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java @@ -71,6 +71,7 @@ import org.apache.maven.extension.internal.CoreExtensionEntry; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.plugin.ExtensionRealmCache; +import org.apache.maven.plugin.MavenPluginManager; import org.apache.maven.plugin.PluginArtifactsCache; import org.apache.maven.plugin.PluginRealmCache; import org.apache.maven.project.MavenProject; @@ -102,6 +103,7 @@ import org.mvndaemon.mvnd.logging.internal.Slf4jLoggerManager; import org.mvndaemon.mvnd.logging.smart.BuildEventListener; import org.mvndaemon.mvnd.logging.smart.LoggingExecutionListener; import org.mvndaemon.mvnd.logging.smart.LoggingOutputStream; +import org.mvndaemon.mvnd.plugin.CliMavenPluginManager; import org.mvndaemon.mvnd.transfer.DaemonMavenTransferListener; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; @@ -505,6 +507,7 @@ public class DaemonMavenCli { bind(PluginArtifactsCache.class).to(CliPluginArtifactsCache.class); bind(PluginRealmCache.class).to(CliPluginRealmCache.class); bind(ProjectArtifactsCache.class).to(CliProjectArtifactsCache.class); + bind(MavenPluginManager.class).to(CliMavenPluginManager.class); } }); diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java index f96cc1ee..268caeab 100644 --- a/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java @@ -48,14 +48,16 @@ public class ConcurrentDownloadsTest { int maxCur = 0; int cur = 0; for (Message m : o.getMessages()) { - if (m.getType() == Message.TRANSFER_STARTED) { - if (((Message.TransferEvent) m).getResourceName().contains("apache-camel")) { - cur++; - maxCur = Math.max(cur, maxCur); - } - } else if (m.getType() == Message.TRANSFER_SUCCEEDED) { - if (((Message.TransferEvent) m).getResourceName().contains("apache-camel")) { - cur--; + if (m instanceof Message.TransferEvent) { + Message.TransferEvent event = (Message.TransferEvent) m; + String resource = event.getResourceName(); + if (resource.contains("apache-camel") || resource.contains("apache-activemq")) { + if (m.getType() == Message.TRANSFER_STARTED) { + cur++; + maxCur = Math.max(cur, maxCur); + } else if (m.getType() == Message.TRANSFER_SUCCEEDED) { + cur--; + } } } } diff --git a/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml b/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml index b741e7c6..0d102a9e 100644 --- a/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml +++ b/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml @@ -34,9 +34,10 @@ maven-compiler-plugin - org.apache.camel - apache-camel - 3.7.0 + org.apache.activemq + apache-activemq + 5.16.0 + bin tar.gz