Skip to content

Commit

Permalink
feat: Resolve release version from new /version endpoint. (#1586)
Browse files Browse the repository at this point in the history
* Resolve release version from new /version endpoint.

* Update to use prod URL.

* Include an application_type parameter in version API request to track usage type.

* Include current version in /version api request.

---------

Co-authored-by: David Gamez <[email protected]>
  • Loading branch information
bdferris-v2 and davidgamez authored Sep 28, 2023
1 parent 6f450a3 commit f3704d7
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.mobilitydata.gtfsvalidator.runner.ApplicationType;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunner;
import org.mobilitydata.gtfsvalidator.util.VersionResolver;

Expand Down Expand Up @@ -75,7 +76,7 @@ private static void createAndShowGUI(String[] args) {
logger.atSevere().withCause(e).log("Error setting system look-and-feel");
}

VersionResolver resolver = new VersionResolver();
VersionResolver resolver = new VersionResolver(ApplicationType.DESKTOP);
ValidationDisplay display = new ValidationDisplay();
MonitoredValidationRunner runner =
new MonitoredValidationRunner(new ValidationRunner(resolver), display);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import org.mobilitydata.gtfsvalidator.notice.schema.NoticeSchemaGenerator;
import org.mobilitydata.gtfsvalidator.runner.ApplicationType;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunner;
import org.mobilitydata.gtfsvalidator.util.VersionResolver;
import org.mobilitydata.gtfsvalidator.validator.ClassGraphDiscovery;
Expand Down Expand Up @@ -64,7 +65,7 @@ public static void main(String[] argv) {
}
}

ValidationRunner runner = new ValidationRunner(new VersionResolver());
ValidationRunner runner = new ValidationRunner(new VersionResolver(ApplicationType.CLI));
if (runner.run(args.toConfig()) != ValidationRunner.Status.SUCCESS) {
System.exit(-1);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.mobilitydata.gtfsvalidator.runner;

/** Specifies the execution environment for the application. */
public enum ApplicationType {
/**
* Command-line execution of validator, as either JAR or embedded in a container (e.g. Docker).
*/
CLI,
/** Desktop-based application. */
DESKTOP,
/** Web-based application. */
WEB
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.mobilitydata.gtfsvalidator.util;

import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.Resource;
import io.github.classgraph.ScanResult;
Expand All @@ -18,8 +21,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.mobilitydata.gtfsvalidator.runner.ApplicationType;

/**
* Methods to resolve the {@link VersionInfo} for the current validator instance. Since resolving
Expand All @@ -30,18 +32,21 @@ public class VersionResolver {

private static final FluentLogger logger = FluentLogger.forEnclosingClass();

/** We look up the latest release version at the following wiki page. */
private static final String LATEST_RELEASE_VERSION_PAGE_URL =
"https://raw.githubusercontent.com/wiki/MobilityData/gtfs-validator/Current-Version.md";

private static final Pattern VERSION_PATTERN = Pattern.compile("version=(\\d+\\.\\d+\\.\\d+)");
/** We look up the latest release version at the JSON api endpoint. */
private static final String LATEST_RELEASE_VERSION_URL =
"https://gtfs-validator.mobilitydata.org/api/version";

private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final ApplicationType applicationType;

private SettableFuture<VersionInfo> resolvedVersionInfo = SettableFuture.create();

private boolean resolutionStarted = false;

public VersionResolver(ApplicationType applicationType) {
this.applicationType = applicationType;
}

/**
* Attempts to resolve the application {@link VersionInfo} within the specified timeout. If the
* version info can't be resolved in the specified timeout, an empty info will be returned.
Expand Down Expand Up @@ -88,7 +93,7 @@ public synchronized void resolve() {
() -> {
try {
Optional<String> currentVersion = resolveCurrentVersion();
Optional<String> latestReleaseVersion = resolveLatestReleaseVersion();
Optional<String> latestReleaseVersion = resolveLatestReleaseVersion(currentVersion);
VersionInfo info = VersionInfo.create(currentVersion, latestReleaseVersion);
resolvedVersionInfo.set(info);
return info;
Expand All @@ -104,7 +109,7 @@ public synchronized void resolve() {
* resolution is slightly complicated, depending on our deployment environment. For the
* application shadow jar, there will be a single MANIFEST.MF entry, with an Implementation-Title
* of `gtfs-validator`. In a non-shadow-jar deployment (e.g. unit-test or gradle :run), there will
* be multiple MANIFEST.MF entries (different jars on the classpath can provide there own), so we
* be multiple MANIFEST.MF entries (different jars on the classpath can provide their own), so we
* look for the `gtfs-validator-core` MANIFEST.MF, since the shadow jar won't be present.
*
* <p>The return value is Optional because it's possible no version info is found.
Expand Down Expand Up @@ -139,17 +144,26 @@ public Optional<String> resolveCurrentVersion() throws IOException {
return gtfsValidatorCoreVersion;
}

private Optional<String> resolveLatestReleaseVersion() throws IOException {
URL url = new URL(LATEST_RELEASE_VERSION_PAGE_URL);
private Optional<String> resolveLatestReleaseVersion(Optional<String> currentVersion)
throws IOException {
URL url =
new URL(
String.format(
"%s?application_type=%s&current_version=%s",
LATEST_RELEASE_VERSION_URL, applicationType, currentVersion.orElse("Ï")));
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) {
String line = null;
while ((line = in.readLine()) != null) {
Matcher m = VERSION_PATTERN.matcher(line);
if (m.matches()) {
return Optional.of(m.group(1));
}
Gson gson = new GsonBuilder().create();
VersionResponse response = gson.fromJson(in, VersionResponse.class);
if (response != null && !Strings.isNullOrEmpty(response.version)) {
logger.atInfo().log("resolved release version=%s", response.version);
return Optional.of(response.version);
}
}
return Optional.empty();
}

/** Serialization object for parsing the /version API response. */
class VersionResponse {
String version;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mobilitydata.gtfsvalidator.runner.ApplicationType;

@RunWith(JUnit4.class)
public class VersionResolverTest {
Expand All @@ -39,48 +40,55 @@ public static void beforeClass() throws IOException {

@Test
public void testResolveLatestReleaseVersion() throws IOException {
StringBuilder b = new StringBuilder();
b.append("Version Information from the wiki\n");
b.append("```\n");
b.append("version=10.0.5\n");
b.append("```\n");
mockStreamHandler.setContent(b.toString());

VersionResolver checker = new VersionResolver();
mockStreamHandler.setContent("{\"version\":\"10.0.5\"}");

VersionResolver checker = new VersionResolver(ApplicationType.CLI);
VersionInfo versionInfo = checker.getVersionInfoWithTimeout(TIMEOUT);

assertThat(versionInfo.latestReleaseVersion()).hasValue("10.0.5");
}

@Test
public void testLatestReleaseVersionNotFound() throws IOException {
StringBuilder b = new StringBuilder();
b.append("Page not found");
mockStreamHandler.setContent(b.toString());
mockStreamHandler.setContent("Page not found");

VersionResolver checker = new VersionResolver();
VersionResolver checker = new VersionResolver(ApplicationType.CLI);
VersionInfo versionInfo = checker.getVersionInfoWithTimeout(TIMEOUT);

assertThat(versionInfo.latestReleaseVersion()).isEmpty();
}

@Test
public void testReleaseVersionUrlParams() throws IOException {
// This property is set via gradle test { } stanza.
String expectedVersion = System.getProperty("gtfsValidatorVersionForTest");
assertThat(expectedVersion).isNotEmpty();

mockStreamHandler.setContent("{\"version\":\"10.0.5\"}");

VersionResolver checker = new VersionResolver(ApplicationType.WEB);
VersionInfo versionInfo = checker.getVersionInfoWithTimeout(TIMEOUT);

assertThat(mockStreamHandler.url).isNotNull();
assertThat(mockStreamHandler.url.getQuery())
.isEqualTo("application_type=WEB&current_version=" + expectedVersion);
}

@Test
public void testLocalVersion() {
// This property is set via gradle test { } stanza.
String expectedVersion = System.getProperty("gtfsValidatorVersionForTest");
assertThat(expectedVersion).isNotEmpty();

VersionResolver checker = new VersionResolver();
VersionResolver checker = new VersionResolver(ApplicationType.CLI);
VersionInfo versionInfo = checker.getVersionInfoWithTimeout(TIMEOUT);

assertThat(versionInfo.currentVersion()).hasValue(expectedVersion);
}

@Test
public void testVersionCallback() throws IOException, InterruptedException {
StringBuilder b = new StringBuilder();
b.append("version=10.0.5\n");
mockStreamHandler.setContent(b.toString());
mockStreamHandler.setContent("{\"version\":\"10.0.5\"}");

// A dummy callback that captures the updated version info and triggers a countdown latch
// that our test can wait for.
Expand All @@ -92,7 +100,7 @@ public void testVersionCallback() throws IOException, InterruptedException {
callbackLatch.countDown();
};

VersionResolver checker = new VersionResolver();
VersionResolver checker = new VersionResolver(ApplicationType.CLI);
checker.addCallback(callback);

// Wait until the callback is actually triggered.
Expand All @@ -105,13 +113,20 @@ private static class MockStreamHandler extends URLStreamHandler {

private URLConnection connection = mock(URLConnection.class);

private URL url = null;

public void setContent(String content) throws IOException {
when(connection.getInputStream())
.thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
}

public URL getUrl() {
return this.url;
}

@Override
protected URLConnection openConnection(URL u) throws IOException {
this.url = u;
return connection;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.mobilitydata.gtfsvalidator.web.service;

import org.mobilitydata.gtfsvalidator.runner.ApplicationType;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunner;
import org.mobilitydata.gtfsvalidator.util.VersionResolver;
import org.springframework.boot.SpringApplication;
Expand All @@ -35,6 +36,6 @@ public ValidationRunner validationRunner() {

@Bean
public VersionResolver versionResolver() {
return new VersionResolver();
return new VersionResolver(ApplicationType.WEB);
}
}

0 comments on commit f3704d7

Please sign in to comment.