Skip to content

Commit

Permalink
[MNG-7960] Artifact collection filtering (#1353)
Browse files Browse the repository at this point in the history
Make Maven more friendly (and tunable) when ranges are being used. There new options are merely affecting range processing, allowing different narrowing strategies. Relies on latest Resolver alpha-6 features.

https://issues.apache.org/jira/browse/MNG-7960
  • Loading branch information
cstamas authored Jan 11, 2024
1 parent 63a207a commit a952c9e
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.maven.RepositoryUtils;
Expand Down Expand Up @@ -59,6 +60,9 @@
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.VersionFilter;
import org.eclipse.aether.repository.AuthenticationContext;
import org.eclipse.aether.repository.AuthenticationSelector;
import org.eclipse.aether.repository.ProxySelector;
Expand All @@ -67,6 +71,7 @@
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.resolution.ResolutionErrorPolicy;
import org.eclipse.aether.util.graph.manager.ClassicDependencyManager;
import org.eclipse.aether.util.graph.version.*;
import org.eclipse.aether.util.listener.ChainedRepositoryListener;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager;
Expand All @@ -75,6 +80,10 @@
import org.eclipse.aether.util.repository.DefaultProxySelector;
import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
import org.eclipse.aether.util.repository.SimpleResolutionErrorPolicy;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.Version;
import org.eclipse.aether.version.VersionRange;
import org.eclipse.aether.version.VersionScheme;
import org.eclipse.sisu.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -84,6 +93,25 @@
*/
@Named
public class DefaultRepositorySystemSessionFactory {
/**
* User property for version filters expression, a semicolon separated list of filters to apply. By default, no version
* filter is applied (like in Maven 3).
* <p>
* Supported filters:
* <ul>
* <li>"h" or "h(num)" - highest version or top list of highest ones filter</li>
* <li>"l" or "l(num)" - lowest version or bottom list of lowest ones filter</li>
* <li>"s" - contextual snapshot filter</li>
* <li>"e(G:A:V)" - predicate filter (leaves out G:A:V from range, if hit, V can be range)</li>
* </ul>
* Example filter expression: {@code "h(5);s;e(org.foo:bar:1)} will cause: ranges are filtered for "top 5" (instead
* full range), snapshots are banned if root project is not a snapshot, and if range for {@code org.foo:bar} is
* being processed, version 1 is omitted.
*
* @since 4.0.0
*/
private static final String MAVEN_VERSION_FILTERS = "maven.versionFilters";

/**
* User property for chained LRM: list of "tail" local repository paths (separated by comma), to be used with
* {@link ChainedLocalRepositoryManager}.
Expand Down Expand Up @@ -162,6 +190,8 @@ public class DefaultRepositorySystemSessionFactory {

private final TypeRegistry typeRegistry;

private final VersionScheme versionScheme;

@SuppressWarnings("checkstyle:ParameterNumber")
@Inject
public DefaultRepositorySystemSessionFactory(
Expand All @@ -171,14 +201,16 @@ public DefaultRepositorySystemSessionFactory(
SettingsDecrypter settingsDecrypter,
EventSpyDispatcher eventSpyDispatcher,
RuntimeInformation runtimeInformation,
TypeRegistry typeRegistry) {
TypeRegistry typeRegistry,
VersionScheme versionScheme) {
this.artifactHandlerManager = artifactHandlerManager;
this.repoSystem = repoSystem;
this.workspaceRepository = workspaceRepository;
this.settingsDecrypter = settingsDecrypter;
this.eventSpyDispatcher = eventSpyDispatcher;
this.runtimeInformation = runtimeInformation;
this.typeRegistry = typeRegistry;
this.versionScheme = versionScheme;
}

@Deprecated
Expand Down Expand Up @@ -222,6 +254,11 @@ public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request)
session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(
request.isIgnoreMissingArtifactDescriptor(), request.isIgnoreInvalidArtifactDescriptor()));

VersionFilter versionFilter = buildVersionFilter((String) configProps.get(MAVEN_VERSION_FILTERS));
if (versionFilter != null) {
session.setVersionFilter(versionFilter);
}

session.setArtifactTypeRegistry(RepositoryUtils.newArtifactTypeRegistry(artifactHandlerManager));

session.setWorkspaceReader(
Expand Down Expand Up @@ -426,6 +463,72 @@ public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request)
return session;
}

private VersionFilter buildVersionFilter(String filterExpression) {
ArrayList<VersionFilter> filters = new ArrayList<>();
if (filterExpression != null) {
List<String> expressions = Arrays.stream(filterExpression.split(";"))
.filter(s -> s != null && !s.trim().isEmpty())
.collect(Collectors.toList());
for (String expression : expressions) {
if ("h".equals(expression)) {
filters.add(new HighestVersionFilter());
} else if (expression.startsWith("h(") && expression.endsWith(")")) {
int num = Integer.parseInt(expression.substring(2, expression.length() - 1));
filters.add(new HighestVersionFilter(num));
} else if ("l".equals(expression)) {
filters.add(new LowestVersionFilter());
} else if (expression.startsWith("l(") && expression.endsWith(")")) {
int num = Integer.parseInt(expression.substring(2, expression.length() - 1));
filters.add(new LowestVersionFilter(num));
} else if ("s".equals(expression)) {
filters.add(new ContextualSnapshotVersionFilter());
} else if (expression.startsWith("e(") && expression.endsWith(")")) {
Artifact artifact = new DefaultArtifact(expression.substring(2, expression.length() - 1));
VersionRange versionRange =
artifact.getVersion().contains(",") ? parseVersionRange(artifact.getVersion()) : null;
Predicate<Artifact> predicate = a -> {
if (artifact.getGroupId().equals(a.getGroupId())
&& artifact.getArtifactId().equals(a.getArtifactId())) {
if (versionRange != null) {
Version v = parseVersion(a.getVersion());
return !versionRange.containsVersion(v);
} else {
return !artifact.getVersion().equals(a.getVersion());
}
}
return true;
};
filters.add(new PredicateVersionFilter(predicate));
} else {
throw new IllegalArgumentException("Unsupported filter expression: " + expression);
}
}
}
if (filters.isEmpty()) {
return null;
} else if (filters.size() == 1) {
return filters.get(0);
} else {
return ChainedVersionFilter.newInstance(filters);
}
}

private Version parseVersion(String spec) {
try {
return versionScheme.parseVersion(spec);
} catch (InvalidVersionSpecificationException e) {
throw new RuntimeException(e);
}
}

private VersionRange parseVersionRange(String spec) {
try {
return versionScheme.parseVersionRange(spec);
} catch (InvalidVersionSpecificationException e) {
throw new RuntimeException(e);
}
}

private Map<?, ?> getPropertiesFromRequestedProfiles(MavenExecutionRequest request) {
HashSet<String> activeProfileId =
new HashSet<>(request.getProfileActivation().getRequiredActiveProfileIds());
Expand Down Expand Up @@ -508,7 +611,7 @@ private org.apache.maven.repository.Proxy getProxy(ProxySelector selector, Artif
return null;
}

public void injectAuthentication(AuthenticationSelector selector, List<ArtifactRepository> repositories) {
private void injectAuthentication(AuthenticationSelector selector, List<ArtifactRepository> repositories) {
if (repositories != null && selector != null) {
for (ArtifactRepository repository : repositories) {
repository.setAuthentication(getAuthentication(selector, repository));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@
import org.codehaus.plexus.testing.PlexusTest;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.collection.VersionFilter;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.util.graph.version.*;
import org.eclipse.aether.version.VersionScheme;
import org.junit.jupiter.api.Test;

import static org.codehaus.plexus.testing.PlexusExtension.getBasedir;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.*;

/**
* UT for {@link DefaultRepositorySystemSessionFactory}.
Expand Down Expand Up @@ -76,6 +76,9 @@ public class DefaultRepositorySystemSessionFactoryTest {
@Inject
protected DefaultTypeRegistry defaultTypeRegistry;

@Inject
protected VersionScheme versionScheme;

@Test
void isNoSnapshotUpdatesTest() throws InvalidRepositoryException {
DefaultRepositorySystemSessionFactory systemSessionFactory = new DefaultRepositorySystemSessionFactory(
Expand All @@ -85,7 +88,8 @@ void isNoSnapshotUpdatesTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setLocalRepository(getLocalRepository());
Expand All @@ -108,7 +112,8 @@ void isSnapshotUpdatesTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setLocalRepository(getLocalRepository());
Expand Down Expand Up @@ -143,7 +148,8 @@ void wagonProviderConfigurationTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

PlexusConfiguration plexusConfiguration = (PlexusConfiguration) systemSessionFactory
.newRepositorySession(request)
Expand Down Expand Up @@ -186,7 +192,8 @@ void httpConfigurationWithHttpHeadersTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

Map<String, String> headers = (Map<String, String>) systemSessionFactory
.newRepositorySession(request)
Expand Down Expand Up @@ -223,7 +230,8 @@ void connectTimeoutConfigurationTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

int connectionTimeout = (Integer) systemSessionFactory
.newRepositorySession(request)
Expand Down Expand Up @@ -264,7 +272,8 @@ void connectionTimeoutFromHttpConfigurationTest() throws InvalidRepositoryExcept
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

int connectionTimeout = (Integer) systemSessionFactory
.newRepositorySession(request)
Expand Down Expand Up @@ -299,7 +308,8 @@ void requestTimeoutConfigurationTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

int requestTimeout = (Integer) systemSessionFactory
.newRepositorySession(request)
Expand Down Expand Up @@ -340,7 +350,8 @@ void readTimeoutFromHttpConfigurationTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

int requestTimeout = (Integer) systemSessionFactory
.newRepositorySession(request)
Expand All @@ -358,7 +369,8 @@ void transportConfigurationTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setLocalRepository(getLocalRepository());
Expand Down Expand Up @@ -395,6 +407,66 @@ void transportConfigurationTest() throws InvalidRepositoryException {
properties.remove("maven.resolver.transport");
}

@Test
void versionFilteringTest() throws InvalidRepositoryException {
DefaultRepositorySystemSessionFactory systemSessionFactory = new DefaultRepositorySystemSessionFactory(
artifactHandlerManager,
aetherRepositorySystem,
null,
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry,
versionScheme);

MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setLocalRepository(getLocalRepository());

VersionFilter versionFilter;

// single one
request.getUserProperties().put("maven.versionFilters", "s");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof ContextualSnapshotVersionFilter);

request.getUserProperties().put("maven.versionFilters", "h");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof HighestVersionFilter);

request.getUserProperties().put("maven.versionFilters", "h(5)");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof HighestVersionFilter);

request.getUserProperties().put("maven.versionFilters", "l");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof LowestVersionFilter);

request.getUserProperties().put("maven.versionFilters", "l(5)");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof LowestVersionFilter);

request.getUserProperties().put("maven.versionFilters", "e(g:a:v)");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof PredicateVersionFilter);

request.getUserProperties().put("maven.versionFilters", "e(g:a:[1,2])");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof PredicateVersionFilter);

// chained
request.getUserProperties().put("maven.versionFilters", "h(5);s;e(org.foo:bar:1)");
versionFilter = systemSessionFactory.newRepositorySession(request).getVersionFilter();
assertNotNull(versionFilter);
assertTrue(versionFilter instanceof ChainedVersionFilter);
}

protected ArtifactRepository getLocalRepository() throws InvalidRepositoryException {
File repoDir = new File(getBasedir(), "target/local-repo").getAbsoluteFile();

Expand Down

0 comments on commit a952c9e

Please sign in to comment.