| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.maven.project; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ForkJoinPool; |
| import java.util.concurrent.ForkJoinTask; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.maven.api.model.Dependency; |
| import org.apache.maven.api.model.Model; |
| import org.apache.maven.api.model.Parent; |
| import org.apache.maven.api.model.Repository; |
| import org.apache.maven.internal.impl.resolver.ArtifactDescriptorUtils; |
| import org.apache.maven.model.building.ArtifactModelSource; |
| import org.apache.maven.model.building.FileModelSource; |
| import org.apache.maven.model.building.ModelSource; |
| import org.apache.maven.model.resolution.InvalidRepositoryException; |
| import org.apache.maven.model.resolution.ModelResolver; |
| import org.apache.maven.model.resolution.UnresolvableModelException; |
| import org.eclipse.aether.RepositorySystem; |
| import org.eclipse.aether.RepositorySystemSession; |
| import org.eclipse.aether.RequestTrace; |
| import org.eclipse.aether.artifact.Artifact; |
| import org.eclipse.aether.artifact.DefaultArtifact; |
| import org.eclipse.aether.impl.RemoteRepositoryManager; |
| import org.eclipse.aether.repository.RemoteRepository; |
| import org.eclipse.aether.resolution.ArtifactRequest; |
| import org.eclipse.aether.resolution.ArtifactResolutionException; |
| import org.eclipse.aether.resolution.VersionRangeRequest; |
| import org.eclipse.aether.resolution.VersionRangeResolutionException; |
| import org.eclipse.aether.resolution.VersionRangeResult; |
| |
| /** |
| * A model resolver to assist building of projects. This resolver gives priority to those repositories that have been |
| * declared in the POM. |
| * |
| */ |
| public class ProjectModelResolver implements ModelResolver { |
| |
| private static final int MAX_CAP = 0x7fff; |
| |
| private final RepositorySystemSession session; |
| |
| private final RequestTrace trace; |
| |
| private final String context = "project"; |
| |
| private List<RemoteRepository> repositories; |
| |
| private List<RemoteRepository> pomRepositories; |
| |
| private final List<RemoteRepository> externalRepositories; |
| |
| private final RepositorySystem resolver; |
| |
| private final RemoteRepositoryManager remoteRepositoryManager; |
| |
| private final Set<String> repositoryIds; |
| |
| private final ReactorModelPool modelPool; |
| |
| private final ProjectBuildingRequest.RepositoryMerging repositoryMerging; |
| |
| private final Map<String, Future<Result>> parentCache; |
| |
| public ProjectModelResolver( |
| RepositorySystemSession session, |
| RequestTrace trace, |
| RepositorySystem resolver, |
| RemoteRepositoryManager remoteRepositoryManager, |
| List<RemoteRepository> repositories, |
| ProjectBuildingRequest.RepositoryMerging repositoryMerging, |
| ReactorModelPool modelPool, |
| Map<String, Object> parentCache) { |
| this.session = session; |
| this.trace = trace; |
| this.resolver = resolver; |
| this.remoteRepositoryManager = remoteRepositoryManager; |
| this.pomRepositories = new ArrayList<>(); |
| this.externalRepositories = Collections.unmodifiableList(new ArrayList<>(repositories)); |
| this.repositories = new ArrayList<>(); |
| this.repositories.addAll(externalRepositories); |
| this.repositoryMerging = repositoryMerging; |
| this.repositoryIds = new HashSet<>(); |
| this.modelPool = modelPool; |
| this.parentCache = parentCache != null ? (Map) parentCache : new ConcurrentHashMap<>(); |
| } |
| |
| private ProjectModelResolver(ProjectModelResolver original) { |
| this.session = original.session; |
| this.trace = original.trace; |
| this.resolver = original.resolver; |
| this.remoteRepositoryManager = original.remoteRepositoryManager; |
| this.pomRepositories = new ArrayList<>(original.pomRepositories); |
| this.externalRepositories = original.externalRepositories; |
| this.repositories = new ArrayList<>(original.repositories); |
| this.repositoryMerging = original.repositoryMerging; |
| this.repositoryIds = new HashSet<>(original.repositoryIds); |
| this.modelPool = original.modelPool; |
| this.parentCache = original.parentCache; |
| } |
| |
| public void addRepository(Repository repository) throws InvalidRepositoryException { |
| addRepository(repository, false); |
| } |
| |
| @Override |
| public void addRepository(final Repository repository, boolean replace) throws InvalidRepositoryException { |
| if (!repositoryIds.add(repository.getId())) { |
| if (!replace) { |
| return; |
| } |
| |
| // Remove any previous repository with this Id |
| removeMatchingRepository(repositories, repository.getId()); |
| removeMatchingRepository(pomRepositories, repository.getId()); |
| } |
| |
| List<RemoteRepository> newRepositories = |
| Collections.singletonList(ArtifactDescriptorUtils.toRemoteRepository(repository)); |
| |
| if (ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT.equals(repositoryMerging)) { |
| repositories = remoteRepositoryManager.aggregateRepositories(session, repositories, newRepositories, true); |
| } else { |
| pomRepositories = |
| remoteRepositoryManager.aggregateRepositories(session, pomRepositories, newRepositories, true); |
| repositories = remoteRepositoryManager.aggregateRepositories( |
| session, pomRepositories, externalRepositories, false); |
| } |
| } |
| |
| private static void removeMatchingRepository(Iterable<RemoteRepository> repositories, final String id) { |
| Iterator<RemoteRepository> iterator = repositories.iterator(); |
| while (iterator.hasNext()) { |
| RemoteRepository next = iterator.next(); |
| if (next.getId().equals(id)) { |
| iterator.remove(); |
| } |
| } |
| } |
| |
| public ModelResolver newCopy() { |
| return new ProjectModelResolver(this); |
| } |
| |
| public ModelSource resolveModel(String groupId, String artifactId, String version) |
| throws UnresolvableModelException { |
| Artifact pomArtifact = new DefaultArtifact(groupId, artifactId, "", "pom", version); |
| |
| try { |
| ArtifactRequest request = new ArtifactRequest(pomArtifact, repositories, context); |
| request.setTrace(trace); |
| pomArtifact = resolver.resolveArtifact(session, request).getArtifact(); |
| } catch (ArtifactResolutionException e) { |
| throw new UnresolvableModelException(e.getMessage(), groupId, artifactId, version, e); |
| } |
| |
| return new ArtifactModelSource(pomArtifact.getFile(), groupId, artifactId, version); |
| } |
| |
| record Result(ModelSource source, Parent parent, Exception e) {} |
| |
| @Override |
| public ModelSource resolveModel(final Parent parent, AtomicReference<Parent> modified) |
| throws UnresolvableModelException { |
| Result result; |
| try { |
| Future<Result> future = parentCache.computeIfAbsent(parent.getId(), id -> { |
| ForkJoinPool pool = new ForkJoinPool(MAX_CAP); |
| ForkJoinTask<Result> task = new ForkJoinTask<>() { |
| Result result; |
| |
| @Override |
| public Result getRawResult() { |
| return result; |
| } |
| |
| @Override |
| protected void setRawResult(Result result) { |
| this.result = result; |
| } |
| |
| @Override |
| protected boolean exec() { |
| try { |
| AtomicReference<Parent> modified = new AtomicReference<>(); |
| ModelSource source = doResolveModel(parent, modified); |
| result = new Result(source, modified.get(), null); |
| } catch (Exception e) { |
| result = new Result(null, null, e); |
| } finally { |
| pool.shutdown(); |
| } |
| return true; |
| } |
| }; |
| pool.submit(task); |
| return task; |
| }); |
| result = future.get(); |
| } catch (Exception e) { |
| throw new UnresolvableModelException(e, parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); |
| } |
| if (result.e != null) { |
| uncheckedThrow(result.e); |
| return null; |
| } else { |
| if (result.parent != null && modified != null) { |
| modified.set(result.parent); |
| } |
| return result.source; |
| } |
| } |
| |
| static <T extends Throwable> void uncheckedThrow(Throwable t) throws T { |
| throw (T) t; // rely on vacuous cast |
| } |
| |
| private ModelSource doResolveModel(Parent parent, AtomicReference<Parent> modified) |
| throws UnresolvableModelException { |
| try { |
| final Artifact artifact = |
| new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion()); |
| |
| final VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, repositories, context); |
| versionRangeRequest.setTrace(trace); |
| |
| final VersionRangeResult versionRangeResult = resolver.resolveVersionRange(session, versionRangeRequest); |
| |
| if (versionRangeResult.getHighestVersion() == null) { |
| throw new UnresolvableModelException( |
| String.format( |
| "No versions matched the requested parent version range '%s'", parent.getVersion()), |
| parent.getGroupId(), |
| parent.getArtifactId(), |
| parent.getVersion()); |
| } |
| |
| if (versionRangeResult.getVersionConstraint() != null |
| && versionRangeResult.getVersionConstraint().getRange() != null |
| && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) { |
| // Message below is checked for in the MNG-2199 core IT. |
| throw new UnresolvableModelException( |
| String.format( |
| "The requested parent version range '%s' does not specify an upper bound", |
| parent.getVersion()), |
| parent.getGroupId(), |
| parent.getArtifactId(), |
| parent.getVersion()); |
| } |
| |
| String newVersion = versionRangeResult.getHighestVersion().toString(); |
| if (!parent.getVersion().equals(newVersion)) { |
| modified.set(parent.withVersion(newVersion)); |
| } |
| |
| return resolveModel(parent.getGroupId(), parent.getArtifactId(), newVersion); |
| } catch (final VersionRangeResolutionException e) { |
| throw new UnresolvableModelException( |
| e.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), e); |
| } |
| } |
| |
| @Override |
| public ModelSource resolveModel(final Dependency dependency, AtomicReference<Dependency> modified) |
| throws UnresolvableModelException { |
| try { |
| final Artifact artifact = new DefaultArtifact( |
| dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion()); |
| |
| final VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, repositories, context); |
| versionRangeRequest.setTrace(trace); |
| |
| final VersionRangeResult versionRangeResult = resolver.resolveVersionRange(session, versionRangeRequest); |
| |
| if (versionRangeResult.getHighestVersion() == null) { |
| throw new UnresolvableModelException( |
| String.format( |
| "No versions matched the requested dependency version range '%s'", |
| dependency.getVersion()), |
| dependency.getGroupId(), |
| dependency.getArtifactId(), |
| dependency.getVersion()); |
| } |
| |
| if (versionRangeResult.getVersionConstraint() != null |
| && versionRangeResult.getVersionConstraint().getRange() != null |
| && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) { |
| // Message below is checked for in the MNG-4463 core IT. |
| throw new UnresolvableModelException( |
| String.format( |
| "The requested dependency version range '%s' does not specify an upper bound", |
| dependency.getVersion()), |
| dependency.getGroupId(), |
| dependency.getArtifactId(), |
| dependency.getVersion()); |
| } |
| |
| String newVersion = versionRangeResult.getHighestVersion().toString(); |
| if (!dependency.getVersion().equals(newVersion)) { |
| modified.set(dependency.withVersion(newVersion)); |
| } |
| |
| if (modelPool != null) { |
| Model model = modelPool.get(dependency.getGroupId(), dependency.getArtifactId(), newVersion); |
| |
| if (model != null) { |
| return new FileModelSource(model.getPomFile()); |
| } |
| } |
| |
| return resolveModel(dependency.getGroupId(), dependency.getArtifactId(), newVersion); |
| } catch (VersionRangeResolutionException e) { |
| throw new UnresolvableModelException( |
| e.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), e); |
| } |
| } |
| |
| @Override |
| public ModelSource resolveModel(org.apache.maven.model.Parent parent) throws UnresolvableModelException { |
| AtomicReference<org.apache.maven.api.model.Parent> resolvedParent = new AtomicReference<>(); |
| ModelSource result = resolveModel(parent.getDelegate(), resolvedParent); |
| if (resolvedParent.get() != null) { |
| parent.setVersion(resolvedParent.get().getVersion()); |
| } |
| return result; |
| } |
| |
| @Override |
| public ModelSource resolveModel(org.apache.maven.model.Dependency dependency) throws UnresolvableModelException { |
| AtomicReference<org.apache.maven.api.model.Dependency> resolvedDependency = new AtomicReference<>(); |
| ModelSource result = resolveModel(dependency.getDelegate(), resolvedDependency); |
| if (resolvedDependency.get() != null) { |
| dependency.setVersion(resolvedDependency.get().getVersion()); |
| } |
| return result; |
| } |
| |
| @Override |
| public void addRepository(org.apache.maven.model.Repository repository) throws InvalidRepositoryException { |
| addRepository(repository.getDelegate()); |
| } |
| |
| @Override |
| public void addRepository(org.apache.maven.model.Repository repository, boolean replace) |
| throws InvalidRepositoryException { |
| addRepository(repository.getDelegate(), replace); |
| } |
| } |