diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java
new file mode 100644
index 000000000..e6253806e
--- /dev/null
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java
@@ -0,0 +1,779 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.mvndaemon.mvnd.plugin;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.classrealm.ClassRealmManager;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.monitor.logging.DefaultLog;
+import org.apache.maven.plugin.ContextEnabled;
+import org.apache.maven.plugin.DebugConfigurationListener;
+import org.apache.maven.plugin.ExtensionRealmCache;
+import org.apache.maven.plugin.InvalidPluginDescriptorException;
+import org.apache.maven.plugin.MavenPluginManager;
+import org.apache.maven.plugin.MavenPluginValidator;
+import org.apache.maven.plugin.Mojo;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojoNotFoundException;
+import org.apache.maven.plugin.PluginArtifactsCache;
+import org.apache.maven.plugin.PluginConfigurationException;
+import org.apache.maven.plugin.PluginContainerException;
+import org.apache.maven.plugin.PluginDescriptorCache;
+import org.apache.maven.plugin.PluginDescriptorParsingException;
+import org.apache.maven.plugin.PluginIncompatibleException;
+import org.apache.maven.plugin.PluginManagerException;
+import org.apache.maven.plugin.PluginParameterException;
+import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
+import org.apache.maven.plugin.PluginRealmCache;
+import org.apache.maven.plugin.PluginResolutionException;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.Parameter;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
+import org.apache.maven.plugin.internal.PluginDependenciesResolver;
+import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
+import org.apache.maven.plugin.version.PluginVersionRequest;
+import org.apache.maven.plugin.version.PluginVersionResolutionException;
+import org.apache.maven.plugin.version.PluginVersionResolver;
+import org.apache.maven.project.ExtensionDescriptor;
+import org.apache.maven.project.ExtensionDescriptorBuilder;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.rtinfo.RuntimeInformation;
+import org.apache.maven.session.scope.internal.SessionScopeModule;
+import org.codehaus.plexus.DefaultPlexusContainer;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
+import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
+import org.codehaus.plexus.component.configurator.ComponentConfigurator;
+import org.codehaus.plexus.component.configurator.ConfigurationListener;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
+import org.codehaus.plexus.component.repository.ComponentDescriptor;
+import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.configuration.PlexusConfiguration;
+import org.codehaus.plexus.configuration.PlexusConfigurationException;
+import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.logging.LoggerManager;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.util.filter.AndDependencyFilter;
+import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
+import org.eclipse.sisu.Priority;
+import org.eclipse.sisu.Typed;
+
+/*
+ * 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
+ * plugins as well as special purpose plugins like reports.
+ *
+ * @author Benjamin Bentmann
+ * @since 3.0
+ */
+@Singleton
+@Named
+@Priority(10)
+@Typed(MavenPluginManager.class)
+public class CliMavenPluginManager
+ implements MavenPluginManager {
+
+ /**
+ *
+ * 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 = CliMavenPluginManager.class.getName() + "/extensionsRealms";
+
+ @Inject
+ private Logger logger;
+
+ @Inject
+ private LoggerManager loggerManager;
+
+ @Inject
+ private PlexusContainer container;
+
+ @Inject
+ private ClassRealmManager classRealmManager;
+
+ @Inject
+ private CliPluginDescriptorCache pluginDescriptorCache;
+
+ @Inject
+ private CliPluginRealmCache pluginRealmCache;
+
+ @Inject
+ private PluginDependenciesResolver pluginDependenciesResolver;
+
+ @Inject
+ private RuntimeInformation runtimeInformation;
+
+ @Inject
+ private ExtensionRealmCache extensionRealmCache;
+
+ @Inject
+ private PluginVersionResolver pluginVersionResolver;
+
+ @Inject
+ private PluginArtifactsCache pluginArtifactsCache;
+
+ private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
+
+ private PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
+
+ public PluginDescriptor getPluginDescriptor(Plugin plugin, List repositories,
+ RepositorySystemSession session)
+ throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
+ PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
+
+ PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
+ org.eclipse.aether.artifact.Artifact artifact = pluginDependenciesResolver.resolve(plugin, repositories, session);
+
+ Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
+
+ PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
+
+ descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null));
+
+ return descriptor;
+ });
+
+ pluginDescriptor.setPlugin(plugin);
+
+ return pluginDescriptor;
+ }
+
+ private PluginDescriptor extractPluginDescriptor(Artifact pluginArtifact, Plugin plugin)
+ throws PluginDescriptorParsingException, InvalidPluginDescriptorException {
+ PluginDescriptor pluginDescriptor = null;
+
+ File pluginFile = pluginArtifact.getFile();
+
+ try {
+ if (pluginFile.isFile()) {
+ try (JarFile pluginJar = new JarFile(pluginFile, false)) {
+ ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
+
+ if (pluginDescriptorEntry != null) {
+ InputStream is = pluginJar.getInputStream(pluginDescriptorEntry);
+
+ pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath());
+ }
+ }
+ } else {
+ File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
+
+ if (pluginXml.isFile()) {
+ try (InputStream is = new BufferedInputStream(new FileInputStream(pluginXml))) {
+ pluginDescriptor = parsePluginDescriptor(is, plugin, pluginXml.getAbsolutePath());
+ }
+ }
+ }
+
+ if (pluginDescriptor == null) {
+ throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
+ }
+ } catch (IOException e) {
+ throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
+ }
+
+ MavenPluginValidator validator = new MavenPluginValidator(pluginArtifact);
+
+ validator.validate(pluginDescriptor);
+
+ if (validator.hasErrors()) {
+ throw new InvalidPluginDescriptorException(
+ "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors());
+ }
+
+ pluginDescriptor.setPluginArtifact(pluginArtifact);
+
+ return pluginDescriptor;
+ }
+
+ private String getPluginDescriptorLocation() {
+ return "META-INF/maven/plugin.xml";
+ }
+
+ private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation)
+ throws PluginDescriptorParsingException {
+ try {
+ Reader reader = ReaderFactory.newXmlReader(is);
+
+ PluginDescriptor pluginDescriptor = builder.build(reader, descriptorLocation);
+
+ return pluginDescriptor;
+ } catch (IOException | PlexusConfigurationException e) {
+ throw new PluginDescriptorParsingException(plugin, descriptorLocation, e);
+ }
+ }
+
+ public MojoDescriptor getMojoDescriptor(Plugin plugin, String goal, List repositories,
+ RepositorySystemSession session)
+ throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
+ InvalidPluginDescriptorException {
+ PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session);
+
+ MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal);
+
+ if (mojoDescriptor == null) {
+ throw new MojoNotFoundException(goal, pluginDescriptor);
+ }
+
+ return mojoDescriptor;
+ }
+
+ public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor)
+ throws PluginIncompatibleException {
+ String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion();
+ if (StringUtils.isNotBlank(requiredMavenVersion)) {
+ try {
+ if (!runtimeInformation.isMavenVersion(requiredMavenVersion)) {
+ throw new PluginIncompatibleException(pluginDescriptor.getPlugin(),
+ "The plugin " + pluginDescriptor.getId()
+ + " requires Maven version " + requiredMavenVersion);
+ }
+ } catch (RuntimeException e) {
+ logger.warn("Could not verify plugin's Maven prerequisite: " + e.getMessage());
+ }
+ }
+ }
+
+ public void setupPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session,
+ ClassLoader parent, List 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, () -> {
+ createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter);
+ return new PluginRealmCache.CacheRecord(pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts());
+ });
+
+ if (cacheRecord != null) {
+ pluginDescriptor.setClassRealm(cacheRecord.getRealm());
+ pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts()));
+ for (ComponentDescriptor> componentDescriptor : pluginDescriptor.getComponents()) {
+ componentDescriptor.setRealm(cacheRecord.getRealm());
+ }
+ }
+
+ pluginRealmCache.register(project, cacheKey, cacheRecord);
+ }
+ }
+
+ private void createPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent,
+ Map 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 synchronized 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/CliPluginDescriptorCache.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginDescriptorCache.java
new file mode 100644
index 000000000..01d65da6e
--- /dev/null
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginDescriptorCache.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.mvndaemon.mvnd.plugin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.plugin.InvalidPluginDescriptorException;
+import org.apache.maven.plugin.PluginDescriptorCache;
+import org.apache.maven.plugin.PluginDescriptorParsingException;
+import org.apache.maven.plugin.PluginResolutionException;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.codehaus.plexus.component.repository.ComponentDescriptor;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.WorkspaceRepository;
+import org.eclipse.sisu.Priority;
+import org.eclipse.sisu.Typed;
+
+/*
+ * gnodet: This file is based on maven DefaultPluginDescriptorCache and changed in order
+ * to better support parallel builds. See https://github.com/mvndaemon/mvnd/issues/310
+ */
+/**
+ * Caches raw plugin descriptors. A raw plugin descriptor is a descriptor that has just been extracted from the plugin
+ * artifact and does not contain any runtime specific data. The cache must not be used for descriptors that hold runtime
+ * data like the plugin realm. Warning: This is an internal utility interface that is only public for
+ * technical reasons, it is not part of the public API. In particular, this interface can be changed or deleted without
+ * prior notice.
+ *
+ * @since 3.0
+ * @author Benjamin Bentmann
+ */
+@Singleton
+@Named
+@Priority(10)
+@Typed(CliPluginDescriptorCache.class)
+public class CliPluginDescriptorCache
+ implements PluginDescriptorCache {
+
+ @FunctionalInterface
+ public interface PluginDescriptorSupplier {
+ PluginDescriptor load()
+ throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException;
+ }
+
+ private static class Holder {
+
+ PluginDescriptor descriptor;
+
+ public synchronized PluginDescriptor get() {
+ return descriptor;
+ }
+
+ public synchronized void set(PluginDescriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ public synchronized PluginDescriptor get(PluginDescriptorSupplier supplier)
+ throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
+ if (descriptor == null) {
+ descriptor = supplier.load();
+ }
+ return descriptor;
+ }
+ }
+
+ private ConcurrentMap descriptors = new ConcurrentHashMap<>(128);
+
+ public void flush() {
+ descriptors.clear();
+ }
+
+ public Key createKey(Plugin plugin, List repositories, RepositorySystemSession session) {
+ return new CacheKey(plugin, repositories, session);
+ }
+
+ public PluginDescriptor get(Key cacheKey) {
+ return clone(descriptors.computeIfAbsent(cacheKey, k -> new Holder()).get());
+ }
+
+ public PluginDescriptor get(Key cacheKey, PluginDescriptorSupplier supplier)
+ throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
+ return clone(descriptors.computeIfAbsent(cacheKey, k -> new Holder()).get(supplier));
+ }
+
+ public void put(Key cacheKey, PluginDescriptor pluginDescriptor) {
+ descriptors.computeIfAbsent(cacheKey, k -> new Holder()).set(clone(pluginDescriptor));
+ }
+
+ protected static PluginDescriptor clone(PluginDescriptor original) {
+ PluginDescriptor clone = null;
+
+ if (original != null) {
+ clone = new PluginDescriptor();
+
+ clone.setGroupId(original.getGroupId());
+ clone.setArtifactId(original.getArtifactId());
+ clone.setVersion(original.getVersion());
+ clone.setGoalPrefix(original.getGoalPrefix());
+ clone.setInheritedByDefault(original.isInheritedByDefault());
+
+ clone.setName(original.getName());
+ clone.setDescription(original.getDescription());
+ clone.setRequiredMavenVersion(original.getRequiredMavenVersion());
+
+ clone.setPluginArtifact(ArtifactUtils.copyArtifactSafe(original.getPluginArtifact()));
+
+ clone.setComponents(clone(original.getMojos(), clone));
+ clone.setId(original.getId());
+ clone.setIsolatedRealm(original.isIsolatedRealm());
+ clone.setSource(original.getSource());
+
+ clone.setDependencies(original.getDependencies());
+ }
+
+ return clone;
+ }
+
+ private static List> clone(List mojos, PluginDescriptor pluginDescriptor) {
+ List> clones = null;
+
+ if (mojos != null) {
+ clones = new ArrayList<>(mojos.size());
+
+ for (MojoDescriptor mojo : mojos) {
+ MojoDescriptor clone = mojo.clone();
+ clone.setPluginDescriptor(pluginDescriptor);
+ clones.add(clone);
+ }
+ }
+
+ return clones;
+ }
+
+ private static final class CacheKey
+ implements Key {
+
+ private final String groupId;
+
+ private final String artifactId;
+
+ private final String version;
+
+ private final WorkspaceRepository workspace;
+
+ private final LocalRepository localRepo;
+
+ private final List repositories;
+
+ private final int hashCode;
+
+ CacheKey(Plugin plugin, List repositories, RepositorySystemSession session) {
+ groupId = plugin.getGroupId();
+ artifactId = plugin.getArtifactId();
+ version = plugin.getVersion();
+
+ workspace = RepositoryUtils.getWorkspace(session);
+ localRepo = session.getLocalRepository();
+ this.repositories = new ArrayList<>(repositories.size());
+ for (RemoteRepository repository : repositories) {
+ if (repository.isRepositoryManager()) {
+ this.repositories.addAll(repository.getMirroredRepositories());
+ } else {
+ this.repositories.add(repository);
+ }
+ }
+
+ int hash = 17;
+ hash = hash * 31 + groupId.hashCode();
+ hash = hash * 31 + artifactId.hashCode();
+ hash = hash * 31 + version.hashCode();
+ hash = hash * 31 + hash(workspace);
+ hash = hash * 31 + localRepo.hashCode();
+ hash = hash * 31 + RepositoryUtils.repositoriesHashCode(repositories);
+ this.hashCode = hash;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof CacheKey)) {
+ return false;
+ }
+
+ CacheKey that = (CacheKey) obj;
+
+ return Objects.equals(this.artifactId, that.artifactId)
+ && Objects.equals(this.groupId, that.groupId)
+ && Objects.equals(this.version, that.version)
+ && Objects.equals(this.localRepo, that.localRepo)
+ && Objects.equals(this.workspace, that.workspace)
+ && RepositoryUtils.repositoriesEquals(this.repositories, that.repositories);
+ }
+
+ @Override
+ public String toString() {
+ return groupId + ':' + artifactId + ':' + version;
+ }
+
+ private static int hash(Object obj) {
+ return obj != null ? obj.hashCode() : 0;
+ }
+
+ }
+
+}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCache.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCache.java
index 7277a7296..facab020b 100644
--- a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCache.java
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCache.java
@@ -40,7 +40,9 @@
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Plugin;
+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;
@@ -67,6 +69,31 @@
@Typed(PluginRealmCache.class)
public class CliPluginRealmCache
implements PluginRealmCache, Disposable {
+
+ @FunctionalInterface
+ public interface PluginRealmSupplier {
+ CacheRecord load()
+ throws PluginResolutionException, PluginContainerException;
+ }
+
+ protected static class Holder {
+
+ ValidableCacheRecord record;
+
+ public synchronized ValidableCacheRecord get() {
+ if (record != null && !record.isValid()) {
+ record.dispose();
+ record = null;
+ }
+ return record;
+ }
+
+ public synchronized void set(ValidableCacheRecord record) {
+ this.record = record;
+ }
+
+ }
+
/**
* CacheKey
*/
@@ -324,17 +351,11 @@ public Registration(WatchKey watchKey) {
}
}
- public ValidableCacheRecord newRecord(ClassRealm pluginRealm, List pluginArtifacts) {
- final ValidableCacheRecord result = new ValidableCacheRecord(pluginRealm, pluginArtifacts);
- add(result);
- return result;
- }
-
}
private static final Logger LOG = LoggerFactory.getLogger(CliPluginRealmCache.class);
- protected final Map cache = new ConcurrentHashMap<>();
+ protected final Map cache = new ConcurrentHashMap<>();
private final RecordValidator watcher;
public CliPluginRealmCache() {
@@ -349,25 +370,48 @@ public Key createKey(Plugin plugin, ClassLoader parentRealm, Map {
- if (!r.isValid()) {
- r.dispose();
- return null;
- } else {
- return r;
+ Holder h = cache.get(key);
+ return h != null ? h.get() : null;
+ }
+
+ public CacheRecord get(Key key, PluginRealmSupplier supplier)
+ throws PluginResolutionException, PluginContainerException {
+ watcher.validateRecords();
+ Holder h = cache.computeIfAbsent(key, k -> new Holder());
+ synchronized (h) {
+ ValidableCacheRecord vr = h.get();
+ if (vr != null) {
+ if (vr.isValid()) {
+ return vr;
+ }
+ vr.dispose();
}
- });
+ CacheRecord r = supplier.load();
+ vr = new ValidableCacheRecord(r.getRealm(), r.getArtifacts());
+ watcher.add(vr);
+ h.set(vr);
+ return vr;
+ }
}
public CacheRecord put(Key key, ClassRealm pluginRealm, List pluginArtifacts) {
Objects.requireNonNull(pluginRealm, "pluginRealm cannot be null");
Objects.requireNonNull(pluginArtifacts, "pluginArtifacts cannot be null");
- return cache.computeIfAbsent(key, k -> watcher.newRecord(pluginRealm, pluginArtifacts));
+ Holder h = cache.computeIfAbsent(key, k -> new Holder());
+ synchronized (h) {
+ ValidableCacheRecord r = new ValidableCacheRecord(pluginRealm, pluginArtifacts);
+ watcher.add(r);
+ h.set(r);
+ return r;
+ }
}
public void flush() {
- for (ValidableCacheRecord record : cache.values()) {
- record.dispose();
+ for (Holder holder : cache.values()) {
+ ValidableCacheRecord record = holder.get();
+ if (record != null) {
+ record.dispose();
+ }
}
cache.clear();
}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCacheEventSpy.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCacheEventSpy.java
index d51b80443..6598d2688 100644
--- a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCacheEventSpy.java
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliPluginRealmCacheEventSpy.java
@@ -55,11 +55,11 @@ public void onEvent(Object event) throws Exception {
multiModuleProjectDirectory = ((MavenExecutionRequest) event).getMultiModuleProjectDirectory().toPath();
} else if (event instanceof MavenExecutionResult) {
/* Evict the entries refering to jars under multiModuleProjectDirectory */
- final Iterator> i = cache.cache
+ final Iterator> i = cache.cache
.entrySet().iterator();
while (i.hasNext()) {
- final Map.Entry entry = i.next();
- final CliPluginRealmCache.ValidableCacheRecord record = entry.getValue();
+ final Map.Entry entry = i.next();
+ final CliPluginRealmCache.ValidableCacheRecord record = entry.getValue().get();
for (URL url : record.getRealm().getURLs()) {
if (url.getProtocol().equals("file")) {
final Path path = Paths.get(url.toURI());
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 000000000..fb0510324
--- /dev/null
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.mvndaemon.mvnd.plugin;
+
+import java.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;
+
+/*
+ * gnodet: This file is a copy of maven's ValidatingConfigurationListener because it's visibility
+ * is restricted to its defining package.
+ * See https://github.com/mvndaemon/mvnd/issues/310
+ */
+/**
+ * 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);
+ }
+ }
+
+}