Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#217] Include locally deployed artifact versions if they are available in remote as well #246

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.wildfly.channel.maven;

import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.DefaultMetadata;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.version.Version;
import org.jboss.logging.Logger;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

class RemoteArtifactVersionsFilter {

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

private final RepositorySystemSession session;
private final Artifact artifact;
private final List<RemoteRepository> repositories;
private final Set<String> remoteRepositoryIds;
private final VersionRangeResult versionRangeResult;

RemoteArtifactVersionsFilter(RepositorySystemSession session, VersionRangeResult versionRangeResult) {
this.session = session;
this.artifact = versionRangeResult.getRequest().getArtifact();
this.versionRangeResult = versionRangeResult;
this.repositories = versionRangeResult.getRequest().getRepositories();
this.remoteRepositoryIds = repositories.stream().map(RemoteRepository::getId).collect(Collectors.toSet());
}

/**
* rejects versions available only in the local repository
*
* @param version
* @return
*/
boolean accept(Version version) {
if (repositories.isEmpty()) {
return false;
}

// if the version was resolved from a remote repository, accept this version
if (versionRangeResult.getRepository(version) != null && remoteRepositoryIds.contains(versionRangeResult.getRepository(version).getId())) {
return true;
}

/*
* If the version was resolved from a local repository, it might be because the local version masks the
* remote repository (see DefaultVersionRangeResolver#getVersions).
*
* During the resolution, the versions from remote repositories are cached in maven-metadata-*.xml.
* We are checking those to see if any of the remote repositories contain the same version as local.
*/
for (RemoteRepository repository : repositories) {
final DefaultMetadata artifactMetadata = new DefaultMetadata(
artifact.getGroupId(),
artifact.getArtifactId(),
"maven-metadata.xml",
Metadata.Nature.RELEASE);
final String pathForRemoteMetadata = session.getLocalRepositoryManager().getPathForRemoteMetadata(artifactMetadata, repository, null);
final File metadataFile = new File(session.getLocalRepository().getBasedir(), pathForRemoteMetadata);

if (metadataFile.exists()) {
try {
final org.apache.maven.artifact.repository.metadata.Metadata metadata = new MetadataXpp3Reader().read(new FileReader(metadataFile));
if (metadata.getVersioning().getVersions().contains(version.toString())) {
return true;
}
} catch (IOException | XmlPullParserException e) {
LOG.warn("Failed to parse version information in " + metadataFile + ", skipping.", e);
}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,8 @@ public Set<String> getAllVersions(String groupId, String artifactId, String exte
Artifact artifact = new DefaultArtifact(groupId, artifactId, classifier, extension, "[0,)");
VersionRangeRequest versionRangeRequest = new VersionRangeRequest();
versionRangeRequest.setArtifact(artifact);
final Set<RemoteRepository> repos;
if (repositories != null) {
versionRangeRequest.setRepositories(repositories);
repos = new HashSet<>(repositories);
} else {
repos = null;
}

VersionRangeResult versionRangeResult = retryingResolver.attemptResolveMetadata(() -> {
Expand All @@ -160,9 +156,19 @@ public Set<String> getAllVersions(String groupId, String artifactId, String exte
}
}, attemptedRepositories());

return versionRangeResult.getVersions().stream()
.filter(v -> repos != null && repos.contains(versionRangeResult.getRepository(v)))
.map(Version::toString).collect(Collectors.toSet());
final RemoteArtifactVersionsFilter remoteOnlyFilter = new RemoteArtifactVersionsFilter(session, versionRangeResult);
final List<Version> remoteVersions = versionRangeResult.getVersions().stream()
.filter(remoteOnlyFilter::accept)
.collect(Collectors.toList());

if (!versionRangeResult.getVersions().isEmpty() && remoteVersions.isEmpty()) {
LOG.warnf("Error resolving artifact %s:%s versions: the only available version was resolved from local repository, discarding!",
artifact.getGroupId(), artifact.getArtifactId());
}

return remoteVersions.stream()
.map(Version::toString)
.collect(Collectors.toSet());

}

Expand All @@ -180,7 +186,13 @@ public File resolveArtifact(String groupId, String artifactId, String extension,
request.setRepositories(repositories);
}

return retryingResolver.attemptResolve(()->List.of(system.resolveArtifact(session, request).getArtifact().getFile()),
return retryingResolver.attemptResolve(()->{
final ArtifactResult artifactResult = system.resolveArtifact(session, request);
for (Exception exception : artifactResult.getExceptions()) {
LOG.info("Error resolving maven artifact: " + artifactResult.getRequest().getArtifact() + ": " + exception.getMessage(), exception);
}
return List.of(artifactResult.getArtifact().getFile());
},
(ex)->singleton(new ArtifactCoordinate(groupId, artifactId, extension, classifier, version)),
attemptedRepositories()).get(0);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.wildfly.channel.maven;

import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.version.Version;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class RemoteArtifactVersionsFilterTest {

@TempDir
private Path tempDir;

@Test
public void rejectIfNoRepositoriesAreUsed() {
final RepositorySystemSession session = mock(RepositorySystemSession.class);;
final VersionRangeResult result = new VersionRangeResult(mock(VersionRangeRequest.class));

final RemoteArtifactVersionsFilter filter = new RemoteArtifactVersionsFilter(session, result);
Assertions.assertFalse(filter.accept(mock(Version.class)), "A version requests without any remote repository should be rejected");
}

@Test
public void acceptIfTheVersionWasFoundInRemoteRepository() {
final RepositorySystemSession session = mock(RepositorySystemSession.class);;
final RemoteRepository repository = new RemoteRepository.Builder("test", "default", "http://foo.bar").build();
final DefaultArtifact artifact = new DefaultArtifact("org.test", "test-one", "jar", "1.1.1");
final VersionRangeResult result = new VersionRangeResult(new VersionRangeRequest(artifact, List.of(repository), null));
result.setRepository(new TestVersion("1.1.1"), repository);

final RemoteArtifactVersionsFilter filter = new RemoteArtifactVersionsFilter(session, result);
Assertions.assertTrue(filter.accept(new TestVersion("1.1.1")), "A version requests resolved from a remote repository should be accepted");
}

@Test
public void acceptIfTheVersionIsInRemoteCache() throws IOException {
final RepositorySystemSession session = mock(RepositorySystemSession.class);;
final LocalRepositoryManager lrm = mock(LocalRepositoryManager.class);
final LocalRepository lr = mock(LocalRepository.class);
final RemoteRepository repository = new RemoteRepository.Builder("test", "default", "http://foo.bar").build();
final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
final String groupId = "org.test";
final String artifactId = "test-one";
final String version = "1.1.1";
final DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId, "jar", version);
final VersionRangeResult result = new VersionRangeResult(new VersionRangeRequest(artifact, List.of(repository), null));
result.setRepository(new TestVersion(version), localRepository);

when(session.getLocalRepositoryManager()).thenReturn(lrm);
when(lrm.getPathForRemoteMetadata(any(), any(), any())).thenReturn(groupId.replace('.', File.separatorChar) +
File.separatorChar + artifactId + File.separatorChar + "maven-metadata-test.xml");
when(session.getLocalRepository()).thenReturn(lr);
when(lr.getBasedir()).thenReturn(tempDir.toFile());

writeMavenCacheFile(groupId, artifactId, version);

final RemoteArtifactVersionsFilter filter = new RemoteArtifactVersionsFilter(session, result);
Assertions.assertTrue(filter.accept(new TestVersion(version)), "A version requests with a matching version in cache file should be accepted");
}

@Test
public void rejectIfTheVersionIsNotInRemoteCache() throws IOException {
final RepositorySystemSession session = mock(RepositorySystemSession.class);;
final LocalRepositoryManager lrm = mock(LocalRepositoryManager.class);
final LocalRepository lr = mock(LocalRepository.class);
final RemoteRepository repository = new RemoteRepository.Builder("test", "default", "http://foo.bar").build();
final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
final String groupId = "org.test";
final String artifactId = "test-one";
final String version = "1.1.1";
final DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId, "jar", version);
final VersionRangeResult result = new VersionRangeResult(new VersionRangeRequest(artifact, List.of(repository), null));
result.setRepository(new TestVersion(version), localRepository);

when(session.getLocalRepositoryManager()).thenReturn(lrm);
when(lrm.getPathForRemoteMetadata(any(), any(), any())).thenReturn(groupId.replace('.', File.separatorChar) +
File.separatorChar + artifactId + File.separatorChar + "maven-metadata-test.xml");
when(session.getLocalRepository()).thenReturn(lr);
when(lr.getBasedir()).thenReturn(tempDir.toFile());

writeMavenCacheFile(groupId, artifactId, "1.1.2");

final RemoteArtifactVersionsFilter filter = new RemoteArtifactVersionsFilter(session, result);
Assertions.assertFalse(filter.accept(new TestVersion(version)), "A version requests without matching version in cache should be rejected");
}

private void writeMavenCacheFile(String groupId, String artifactId, String version) throws IOException {
final Metadata metadata = new Metadata();
final Versioning versioning = new Versioning();
versioning.setVersions(List.of(version));
metadata.setGroupId(groupId);
metadata.setArtifactId(artifactId);
metadata.setVersioning(versioning);
final Path manifestFile = tempDir.resolve(Path.of(groupId.replace('.', File.separatorChar), artifactId, "maven-metadata-test.xml"));
Files.createDirectories(manifestFile.getParent());
new MetadataXpp3Writer().write(new FileWriter(manifestFile.toFile()), metadata);
}


static class TestVersion implements Version {

private final String version;

TestVersion(String version) {
this.version = version;
}

@Override
public String toString() {
return this.version;
}

@Override
public int compareTo(Version v) {
TestVersion other = (TestVersion) v;
return this.version.compareTo(other.version);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestVersion that = (TestVersion) o;
return Objects.equals(version, that.version);
}

@Override
public int hashCode() {
return Objects.hash(version);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public void testResolverGetAllVersions() throws VersionRangeResolutionException
Version v111 = mock(Version.class);
when(v111.toString()).thenReturn("1.1.1");
versionRangeResult.setVersions(asList(v100, v110, v111));
versionRangeResult.getRequest().setRepositories(List.of(new RemoteRepository.Builder("test", "default", "file://test").build()));
final Repository testRepository = new Repository("test", "file://test");
final ArtifactRepository testArtifactRepository = VersionResolverFactory.DEFAULT_REPOSITORY_MAPPER.apply(testRepository);
for (Version v : versionRangeResult.getVersions()) versionRangeResult.setRepository(v, testArtifactRepository);
Expand Down Expand Up @@ -221,6 +222,7 @@ public void testResolverResolveMetadataUsingGa() throws ArtifactResolutionExcept
Version v111 = mock(Version.class);
when(v111.toString()).thenReturn("1.1.1");
versionRangeResult.setVersions(asList(v100, v110, v111));
versionRangeResult.getRequest().setRepositories(List.of(new RemoteRepository.Builder("test", "default", "file://test").build()));
final Repository testRepository = new Repository("test", "file://test");
final ArtifactRepository testArtifactRepository = VersionResolverFactory.DEFAULT_REPOSITORY_MAPPER.apply(testRepository);
for (Version v : versionRangeResult.getVersions()) versionRangeResult.setRepository(v, testArtifactRepository);
Expand Down
Loading