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/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..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 @@ -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,22 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa } } + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + 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/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; + }); + } } } 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/CliMavenPluginManager.java b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java new file mode 100644 index 00000000..ac3f40f5 --- /dev/null +++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java @@ -0,0 +1,796 @@ +/* + * 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. + */ +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; +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 + * 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 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..4aa7d5b0 --- /dev/null +++ b/daemon/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java @@ -0,0 +1,98 @@ +/* + * 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. + */ +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); + } + } + +} 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..268caeab --- /dev/null +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/ConcurrentDownloadsTest.java @@ -0,0 +1,67 @@ +/* + * 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 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--; + } + } + } + } + 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..0d102a9e --- /dev/null +++ b/integration-tests/src/test/projects/concurrent-downloads/mod1/pom.xml @@ -0,0 +1,54 @@ + + + 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.activemq + apache-activemq + 5.16.0 + bin + 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