+ * This is the work {@code org.apache.maven.bridge.MavenRepositorySystem} does in its session-based + * {@code injectMirror}/{@code injectProxy}/{@code injectAuthentication} methods, reimplemented here because + * {@code org.apache.maven.bridge} is not one of the packages maven-core exports to plugin class realms — a plugin + * that references it compiles and unit-tests cleanly and then fails at runtime with + * {@code NoClassDefFoundError: org/apache/maven/bridge/MavenRepositorySystem}. Everything used below is in a package + * maven-core does export: {@code org.apache.maven} (for {@link RepositoryUtils}), {@code org.apache.maven.artifact}, + * {@code org.apache.maven.repository} and {@code org.eclipse.aether.repository}. + *
+ * The session's selectors are populated by Maven from the mirrors, proxies and servers of
+ *
+ * Class copied verbatim from maven-compat so that the maven-compat dependency can be dropped. It is used by the
+ * unit tests only, never by the plugin itself, and it is a plain data holder over
+ * {@link AbstractRepositoryMetadata} (which lives in maven-core), so copying it is enough — there is no component
+ * implementation left behind in maven-compat. Same technique as maven-plugin-plugin used for
+ * {@code GroupRepositoryMetadata} in MPLUGIN-384.
+ *
+ * @author Brett Porter
+ */
+public class SnapshotArtifactRepositoryMetadata extends AbstractRepositoryMetadata {
+ private final Artifact artifact;
+
+ public SnapshotArtifactRepositoryMetadata(Artifact artifact) {
+ super(createMetadata(artifact, null));
+ this.artifact = artifact;
+ }
+
+ public SnapshotArtifactRepositoryMetadata(Artifact artifact, Snapshot snapshot) {
+ super(createMetadata(artifact, createVersioning(snapshot)));
+ this.artifact = artifact;
+ }
+
+ @Override
+ public boolean storedInGroupDirectory() {
+ return false;
+ }
+
+ @Override
+ public boolean storedInArtifactVersionDirectory() {
+ return true;
+ }
+
+ @Override
+ public String getGroupId() {
+ return artifact.getGroupId();
+ }
+
+ @Override
+ public String getArtifactId() {
+ return artifact.getArtifactId();
+ }
+
+ @Override
+ public String getBaseVersion() {
+ return artifact.getBaseVersion();
+ }
+
+ @Override
+ public Object getKey() {
+ return "snapshot " + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getBaseVersion();
+ }
+
+ @Override
+ public boolean isSnapshot() {
+ return artifact.isSnapshot();
+ }
+
+ @Override
+ public int getNature() {
+ return isSnapshot() ? SNAPSHOT : RELEASE;
+ }
+
+ @Override
+ public ArtifactRepository getRepository() {
+ return artifact.getRepository();
+ }
+
+ @Override
+ public void setRepository(ArtifactRepository remoteRepository) {
+ artifact.setRepository(remoteRepository);
+ }
+}
diff --git a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java
index 2cdb47831..028500278 100644
--- a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java
+++ b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java
@@ -21,6 +21,8 @@
import javax.inject.Inject;
import java.net.InetAddress;
+import java.net.URI;
+import java.nio.file.Path;
import java.util.Collections;
import org.apache.maven.api.plugin.testing.Basedir;
@@ -31,10 +33,18 @@
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.DefaultProjectBuildingRequest;
+import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.util.repository.AuthenticationBuilder;
+import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
+import org.eclipse.aether.util.repository.DefaultProxySelector;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
@@ -46,10 +56,13 @@
import org.eclipse.jetty.util.security.Constraint;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import static org.apache.maven.api.plugin.testing.MojoExtension.getTestPath;
import static org.apache.maven.api.plugin.testing.MojoExtension.setVariableValueToObject;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.when;
@@ -57,9 +70,17 @@
@Basedir("/unit/get-test")
class TestGetMojo {
+ private static final String PROXY_HOST = "proxy.invalid";
+
@Inject
private MavenSession session;
+ @Inject
+ private RepositorySystem repositorySystem;
+
+ @TempDir
+ private Path isolatedLocalRepository;
+
@BeforeEach
void setUp() {
Settings settings = new Settings();
@@ -72,6 +93,57 @@ void setUp() {
settings.addServer(server);
}
+ /**
+ * Mirrors the {@code
+ * The local repository is swapped for an empty one at the same time, so that the tests below depend on the
+ * transfer actually happening rather than on what an earlier test left in the shared local repository.
+ */
+ private void applySettingsToRepositorySession() {
+ Settings settings = session.getSettings();
+
+ DefaultAuthenticationSelector authenticationSelector = new DefaultAuthenticationSelector();
+ for (Server server : settings.getServers()) {
+ authenticationSelector.add(
+ server.getId(),
+ new AuthenticationBuilder()
+ .addUsername(server.getUsername())
+ .addPassword(server.getPassword())
+ .addPrivateKey(server.getPrivateKey(), server.getPassphrase())
+ .build());
+ }
+
+ DefaultProxySelector proxySelector = new DefaultProxySelector();
+ for (Proxy proxy : settings.getProxies()) {
+ if (!proxy.isActive()) {
+ // Maven filters the inactive ones out before they ever reach the selector
+ continue;
+ }
+ proxySelector.add(
+ new org.eclipse.aether.repository.Proxy(
+ proxy.getProtocol(),
+ proxy.getHost(),
+ proxy.getPort(),
+ new AuthenticationBuilder()
+ .addUsername(proxy.getUsername())
+ .addPassword(proxy.getPassword())
+ .build()),
+ proxy.getNonProxyHosts());
+ }
+
+ DefaultRepositorySystemSession repositorySession =
+ new DefaultRepositorySystemSession(session.getRepositorySession());
+ repositorySession.setAuthenticationSelector(authenticationSelector);
+ repositorySession.setProxySelector(proxySelector);
+ repositorySession.setLocalRepositoryManager(repositorySystem.newLocalRepositoryManager(
+ repositorySession, new LocalRepository(isolatedLocalRepository.toFile())));
+ when(session.getRepositorySession()).thenReturn(repositorySession);
+ }
+
/**
* Test transitive parameter
*
@@ -126,15 +198,80 @@ void testRemoteRepositoriesAuthentication(GetMojo mojo) throws Exception {
org.eclipse.jetty.server.Server server = createServer();
try {
server.start();
- ServerConnector serverConnector = (ServerConnector) server.getConnectors()[0];
- String url = "http://"
- + (serverConnector.getHost() == null
- ? InetAddress.getLoopbackAddress().getHostName()
- : serverConnector.getHost());
- url = url + ":" + serverConnector.getLocalPort() + "/maven";
+
+ setVariableValueToObject(mojo, "remoteRepositories", "myserver::default::" + serverUrl(server));
+
+ applySettingsToRepositorySession();
+
+ DefaultProjectBuildingRequest pbr = new DefaultProjectBuildingRequest();
+ pbr.setRepositorySession(session.getRepositorySession());
+ when(session.getProjectBuildingRequest()).thenReturn(pbr);
+
+ mojo.setGroupId("test");
+ mojo.setArtifactId("test");
+ mojo.setVersion("1.0");
+
+ mojo.execute();
+ } finally {
+ server.stop();
+ }
+ }
+
+ /**
+ * Test that an active proxy from the settings is applied to the repositories given by the
+ * settings.xml, with the servers and proxies already decrypted, and they are the same selectors
+ * Resolver consults when it performs the transfer.
+ */
+public class RepositorySessionInjector {
+
+ private final MapremoteRepositories parameter. The proxy points at a host that cannot resolve, so a successful
+ * resolution would mean the proxy was never applied.
+ *
+ * @throws Exception in case of errors
+ */
+ @Test
+ @InjectMojo(goal = "get")
+ void testRemoteRepositoriesProxy(GetMojo mojo) throws Exception {
+ org.eclipse.jetty.server.Server server = createServer();
+ try {
+ server.start();
+
+ setVariableValueToObject(mojo, "remoteRepositories", "myserver::default::" + serverUrl(server));
+
+ session.getSettings().addProxy(createProxy(null));
+ applySettingsToRepositorySession();
+
+ DefaultProjectBuildingRequest pbr = new DefaultProjectBuildingRequest();
+ pbr.setRepositorySession(session.getRepositorySession());
+ when(session.getProjectBuildingRequest()).thenReturn(pbr);
+
+ mojo.setGroupId("test");
+ mojo.setArtifactId("test");
+ mojo.setVersion("1.0");
+
+ MojoExecutionException e = assertThrows(MojoExecutionException.class, mojo::execute);
+ assertTrue(
+ mentionsProxyHost(e),
+ "Expected the resolution to have been attempted through the unreachable proxy, got: " + e);
+ } finally {
+ server.stop();
+ }
+ }
+
+ /**
+ * Test that nonProxyHosts excludes a repository from the proxy that would otherwise match it: the
+ * same unreachable proxy as above must not be applied, so the resolution has to succeed.
+ *
+ * @throws Exception in case of errors
+ */
+ @Test
+ @InjectMojo(goal = "get")
+ void testRemoteRepositoriesNonProxyHosts(GetMojo mojo) throws Exception {
+ org.eclipse.jetty.server.Server server = createServer();
+ try {
+ server.start();
+ String url = serverUrl(server);
setVariableValueToObject(mojo, "remoteRepositories", "myserver::default::" + url);
+ session.getSettings().addProxy(createProxy(URI.create(url).getHost()));
+ applySettingsToRepositorySession();
+
DefaultProjectBuildingRequest pbr = new DefaultProjectBuildingRequest();
pbr.setRepositorySession(session.getRepositorySession());
when(session.getProjectBuildingRequest()).thenReturn(pbr);
@@ -149,6 +286,41 @@ void testRemoteRepositoriesAuthentication(GetMojo mojo) throws Exception {
}
}
+ /**
+ * Whether the failure came from the proxy rather than from, say, a missing artifact or a rejected login — the
+ * host of {@link #createProxy(String)} only ever appears if the proxy really was applied to the repository.
+ */
+ private boolean mentionsProxyHost(Throwable throwable) {
+ for (Throwable cause = throwable; cause != null; cause = cause.getCause()) {
+ if (cause.getMessage() != null && cause.getMessage().contains(PROXY_HOST)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * An active HTTP proxy pointing at a name that is guaranteed not to resolve (RFC 2606 reserved TLD), so that
+ * applying it is always observable as a failure.
+ */
+ private Proxy createProxy(String nonProxyHosts) {
+ Proxy proxy = new Proxy();
+ proxy.setActive(true);
+ proxy.setProtocol("http");
+ proxy.setHost(PROXY_HOST);
+ proxy.setPort(3128);
+ proxy.setNonProxyHosts(nonProxyHosts);
+ return proxy;
+ }
+
+ private String serverUrl(org.eclipse.jetty.server.Server server) throws Exception {
+ ServerConnector serverConnector = (ServerConnector) server.getConnectors()[0];
+ String host = serverConnector.getHost() == null
+ ? InetAddress.getLoopbackAddress().getHostName()
+ : serverConnector.getHost();
+ return "http://" + host + ":" + serverConnector.getLocalPort() + "/maven";
+ }
+
/**
* Test parsing of the remote repositories parameter
*
diff --git a/src/test/java/org/apache/maven/plugins/dependency/utils/RepositorySessionInjectorTest.java b/src/test/java/org/apache/maven/plugins/dependency/utils/RepositorySessionInjectorTest.java
new file mode 100644
index 000000000..22e7ec1b4
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/dependency/utils/RepositorySessionInjectorTest.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.utils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
+import org.apache.maven.artifact.repository.MavenArtifactRepository;
+import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
+import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.util.repository.AuthenticationBuilder;
+import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
+import org.eclipse.aether.util.repository.DefaultMirrorSelector;
+import org.eclipse.aether.util.repository.DefaultProxySelector;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Covers the mirror, proxy and authentication selection that {@link RepositorySessionInjector} takes over from
+ * {@code MavenRepositorySystem}, which a plugin cannot reference.
+ */
+class RepositorySessionInjectorTest {
+
+ private RepositorySessionInjector injector;
+
+ private DefaultRepositorySystemSession session;
+
+ @BeforeEach
+ void setUp() {
+ Map