2
0
Fork 0
mirror of https://github.com/ethauvin/rife2.git synced 2025-05-01 11:08:11 -07:00

WIP maven dependency management

This commit is contained in:
Geert Bevin 2023-03-08 17:46:39 -05:00
parent 1672aaf0cb
commit e090235b89
13 changed files with 617 additions and 5 deletions

View file

@ -4,8 +4,6 @@
*/ */
package rife.cli; package rife.cli;
import rife.tools.exceptions.FileUtilsErrorException;
public interface CliCommand { public interface CliCommand {
boolean execute() boolean execute()
throws Exception; throws Exception;

View file

@ -52,7 +52,9 @@ public class HelpCommand implements CliCommand {
Common: Common:
help Provides help about any of the other commands help Provides help about any of the other commands
new Creates a new RIFE2 application new Creates a new RIFE2 application
download Downloads the application dependencies
build Compiles a RIFE2 application build Compiles a RIFE2 application
clean Cleans the RIFE2 build files
run Compiles and runs a RIFE2 application run Compiles and runs a RIFE2 application
jar Creates an uberJar archive for a RIFE2 application jar Creates an uberJar archive for a RIFE2 application
war Creates a war archive for a RIFE2 application war Creates a war archive for a RIFE2 application

View file

@ -0,0 +1,24 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies;
public record Dependency(String groupId, String artifactId, VersionNumber version) {
public Dependency(String groupId, String artifactId) {
this(groupId, artifactId, null);
}
public Dependency(String groupId, String artifactId, VersionNumber version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = (version == null ? VersionNumber.UNKNOWN : version);
}
public String toString() {
if (version.equals(VersionNumber.UNKNOWN)) {
return groupId + ":" + artifactId;
}
return groupId + ":" + artifactId + ":" + version;
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies;
import rife.cli.dependencies.exceptions.*;
import java.io.IOException;
import java.net.URI;
import java.net.http.*;
import java.util.List;
public class DependencyResolver {
public static final String MAVEN_METADATA_XML = "maven-metadata.xml";
private final String repository_ = "https://repo1.maven.org/maven2/";
private final String groupPath_;
private final String artifactUrl_;
private Xml2MavenMetadata metadata_ = null;
private final Dependency dependency_;
public DependencyResolver(Dependency dependency) {
dependency_ = dependency;
groupPath_ = dependency_.groupId().replace(".", "/");
artifactUrl_ = repository_ + groupPath_ + "/" + dependency_.artifactId() + "/";
}
public boolean exists() {
try {
if (getMavenMetadata() == null) {
return false;
}
if (!dependency_.version().equals(VersionNumber.UNKNOWN)) {
return getMavenMetadata().getVersions().contains(dependency_.version());
}
return true;
} catch (ArtifactNotFoundException e) {
return false;
}
}
public List<VersionNumber> listVersions() {
return getMavenMetadata().getVersions();
}
public VersionNumber latestVersion() {
return getMavenMetadata().getLatest();
}
public VersionNumber releaseVersion() {
return getMavenMetadata().getRelease();
}
private Xml2MavenMetadata getMavenMetadata() {
if (metadata_ == null) {
String metadata;
var uri = artifactUrl_ + MAVEN_METADATA_XML;
try {
var response_get = repositoryRequest(uri);
if (response_get.statusCode() != 200) {
throw new ArtifactNotFoundException(dependency_, uri, response_get.statusCode());
}
metadata = response_get.body();
} catch (IOException | InterruptedException e) {
throw new ArtifactRetrievalErrorException(dependency_, uri, e);
}
var xml = new Xml2MavenMetadata(dependency_);
if (!xml.processXml(metadata)) {
throw new DependencyXmlParsingErrorException(dependency_, uri, xml.getErrors());
}
metadata_ = xml;
}
return metadata_;
}
private HttpResponse<String> repositoryRequest(String uri)
throws IOException, InterruptedException {
return HttpClient.newHttpClient()
.send(HttpRequest.newBuilder()
.uri(URI.create(uri))
.GET()
.build(), HttpResponse.BodyHandlers.ofString());
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies;
import java.util.Objects;
import java.util.regex.Pattern;
public record VersionNumber(int major, int minor, int revision, String qualifier) {
public static final VersionNumber UNKNOWN = new VersionNumber(0, 0, 0, "");
private static final Pattern VERSION_PATTERN = Pattern.compile("^(?<major>\\d+)(?:\\.(?<minor>\\d+)(?:\\.(?<revision>\\d+))?)?+(?:[.\\-](?<qualifier>.*[^.\\-]))??$");
public static VersionNumber parse(String version) {
if (version == null || version.isEmpty()) {
return UNKNOWN;
}
var matcher = VERSION_PATTERN.matcher(version);
if (!matcher.matches()) {
return UNKNOWN;
}
var major = Integer.parseInt(matcher.group("major"));
var minor = Integer.parseInt(Objects.requireNonNullElse(matcher.group("minor"), "0"));
var revision = Integer.parseInt(Objects.requireNonNullElse(matcher.group("revision"), "0"));
var qualifier = matcher.group("qualifier");
return new VersionNumber(major, minor, revision, qualifier);
}
public VersionNumber(int major) {
this(major, 0, 0, "");
}
public VersionNumber(int major, int minor) {
this(major, minor, 0, "");
}
public VersionNumber(int major, int minor, int revision) {
this(major, minor, revision, "");
}
public VersionNumber(int major, int minor, int revision, String qualifier) {
this.major = major;
this.minor = minor;
this.revision = revision;
this.qualifier = (qualifier == null ? "" : qualifier);
}
public VersionNumber getBaseVersion() {
return new VersionNumber(major, minor, revision, null);
}
public int compareTo(VersionNumber other) {
if (major != other.major) {
return major - other.major;
}
if (minor != other.minor) {
return minor - other.minor;
}
if (revision != other.revision) {
return revision - other.revision;
}
if (qualifier.equals(other.qualifier)) {
return 0;
}
else if (qualifier.isEmpty()) {
return 1;
} else if (other.qualifier.isEmpty()) {
return -1;
}
return qualifier.toLowerCase().compareTo(other.qualifier.toLowerCase());
}
public String toString() {
var version = new StringBuilder();
version.append(major);
version.append(".");
version.append(minor);
version.append(".");
version.append(revision);
if (qualifier != null && !qualifier.isEmpty()) {
version.append("-");
version.append(qualifier);
}
return version.toString();
}
@Override
public boolean equals(Object other) {
return other instanceof VersionNumber && compareTo((VersionNumber) other) == 0;
}
@Override
public int hashCode() {
int result = major;
result = 31 * result + minor;
result = 31 * result + minor;
result = 31 * result + Objects.hashCode(qualifier);
return result;
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies;
import org.xml.sax.Attributes;
import rife.xml.Xml2Data;
import java.util.ArrayList;
import java.util.List;
public class Xml2MavenMetadata extends Xml2Data {
private final Dependency dependency_;
private VersionNumber latest_ = VersionNumber.UNKNOWN;
private VersionNumber release_ = VersionNumber.UNKNOWN;
private List<VersionNumber> versions_;
private StringBuilder characterData_ = null;
public Xml2MavenMetadata(Dependency dependency) {
dependency_ = dependency;
versions_ = new ArrayList<>();
}
public VersionNumber getLatest() {
return latest_;
}
public VersionNumber getRelease() {
return release_;
}
public List<VersionNumber> getVersions() {
return versions_;
}
public void startElement(String uri, String localName, String qName, Attributes attributes) {
characterData_ = new StringBuilder();
}
public void endElement(String uri, String localName, String qName) {
switch (qName) {
case "latest" -> latest_ = VersionNumber.parse(characterData_.toString());
case "release" -> release_ = VersionNumber.parse(characterData_.toString());
case "version" -> versions_.add(VersionNumber.parse(characterData_.toString()));
}
characterData_ = null;
}
public void characters(char[] ch, int start, int length) {
if (characterData_ != null) {
characterData_.append(String.copyValueOf(ch, start, length));
}
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies.exceptions;
import rife.cli.dependencies.Dependency;
import java.io.Serial;
public class ArtifactNotFoundException extends DependencyException {
@Serial private static final long serialVersionUID = -4463592998915863162L;
private final Dependency dependency_;
private final String uri_;
private final int statusCode_;
public ArtifactNotFoundException(Dependency dependency, String uri, int statusCode) {
super("Couldn't find artifact for dependency '" + dependency + "' at '" + uri + "' (status code: " + statusCode + ")");
dependency_ = dependency;
uri_ = uri;
statusCode_ = statusCode;
}
public Dependency getDependency() {
return dependency_;
}
public String getUrl() {
return uri_;
}
public int getStatusCode() {
return statusCode_;
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies.exceptions;
import rife.cli.dependencies.Dependency;
import java.io.Serial;
public class ArtifactRetrievalErrorException extends DependencyException {
@Serial private static final long serialVersionUID = 339863133681418524L;
private final Dependency dependency_;
private final String uri_;
public ArtifactRetrievalErrorException(Dependency dependency, String uri, Throwable e) {
super("Unexpected error while retrieving artifact for dependency '" + dependency + "' from '" + uri + "'", e);
dependency_ = dependency;
uri_ = uri;
}
public Dependency getDependency() {
return dependency_;
}
public String getUrl() {
return uri_;
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies.exceptions;
import java.io.Serial;
public class DependencyException extends RuntimeException {
@Serial private static final long serialVersionUID = 7683888067001718316L;
public DependencyException(String message) {
super(message);
}
public DependencyException(String message, Throwable cause) {
super(message, cause);
}
public DependencyException(Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies.exceptions;
import rife.cli.dependencies.Dependency;
import rife.tools.StringUtils;
import java.io.Serial;
import java.util.List;
import java.util.Set;
public class DependencyXmlParsingErrorException extends DependencyException {
@Serial private static final long serialVersionUID = 6036121359540018004L;
private final Dependency dependency_;
private final String uri_;
private final Set<String> errors_;
public DependencyXmlParsingErrorException(Dependency dependency, String uri, Set<String> errors) {
super("Unable to parse artifact document for dependency '" + dependency + "' from '" + uri + "' :\n" + StringUtils.join(errors, "\n"));
dependency_ = dependency;
uri_ = uri;
errors_ = errors;
}
public Dependency getDependency() {
return dependency_;
}
public String getUrl() {
return uri_;
}
public Set<String> getErrors() {
return errors_;
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies;
import org.junit.jupiter.api.Test;
import rife.cli.dependencies.exceptions.ArtifactNotFoundException;
import static org.junit.jupiter.api.Assertions.*;
public class TestDependencyResolver {
@Test
void testInstantiation() {
var resolver = new DependencyResolver(new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 4, 0)));
assertNotNull(resolver);
}
@Test
void testNotFound() {
var resolver = new DependencyResolver(new Dependency("com.org.unknown", "voidthing"));
assertFalse(resolver.exists());
}
@Test
void testCheckExistence() {
var resolver = new DependencyResolver(new Dependency("com.uwyn.rife2", "rife2"));
assertTrue(resolver.exists());
}
@Test
void testCheckExistenceVersion() {
var resolver = new DependencyResolver(new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 4, 0)));
assertTrue(resolver.exists());
}
@Test
void testCheckExistenceMissingVersion() {
var resolver = new DependencyResolver(new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 3, 9)));
assertFalse(resolver.exists());
}
@Test
void testListVersions() {
var resolver1 = new DependencyResolver(new Dependency("com.uwyn.rife2", "rife2"));
var versions1 = resolver1.listVersions();
assertNotNull(versions1);
assertFalse(versions1.isEmpty());
assertFalse(versions1.contains(VersionNumber.UNKNOWN));
assertTrue(versions1.contains(new VersionNumber(1, 0, 0)));
assertTrue(versions1.contains(new VersionNumber(1, 2, 1)));
var resolver2 = new DependencyResolver(new Dependency("org.eclipse.jetty", "jetty-server"));
var versions2 = resolver2.listVersions();
assertNotNull(versions2);
assertFalse(versions2.isEmpty());
assertFalse(versions2.contains(VersionNumber.UNKNOWN));
assertTrue(versions2.contains(new VersionNumber(9, 4, 51, "v20230217")));
assertTrue(versions2.contains(new VersionNumber(11, 0, 14)));
}
@Test
void testGetLatestVersion() {
var resolver = new DependencyResolver(new Dependency("com.uwyn.rife2", "rife2"));
var version = resolver.latestVersion();
assertNotNull(version);
assertTrue(version.compareTo(new VersionNumber(1, 4)) >= 0);
}
@Test
void testGetReleaseVersion() {
var resolver = new DependencyResolver(new Dependency("com.uwyn.rife2", "rife2"));
var version = resolver.releaseVersion();
assertNotNull(version);
assertTrue(version.compareTo(new VersionNumber(1, 4)) >= 0);
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.cli.dependencies;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestVersionNumber {
@Test
void testInstantiation() {
var version1 = new VersionNumber(1);
var version10 = new VersionNumber(1, 0);
var version100 = new VersionNumber(1, 0, 0);
var version100s = new VersionNumber(1, 0, 0, "SNAPSHOT");
assertEquals("1.0.0", version1.toString());
assertEquals("1.0.0", version10.toString());
assertEquals("1.0.0", version100.toString());
assertEquals("1.0.0-SNAPSHOT", version100s.toString());
}
@Test
void testParsing() {
assertEquals(VersionNumber.parse("1"), new VersionNumber(1, 0, 0, null));
assertEquals(VersionNumber.parse("1.0"), new VersionNumber(1, 0, 0, null));
assertEquals(VersionNumber.parse("1.0.0"), new VersionNumber(1, 0, 0, null));
assertEquals(VersionNumber.parse("1.2"), new VersionNumber(1, 2, 0, null));
assertEquals(VersionNumber.parse("1.2.3"), new VersionNumber(1, 2, 3, null));
assertEquals(VersionNumber.parse("1-rc1-SNAPSHOT"), new VersionNumber(1, 0, 0, "rc1-SNAPSHOT"));
assertEquals(VersionNumber.parse("1.2-rc1-SNAPSHOT"), new VersionNumber(1, 2, 0, "rc1-SNAPSHOT"));
assertEquals(VersionNumber.parse("1.2.3-rc1-SNAPSHOT"), new VersionNumber(1, 2, 3, "rc1-SNAPSHOT"));
assertEquals(VersionNumber.parse("11.22"), new VersionNumber(11, 22, 0, null));
assertEquals(VersionNumber.parse("11.22.33"), new VersionNumber(11, 22, 33, null));
assertEquals(VersionNumber.parse("11.22.33-eap"), new VersionNumber(11, 22, 33, "eap"));
assertEquals(VersionNumber.parse("11.fortyfour"), new VersionNumber(11, 0, 0, "fortyfour"));
assertEquals(VersionNumber.parse("1.0.0.0"), new VersionNumber(1, 0, 0, "0"));
assertEquals(VersionNumber.parse("1.0.0.0.0.0.0"), new VersionNumber(1, 0, 0, "0.0.0.0"));
assertEquals(VersionNumber.parse("1.2.3.4-rc1-SNAPSHOT"), new VersionNumber(1, 2, 3, "4-rc1-SNAPSHOT"));
assertEquals(VersionNumber.parse("1.2.3.4.rc1-SNAPSHOT"), new VersionNumber(1, 2, 3, "4.rc1-SNAPSHOT"));
}
@Test
void testInvalidParsed() {
assertEquals(VersionNumber.parse(null), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse(""), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("foo"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("1."), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("1.2.3-"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("."), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("_"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("-"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse(".1"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("a.1"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("1_2"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("1_2_2"), VersionNumber.UNKNOWN);
assertEquals(VersionNumber.parse("1.2.3_4"), VersionNumber.UNKNOWN);
}
@Test
void testAccessors() {
var version = new VersionNumber(1, 2, 3, "beta");
assertEquals(1, version.major());
assertEquals(2, version.minor());
assertEquals(3, version.revision());
assertEquals("beta", version.qualifier());
}
@Test
void testStringRepresentation() {
assertEquals(VersionNumber.parse("1.0").toString(), "1.0.0");
assertEquals(VersionNumber.parse("1.2.3").toString(), "1.2.3");
assertEquals(VersionNumber.parse("1.2.3.4").toString(), "1.2.3-4");
assertEquals(VersionNumber.parse("1-rc-1").toString(), "1.0.0-rc-1");
assertEquals(VersionNumber.parse("1.2.3-rc-1").toString(), "1.2.3-rc-1");
}
@Test
void testEquality() {
var version = new VersionNumber(1, 1, 1, null);
var qualified = new VersionNumber(1, 1, 1, "beta-2");
assertEquals(new VersionNumber(1, 1, 1, null), version);
assertNotEquals(new VersionNumber(2, 1, 1, null), version);
assertNotEquals(new VersionNumber(1, 2, 1, null), version);
assertNotEquals(new VersionNumber(1, 1, 2, null), version);
assertNotEquals(new VersionNumber(1, 1, 1, "rc"), version);
assertEquals(new VersionNumber(1, 1, 1, "beta-2"), qualified);
assertNotEquals(new VersionNumber(1, 1, 1, "beta-3"), qualified);
}
@Test
void testComparison() {
assertEquals(0, new VersionNumber(1, 1, 1, null).compareTo(new VersionNumber(1, 1, 1, null)));
assertTrue(new VersionNumber(2, 1, 1, null).compareTo(new VersionNumber(1, 1, 1, null)) > 0);
assertTrue(new VersionNumber(1, 2, 1, null).compareTo(new VersionNumber(1, 1, 1, null)) > 0);
assertTrue(new VersionNumber(1, 1, 2, null).compareTo(new VersionNumber(1, 1, 1, null)) > 0);
assertTrue(new VersionNumber(1, 1, 1, "rc").compareTo(new VersionNumber(1, 1, 1, null)) < 0);
assertTrue(new VersionNumber(1, 1, 1, "beta").compareTo(new VersionNumber(1, 1, 1, "alpha")) > 0);
assertTrue(new VersionNumber(1, 1, 1, "RELEASE").compareTo(new VersionNumber(1, 1, 1, "beta")) > 0);
assertTrue(new VersionNumber(1, 1, 1, "SNAPSHOT").compareTo(new VersionNumber(1, 1, 1, null)) < 0);
assertTrue(new VersionNumber(1, 1, 1, null).compareTo(new VersionNumber(2, 1, 1, null)) < 0);
assertTrue(new VersionNumber(1, 1, 1, null).compareTo(new VersionNumber(1, 2, 1, null)) < 0);
assertTrue(new VersionNumber(1, 1, 1, null).compareTo(new VersionNumber(1, 1, 2, null)) < 0);
assertTrue(new VersionNumber(1, 1, 1, null).compareTo(new VersionNumber(1, 1, 1, "rc")) > 0);
assertTrue(new VersionNumber(1, 1, 1, "alpha").compareTo(new VersionNumber(1, 1, 1, "beta")) < 0);
assertTrue(new VersionNumber(1, 1, 1, "beta").compareTo(new VersionNumber(1, 1, 1, "RELEASE")) < 0);
}
@Test
void testBaseVersion() {
assertEquals(new VersionNumber(1, 2, 3, null).getBaseVersion(), new VersionNumber(1, 2, 3, null));
assertEquals(new VersionNumber(1, 2, 3, "beta").getBaseVersion(), new VersionNumber(1, 2, 3, null));
}
}

View file

@ -13,14 +13,14 @@ import static org.junit.jupiter.api.Assertions.*;
public class TestXml2Data { public class TestXml2Data {
@Test @Test
public void testInstantiation() void testInstantiation()
throws ResourceFinderErrorException { throws ResourceFinderErrorException {
var xml = new Xml2DataTest(); var xml = new Xml2DataTest();
assertNotNull(xml); assertNotNull(xml);
} }
@Test @Test
public void testProcess() void testProcess()
throws ResourceFinderErrorException { throws ResourceFinderErrorException {
var xml = new Xml2DataTest(); var xml = new Xml2DataTest();
var content = ResourceFinderClasspath.instance().getContent("xml/jetty-server-11.0.14.pom.xml"); var content = ResourceFinderClasspath.instance().getContent("xml/jetty-server-11.0.14.pom.xml");
@ -88,7 +88,7 @@ public class TestXml2Data {
@Test @Test
public void testProcessValidated() void testProcessValidated()
throws ResourceFinderErrorException { throws ResourceFinderErrorException {
var xml = new Xml2DataTest(); var xml = new Xml2DataTest();
xml.enableValidation(true); xml.enableValidation(true);