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

org.simplify4u.plugins.ArtifactResolver Maven / Gradle / Ivy

/*
 * Copyright 2019-2021 Slawomir Jaranowski
 * Portions Copyright 2019-2020 Danny van Heumen
 *
 * 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.simplify4u.plugins;

import javax.inject.Inject;
import javax.inject.Named;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.vavr.control.Try;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.ModelBase;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.model.Reporting;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.RequestTrace;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.util.artifact.SubArtifact;
import org.simplify4u.plugins.skipfilters.SkipFilter;
import org.simplify4u.plugins.utils.MavenCompilerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Objects.requireNonNull;
import static org.simplify4u.plugins.utils.MavenCompilerUtils.extractAnnotationProcessors;

/**
 * Artifact resolver for project dependencies, build plug-ins, and build plug-in dependencies.
 */
@Named
public class ArtifactResolver {

    private static final Logger LOG = LoggerFactory.getLogger(ArtifactResolver.class);

    private static final VersionRange SUREFIRE_PLUGIN_VERSION_RANGE = Try.of(
                    () -> VersionRange.createFromVersionSpec("(2.999999999,4)"))
            .getOrElseThrow(e -> new IllegalStateException("BUG: Failed to create version range.", e));

    private final RepositorySystem repositorySystem;

    private final RepositorySystemSession repositorySession;

    private final List remoteProjectRepositories;

    private final List remotePluginRepositories;

    private final ArtifactHandlerManager artifactHandlerManager;

