diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index a6bcfe34cdc..64b4be8bef4 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -38,7 +38,8 @@ dependencies { testImplementation(project(":rewrite-test")) testImplementation("com.squareup.okhttp3:mockwebserver:4.+") - testImplementation("com.squareup.okio:okio-jvm:3.0.0") + testImplementation("com.squareup.okhttp3:okhttp-tls:4.+") + testImplementation("com.squareup.okio:okio-jvm:3.9.1") testImplementation("org.mapdb:mapdb:latest.release") testImplementation("guru.nidi:graphviz-java:latest.release") diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java index 50a829f0a8c..b3cbe440c88 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java @@ -17,16 +17,21 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import okhttp3.OkHttpClient; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; +import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.openrewrite.HttpSenderExecutionContextView; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; import org.openrewrite.ParseExceptionResult; import org.openrewrite.Parser; +import org.openrewrite.ipc.http.OkHttpSender; import org.openrewrite.maven.internal.MavenParsingException; import org.openrewrite.maven.tree.*; import org.openrewrite.test.RewriteTest; @@ -34,10 +39,11 @@ import org.openrewrite.tree.ParseError; import java.io.IOException; +import java.net.InetAddress; import java.nio.file.Paths; import java.util.Base64; import java.util.List; -import java.util.stream.StreamSupport; +import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -906,13 +912,23 @@ void mirrorsAndAuth() throws IOException { var username = "admin"; var password = "password"; try (MockWebServer mockRepo = new MockWebServer()) { + // TLS server setup based on https://github.com/square/okhttp/blob/master/okhttp-tls/README.md + String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); + HeldCertificate localhostCertificate = new HeldCertificate.Builder() + .addSubjectAlternativeName(localhost) + .build(); + HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .heldCertificate(localhostCertificate) + .build(); + mockRepo.useHttps(serverCertificates.sslSocketFactory(), false); + mockRepo.setDispatcher(new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) { MockResponse resp = new MockResponse(); - if (StreamSupport.stream(request.getHeaders().spliterator(), false) - .noneMatch(it -> it.getFirst().equals("Authorization") && - it.getSecond().equals("Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes())))) { + if (!Objects.equals( + request.getHeader("Authorization"), + "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()))) { return resp.setResponseCode(401); } else { if (!"HEAD".equalsIgnoreCase(request.getMethod())) { @@ -935,7 +951,7 @@ public MockResponse dispatch(RecordedRequest request) { }); mockRepo.start(); - var ctx = MavenExecutionContextView.view(new InMemoryExecutionContext(t -> { + var mavenCtx = MavenExecutionContextView.view(new InMemoryExecutionContext(t -> { throw new RuntimeException(t); })); var settings = MavenSettings.parse(Parser.Input.fromString(Paths.get("settings.xml"), @@ -959,8 +975,17 @@ public MockResponse dispatch(RecordedRequest request) { """.formatted(mockRepo.getHostName(), mockRepo.getPort(), username, password) - ), ctx); - ctx.setMavenSettings(settings); + ), mavenCtx); + mavenCtx.setMavenSettings(settings); + + // TLS client setup (just make it trust the self-signed certificate) + HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder() + .addTrustedCertificate(localhostCertificate.certificate()) + .build(); + OkHttpClient client = new OkHttpClient.Builder() + .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()) + .build(); + var ctx = new HttpSenderExecutionContextView(mavenCtx).setHttpSender(new OkHttpSender(client)); var maven = MavenParser.builder().build().parse( ctx, diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeParentVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeParentVersionTest.java index e248627b44c..9b25228a908 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeParentVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeParentVersionTest.java @@ -61,7 +61,7 @@ void doesNotDowngradeVersion() { void nonMavenCentralRepository() { rewriteRun( spec -> spec - .recipe(new UpgradeParentVersion("org.jenkins-ci.plugins", "plugin", "4.40", null)) + .recipe(new UpgradeParentVersion("org.jenkins-ci", "jenkins", "1.125", null)) .executionContext( MavenExecutionContextView .view(new InMemoryExecutionContext()) @@ -73,22 +73,22 @@ void nonMavenCentralRepository() { """ - org.jenkins-ci.plugins - plugin - 4.33 + org.jenkins-ci + jenkins + 1.124 - antisamy-markup-formatter + example 1.0.0 """, """ - org.jenkins-ci.plugins - plugin - 4.40 + org.jenkins-ci + jenkins + 1.125 - antisamy-markup-formatter + example 1.0.0 """ diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java index 93d9bd3f599..9e3be221b3f 100755 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java @@ -15,12 +15,16 @@ */ package org.openrewrite.maven.internal; +import okhttp3.OkHttpClient; import okhttp3.mockwebserver.*; +import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; import org.assertj.core.api.Condition; import org.intellij.lang.annotations.Language; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -29,13 +33,16 @@ import org.openrewrite.*; import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.ipc.http.HttpUrlConnectionSender; +import org.openrewrite.ipc.http.OkHttpSender; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.MavenParser; import org.openrewrite.maven.MavenSettings; import org.openrewrite.maven.tree.*; +import javax.net.ssl.SSLSocketFactory; import java.io.IOException; +import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.file.Files; import java.nio.file.Path; @@ -45,10 +52,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; +import static java.util.Collections.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -57,28 +61,6 @@ @SuppressWarnings({"HttpUrlsUsage"}) class MavenPomDownloaderTest { - private final ExecutionContext ctx = HttpSenderExecutionContextView.view(new InMemoryExecutionContext()) - .setHttpSender(new HttpUrlConnectionSender(Duration.ofMillis(250), Duration.ofMillis(250))); - - private void mockServer(Integer responseCode, Consumer block) { - try (MockWebServer mockRepo = new MockWebServer()) { - mockRepo.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - return new MockResponse().setResponseCode(responseCode).setBody(""); - } - }); - mockRepo.start(); - block.accept(mockRepo); - assertThat(mockRepo.getRequestCount()) - .as("The mock repository received no requests. The test is not using it.") - .isGreaterThan(0); - mockRepo.shutdown(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - @Test void ossSonatype() { InMemoryExecutionContext ctx = new InMemoryExecutionContext(); @@ -92,831 +74,923 @@ void ossSonatype() { assertThat(repo).isNotNull().extracting((MavenRepository::getUri)).isEqualTo(ossSonatype.getUri()); } - @Issue("https://github.com/openrewrite/rewrite/issues/3908") - @Test - void centralIdOverridesDefaultRepository() { - var ctx = MavenExecutionContextView.view(this.ctx); - ctx.setMavenSettings(MavenSettings.parse(Parser.Input.fromString(Paths.get("settings.xml"), - //language=xml - """ - - - - central - - - central - https://internalartifactrepository.yourorg.com - - - - - - central - - - """ - ), ctx)); - - // Avoid actually trying to reach the made-up https://internalartifactrepository.yourorg.com - for (MavenRepository repository : ctx.getRepositories()) { - repository.setKnownToExist(true); - } - - var downloader = new MavenPomDownloader(emptyMap(), ctx); - Collection repos = downloader.distinctNormalizedRepositories(emptyList(), null, null); - assertThat(repos).areExactly(1, new Condition<>(repo -> "central".equals(repo.getId()), - "id \"central\"")); - assertThat(repos).areExactly(1, new Condition<>(repo -> "https://internalartifactrepository.yourorg.com".equals(repo.getUri()), - "URI https://internalartifactrepository.yourorg.com")); + @CsvSource(textBlock = """ + https://repo1.maven.org/maven2/, https://repo1.maven.org/maven2/ + https://repo1.maven.org/maven2, https://repo1.maven.org/maven2/ + http://repo1.maven.org/maven2/, https://repo1.maven.org/maven2/ + + https://oss.sonatype.org/content/repositories/snapshots/, https://oss.sonatype.org/content/repositories/snapshots/ + https://artifactory.moderne.ninja/artifactory/moderne-public/, https://artifactory.moderne.ninja/artifactory/moderne-public/ + https://repo.maven.apache.org/maven2/, https://repo.maven.apache.org/maven2/ + https://jitpack.io/, https://jitpack.io/ + """) + @ParameterizedTest + void normalizeRepository(String originalUrl, String expectedUrl) throws Throwable { + MavenPomDownloader downloader = new MavenPomDownloader(new InMemoryExecutionContext()); + MavenRepository repository = new MavenRepository("id", originalUrl, null, null, null, null, null); + MavenRepository normalized = downloader.normalizeRepository(repository); + assertThat(normalized).isNotNull(); + assertThat(normalized.getUri()).isEqualTo(expectedUrl); } - @Test - void listenerRecordsRepository() { - var ctx = MavenExecutionContextView.view(this.ctx); - // Avoid actually trying to reach the made-up https://internalartifactrepository.yourorg.com - for (MavenRepository repository : ctx.getRepositories()) { - repository.setKnownToExist(true); - } + @Nested + class WithNativeHttpURLConnectionAndTLS { + private final ExecutionContext ctx = HttpSenderExecutionContextView.view(new InMemoryExecutionContext()) + .setHttpSender(new HttpUrlConnectionSender(Duration.ofMillis(250), Duration.ofMillis(250))); - MavenRepository nonexistentRepo = new MavenRepository("repo", "http://internalartifactrepository.yourorg.com", null, null, true, null, null, null, null); - List attemptedUris = new ArrayList<>(); - List discoveredRepositories = new ArrayList<>(); - ctx.setResolutionListener(new ResolutionEventListener() { - @Override - public void downloadError(GroupArtifactVersion gav, List uris, @Nullable Pom containing) { - attemptedUris.addAll(uris); + @Issue("https://github.com/openrewrite/rewrite/issues/3908") + @Test + void centralIdOverridesDefaultRepository() { + var ctx = MavenExecutionContextView.view(this.ctx); + ctx.setMavenSettings(MavenSettings.parse(Parser.Input.fromString(Paths.get("settings.xml"), + //language=xml + """ + + + + central + + + central + https://internalartifactrepository.yourorg.com + + + + + + central + + + """ + ), ctx)); + + // Avoid actually trying to reach the made-up https://internalartifactrepository.yourorg.com + for (MavenRepository repository : ctx.getRepositories()) { + repository.setKnownToExist(true); } - @Override - public void repository(MavenRepository mavenRepository, @Nullable ResolvedPom containing) { - discoveredRepositories.add(mavenRepository); + var downloader = new MavenPomDownloader(emptyMap(), ctx); + Collection repos = downloader.distinctNormalizedRepositories(emptyList(), null, null); + assertThat(repos).areExactly(1, new Condition<>(repo -> "central".equals(repo.getId()), + "id \"central\"")); + assertThat(repos).areExactly(1, new Condition<>(repo -> "https://internalartifactrepository.yourorg.com".equals(repo.getUri()), + "URI https://internalartifactrepository.yourorg.com")); + } + + @Test + void listenerRecordsRepository() { + var ctx = MavenExecutionContextView.view(this.ctx); + // Avoid actually trying to reach the made-up https://internalartifactrepository.yourorg.com + for (MavenRepository repository : ctx.getRepositories()) { + repository.setKnownToExist(true); } - }); - try { - new MavenPomDownloader(ctx) - .download(new GroupArtifactVersion("org.openrewrite", "rewrite-core", "7.0.0"), null, null, singletonList(nonexistentRepo)); - } catch (Exception e) { - // not expected to succeed - } - assertThat(attemptedUris) - .containsExactly("http://internalartifactrepository.yourorg.com/org/openrewrite/rewrite-core/7.0.0/rewrite-core-7.0.0.pom"); - assertThat(discoveredRepositories) - .containsExactly(nonexistentRepo); - } + MavenRepository nonexistentRepo = new MavenRepository("repo", "http://internalartifactrepository.yourorg.com", null, null, true, null, null, null, null); + List attemptedUris = new ArrayList<>(); + List discoveredRepositories = new ArrayList<>(); + ctx.setResolutionListener(new ResolutionEventListener() { + @Override + public void downloadError(GroupArtifactVersion gav, List uris, @Nullable Pom containing) { + attemptedUris.addAll(uris); + } - @Test - void listenerRecordsFailedRepositoryAccess() { - var ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); - // Avoid actually trying to reach a made-up URL - String httpUrl = "http://%s.com".formatted(UUID.randomUUID()); - MavenRepository nonexistentRepo = new MavenRepository("repo", httpUrl, null, null, false, null, null, null, null); - Map attemptedUris = new HashMap<>(); - List discoveredRepositories = new ArrayList<>(); - ctx.setResolutionListener(new ResolutionEventListener() { - @Override - public void repositoryAccessFailed(String uri, Throwable e) { - attemptedUris.put(uri, e); + @Override + public void repository(MavenRepository mavenRepository, @Nullable ResolvedPom containing) { + discoveredRepositories.add(mavenRepository); + } + }); + + try { + new MavenPomDownloader(ctx) + .download(new GroupArtifactVersion("org.openrewrite", "rewrite-core", "7.0.0"), null, null, singletonList(nonexistentRepo)); + } catch (Exception e) { + // not expected to succeed } - }); + assertThat(attemptedUris) + .containsExactly("http://internalartifactrepository.yourorg.com/org/openrewrite/rewrite-core/7.0.0/rewrite-core-7.0.0.pom"); + assertThat(discoveredRepositories) + .containsExactly(nonexistentRepo); + } + + @Test + void listenerRecordsFailedRepositoryAccess() { + var ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); + // Avoid actually trying to reach a made-up URL + String httpUrl = "http://%s.com".formatted(UUID.randomUUID()); + MavenRepository nonexistentRepo = new MavenRepository("repo", httpUrl, null, null, false, null, null, null, null); + Map attemptedUris = new HashMap<>(); + List discoveredRepositories = new ArrayList<>(); + ctx.setResolutionListener(new ResolutionEventListener() { + @Override + public void repositoryAccessFailed(String uri, Throwable e) { + attemptedUris.put(uri, e); + } + }); - try { - new MavenPomDownloader(ctx) - .download(new GroupArtifactVersion("org.openrewrite", "rewrite-core", "7.0.0"), null, null, singletonList(nonexistentRepo)); - } catch (Exception e) { - // not expected to succeed + try { + new MavenPomDownloader(ctx) + .download(new GroupArtifactVersion("org.openrewrite", "rewrite-core", "7.0.0"), null, null, singletonList(nonexistentRepo)); + } catch (Exception e) { + // not expected to succeed + } + assertThat(attemptedUris).isNotEmpty(); + assertThat(attemptedUris.get(httpUrl)).isInstanceOf(UnknownHostException.class); + assertThat(discoveredRepositories).isEmpty(); } - assertThat(attemptedUris).isNotEmpty(); - assertThat(attemptedUris.get(httpUrl)).isInstanceOf(UnknownHostException.class); - assertThat(discoveredRepositories).isEmpty(); - } - @Test - void mirrorsOverrideRepositoriesInPom() { - var ctx = MavenExecutionContextView.view(this.ctx); - ctx.setMavenSettings(MavenSettings.parse(Parser.Input.fromString(Paths.get("settings.xml"), - //language=xml - """ - - - - mirror - https://artifactory.moderne.ninja/artifactory/moderne-cache - * - - - - """ - ), ctx)); - - Path pomPath = Paths.get("pom.xml"); - Pom pom = Pom.builder() - .sourcePath(pomPath) - .repository(MAVEN_CENTRAL) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .gav(new ResolvedGroupArtifactVersion( - "${REPO_URL}", "org.openrewrite", "rewrite-core", "7.0.0", null)) - .build(); - ResolvedPom resolvedPom = ResolvedPom.builder() - .requested(pom) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .repositories(singletonList(MAVEN_CENTRAL)) - .build(); + @Test + void mirrorsOverrideRepositoriesInPom() { + var ctx = MavenExecutionContextView.view(this.ctx); + ctx.setMavenSettings(MavenSettings.parse(Parser.Input.fromString(Paths.get("settings.xml"), + //language=xml + """ + + + + mirror + https://artifactory.moderne.ninja/artifactory/moderne-cache + * + + + + """ + ), ctx)); + + Path pomPath = Paths.get("pom.xml"); + Pom pom = Pom.builder() + .sourcePath(pomPath) + .repository(MAVEN_CENTRAL) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", "org.openrewrite", "rewrite-core", "7.0.0", null)) + .build(); + ResolvedPom resolvedPom = ResolvedPom.builder() + .requested(pom) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .repositories(singletonList(MAVEN_CENTRAL)) + .build(); - Map pomsByPath = new HashMap<>(); - pomsByPath.put(pomPath, pom); - - MavenPomDownloader mpd = new MavenPomDownloader(pomsByPath, ctx); - MavenRepository normalized = mpd.normalizeRepository( - MavenRepository.builder().id("whatever").uri("${REPO_URL}").build(), - ctx, - resolvedPom - ); - assertThat(normalized) - .extracting(MavenRepository::getUri) - .isEqualTo("https://artifactory.moderne.ninja/artifactory/moderne-cache/"); - } + Map pomsByPath = new HashMap<>(); + pomsByPath.put(pomPath, pom); - @Disabled("Flaky on CI") - @Test - void normalizeOssSnapshots() { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - MavenRepository oss = downloader.normalizeRepository( - MavenRepository.builder().id("oss").uri("https://oss.sonatype.org/content/repositories/snapshots").build(), - MavenExecutionContextView.view(ctx), null); + MavenPomDownloader mpd = new MavenPomDownloader(pomsByPath, ctx); + MavenRepository normalized = mpd.normalizeRepository( + MavenRepository.builder().id("whatever").uri("${REPO_URL}").build(), + ctx, + resolvedPom + ); + assertThat(normalized) + .extracting(MavenRepository::getUri) + .isEqualTo("https://artifactory.moderne.ninja/artifactory/moderne-cache/"); + } - assertThat(oss).isNotNull(); - assertThat(oss.getUri()).isEqualTo("https://oss.sonatype.org/content/repositories/snapshots/"); - } + @Disabled("Flaky on CI") + @Test + void normalizeOssSnapshots() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + MavenRepository oss = downloader.normalizeRepository( + MavenRepository.builder().id("oss").uri("https://oss.sonatype.org/content/repositories/snapshots").build(), + MavenExecutionContextView.view(ctx), null); - @ParameterizedTest - @Issue("https://github.com/openrewrite/rewrite/issues/3141") - @ValueSource(strings = {"http://0.0.0.0", "https://0.0.0.0", "0.0.0.0:443"}) - void skipBlockedRepository(String url) { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - MavenRepository oss = downloader.normalizeRepository( - MavenRepository.builder().id("myRepo").uri(url).build(), - MavenExecutionContextView.view(ctx), null); + assertThat(oss).isNotNull(); + assertThat(oss.getUri()).isEqualTo("https://oss.sonatype.org/content/repositories/snapshots/"); + } - assertThat(oss).isNull(); - } + @ParameterizedTest + @Issue("https://github.com/openrewrite/rewrite/issues/3141") + @ValueSource(strings = {"http://0.0.0.0", "https://0.0.0.0", "0.0.0.0:443"}) + void skipBlockedRepository(String url) { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + MavenRepository oss = downloader.normalizeRepository( + MavenRepository.builder().id("myRepo").uri(url).build(), + MavenExecutionContextView.view(ctx), null); - @ParameterizedTest - @ValueSource(ints = {500, 400}) - void normalizeAcceptErrorStatuses(Integer status) { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - mockServer(status, mockRepo -> { - var originalRepo = MavenRepository.builder() - .id("id") - .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) - .build(); - var normalizedRepo = downloader.normalizeRepository(originalRepo, MavenExecutionContextView.view(ctx), null); - assertThat(normalizedRepo).isEqualTo(originalRepo); - }); - } + assertThat(oss).isNull(); + } - @Test - void retryConnectException() throws Throwable { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - try (MockWebServer server = new MockWebServer()) { - server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE)); - server.enqueue(new MockResponse().setResponseCode(200).setBody("body")); - String body = new String(downloader.sendRequest(new HttpSender.Request(server.url("/test").url(), "request".getBytes(), HttpSender.Method.GET, Map.of(), null, null))); - assertThat(body).isEqualTo("body"); - assertThat(server.getRequestCount()).isEqualTo(2); - server.shutdown(); + @Test + void retryConnectException() throws Throwable { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + try (MockWebServer server = new MockWebServer()) { + server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE)); + server.enqueue(new MockResponse().setResponseCode(200).setBody("body")); + String body = new String(downloader.sendRequest(new HttpSender.Request(server.url("/test").url(), "request".getBytes(), HttpSender.Method.GET, Map.of(), null, null))); + assertThat(body).isEqualTo("body"); + assertThat(server.getRequestCount()).isEqualTo(2); + server.shutdown(); + } } - } - @Test - void normalizeRejectConnectException() { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - var normalizedRepository = downloader.normalizeRepository( - MavenRepository.builder().id("id").uri("https//localhost").build(), - MavenExecutionContextView.view(ctx), null); - assertThat(normalizedRepository).isEqualTo(null); - } + @Test + void normalizeRejectConnectException() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + var normalizedRepository = downloader.normalizeRepository( + MavenRepository.builder().id("id").uri("https//localhost").build(), + MavenExecutionContextView.view(ctx), null); + assertThat(normalizedRepository).isEqualTo(null); + } - @Test - void invalidArtifact() { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); - mockServer(500, - repo1 -> mockServer(400, repo2 -> { - var repositories = List.of( - MavenRepository.builder() + @Test + void useHttpWhenHttpsFails() throws IOException { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + try (MockWebServer mockRepo = new MockWebServer()) { + mockRepo.enqueue(new MockResponse().setResponseCode(200).setBody("body")); + var httpRepo = MavenRepository.builder() .id("id") - .uri("http://%s:%d/maven".formatted(repo1.getHostName(), repo1.getPort())) - .build(), - MavenRepository.builder() - .id("id2") - .uri("http://%s:%d/maven".formatted(repo2.getHostName(), repo2.getPort())) - .build() - ); - - assertThatThrownBy(() -> downloader.download(gav, null, null, repositories)) - .isInstanceOf(MavenDownloadingException.class) - .hasMessageContaining("http://%s:%d/maven".formatted(repo1.getHostName(), repo1.getPort())) - .hasMessageContaining("http://%s:%d/maven".formatted(repo2.getHostName(), repo2.getPort())); - }) - ); - } + .uri("http://%s:%d/maven/".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .build(); - @Test - @Issue("https://github.com/openrewrite/rewrite/issues/3152") - void useSnapshotTimestampVersion() { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - var gav = new GroupArtifactVersion("fred", "fred", "2020.0.2-20210127.131051-2"); - try (MockWebServer mockRepo = new MockWebServer()) { - mockRepo.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - assert recordedRequest.getPath() != null; - return !recordedRequest.getPath().endsWith("fred/fred/2020.0.2-SNAPSHOT/fred-2020.0.2-20210127.131051-2.pom") ? - new MockResponse().setResponseCode(404).setBody("") : - new MockResponse().setResponseCode(200).setBody( - //language=xml - """ - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - """); - } - }); - mockRepo.start(); - var repositories = List.of(MavenRepository.builder() - .id("id") - .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) - .username("user") - .password("pass") - .build()); - - assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } + var normalizedRepository = downloader.normalizeRepository(httpRepo, MavenExecutionContextView.view(ctx), null); - @Test - void usesAnonymousRequestIfRepositoryRejectsCredentials() { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); - try (MockWebServer mockRepo = new MockWebServer()) { - mockRepo.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - return recordedRequest.getHeaders().get("Authorization") != null ? - new MockResponse().setResponseCode(401).setBody("") : - new MockResponse().setResponseCode(200).setBody( - //language=xml - """ - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - """); - } - }); - mockRepo.start(); - var repositories = List.of(MavenRepository.builder() - .id("id") - .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) - .username("user") - .password("pass") - .build()); - - assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); - } catch (IOException e) { - throw new RuntimeException(e); + assertThat(normalizedRepository).isEqualTo(httpRepo); + } } - } - @Test - void usesAuthenticationIfRepositoryHasCredentials() { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); - try (MockWebServer mockRepo = new MockWebServer()) { - mockRepo.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - MockResponse response = new MockResponse(); - if (recordedRequest.getHeaders().get("Authorization") != null) { - response.setResponseCode(200); - if (!"HEAD".equalsIgnoreCase(recordedRequest.getMethod())) { + @Test + @Disabled + void dontFetchSnapshotsFromReleaseRepos() { + try (MockWebServer snapshotRepo = new MockWebServer(); + MockWebServer releaseRepo = new MockWebServer()) { + snapshotRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + MockResponse response = new MockResponse().setResponseCode(200); + if (request.getPath() != null && request.getPath().contains("maven-metadata")) { response.setBody( //language=xml """ - + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + + 20220201.001946 + 85 + + 20220201001950 + + + pom + 2.10.0-20220201.001946-85 + 20220201001946 + + + + + """ + ); + } else if (request.getPath() != null && request.getPath().endsWith(".pom")) { + response.setBody( + //language=xml + """ + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + """ + ); + } + return response; + } + }); + + var metadataPaths = new AtomicInteger(0); + releaseRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + MockResponse response = new MockResponse().setResponseCode(404); + if (request.getPath() != null && request.getPath().contains("maven-metadata")) { + metadataPaths.incrementAndGet(); + } + return response; + } + }); + + releaseRepo.start(); + snapshotRepo.start(); + + MavenParser.builder().build().parse(ctx, + //language=xml + """ + + 4.0.0 + + org.openrewrite.test + foo + 0.1.0-SNAPSHOT + + + + snapshot + + true + + http://%s:%d + + + release + + false + + http://%s:%d + + + + + org.springframework.cloud spring-cloud-dataflow-build 2.10.0-SNAPSHOT - - """); - } - } else { - response.setResponseCode(401); - } - return response; - } - }); - mockRepo.start(); - var repositories = List.of(MavenRepository.builder() - .id("id") - .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) - .username("user") - .password("pass") - .build()); - - assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); - } catch (IOException e) { - throw new RuntimeException(e); + + + + """.formatted(snapshotRepo.getHostName(), snapshotRepo.getPort(), releaseRepo.getHostName(), releaseRepo.getPort()) + ); + + assertThat(snapshotRepo.getRequestCount()).isGreaterThan(1); + assertThat(metadataPaths.get()).isEqualTo(0); + } catch (IOException e) { + throw new RuntimeException(e); + } } - } - @Test - @DisplayName("When username or password are environment properties that cannot be resolved, they should not be used") - @Issue("https://github.com/openrewrite/rewrite/issues/3142") - void doesNotUseAuthenticationIfCredentialsCannotBeResolved() { - var downloader = new MavenPomDownloader(emptyMap(), ctx); - var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); - try (MockWebServer mockRepo = new MockWebServer()) { - mockRepo.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - MockResponse response = new MockResponse(); - if (recordedRequest.getHeaders().get("Authorization") != null) { - response.setResponseCode(401); - } else if (recordedRequest.getMethod() == null || !recordedRequest.getMethod().equalsIgnoreCase("HEAD")) { - response.setBody( - //language=xml - """ - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - """); - } - return response; - } - }); - mockRepo.start(); - var repositories = List.of(MavenRepository.builder() - .id("id") - .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) - .username("${env.ARTIFACTORY_USERNAME}") - .password("${env.ARTIFACTORY_USERNAME}") - .build()); - - assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); - } catch (IOException e) { - throw new RuntimeException(e); + @Test + void deriveMetaDataFromFileRepository(@TempDir Path repoPath) throws IOException, MavenDownloadingException { + Path fred = repoPath.resolve("fred/fred"); + + for (String version : Arrays.asList("1.0.0", "1.1.0", "2.0.0")) { + Path versionPath = fred.resolve(version); + Files.createDirectories(versionPath); + Files.writeString(versionPath.resolve("fred-" + version + ".pom"), ""); + } + + MavenRepository repository = MavenRepository.builder() + .id("file-based") + .uri(repoPath.toUri().toString()) + .knownToExist(true) + .deriveMetadataIfMissing(true) + .build(); + MavenMetadata metaData = new MavenPomDownloader(emptyMap(), ctx) + .downloadMetadata(new GroupArtifact("fred", "fred"), null, List.of(repository)); + assertThat(metaData.getVersioning().getVersions()).hasSize(3).containsAll(Arrays.asList("1.0.0", "1.1.0", "2.0.0")); } - } - @Test - @Disabled - void dontFetchSnapshotsFromReleaseRepos() { - try (MockWebServer snapshotRepo = new MockWebServer(); - MockWebServer releaseRepo = new MockWebServer()) { - snapshotRepo.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - MockResponse response = new MockResponse().setResponseCode(200); - if (request.getPath() != null && request.getPath().contains("maven-metadata")) { - response.setBody( - //language=xml - """ - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - - 20220201.001946 - 85 - - 20220201001950 - - - pom - 2.10.0-20220201.001946-85 - 20220201001946 - - - - - """ - ); - } else if (request.getPath() != null && request.getPath().endsWith(".pom")) { - response.setBody( - //language=xml - """ - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - """ - ); - } - return response; - } - }); + @SuppressWarnings("ConstantConditions") + @Test + void mergeMetadata() throws IOException { + @Language("xml") String metadata1 = """ + + org.springframework.boot + spring-boot + + + 2.3.3 + 2.4.1 + 2.4.2 + + + 20220927.033510 + 223 + + + + pom.asc + 0.1.0-20220927.033510-223 + 20220927033510 + + + + + """; + + @Language("xml") String metadata2 = """ + + org.springframework.boot + spring-boot + + + 2.3.2 + 2.3.3 + + + 20210115.042754 + 180 + + + + pom.asc + 0.1.0-20210115.042754-180 + 20210115042754 + + + + + """; + + var m1 = MavenMetadata.parse(metadata1.getBytes()); + var m2 = MavenMetadata.parse(metadata2.getBytes()); + + var merged = new MavenPomDownloader(emptyMap(), ctx).mergeMetadata(m1, m2); + + assertThat(merged.getVersioning().getSnapshot().getTimestamp()).isEqualTo("20220927.033510"); + assertThat(merged.getVersioning().getSnapshot().getBuildNumber()).isEqualTo("223"); + assertThat(merged.getVersioning().getVersions()).hasSize(4).contains("2.3.2", "2.3.3", "2.4.1", "2.4.2"); + assertThat(merged.getVersioning().getSnapshotVersions()).hasSize(2); + assertThat(merged.getVersioning().getSnapshotVersions().get(0).getExtension()).isNotNull(); + assertThat(merged.getVersioning().getSnapshotVersions().get(0).getValue()).isNotNull(); + assertThat(merged.getVersioning().getSnapshotVersions().get(0).getUpdated()).isNotNull(); + } - var metadataPaths = new AtomicInteger(0); - releaseRepo.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - MockResponse response = new MockResponse().setResponseCode(404); - if (request.getPath() != null && request.getPath().contains("maven-metadata")) { - metadataPaths.incrementAndGet(); - } - return response; - } - }); + @Test + void skipsLocalInvalidArtifactsMissingJar(@TempDir Path localRepository) throws IOException { + Path localArtifact = localRepository.resolve("com/bad/bad-artifact"); + assertThat(localArtifact.toFile().mkdirs()).isTrue(); + Files.createDirectories(localArtifact.resolve("1")); - releaseRepo.start(); - snapshotRepo.start(); + Path localPom = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.pom"); + Files.writeString(localPom, + //language=xml + """ + + com.bad + bad-artifact + 1 + + """ + ); + + MavenRepository mavenLocal = MavenRepository.builder() + .id("local") + .uri(localRepository.toUri().toString()) + .snapshots(false) + .knownToExist(true) + .build(); + + // Does not return invalid dependency. + assertThrows(MavenDownloadingException.class, () -> + new MavenPomDownloader(emptyMap(), ctx) + .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); + } + + @Test + void skipsLocalInvalidArtifactsEmptyJar(@TempDir Path localRepository) throws IOException { + Path localArtifact = localRepository.resolve("com/bad/bad-artifact"); + assertThat(localArtifact.toFile().mkdirs()).isTrue(); + Files.createDirectories(localArtifact.resolve("1")); - MavenParser.builder().build().parse(ctx, + Path localPom = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.pom"); + Files.writeString(localPom, //language=xml """ - - 4.0.0 - - org.openrewrite.test - foo - 0.1.0-SNAPSHOT - - - - snapshot - - true - - http://%s:%d - - - release - - false - - http://%s:%d - - - - - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - - - """.formatted(snapshotRepo.getHostName(), snapshotRepo.getPort(), releaseRepo.getHostName(), releaseRepo.getPort()) + + com.bad + bad-artifact + 1 + + """ ); + Path localJar = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.jar"); + Files.writeString(localJar, ""); + + MavenRepository mavenLocal = MavenRepository.builder() + .id("local") + .uri(localRepository.toUri().toString()) + .snapshots(false) + .knownToExist(true) + .build(); - assertThat(snapshotRepo.getRequestCount()).isGreaterThan(1); - assertThat(metadataPaths.get()).isEqualTo(0); - } catch (IOException e) { - throw new RuntimeException(e); + // Does not return invalid dependency. + assertThrows(MavenDownloadingException.class, () -> + new MavenPomDownloader(emptyMap(), ctx) + .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); } - } - @Test - void deriveMetaDataFromFileRepository(@TempDir Path repoPath) throws IOException, MavenDownloadingException { - Path fred = repoPath.resolve("fred/fred"); + @Test + void doNotRenameRepoForCustomMavenLocal(@TempDir Path tempDir) throws MavenDownloadingException, IOException { + GroupArtifactVersion gav = createArtifact(tempDir); + MavenExecutionContextView.view(ctx).setLocalRepository(MavenRepository.MAVEN_LOCAL_DEFAULT.withUri(tempDir.toUri().toString())); + var downloader = new MavenPomDownloader(emptyMap(), ctx); - for (String version : Arrays.asList("1.0.0", "1.1.0", "2.0.0")) { - Path versionPath = fred.resolve(version); - Files.createDirectories(versionPath); - Files.writeString(versionPath.resolve("fred-" + version + ".pom"), ""); + var result = downloader.download(gav, null, null, List.of()); + //noinspection DataFlowIssue + assertThat(result.getRepository()).isNotNull(); + assertThat(result.getRepository().getUri()).startsWith(tempDir.toUri().toString()); } - MavenRepository repository = MavenRepository.builder() - .id("file-based") - .uri(repoPath.toUri().toString()) - .knownToExist(true) - .deriveMetadataIfMissing(true) - .build(); - MavenMetadata metaData = new MavenPomDownloader(emptyMap(), ctx) - .downloadMetadata(new GroupArtifact("fred", "fred"), null, List.of(repository)); - assertThat(metaData.getVersioning().getVersions()).hasSize(3).containsAll(Arrays.asList("1.0.0", "1.1.0", "2.0.0")); - } + @Issue("https://github.com/openrewrite/rewrite/issues/4080") + @Test + void connectTimeout() { + var downloader = new MavenPomDownloader(ctx); + var gav = new GroupArtifactVersion("org.openrewrite", "rewrite-core", "7.0.0"); + var repos = singletonList(MavenRepository.builder() + .id("non-routable").uri("http://10.0.0.0/maven").knownToExist(true).build()); + + assertThatThrownBy(() -> downloader.download(gav, null, null, repos)) + .isInstanceOf(MavenDownloadingException.class) + .hasMessageContaining("rewrite-core") + .hasMessageContaining("10.0.0.0"); + } - @SuppressWarnings("ConstantConditions") - @Test - void mergeMetadata() throws IOException { - @Language("xml") String metadata1 = """ - - org.springframework.boot - spring-boot - - - 2.3.3 - 2.4.1 - 2.4.2 - - - 20220927.033510 - 223 - - - - pom.asc - 0.1.0-20220927.033510-223 - 20220927033510 - - - - - """; - - @Language("xml") String metadata2 = """ - - org.springframework.boot - spring-boot - - - 2.3.2 - 2.3.3 - - - 20210115.042754 - 180 - - - - pom.asc - 0.1.0-20210115.042754-180 - 20210115042754 - - - - - """; - - var m1 = MavenMetadata.parse(metadata1.getBytes()); - var m2 = MavenMetadata.parse(metadata2.getBytes()); - - var merged = new MavenPomDownloader(emptyMap(), ctx).mergeMetadata(m1, m2); - - assertThat(merged.getVersioning().getSnapshot().getTimestamp()).isEqualTo("20220927.033510"); - assertThat(merged.getVersioning().getSnapshot().getBuildNumber()).isEqualTo("223"); - assertThat(merged.getVersioning().getVersions()).hasSize(4).contains("2.3.2", "2.3.3", "2.4.1", "2.4.2"); - assertThat(merged.getVersioning().getSnapshotVersions()).hasSize(2); - assertThat(merged.getVersioning().getSnapshotVersions().get(0).getExtension()).isNotNull(); - assertThat(merged.getVersioning().getSnapshotVersions().get(0).getValue()).isNotNull(); - assertThat(merged.getVersioning().getSnapshotVersions().get(0).getUpdated()).isNotNull(); - } + private static GroupArtifactVersion createArtifact(Path repository) throws IOException { + Path target = repository.resolve(Paths.get("org", "openrewrite", "rewrite", "1.0.0")); + Path pom = target.resolve("rewrite-1.0.0.pom"); + Path jar = target.resolve("rewrite-1.0.0.jar"); + Files.createDirectories(target); + Files.createFile(pom); + Files.createFile(jar); - @Test - void skipsLocalInvalidArtifactsMissingJar(@TempDir Path localRepository) throws IOException { - Path localArtifact = localRepository.resolve("com/bad/bad-artifact"); - assertThat(localArtifact.toFile().mkdirs()).isTrue(); - Files.createDirectories(localArtifact.resolve("1")); - - Path localPom = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.pom"); - Files.writeString(localPom, - //language=xml - """ - - com.bad - bad-artifact - 1 - - """ - ); - - MavenRepository mavenLocal = MavenRepository.builder() - .id("local") - .uri(localRepository.toUri().toString()) - .snapshots(false) - .knownToExist(true) - .build(); + Files.write(pom, + //language=xml + """ + + org.openrewrite + rewrite + 1.0.0 + + """.getBytes()); + Files.write(jar, "I'm a jar".getBytes()); // empty jars get ignored + return new GroupArtifactVersion("org.openrewrite", "rewrite", "1.0.0"); + } - // Does not return invalid dependency. - assertThrows(MavenDownloadingException.class, () -> - new MavenPomDownloader(emptyMap(), ctx) - .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); - } + @Test + void shouldNotThrowExceptionForModulesInModulesWithRightProperty() { + var gav = new GroupArtifactVersion("test", "test2", "${test}"); + + Path pomPath = Paths.get("test/test2/pom.xml"); + Pom pom = Pom.builder() + .sourcePath(pomPath) + .repository(MAVEN_CENTRAL) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .parent(new Parent(new GroupArtifactVersion("test", "test", "${test}"), "../pom.xml")) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", "test", "test2", "7.0.0", null)) + .build(); - @Test - void skipsLocalInvalidArtifactsEmptyJar(@TempDir Path localRepository) throws IOException { - Path localArtifact = localRepository.resolve("com/bad/bad-artifact"); - assertThat(localArtifact.toFile().mkdirs()).isTrue(); - Files.createDirectories(localArtifact.resolve("1")); - - Path localPom = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.pom"); - Files.writeString(localPom, - //language=xml - """ - - com.bad - bad-artifact - 1 - - """ - ); - Path localJar = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.jar"); - Files.writeString(localJar, ""); - - MavenRepository mavenLocal = MavenRepository.builder() - .id("local") - .uri(localRepository.toUri().toString()) - .snapshots(false) - .knownToExist(true) - .build(); + ResolvedPom resolvedPom = ResolvedPom.builder() + .requested(pom) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .repositories(singletonList(MAVEN_CENTRAL)) + .build(); - // Does not return invalid dependency. - assertThrows(MavenDownloadingException.class, () -> - new MavenPomDownloader(emptyMap(), ctx) - .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); - } + Path pomPath2 = Paths.get("test/pom.xml"); + Pom pom2 = Pom.builder() + .sourcePath(pomPath) + .repository(MAVEN_CENTRAL) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .parent(new Parent(new GroupArtifactVersion("test", "root-test", "${test}"), "../pom.xml")) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", "test", "test", "7.0.0", null)) + .build(); - @Test - void doNotRenameRepoForCustomMavenLocal(@TempDir Path tempDir) throws MavenDownloadingException, IOException { - GroupArtifactVersion gav = createArtifact(tempDir); - MavenExecutionContextView.view(ctx).setLocalRepository(MavenRepository.MAVEN_LOCAL_DEFAULT.withUri(tempDir.toUri().toString())); - var downloader = new MavenPomDownloader(emptyMap(), ctx); - - var result = downloader.download(gav, null, null, List.of()); - //noinspection DataFlowIssue - assertThat(result.getRepository()).isNotNull(); - assertThat(result.getRepository().getUri()).startsWith(tempDir.toUri().toString()); - } - @Issue("https://github.com/openrewrite/rewrite/issues/4080") - @Test - void connectTimeout() { - var downloader = new MavenPomDownloader(ctx); - var gav = new GroupArtifactVersion("org.openrewrite", "rewrite-core", "7.0.0"); - var repos = singletonList(MavenRepository.builder() - .id("non-routable").uri("http://10.0.0.0/maven").knownToExist(true).build()); - - assertThatThrownBy(() -> downloader.download(gav, null, null, repos)) - .isInstanceOf(MavenDownloadingException.class) - .hasMessageContaining("rewrite-core") - .hasMessageContaining("10.0.0.0"); - } + Path parentPomPath = Paths.get("pom.xml"); + Pom parentPom = Pom.builder() + .sourcePath(parentPomPath) + .repository(MAVEN_CENTRAL) + .properties(singletonMap("test", "7.0.0")) + .parent(null) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", "test", "root-test", "7.0.0", null)) + .build(); - @CsvSource(textBlock = """ - https://repo1.maven.org/maven2/, https://repo1.maven.org/maven2/ - https://repo1.maven.org/maven2, https://repo1.maven.org/maven2/ - http://repo1.maven.org/maven2/, https://repo1.maven.org/maven2/ - - https://oss.sonatype.org/content/repositories/snapshots/, https://oss.sonatype.org/content/repositories/snapshots/ - https://artifactory.moderne.ninja/artifactory/moderne-public/, https://artifactory.moderne.ninja/artifactory/moderne-public/ - https://repo.maven.apache.org/maven2/, https://repo.maven.apache.org/maven2/ - https://jitpack.io/, https://jitpack.io/ - """) - @ParameterizedTest - void normalizeRepository(String originalUrl, String expectedUrl) throws Throwable { - MavenPomDownloader downloader = new MavenPomDownloader(new InMemoryExecutionContext()); - MavenRepository repository = new MavenRepository("id", originalUrl, null, null, null, null, null); - MavenRepository normalized = downloader.normalizeRepository(repository); - assertThat(normalized).isNotNull(); - assertThat(normalized.getUri()).isEqualTo(expectedUrl); - } + Map pomsByPath = new HashMap<>(); + pomsByPath.put(parentPomPath, parentPom); + pomsByPath.put(pomPath, pom); + pomsByPath.put(pomPath2, pom2); - private static GroupArtifactVersion createArtifact(Path repository) throws IOException { - Path target = repository.resolve(Paths.get("org", "openrewrite", "rewrite", "1.0.0")); - Path pom = target.resolve("rewrite-1.0.0.pom"); - Path jar = target.resolve("rewrite-1.0.0.jar"); - Files.createDirectories(target); - Files.createFile(pom); - Files.createFile(jar); - - Files.write(pom, - //language=xml - """ - - org.openrewrite - rewrite - 1.0.0 - - """.getBytes()); - Files.write(jar, "I'm a jar".getBytes()); // empty jars get ignored - return new GroupArtifactVersion("org.openrewrite", "rewrite", "1.0.0"); - } + String httpUrl = "http://%s.com".formatted(UUID.randomUUID()); + MavenRepository nonexistentRepo = new MavenRepository("repo", httpUrl, null, null, false, null, null, null, null); - @Test - void shouldNotThrowExceptionForModulesInModulesWithRightProperty() { - var gav = new GroupArtifactVersion("test", "test2", "${test}"); - - Path pomPath = Paths.get("test/test2/pom.xml"); - Pom pom = Pom.builder() - .sourcePath(pomPath) - .repository(MAVEN_CENTRAL) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .parent(new Parent(new GroupArtifactVersion("test", "test", "${test}"), "../pom.xml")) - .gav(new ResolvedGroupArtifactVersion( - "${REPO_URL}", "test", "test2", "7.0.0", null)) - .build(); + MavenPomDownloader downloader = new MavenPomDownloader(pomsByPath, ctx); - ResolvedPom resolvedPom = ResolvedPom.builder() - .requested(pom) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .repositories(singletonList(MAVEN_CENTRAL)) - .build(); + assertDoesNotThrow(() -> downloader.download(gav, Objects.requireNonNull(pom.getParent()).getRelativePath(), resolvedPom, singletonList(nonexistentRepo))); + } - Path pomPath2 = Paths.get("test/pom.xml"); - Pom pom2 = Pom.builder() - .sourcePath(pomPath) - .repository(MAVEN_CENTRAL) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .parent(new Parent(new GroupArtifactVersion("test", "root-test", "${test}"), "../pom.xml")) - .gav(new ResolvedGroupArtifactVersion( - "${REPO_URL}", "test", "test", "7.0.0", null)) - .build(); + @Test + void shouldThrowExceptionForModulesInModulesWithNoRightProperty() { + var gav = new GroupArtifactVersion("test", "test2", "${test}"); + + Path pomPath = Paths.get("test/test2/pom.xml"); + Pom pom = Pom.builder() + .sourcePath(pomPath) + .repository(MAVEN_CENTRAL) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .parent(new Parent(new GroupArtifactVersion("test", "test", "${test}"), "../pom.xml")) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", "test", "test2", "7.0.0", null)) + .build(); + ResolvedPom resolvedPom = ResolvedPom.builder() + .requested(pom) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .repositories(singletonList(MAVEN_CENTRAL)) + .build(); - Path parentPomPath = Paths.get("pom.xml"); - Pom parentPom = Pom.builder() - .sourcePath(parentPomPath) - .repository(MAVEN_CENTRAL) - .properties(singletonMap("test", "7.0.0")) - .parent(null) - .gav(new ResolvedGroupArtifactVersion( - "${REPO_URL}", "test", "root-test", "7.0.0", null)) - .build(); + Path pomPath2 = Paths.get("test/pom.xml"); + Pom pom2 = Pom.builder() + .sourcePath(pomPath) + .repository(MAVEN_CENTRAL) + .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) + .parent(new Parent(new GroupArtifactVersion("test", "root-test", "${test}"), "../pom.xml")) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", "test", "test", "7.0.0", null)) + .build(); - Map pomsByPath = new HashMap<>(); - pomsByPath.put(parentPomPath, parentPom); - pomsByPath.put(pomPath, pom); - pomsByPath.put(pomPath2, pom2); - String httpUrl = "http://%s.com".formatted(UUID.randomUUID()); - MavenRepository nonexistentRepo = new MavenRepository("repo", httpUrl, null, null, false, null, null, null, null); + Path parentPomPath = Paths.get("pom.xml"); + Pom parentPom = Pom.builder() + .sourcePath(parentPomPath) + .repository(MAVEN_CENTRAL) + .properties(singletonMap("tt", "7.0.0")) + .parent(null) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", "test", "root-test", "7.0.0", null)) + .build(); + + Map pomsByPath = new HashMap<>(); + pomsByPath.put(parentPomPath, parentPom); + pomsByPath.put(pomPath, pom); + pomsByPath.put(pomPath2, pom2); - MavenPomDownloader downloader = new MavenPomDownloader(pomsByPath, ctx); + String httpUrl = "http://%s.com".formatted(UUID.randomUUID()); + MavenRepository nonexistentRepo = new MavenRepository("repo", httpUrl, null, null, false, null, null, null, null); - assertDoesNotThrow(() -> downloader.download(gav, Objects.requireNonNull(pom.getParent()).getRelativePath(), resolvedPom, singletonList(nonexistentRepo))); + MavenPomDownloader downloader = new MavenPomDownloader(pomsByPath, ctx); + + assertThrows(IllegalArgumentException.class, () -> downloader.download(gav, Objects.requireNonNull(pom.getParent()).getRelativePath(), resolvedPom, singletonList(nonexistentRepo))); + } } - @Test - void shouldThrowExceptionForModulesInModulesWithNoRightProperty() { - var gav = new GroupArtifactVersion("test", "test2", "${test}"); - - Path pomPath = Paths.get("test/test2/pom.xml"); - Pom pom = Pom.builder() - .sourcePath(pomPath) - .repository(MAVEN_CENTRAL) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .parent(new Parent(new GroupArtifactVersion("test", "test", "${test}"), "../pom.xml")) - .gav(new ResolvedGroupArtifactVersion( - "${REPO_URL}", "test", "test2", "7.0.0", null)) - .build(); + @Nested + class WithOkHttpClientAndSelfSignedTLS { + private final SSLSocketFactory sslSocketFactory; + private final ExecutionContext ctx; - ResolvedPom resolvedPom = ResolvedPom.builder() - .requested(pom) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .repositories(singletonList(MAVEN_CENTRAL)) - .build(); + WithOkHttpClientAndSelfSignedTLS() throws UnknownHostException { + String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); + HeldCertificate localhostCertificate = new HeldCertificate.Builder() + .addSubjectAlternativeName(localhost) + .build(); + sslSocketFactory = new HandshakeCertificates.Builder() + .heldCertificate(localhostCertificate) + .build().sslSocketFactory(); - Path pomPath2 = Paths.get("test/pom.xml"); - Pom pom2 = Pom.builder() - .sourcePath(pomPath) - .repository(MAVEN_CENTRAL) - .properties(singletonMap("REPO_URL", MAVEN_CENTRAL.getUri())) - .parent(new Parent(new GroupArtifactVersion("test", "root-test", "${test}"), "../pom.xml")) - .gav(new ResolvedGroupArtifactVersion( - "${REPO_URL}", "test", "test", "7.0.0", null)) - .build(); + HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder() + .addTrustedCertificate(localhostCertificate.certificate()) + .build(); + OkHttpClient client = new OkHttpClient.Builder() + .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()) + .connectTimeout(Duration.ofMillis(250)) + .readTimeout(Duration.ofMillis(250)) + .build(); + ctx = HttpSenderExecutionContextView.view(new InMemoryExecutionContext()) + .setHttpSender(new OkHttpSender(client)); + } + private MockWebServer getMockServer() { + var mockWebServer = new MockWebServer(); + mockWebServer.useHttps(sslSocketFactory, false); + return mockWebServer; + } - Path parentPomPath = Paths.get("pom.xml"); - Pom parentPom = Pom.builder() - .sourcePath(parentPomPath) - .repository(MAVEN_CENTRAL) - .properties(singletonMap("tt", "7.0.0")) - .parent(null) - .gav(new ResolvedGroupArtifactVersion( - "${REPO_URL}", "test", "root-test", "7.0.0", null)) - .build(); + private void mockServer(Integer responseCode, Consumer block) { + try (MockWebServer mockRepo = getMockServer()) { + mockRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + return new MockResponse().setResponseCode(responseCode).setBody(""); + } + }); + mockRepo.start(); + block.accept(mockRepo); + assertThat(mockRepo.getRequestCount()) + .as("The mock repository received no requests. The test is not using it.") + .isGreaterThan(0); + } catch (IOException e) { + throw new RuntimeException(e); + } + } - Map pomsByPath = new HashMap<>(); - pomsByPath.put(parentPomPath, parentPom); - pomsByPath.put(pomPath, pom); - pomsByPath.put(pomPath2, pom2); + @Test + void useHttpsWhenAvailable() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + mockServer(200, mockRepo -> { + var normalizedRepository = downloader.normalizeRepository( + MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven/".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .build(), + MavenExecutionContextView.view(ctx), + null); + + assertThat(normalizedRepository).isEqualTo( + MavenRepository.builder() + .id("id") + .uri("https://%s:%d/maven/".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .build()); + }); + } - String httpUrl = "http://%s.com".formatted(UUID.randomUUID()); - MavenRepository nonexistentRepo = new MavenRepository("repo", httpUrl, null, null, false, null, null, null, null); + @ParameterizedTest + @ValueSource(ints = {500, 400}) + void normalizeAcceptErrorStatuses(Integer status) { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + mockServer(status, mockRepo -> { + var originalRepo = MavenRepository.builder() + .id("id") + .uri("https://%s:%d/maven/".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .build(); + var normalizedRepo = downloader.normalizeRepository(originalRepo, MavenExecutionContextView.view(ctx), null); + assertThat(normalizedRepo).isEqualTo(originalRepo); + }); + } - MavenPomDownloader downloader = new MavenPomDownloader(pomsByPath, ctx); + @Test + void invalidArtifact() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); + mockServer(500, + repo1 -> mockServer(400, repo2 -> { + var repositories = List.of( + MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven".formatted(repo1.getHostName(), repo1.getPort())) + .build(), + MavenRepository.builder() + .id("id2") + .uri("http://%s:%d/maven".formatted(repo2.getHostName(), repo2.getPort())) + .build() + ); + + assertThatThrownBy(() -> downloader.download(gav, null, null, repositories)) + .isInstanceOf(MavenDownloadingException.class) + .hasMessageContaining("https://%s:%d/maven".formatted(repo1.getHostName(), repo1.getPort())) + .hasMessageContaining("https://%s:%d/maven".formatted(repo2.getHostName(), repo2.getPort())); + }) + ); + } - assertThrows(IllegalArgumentException.class, () -> downloader.download(gav, Objects.requireNonNull(pom.getParent()).getRelativePath(), resolvedPom, singletonList(nonexistentRepo))); - } + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3152") + void useSnapshotTimestampVersion() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + var gav = new GroupArtifactVersion("fred", "fred", "2020.0.2-20210127.131051-2"); + try (MockWebServer mockRepo = getMockServer()) { + mockRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + assert recordedRequest.getPath() != null; + return !recordedRequest.getPath().endsWith("fred/fred/2020.0.2-SNAPSHOT/fred-2020.0.2-20210127.131051-2.pom") ? + new MockResponse().setResponseCode(404).setBody("") : + new MockResponse().setResponseCode(200).setBody( + //language=xml + """ + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + """); + } + }); + mockRepo.start(); + var repositories = List.of(MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .username("user") + .password("pass") + .build()); + + assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Test + void usesAnonymousRequestIfRepositoryRejectsCredentials() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); + try (MockWebServer mockRepo = getMockServer()) { + mockRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + return recordedRequest.getHeaders().get("Authorization") != null ? + new MockResponse().setResponseCode(401).setBody("") : + new MockResponse().setResponseCode(200).setBody( + //language=xml + """ + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + """); + } + }); + mockRepo.start(); + var repositories = List.of(MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .username("user") + .password("pass") + .build()); + + assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + void usesAuthenticationIfRepositoryHasCredentials() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); + try (MockWebServer mockRepo = getMockServer()) { + mockRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + MockResponse response = new MockResponse(); + if (recordedRequest.getHeaders().get("Authorization") != null) { + response.setResponseCode(200); + if (!"HEAD".equalsIgnoreCase(recordedRequest.getMethod())) { + response.setBody( + //language=xml + """ + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + """); + } + } else { + response.setResponseCode(401); + } + return response; + } + }); + mockRepo.start(); + var repositories = List.of(MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .username("user") + .password("pass") + .build()); + + assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("When username or password are environment properties that cannot be resolved, they should not be used") + @Issue("https://github.com/openrewrite/rewrite/issues/3142") + void doesNotUseAuthenticationIfCredentialsCannotBeResolved() { + var downloader = new MavenPomDownloader(emptyMap(), ctx); + var gav = new GroupArtifactVersion("fred", "fred", "1.0.0"); + try (MockWebServer mockRepo = getMockServer()) { + mockRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + MockResponse response = new MockResponse(); + if (recordedRequest.getHeaders().get("Authorization") != null) { + response.setResponseCode(401); + } else if (recordedRequest.getMethod() == null || !recordedRequest.getMethod().equalsIgnoreCase("HEAD")) { + response.setBody( + //language=xml + """ + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + """); + } + return response; + } + }); + mockRepo.start(); + var repositories = List.of(MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .username("${env.ARTIFACTORY_USERNAME}") + .password("${env.ARTIFACTORY_USERNAME}") + .build()); + + assertDoesNotThrow(() -> downloader.download(gav, null, null, repositories)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } }