Skip to content

Commit

Permalink
[wildfly-extras#35] Support multiple version of a stream
Browse files Browse the repository at this point in the history
* add a `stream.versions` field to be able to configure different
* versions for the same artifact.
  This field is an object. Its keys are regular expressions that matches
  the base version of the artifact. The values are regular expressions
that determins the latest version of the given base version

As an example:

```
streams:
- groupId: "io.undertow"
  artifactId: "undertow-core"
  versions:
    "\Q1.0.0.Final\E": "\Q1.0.0.Final\E"
    "\Q2.0.0.Final\E": "2\..*"
```

For the base version `1.0.0.Final`, it would also fetch the
`1.0.0.Final` version.

For the base version `2.0.0.Final`, it would fetch the latest version
that matches `2\..*` (eg `2.3.4.Final`)

This fixes wildfly-extras#35

Signed-off-by: Jeff Mesnil <[email protected]>
  • Loading branch information
jmesnil committed Jun 1, 2022
2 parents 6735e43 + b7ce729 commit 2d53862
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 21 deletions.
14 changes: 12 additions & 2 deletions core/src/main/java/org/wildfly/channel/Channel.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.wildfly.channel.spi.MavenVersionsResolver;
import org.wildfly.channel.version.VersionMatcher;
import org.wildfly.channel.version.VersionPatternMatcher;

/**
* Java representation of a Channel.
Expand Down Expand Up @@ -177,7 +179,7 @@ static class ResolveLatestVersionResult {
}


Optional<ResolveLatestVersionResult> resolveLatestVersion(String groupId, String artifactId, String extension, String classifier) {
Optional<ResolveLatestVersionResult> resolveLatestVersion(String groupId, String artifactId, String extension, String classifier, String baseVersion) {
requireNonNull(groupId);
requireNonNull(artifactId);
requireNonNull(resolver);
Expand All @@ -190,7 +192,7 @@ Optional<ResolveLatestVersionResult> resolveLatestVersion(String groupId, String
// we return the latest value from the required channels
Map<String, Channel> foundVersions = new HashMap<>();
for (Channel requiredChannel : requiredChannels) {
Optional<Channel.ResolveLatestVersionResult> found = requiredChannel.resolveLatestVersion(groupId, artifactId, extension, classifier);
Optional<Channel.ResolveLatestVersionResult> found = requiredChannel.resolveLatestVersion(groupId, artifactId, extension, classifier, baseVersion);
if (found.isPresent()) {
foundVersions.put(found.get().version, found.get().channel);
}
Expand All @@ -211,6 +213,14 @@ Optional<ResolveLatestVersionResult> resolveLatestVersion(String groupId, String
// if there is a version pattern, we resolve all versions from Maven to find the latest one
Set<String> versions = resolver.getAllVersions(groupId, artifactId, extension, classifier);
foundVersion = foundStream.get().getVersionComparator().matches(versions);
} else if (stream.getVersions() != null) {
Map<String, Pattern> versionStreams = stream.getVersions();
for (Map.Entry<String, Pattern> entry : versionStreams.entrySet()) {
if (Pattern.compile(entry.getKey()).matcher(baseVersion).matches()) {
Set<String> versions = resolver.getAllVersions(groupId, artifactId, extension, classifier);
foundVersion = new VersionPatternMatcher(entry.getValue()).matches(versions);
}
}
}

if (foundVersion.isPresent()) {
Expand Down
29 changes: 25 additions & 4 deletions core/src/main/java/org/wildfly/channel/ChannelRecorder.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
*/
package org.wildfly.channel;

import static java.util.regex.Pattern.compile;
import static java.util.regex.Pattern.quote;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

class ChannelRecorder {

Expand All @@ -26,10 +33,24 @@ class ChannelRecorder {
null,
Collections.emptyList());

void recordStream(String groupId, String artifactId, String version) {
boolean isRecorded = recordedChannel.getStreams().stream().anyMatch(s -> s.getGroupId().equals(groupId) && s.getArtifactId().equals(artifactId) && s.getVersion().equals(version));
if (!isRecorded) {
recordedChannel.addStream(new Stream(groupId, artifactId, version, null));
void recordStream(String groupId, String artifactId, String newVersion) {
Optional<Stream> optStream = recordedChannel.getStreams().stream().filter(s -> s.getGroupId().equals(groupId) && s.getArtifactId().equals(artifactId)).findFirst();
if (!optStream.isPresent()) {
recordedChannel.addStream(new Stream(groupId, artifactId, newVersion, null, null));
} else {
Stream stream = optStream.get();
String version = stream.getVersion();
Map<String, Pattern> versions = stream.getVersions();
if (version != null) {
if (version.equals(newVersion)) {
return;
}
versions = new HashMap<>();
versions.put(quote(version), compile(quote(version)));
}
versions.put(quote(newVersion), compile(quote(newVersion)));
recordedChannel.getStreams().remove(stream);
recordedChannel.addStream(new Stream(groupId, artifactId, null, null, versions));
}
}

Expand Down
11 changes: 6 additions & 5 deletions core/src/main/java/org/wildfly/channel/ChannelSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public MavenArtifact resolveMavenArtifact(String groupId, String artifactId, Str
// baseVersion is not used at the moment but will provide essential to support advanced use cases to determine multiple streams of the same Maven component.
requireNonNull(baseVersion);

Channel.ResolveLatestVersionResult result = findChannelWithLatestVersion(groupId, artifactId, extension, classifier);
Channel.ResolveLatestVersionResult result = findChannelWithLatestVersion(groupId, artifactId, extension, classifier, baseVersion);
String latestVersion = result.version;
Channel channel = result.channel;

Expand Down Expand Up @@ -105,11 +105,12 @@ public MavenArtifact resolveDirectMavenArtifact(String groupId, String artifactI
* @param artifactId - required
* @param extension - can be null
* @param classifier - can be null
* @param baseVersion - required
* @return the latest version if a Maven artifact
* @throws UnresolvedMavenArtifactException if the latest version cannot be established
*/
public String findLatestMavenArtifactVersion(String groupId, String artifactId, String extension, String classifier) throws UnresolvedMavenArtifactException {
return findChannelWithLatestVersion(groupId, artifactId, extension, classifier).version;
public String findLatestMavenArtifactVersion(String groupId, String artifactId, String extension, String classifier, String baseVersion) throws UnresolvedMavenArtifactException {
return findChannelWithLatestVersion(groupId, artifactId, extension, classifier, baseVersion).version;
}

@Override
Expand All @@ -132,12 +133,12 @@ public Channel getRecordedChannel() {
return recorder.getRecordedChannel();
}

private Channel.ResolveLatestVersionResult findChannelWithLatestVersion(String groupId, String artifactId, String extension, String classifier) throws UnresolvedMavenArtifactException {
private Channel.ResolveLatestVersionResult findChannelWithLatestVersion(String groupId, String artifactId, String extension, String classifier, String baseVersion) throws UnresolvedMavenArtifactException {
requireNonNull(groupId);
requireNonNull(artifactId);

for (Channel channel : channels) {
Optional<Channel.ResolveLatestVersionResult> result = channel.resolveLatestVersion(groupId, artifactId, extension, classifier);
Optional<Channel.ResolveLatestVersionResult> result = channel.resolveLatestVersion(groupId, artifactId, extension, classifier, baseVersion);
if (result.isPresent()) {
return result.get();
}
Expand Down
35 changes: 29 additions & 6 deletions core/src/main/java/org/wildfly/channel/Stream.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import static java.util.Objects.requireNonNull;

import java.util.Map;
import java.util.regex.Pattern;

import com.fasterxml.jackson.annotation.JsonCreator;
Expand Down Expand Up @@ -61,28 +62,41 @@ public class Stream implements Comparable<Stream> {
*/
private final Pattern versionPattern;

/**
* Multiple versions stream
* The key is a regular expression to express the baseline version.
* The value is a regular expression that matches the latest version of that baseline.
*
* Only one of {@code version}, {@code versionPattern}, {@code versions} must be set.
*/
private final Map<String, Pattern> versions;

private VersionMatcher versionMatcher;

@JsonCreator
public Stream(@JsonProperty(value = "groupId", required = true) String groupId,
@JsonProperty(value = "artifactId", required = true) String artifactId,
@JsonProperty("version") String version,
@JsonProperty("versionPattern") Pattern versionPattern) {
@JsonProperty("versionPattern") Pattern versionPattern,
@JsonProperty("versions") Map<String, Pattern> versions) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.versionPattern = versionPattern;
this.versions = versions;
validate();
initVersionMatcher();
}

private void initVersionMatcher() {
if (version != null) {
versionMatcher = new FixedVersionMatcher(version);
} else {
} else if (versionPattern != null ){
requireNonNull(versionPattern);
// let's instead find a version matching the pattern
versionMatcher = new VersionPatternMatcher(versionPattern);
} else {
//TODO
}
}

Expand All @@ -92,10 +106,13 @@ private void validate() {
String.format("Invalid stream. the groupId does not accept wildcard '*'"));
}

if ((version != null && versionPattern != null) ||
(version == null && versionPattern == null )) {
if ((version == null && versionPattern == null && versions == null) ||
(version != null && (versionPattern != null || versions != null)) ||
(versionPattern != null && (version != null || versions != null)) ||
(versions != null && (version != null || versionPattern != null))
) {
throw new IllegalArgumentException(
String.format("Invalid stream. Only one of version, versionPattern field must be set"));
String.format("Invalid stream. Only one of version, versionPattern, versionStreams field must be set"));
}
}

Expand All @@ -117,6 +134,11 @@ public Pattern getVersionPattern() {
return versionPattern;
}

@JsonInclude(NON_NULL)
public Map<String, Pattern> getVersions() {
return versions;
}

@JsonIgnore
public VersionMatcher getVersionComparator() {
return versionMatcher;
Expand All @@ -128,7 +150,8 @@ public String toString() {
"groupId='" + groupId + '\'' +
", artifactId='" + artifactId + '\'' +
", version='" + version + '\'' +
", versionPattern=" + versionPattern +
", versionPattern=" + versionPattern + '\'' +
", versions=" + versions +
", versionComparator=" + versionMatcher +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
"versionPattern" : {
"description": "VersionPattern of the stream. This is a regular expression that matches any version from this stream. Only one of version, versionPattern must be set.",
"type": "string"
},
"versionStreams" : {
"description": "Multiple versions of the stream. The key are regular expressions that matches the baseline version, the value are regular expresssion to determine the latest versio for that baseline",
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["groupId", "artifactId"],
Expand All @@ -85,7 +90,11 @@
},
{
"required": ["versionPattern"]
},
{
"required": ["versions"]
}

]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
*/
package org.wildfly.channel;

import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
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.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
Expand All @@ -28,10 +30,13 @@

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -121,6 +126,57 @@ public void testChannelRecorder() throws IOException, UnresolvedMavenArtifactExc
}
}

@Test
public void testChannelRecorderWithMultipleVersions() throws IOException, UnresolvedMavenArtifactException {
MavenVersionsResolver.Factory factory = mock(MavenVersionsResolver.Factory.class);
MavenVersionsResolver resolver = mock(MavenVersionsResolver.class);

when(factory.create())
.thenReturn(resolver);
when(resolver.getAllVersions(eq("io.undertow"), anyString(), eq(null), eq(null)))
.thenReturn(Set.of("1.0.0.Final", "1.1.1.Final", "2.0.0.Final", "2.1.0.Final", "2.2.0.Final"));
when(resolver.resolveArtifact(anyString(), anyString(), eq(null), eq(null), anyString()))
.thenReturn(mock(File.class));

try (ChannelSession session = new ChannelSession(emptyList(), factory)) {
session.resolveDirectMavenArtifact("io.undertow", "undertow-core", null, null, "1.0.0.Final");
session.resolveDirectMavenArtifact("io.undertow", "undertow-core", null, null, "1.0.0.Final");
session.resolveDirectMavenArtifact("io.undertow", "undertow-core", null, null, "2.0.0.Final");

Channel recordedChannel = session.getRecordedChannel();
System.out.println(ChannelMapper.toYaml(recordedChannel));

Collection<Stream> streams = recordedChannel.getStreams();
assertEquals(1, streams.size());
Stream stream = streams.iterator().next();
assertEquals("io.undertow", stream.getGroupId());
assertEquals("undertow-core", stream.getArtifactId());
assertNull(stream.getVersion());
assertNull(stream.getVersionPattern());
Map<String, Pattern> versions = stream.getVersions();
assertNotNull(versions);
assertEquals(2, versions.size());
assertTrue(versions.containsKey(Pattern.quote("1.0.0.Final")));
assertTrue(versions.containsKey(Pattern.quote("2.0.0.Final")));


// let's test the recorded channel serialization
List<Channel> channels = ChannelMapper.fromString(ChannelMapper.toYaml(recordedChannel));
assertEquals(1, channels.size());
Channel readChannel = channels.get(0);

try (ChannelSession session1 = new ChannelSession(List.of(readChannel), factory)) {
MavenArtifact mavenArtifact_100Final = session1.resolveMavenArtifact("io.undertow", "undertow-core", null, null, "1.0.0.Final");
assertEquals("1.0.0.Final", mavenArtifact_100Final.getVersion());

MavenArtifact mavenArtifact_200Final = session1.resolveMavenArtifact("io.undertow", "undertow-core", null, null, "2.0.0.Final");
assertEquals("2.0.0.Final", mavenArtifact_200Final.getVersion());
}

}

}

private static void assertStreamExistsFor(Collection<Stream> streams, String groupId, String artifactId, String version) {
Optional<Stream> stream = streams.stream().filter(s -> s.getGroupId().equals(groupId) &&
s.getArtifactId().equals(artifactId) &&
Expand Down
Loading

0 comments on commit 2d53862

Please sign in to comment.