    /**
     * Copy of remote repositories with check sum policy set to ignore, we need it for pgp signature resolving.
     * 

* pgp signature *.asc is signature so there is'n signature for signature */ private final List remoteRepositoriesIgnoreCheckSum; @Inject ArtifactResolver(MavenSession session, RepositorySystem repositorySystem, ArtifactHandlerManager artifactHandlerManager) { this.remoteProjectRepositories = requireNonNull(session.getCurrentProject().getRemoteProjectRepositories()); this.remotePluginRepositories = requireNonNull(session.getCurrentProject().getRemotePluginRepositories()); this.repositorySystem = requireNonNull(repositorySystem); this.repositorySession = requireNonNull(session.getRepositorySession()); this.remoteRepositoriesIgnoreCheckSum = repositoriesIgnoreCheckSum(remoteProjectRepositories, remotePluginRepositories); this.artifactHandlerManager = artifactHandlerManager; } /** * Wrap remote repository with ignore check sum policy. * * @param remoteProjectRepositories project repositories to wrap * @param remotePluginRepositories plugin repositories to wrap * @return wrapped repository list */ private List repositoriesIgnoreCheckSum(List remoteProjectRepositories, List remotePluginRepositories) { Stream remoteProjectRepositoryStream = Optional.ofNullable(remoteProjectRepositories) .orElse(Collections.emptyList()) .stream(); Stream remotePluginsRepositoryStream = Optional.ofNullable(remotePluginRepositories) .orElse(Collections.emptyList()) .stream(); List remoteRepositories = Stream.concat(remoteProjectRepositoryStream, remotePluginsRepositoryStream) .map(ArtifactResolver::repositoryIgnoreCheckSum) .collect(Collectors.toList()); return repositorySystem.newResolutionRepositories(repositorySession, remoteRepositories); } private static RemoteRepository repositoryIgnoreCheckSum(RemoteRepository repository) { RepositoryPolicy snapshotPolicy = repository.getPolicy(true); RepositoryPolicy releasePolicy = repository.getPolicy(false); RemoteRepository.Builder builder = new RemoteRepository.Builder(repository); builder.setSnapshotPolicy(policyIgnoreCheckSum(snapshotPolicy)); builder.setReleasePolicy(policyIgnoreCheckSum(releasePolicy)); return builder.build(); } private static RepositoryPolicy policyIgnoreCheckSum(RepositoryPolicy policy) { return new RepositoryPolicy(policy.isEnabled(), policy.getUpdatePolicy(), RepositoryPolicy.CHECKSUM_POLICY_IGNORE); } /** * Types of dependencies: compile, provided, test, runtime, system, maven-plugin. * * @param project the maven project instance * @param config configuration for the artifact resolver * @return Returns set of all artifacts whose signature needs to be verified. */ // @SuppressWarnings( {"deprecation", "java:S1874"}) Set resolveProjectArtifacts(MavenProject project, Configuration config) { final LinkedHashSet allArtifacts = new LinkedHashSet<>(resolveProjectArtifacts(project.getArtifacts(), config.dependencyFilter, config.verifyPomFiles)); if (config.verifyPlugins) { // Resolve transitive dependencies for build plug-ins and reporting plug-ins and their dependencies. // The transitive closure is computed for each plug-in with its dependencies, as individual executions may // depend on a different version of a particular dependency. Therefore, a dependency may be included in the // over-all artifacts list multiple times with different versions. for (final Plugin plugin : project.getBuildPlugins()) { List resolved = resolvePlugin(plugin, config); allArtifacts.addAll(resolved); } List reportPlugins = Optional.ofNullable(project.getModel()) .map(ModelBase::getReporting) .map(Reporting::getPlugins) .orElseGet(Collections::emptyList); for (ReportPlugin plugin : reportPlugins) { Plugin p = new Plugin(); p.setGroupId(plugin.getGroupId()); p.setArtifactId(plugin.getArtifactId()); p.setVersion(plugin.getVersion()); List resolved = resolvePlugin(p, config); allArtifacts.addAll(resolved); } } if (config.verifyAtypical) { // Verify artifacts in atypical locations, such as references in configuration. List artifacts = searchCompilerAnnotationProcessors(project); artifacts.forEach(a -> { Plugin p = new Plugin(); p.setGroupId(a.getGroupId()); p.setArtifactId(a.getArtifactId()); p.setVersion(a.getVersion()); List resolved = resolvePlugin(p, config); allArtifacts.addAll(resolved); }); informSurefire3RuntimeDependencyLoadingLimitation(project); } return allArtifacts; } private List resolvePlugin(Plugin plugin, Configuration config) { org.eclipse.aether.artifact.Artifact pArtifact = toArtifact(plugin); if (config.pluginFilter.shouldSkipArtifact(RepositoryUtils.toArtifact(pArtifact))) { return Collections.emptyList(); } List result; List pluginDependencies = plugin.getDependencies().stream() .filter(d -> !config.dependencyFilter.shouldSkipDependency(d, artifactHandlerManager)) .map(d -> RepositoryUtils.toDependency(d, repositorySession.getArtifactTypeRegistry())) .collect(Collectors.toList()); if (config.verifyPluginDependencies) { // we need resolve all transitive dependencies result = resolvePluginArtifactsTransitive(pArtifact, pluginDependencies, config.dependencyFilter, config.verifyPomFiles); } else { // only resolve plugin artifact List aeArtifacts = new ArrayList<>(); aeArtifacts.add(pArtifact); aeArtifacts.addAll(pluginDependencies.stream() .map(Dependency::getArtifact) .collect(Collectors.toList())); result = resolveArtifacts(aeArtifacts, remotePluginRepositories, config.verifyPomFiles); } return result.stream().map(RepositoryUtils::toArtifact).collect(Collectors.toList()); } private List resolvePluginArtifactsTransitive( org.eclipse.aether.artifact.Artifact artifact, List dependencies, SkipFilter dependencyFilter, boolean verifyPomFiles) { CollectRequest collectRequest = new CollectRequest(new Dependency(artifact, "runtime"), remotePluginRepositories); collectRequest.setDependencies(dependencies); DependencyRequest request = new DependencyRequest(collectRequest, null); DependencyResult dependencyResult = Try.of(() -> repositorySystem.resolveDependencies(repositorySession, request)) .recover(DependencyResolutionException.class, DependencyResolutionException::getResult) .get(); List result = new ArrayList<>(); dependencyResult.getRoot().accept(new DependencyVisitor() { @Override public boolean visitEnter(DependencyNode node) { if (node.getArtifact() != null && !dependencyFilter.shouldSkipDependency(node.getDependency())) { result.add(node.getArtifact()); } return true; } @Override public boolean visitLeave(DependencyNode node) { return true; } }); if (verifyPomFiles) { resolvePoms(result, remotePluginRepositories); } return result; } private List resolveArtifacts( List artifacts, List remoteRepositories, boolean verifyPomFiles) { List requestList = artifacts.stream() .map(a -> new ArtifactRequest(a, remoteRepositories, null)) .collect(Collectors.toList()); List result = new ArrayList<>(Try.of(() -> repositorySystem.resolveArtifacts(repositorySession, requestList)) .recover(ArtifactResolutionException.class, ArtifactResolutionException::getResults) .get() .stream() .map(aResult -> aResult.isResolved() ? aResult.getArtifact() : aResult.getRequest().getArtifact()) .collect(Collectors.toList())); if (verifyPomFiles) { resolvePoms(result, remoteRepositories); } return result; } private void resolvePoms(List result, List remoteRepositories) { List poms = result.stream().filter(a -> !"pom".equals(a.getExtension())) .map(a -> new SubArtifact(a, null, "pom")) .collect(Collectors.toList()); result.addAll(resolveArtifacts(poms, remoteRepositories, false)); } private static org.eclipse.aether.artifact.Artifact toArtifact(Plugin plugin) { String version = plugin.getVersion(); if (version == null || version.isEmpty()) { version = "RELEASE"; } return new DefaultArtifact(plugin.getGroupId(), plugin.getArtifactId(), "jar", version); } private static List searchCompilerAnnotationProcessors(MavenProject project) { return project.getBuildPlugins().stream() .filter(MavenCompilerUtils::checkCompilerPlugin) .flatMap(p -> extractAnnotationProcessors(p).stream()) .collect(Collectors.toList()); } /** * Inform user of limitation with respect to validation of maven-surefire-plugin. * *

* Maven's Surefire plug-in version 3 resolves and loads dependencies at run-time for unit testing frameworks that * are present in the project. The resolution for these dependencies happens at a later stage and therefore * pgpverify will not automagically detect their presence. * *

* For now, we inform the user of this limitation such that the user is informed until a better solution is * implemented. * *

* More information on what dependencies are resolved and loaded at execution time by maven-surefire-plugin, one * needs to run maven-surefire-plugin with debug output enabled. Surefire will list a "provider classpath" which is * dynamically composed out of the frameworks it detected. * *

* For example, in case the JUnit4 framework is present. *

     * [DEBUG] provider(compact) classpath:  surefire-junit47-3.0.0-M3.jar  surefire-api-3.0.0-M3.jar
     * surefire-logger-api-3.0.0-M3.jar  common-java5-3.0.0-M3.jar  common-junit3-3.0.0-M3.jar
     * common-junit4-3.0.0-M3.jar  common-junit48-3.0.0-M3.jar  surefire-grouper-3.0.0-M3.jar
     * 
* * @param project maven project instance */ // TODO: maven-surefire-plugin dependency loading during execution is detected but not handled. Some of surefire's // dependencies are not validated. private static void informSurefire3RuntimeDependencyLoadingLimitation(MavenProject project) { final boolean surefireDynamicLoadingLikely = project.getBuildPlugins().stream() .filter(p -> "org.apache.maven.plugins".equals(p.getGroupId())) .filter(p -> "maven-surefire-plugin".equals(p.getArtifactId())) .anyMatch(ArtifactResolver::matchSurefireVersion); if (surefireDynamicLoadingLikely) { LOG.info("NOTE: maven-surefire-plugin version 3 is present. This version is known to resolve " + "and load dependencies for various unit testing frameworks (called \"providers\") during " + "execution. These dependencies are not validated."); } } private static boolean matchSurefireVersion(Plugin plugin) { return Try.of(() -> new DefaultArtifactVersion(plugin.getVersion())) .map(SUREFIRE_PLUGIN_VERSION_RANGE::containsVersion) .onFailure(e -> LOG.debug("Found build plug-in with overly constrained version specification.", e)) .getOrElse(false); } /** * Retrieves the PGP signature file that corresponds to the given Maven artifact. * * @param artifacts The artifacts for which a signatures are desired. * @return Map artifact to signature */ Map resolveSignatures(Collection artifacts) { List requestList = new ArrayList<>(); artifacts.forEach(a -> { String version = a.getVersion(); if (version == null && a.getVersionRange() != null) { version = a.getVersionRange().toString(); } DefaultArtifact artifact = new DefaultArtifact(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getArtifactHandler().getExtension() + ".asc", version); ArtifactRequest request = new ArtifactRequest(artifact, remoteRepositoriesIgnoreCheckSum, null); request.setTrace(new RequestTrace(a)); requestList.add(request); }); Map result = new HashMap<>(); List artifactResults = Try.of(() -> repositorySystem.resolveArtifacts(repositorySession, requestList)) .recover(ArtifactResolutionException.class, ArtifactResolutionException::getResults) .get(); artifactResults.forEach(aResult -> { Artifact ascArtifact = RepositoryUtils.toArtifact(aResult.getArtifact()); Artifact artifact = (Artifact) aResult.getRequest().getTrace().getData(); if (!aResult.isResolved()) { aResult.getExceptions().forEach( e -> LOG.debug("Failed to resolve asc {}: {}", aResult.getRequest().getArtifact(), e.getMessage())); } result.put(artifact, ascArtifact); }); return result; } /** * Resolve all dependencies provided as input. POMs corresponding to the dependencies may optionally be resolved. * * @param artifacts Dependencies to be resolved. * @param artifactFilter Skip filter to test against to determine whether dependency must be skipped. * @param verifyPom Boolean indicating whether or not POMs corresponding to dependencies should be * resolved. * @return Returns set of resolved artifacts. */ private Set resolveProjectArtifacts(Iterable artifacts, SkipFilter artifactFilter, boolean verifyPom) { final LinkedHashSet collection = new LinkedHashSet<>(); for (Artifact artifact : artifacts) { if (artifactFilter.shouldSkipArtifact(artifact)) { LOG.debug("Skipping artifact: {}", artifact); continue; } org.eclipse.aether.artifact.Artifact aeArtifact = RepositoryUtils.toArtifact(artifact); collection.add(aeArtifact); if (verifyPom) { SubArtifact pomArtifact = new SubArtifact(aeArtifact, null, "pom"); collection.add(pomArtifact); } } List requestList = collection.stream() .map(a -> new ArtifactRequest(a, remoteProjectRepositories, null)) .collect(Collectors.toList()); List artifactResults = Try.of(() -> repositorySystem.resolveArtifacts(repositorySession, requestList)) .recover(ArtifactResolutionException.class, ArtifactResolutionException::getResults) .get(); Set result = new HashSet<>(); artifactResults.forEach(aResult -> { if (aResult.isResolved()) { result.add(RepositoryUtils.toArtifact(aResult.getArtifact())); } else { aResult.getExceptions().forEach( e -> LOG.debug("Failed to resolve {}: {}", aResult.getRequest().getArtifact(), e.getMessage())); } }); return result; } /** * Resolve given artifact. * * @param artifact - an artiact to resolve * @param verifyPomFiles - if true also pom will be resolved for artifact * @return an resolved artifact */ public List resolveArtifact(Artifact artifact, boolean verifyPomFiles) { List artifacts = resolveArtifacts(Collections.singletonList(RepositoryUtils.toArtifact(artifact)), remoteProjectRepositories, verifyPomFiles); return artifacts.stream().map(RepositoryUtils::toArtifact).collect(Collectors.toList()); } /** * Configuration struct for Artifact Resolver. */ public static final class Configuration { final SkipFilter dependencyFilter; final SkipFilter pluginFilter; final boolean verifyPomFiles; final boolean verifyPlugins; final boolean verifyPluginDependencies; final boolean verifyAtypical; /** * Constructor. * * @param dependencyFilter filter for evaluating dependencies * @param pluginFilter filter for evaluating plugins * @param verifyPomFiles verify POM files as well * @param verifyPlugins verify build plugins as well * @param verifyPluginDependencies verify all dependencies of build plug-ins. * @param verifyAtypical verify dependencies in a-typical locations, such as maven-compiler-plugin's */ public Configuration(SkipFilter dependencyFilter, SkipFilter pluginFilter, boolean verifyPomFiles, boolean verifyPlugins, boolean verifyPluginDependencies, boolean verifyAtypical) { this.dependencyFilter = requireNonNull(dependencyFilter); this.pluginFilter = requireNonNull(pluginFilter); this.verifyPomFiles = verifyPomFiles; this.verifyPlugins = verifyPlugins || verifyPluginDependencies; this.verifyPluginDependencies = verifyPluginDependencies; this.verifyAtypical = verifyAtypical; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy