Skip to content

Commit

Permalink
[MNG-7960] Version filtering
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.

---

https://issues.apache.org/jira/browse/MNG-7960
  • Loading branch information
cstamas committed Dec 19, 2023
1 parent fe71f7d commit f8e5021
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 10 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,13 +60,17 @@
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;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.resolution.ResolutionErrorPolicy;
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 @@ -74,6 +79,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 @@ -83,6 +92,25 @@
*/
@Named
public class DefaultRepositorySystemSessionFactory {
/**
* User property for version filters expression, a comma 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 @@ -148,6 +176,8 @@ public class DefaultRepositorySystemSessionFactory {

private final TypeRegistry typeRegistry;

private final VersionScheme versionScheme;

@SuppressWarnings("checkstyle:ParameterNumber")
@Inject
public DefaultRepositorySystemSessionFactory(
Expand All @@ -157,14 +187,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 @@ -208,6 +240,11 @@ public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request)
session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(
request.isIgnoreMissingArtifactDescriptor(), request.isIgnoreInvalidArtifactDescriptor()));

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

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

session.setWorkspaceReader(
Expand Down Expand Up @@ -406,6 +443,75 @@ public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request)
return session;
}

private VersionFilter buildVersionFilter(Map<Object, Object> configProps) {
ArrayList<VersionFilter> filters = new ArrayList<>();
String filterExpression = (String) configProps.get(MAVEN_VERSION_FILTERS);
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));
// MRESOLVER-450
// 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));
// MRESOLVER-450
// 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
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.version.VersionScheme;
import org.junit.jupiter.api.Test;

import static org.codehaus.plexus.testing.PlexusExtension.getBasedir;
Expand Down Expand Up @@ -76,6 +77,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 +89,8 @@ void isNoSnapshotUpdatesTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

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

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

PlexusConfiguration plexusConfiguration = (PlexusConfiguration) systemSessionFactory
.newRepositorySession(request)
Expand Down Expand Up @@ -186,7 +193,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 +231,8 @@ void connectTimeoutConfigurationTest() throws InvalidRepositoryException {
settingsDecrypter,
eventSpyDispatcher,
information,
defaultTypeRegistry);
defaultTypeRegistry,
versionScheme);

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

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

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

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

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

0 comments on commit f8e5021

Please sign in to comment.