All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.java.dependencies.DependencyResolutionDiagnostic Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2023 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 *

* https://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.openrewrite.java.dependencies; import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.java.dependencies.table.GradleDependencyConfigurationErrors; import org.openrewrite.java.dependencies.table.RepositoryAccessibilityReport; import org.openrewrite.marker.Markup; import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.MavenSettings; import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.tree.*; import java.io.UncheckedIOException; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.openrewrite.internal.StringUtils.isBlank; @Value @EqualsAndHashCode(callSuper = false) public class DependencyResolutionDiagnostic extends ScanningRecipe { transient RepositoryAccessibilityReport report = new RepositoryAccessibilityReport(this); transient GradleDependencyConfigurationErrors gradleErrors = new GradleDependencyConfigurationErrors(this); @Override public String getDisplayName() { return "Dependency resolution diagnostic"; } @Override public String getDescription() { //language=markdown return "Recipes which manipulate dependencies must be able to successfully access the artifact repositories " + "and resolve dependencies from them. This recipe produces two data tables used to understand the state " + "of dependency resolution. \n\n" + "The Repository accessibility report lists all the artifact repositories known to the project and whether " + "respond to network access. The network access is attempted while the recipe is run and so is " + "representative of current conditions. \n\n" + "The Gradle dependency configuration errors lists all the dependency configurations that failed to " + "resolve one or more dependencies when the project was parsed. This is representative of conditions at " + "the time the LST was parsed."; } @Option(displayName = "Group ID", description = "The group ID of a dependency to attempt to download from the repository. " + "Default value is \"com.fasterxml.jackson.core\". " + "If this dependency is not found in the repository the error will be noted in the report. " + "There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core.", example = "com.fasterxml.jackson.core", required = false) @Nullable String groupId; @Option(displayName = "Artifact ID", description = "The artifact ID of a dependency to attempt to download from the repository. " + "Default value is \"jackson-core\". " + "If this dependency is not found in the repository the error will be noted in the report. " + "There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core.", example = "jackson-core", required = false) @Nullable String artifactId; @Option(displayName = "Version", description = "The version of a dependency to attempt to download from the repository. " + "Default value is \"2.16.0\". " + "If this dependency is not found in the repository the error will be noted in the report. " + "There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core.", example = "2.16.0", required = false) @Nullable String version; public static class Accumulator { boolean foundGradle; Set repositoriesFromGradle = new HashSet<>(); boolean foundMaven; Set repositoriesFromMaven = new HashSet<>(); } @Override public Accumulator getInitialValue(ExecutionContext ctx) { return new Accumulator(); } @Override public TreeVisitor getScanner(Accumulator acc) { return new TreeVisitor() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (!(tree instanceof SourceFile)) { return null; } tree.getMarkers().findFirst(GradleProject.class).ifPresent(gp -> { acc.foundGradle = true; acc.repositoriesFromGradle.addAll(gp.getMavenRepositories()); acc.repositoriesFromGradle.addAll(gp.getMavenPluginRepositories()); }); tree.getMarkers().findFirst(MavenResolutionResult.class).ifPresent(mrr -> { acc.foundMaven = true; acc.repositoriesFromMaven.addAll(mrr.getPom().getRepositories()); }); return tree; } }; } @Override public Collection generate(Accumulator acc, ExecutionContext ctx) { Set seen = new HashSet<>(); if (acc.foundMaven) { record(true, acc.repositoriesFromMaven, seen, ctx); } if (acc.foundGradle) { record(false, acc.repositoriesFromGradle, seen, ctx); } return emptyList(); } private void record(boolean addMavenDefaultRepositories, Collection repos, Set seen, ExecutionContext ctx) { // Use MavenPomDownloader without any default repositories, so we can test exactly one repository at a time MavenPomDownloader mpd = new MavenPomDownloader(ctx); Collection effectiveRepos = repos; if (addMavenDefaultRepositories) { if (!effectiveRepos.contains(MavenRepository.MAVEN_LOCAL_DEFAULT)) { effectiveRepos = new ArrayList<>(effectiveRepos); effectiveRepos.add(MavenRepository.MAVEN_LOCAL_DEFAULT); } if (!effectiveRepos.contains(MavenRepository.MAVEN_CENTRAL)) { effectiveRepos = new ArrayList<>(effectiveRepos); effectiveRepos.add(MavenRepository.MAVEN_CENTRAL); } } MavenExecutionContextView mctx = MavenExecutionContextView.view(ctx); ResolutionEventListener resolutionListener = mctx.getResolutionListener(); try { for (MavenRepository repo : effectiveRepos) { if (seen.contains(noTrailingSlash(repo.getUri()))) { continue; } AtomicReference nullReason = new AtomicReference<>(); mctx.setResolutionListener(new ResolutionEventListener() { @Override public void repositoryAccessFailed(String uri, Throwable e) { nullReason.set(e); } }); MavenRepository normalized = mpd.normalizeRepository(repo, mctx, null); if (normalized == null) { Throwable reason = nullReason.get(); if (reason == null) { reason = new RuntimeException("Repository unreachable for unknown reason"); } MavenSettings settings = mctx.getSettings(); if (settings != null) { // normalizeRepository() internally applies mirrors,but normalizeRepository() just returned null. // Replicate mirror application so that the correct URL is recorded repo = MavenRepositoryMirror.apply(mctx.getMirrors(settings), repo); } if (seen.add(noTrailingSlash(repo.getUri()))) { report.insertRow(ctx, rowFor(repo, reason, null)); } } else { if (seen.add(noTrailingSlash(normalized.getUri()))) { GroupArtifactVersion gav = new GroupArtifactVersion( isBlank(groupId) ? "com.fasterxml.jackson.core" : groupId, isBlank(artifactId) ? "jackson-core" : artifactId, isBlank(version) ? "2.16.0" : version); Throwable resolutionThrowable = null; try { mpd.download(gav, null, null, singletonList(normalized)); } catch (Exception e) { resolutionThrowable = e; } report.insertRow(ctx, rowFor(normalized, null, resolutionThrowable)); } } } } finally { mctx.setResolutionListener(resolutionListener); } } private static String noTrailingSlash(String uri) { if (uri.endsWith("/")) { return uri.substring(0, uri.length() - 1); } return uri; } private static RepositoryAccessibilityReport.Row rowFor(MavenRepository repo, @Nullable Throwable pingThrowable, @Nullable Throwable resolveThrowable) { Integer pingHttpResponseCode = null; String pingExceptionClass = ""; String pingExceptionMessage = ""; if (pingThrowable instanceof MavenPomDownloader.HttpSenderResponseException) { pingHttpResponseCode = ((MavenPomDownloader.HttpSenderResponseException) pingThrowable).getResponseCode(); pingThrowable = pingThrowable.getCause(); } if (pingThrowable instanceof UncheckedIOException) { pingThrowable = pingThrowable.getCause(); } if (pingThrowable == null) { pingHttpResponseCode = 200; } else { pingExceptionClass = pingThrowable.getClass().getName(); pingExceptionMessage = pingThrowable.getMessage(); } String resolveExceptionClass = ""; String resolveExceptionMessage = ""; if (resolveThrowable instanceof MavenPomDownloader.HttpSenderResponseException) { resolveThrowable = resolveThrowable.getCause(); } if (resolveThrowable instanceof UncheckedIOException) { resolveThrowable = resolveThrowable.getCause(); } if (resolveThrowable != null) { resolveExceptionClass = resolveThrowable.getClass().getName(); resolveExceptionMessage = resolveThrowable.getMessage(); } return new RepositoryAccessibilityReport.Row(noTrailingSlash(repo.getUri()), pingExceptionClass, pingExceptionMessage, pingHttpResponseCode, resolveExceptionClass, resolveExceptionMessage); } @Override public TreeVisitor getVisitor(Accumulator acc) { GroovyIsoVisitor gv = new GroovyIsoVisitor() { @Override public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); if (!maybeGp.isPresent()) { return cu; } GradleProject gp = maybeGp.get(); G.CompilationUnit g = super.visitCompilationUnit(cu, ctx); for (GradleDependencyConfiguration conf : gp.getConfigurations()) { //noinspection ConstantValue if (conf.getExceptionType() == null) { continue; } gradleErrors.insertRow(ctx, new GradleDependencyConfigurationErrors.Row(gp.getPath(), conf.getName(), conf.getExceptionType(), conf.getMessage())); } return g; } }; return new TreeVisitor() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (!(tree instanceof SourceFile)) { return tree; } SourceFile s = (SourceFile) tree; if (s.getSourcePath().endsWith("build.gradle") && !s.getMarkers().findFirst(GradleProject.class).isPresent()) { if (s.getMarkers().getMarkers().stream().anyMatch(marker -> "org.openrewrite.gradle.marker.GradleProject".equals(marker.getClass().getName()))) { s = Markup.error(s, new IllegalStateException( s.getSourcePath() + " has a GradleProject marker, but it is loaded by a different classloader than the recipe.")); } else { s = Markup.warn(s, new IllegalStateException( s.getSourcePath() + " is a Gradle build file, but it is missing a GradleProject marker.")); } } else if (s.getSourcePath().endsWith("pom.xml") && !s.getMarkers().findFirst(MavenResolutionResult.class).isPresent()) { if (s.getMarkers().getMarkers().stream().anyMatch(marker -> "org.openrewrite.maven.tree.MavenResolutionResult".equals(marker.getClass().getName()))) { s = Markup.error(s, new IllegalStateException( s.getSourcePath() + " has a MavenResolutionResult marker, but it is loaded by a different classloader than the recipe.")); } else { s = Markup.warn(s, new IllegalStateException( s.getSourcePath() + " is a Maven pom, but it is missing a MavenResolutionResult marker.")); } } if (gv.isAcceptable(s, ctx)) { s = (SourceFile) gv.visit(s, ctx); } return s; } }; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy