diff --git a/com.io7m.adoptopenjdk.api/src/main/java/com/io7m/adoptopenjdk/api/AdoptOpenJDK.java b/com.io7m.adoptopenjdk.api/src/main/java/com/io7m/adoptopenjdk/api/AdoptOpenJDK.java index 4984725..d07a04e 100644 --- a/com.io7m.adoptopenjdk.api/src/main/java/com/io7m/adoptopenjdk/api/AdoptOpenJDK.java +++ b/com.io7m.adoptopenjdk.api/src/main/java/com/io7m/adoptopenjdk/api/AdoptOpenJDK.java @@ -20,6 +20,7 @@ import com.io7m.adoptopenjdk.spi.AOAPIVersionProviderType; import com.io7m.adoptopenjdk.spi.AOException; import com.io7m.adoptopenjdk.spi.AORelease; +import com.io7m.adoptopenjdk.spi.AOVariant; import java.util.List; import java.util.Objects; @@ -98,6 +99,13 @@ public int rateLimitRemaining() return this.requests.rateLimitRemaining(); } + @Override + public List variants() + throws AOException + { + return this.requests.variants(); + } + @Override public List releasesForVariant(final String variant) throws AOException diff --git a/com.io7m.adoptopenjdk.spi/src/main/java/com/io7m/adoptopenjdk/spi/AOAPIRequestsType.java b/com.io7m.adoptopenjdk.spi/src/main/java/com/io7m/adoptopenjdk/spi/AOAPIRequestsType.java index 98c6e93..bd88dd3 100644 --- a/com.io7m.adoptopenjdk.spi/src/main/java/com/io7m/adoptopenjdk/spi/AOAPIRequestsType.java +++ b/com.io7m.adoptopenjdk.spi/src/main/java/com/io7m/adoptopenjdk/spi/AOAPIRequestsType.java @@ -31,6 +31,17 @@ public interface AOAPIRequestsType int rateLimitRemaining(); + /** + * List the available build variants on the server. + * + * @return A list of variants + * + * @throws AOException On any and all errors + */ + + List variants() + throws AOException; + /** * List the available releases on the server. * diff --git a/com.io7m.adoptopenjdk.spi/src/main/java/com/io7m/adoptopenjdk/spi/AOVariantType.java b/com.io7m.adoptopenjdk.spi/src/main/java/com/io7m/adoptopenjdk/spi/AOVariantType.java new file mode 100644 index 0000000..9d14d14 --- /dev/null +++ b/com.io7m.adoptopenjdk.spi/src/main/java/com/io7m/adoptopenjdk/spi/AOVariantType.java @@ -0,0 +1,42 @@ +/* + * Copyright © 2018 Mark Raynsford http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.adoptopenjdk.spi; + +import org.immutables.value.Value; + +/** + * A build variant (such as {@code "openjdk8"}). + */ + +@ImmutableStyleType +@Value.Immutable +public interface AOVariantType +{ + /** + * @return A variant identifier such as {@code "openjdk8"}. + */ + + @Value.Parameter + String name(); + + /** + * @return A humanly-readable description of the variant (such as {@code "OpenJDK 8 with Hotspot"}) + */ + + @Value.Parameter + String description(); +} diff --git a/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsIntegrationTest.java b/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsIntegrationTest.java index dd1d1ea..ace9abd 100644 --- a/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsIntegrationTest.java +++ b/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsIntegrationTest.java @@ -18,6 +18,7 @@ import com.io7m.adoptopenjdk.spi.AOException; import com.io7m.adoptopenjdk.spi.AORelease; +import com.io7m.adoptopenjdk.spi.AOVariant; import com.io7m.adoptopenjdk.v1.AOv1HTTPException; import com.io7m.adoptopenjdk.v1.AOv1Requests; import com.io7m.adoptopenjdk.v1.AOv1RequestsType; @@ -60,4 +61,22 @@ public void testParseOpenJDK8Releases() } } } + + @Test + public void testParseVariants() + throws AOException + { + try { + final AOv1RequestsType requests = AOv1Requests.open(); + final List variants = requests.variants(); + Assert.assertTrue( + "Must have at least one variant parsed", + variants.size() > 0); + } catch (final AOv1HTTPException e) { + if (e.statusCode() == 429) { + LOG.info("exceeded rate limit on server: ", e); + return; + } + } + } } diff --git a/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsTest.java b/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsTest.java index dcdf785..5017743 100644 --- a/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsTest.java +++ b/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOV1RequestsTest.java @@ -18,6 +18,7 @@ import com.io7m.adoptopenjdk.spi.AOException; import com.io7m.adoptopenjdk.spi.AORelease; +import com.io7m.adoptopenjdk.spi.AOVariant; import com.io7m.adoptopenjdk.tests.spi.AOReleasesTest; import com.io7m.adoptopenjdk.v1.AOv1Requests; import com.io7m.adoptopenjdk.v1.AOv1RequestsType; @@ -132,4 +133,47 @@ public void testParseOpenJDK8ReleasesFiltered() Assert.assertEquals(releases_0, releases_1); } + + @Test + public void testParseVariants() + throws AOException, IOException + { + final MockConnection connection_0 = + MockConnection.create( + Map.of("X-RateLimit-Remaining", "100", + "Retry-After", "3600")); + + final MockConnection connection_1 = + MockConnection.createWithData( + Map.of("X-RateLimit-Remaining", "200", + "Retry-After", "3600"), + resource("variants.json")); + + final MockConnections connections = new MockConnections(); + connections.addConnection(connection_0); + connections.addConnection(connection_1); + + final AOv1RequestsType requests = AOv1Requests.open(connections); + final List variants = requests.variants(); + + Assert.assertEquals(200L, (long) requests.rateLimitRemaining()); + Assert.assertEquals(0L, (long) connections.queue().size()); + Assert.assertEquals(5L, (long) variants.size()); + + Assert.assertEquals( + AOVariant.of("openjdk8", "OpenJDK 8 with Hotspot"), + variants.get(0)); + Assert.assertEquals( + AOVariant.of("openjdk8-openj9", "OpenJDK 8 with Eclipse OpenJ9"), + variants.get(1)); + Assert.assertEquals( + AOVariant.of("openjdk9", "OpenJDK 9 with Hotspot"), + variants.get(2)); + Assert.assertEquals( + AOVariant.of("openjdk9-openj9", "OpenJDK 9 with Eclipse OpenJ9"), + variants.get(3)); + Assert.assertEquals( + AOVariant.of("openjdk10", "OpenJDK 10 with Hotspot"), + variants.get(4)); + } } diff --git a/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOv1ParserTest.java b/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOv1ParserTest.java index 51fd069..f0e04bf 100644 --- a/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOv1ParserTest.java +++ b/com.io7m.adoptopenjdk.tests/src/test/java/com/io7m/adoptopenjdk/tests/v1/AOv1ParserTest.java @@ -18,6 +18,7 @@ import com.io7m.adoptopenjdk.spi.AOParseException; import com.io7m.adoptopenjdk.spi.AORelease; +import com.io7m.adoptopenjdk.spi.AOVariant; import com.io7m.adoptopenjdk.tests.spi.AOReleasesTest; import com.io7m.adoptopenjdk.v1.AOv1Parser; import org.junit.Assert; @@ -155,4 +156,58 @@ public void testOpenJDK8Releases_Bad5() parser.parseReleases(URI.create("openjdk8.json"), stream); } } + + @Test + public void testVariants() + throws Exception + { + final AOv1Parser parser = new AOv1Parser(); + + try (InputStream stream = resource("variants.json")) { + final List variants = + parser.parseVariants(URI.create("variants.json"), stream); + + Assert.assertEquals(5L, (long) variants.size()); + + Assert.assertEquals( + AOVariant.of("openjdk8", "OpenJDK 8 with Hotspot"), + variants.get(0)); + Assert.assertEquals( + AOVariant.of("openjdk8-openj9", "OpenJDK 8 with Eclipse OpenJ9"), + variants.get(1)); + Assert.assertEquals( + AOVariant.of("openjdk9", "OpenJDK 9 with Hotspot"), + variants.get(2)); + Assert.assertEquals( + AOVariant.of("openjdk9-openj9", "OpenJDK 9 with Eclipse OpenJ9"), + variants.get(3)); + Assert.assertEquals( + AOVariant.of("openjdk10", "OpenJDK 10 with Hotspot"), + variants.get(4)); + } + } + + @Test + public void testVariants_Bad0() + throws Exception + { + final AOv1Parser parser = new AOv1Parser(); + + try (InputStream stream = resource("variants_bad_0.json")) { + this.expected.expect(AOParseException.class); + parser.parseVariants(URI.create("variants_bad_0.json"), stream); + } + } + + @Test + public void testVariants_Bad1() + throws Exception + { + final AOv1Parser parser = new AOv1Parser(); + + try (InputStream stream = resource("variants_bad_1.json")) { + this.expected.expect(AOParseException.class); + parser.parseVariants(URI.create("variants_bad_1.json"), stream); + } + } } diff --git a/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants.json b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants.json new file mode 100644 index 0000000..7749931 --- /dev/null +++ b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants.json @@ -0,0 +1,22 @@ +[ + { + "name": "OpenJDK 8 with Hotspot", + "variant": "openjdk8" + }, + { + "name": "OpenJDK 8 with Eclipse OpenJ9", + "variant": "openjdk8-openj9" + }, + { + "name": "OpenJDK 9 with Hotspot", + "variant": "openjdk9" + }, + { + "name": "OpenJDK 9 with Eclipse OpenJ9", + "variant": "openjdk9-openj9" + }, + { + "name": "OpenJDK 10 with Hotspot", + "variant": "openjdk10" + } +] \ No newline at end of file diff --git a/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_bad_0.json b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_bad_0.json new file mode 100644 index 0000000..6431a47 --- /dev/null +++ b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_bad_0.json @@ -0,0 +1,8 @@ +[ + { + "name": "OpenJDK 8 with Hotspot" + }, + { + "variant": "openjdk8-openj9" + } +] \ No newline at end of file diff --git a/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_bad_1.json b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_bad_1.json new file mode 100644 index 0000000..8e2f0be --- /dev/null +++ b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_bad_1.json @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_warnings.json b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_warnings.json new file mode 100644 index 0000000..9c9d6e7 --- /dev/null +++ b/com.io7m.adoptopenjdk.tests/src/test/resources/com/io7m/adoptopenjdk/tests/v1/variants_warnings.json @@ -0,0 +1,29 @@ +[ + { + "name": "OpenJDK 8 with Hotspot", + "extra": "ignore me", + "variant": "openjdk8" + }, + { + "name": "OpenJDK 8 with Eclipse OpenJ9", + "variant": "openjdk8-openj9", + "extra": { + + } + }, + { + "extra": [ + "123" + ], + "name": "OpenJDK 9 with Hotspot", + "variant": "openjdk9" + }, + { + "name": "OpenJDK 9 with Eclipse OpenJ9", + "variant": "openjdk9-openj9" + }, + { + "name": "OpenJDK 10 with Hotspot", + "variant": "openjdk10" + } +] \ No newline at end of file diff --git a/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Parser.java b/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Parser.java index 9d92782..b0e106e 100644 --- a/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Parser.java +++ b/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Parser.java @@ -24,6 +24,7 @@ import com.io7m.adoptopenjdk.spi.AOBinary; import com.io7m.adoptopenjdk.spi.AOParseException; import com.io7m.adoptopenjdk.spi.AORelease; +import com.io7m.adoptopenjdk.spi.AOVariant; import com.io7m.jaffirm.core.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -498,4 +499,132 @@ private AOParseException parseErrorExpectedReceived( return this.parseErrorExpectedReceived( Optional.empty(), uri, parser, expected, received); } + + + /** + * Parse an array of variants. + * + * @param uri The URI of the stream for diagnostic messages + * @param stream The input stream + * + * @return A list of releases + * + * @throws IOException On I/O errors + * @throws AOParseException On parse errors + */ + + public List parseVariants( + final URI uri, + final InputStream stream) + throws AOParseException, IOException + { + Objects.requireNonNull(uri, "uri"); + Objects.requireNonNull(stream, "stream"); + + final Instant time_then = Instant.now(); + + try { + final List variants = new ArrayList<>(8); + final JsonFactory parsers = new JsonFactory(); + try (JsonParser parser = parsers.createParser(stream)) { + while (true) { + final JsonToken next = parser.nextToken(); + LOG.trace("{}: token: {}", uri, next); + if (next == null) { + break; + } + + this.requireOneOf(uri, parser, next, START_ARRAY); + this.parseVariantsArray(uri, parser, variants); + } + } + + return variants; + } catch (final JsonProcessingException e) { + throw new AOParseException(e.getMessage(), e); + } finally { + final Instant time_now = Instant.now(); + LOG.debug("parsed in {}", Duration.between(time_then, time_now)); + } + } + + /** + * Parse an array of variants from the given parser. + */ + + private void parseVariantsArray( + final URI uri, + final JsonParser parser, + final Collection variants) + throws IOException, AOParseException + { + Preconditions.checkPrecondition( + parser.currentToken() == START_ARRAY, + "Parser must be at array start"); + + while (true) { + final JsonToken next = parser.nextToken(); + LOG.trace("{}: token: {}", uri, next); + this.requireNotEOF(uri, parser, next); + + this.requireOneOf(uri, parser, next, START_OBJECT, END_ARRAY); + if (next == END_ARRAY) { + return; + } + + variants.add(this.parseVariant(uri, parser)); + } + } + + /** + * Parse a single variant from the given parser. + */ + + private AOVariant parseVariant( + final URI uri, + final JsonParser parser) + throws IOException, AOParseException + { + Preconditions.checkPrecondition( + parser.currentToken() == START_OBJECT, + "Parser must be at object start"); + + final AOVariant.Builder builder = AOVariant.builder(); + + while (true) { + final JsonToken next = parser.nextToken(); + LOG.trace("{}: token: {}", uri, next); + this.requireNotEOF(uri, parser, next); + + this.requireOneOf(uri, parser, next, FIELD_NAME, END_OBJECT); + if (next == END_OBJECT) { + break; + } + + final String field_name = parser.getText(); + switch (field_name) { + case "name": { + parser.nextToken(); + builder.setDescription(parser.getText()); + break; + } + case "variant": { + parser.nextToken(); + builder.setName(parser.getText()); + break; + } + default: { + LOG.warn("ignoring unrecognized field: {}", field_name); + parser.nextToken(); + parser.skipChildren(); + } + } + } + + try { + return builder.build(); + } catch (final IllegalStateException e) { + throw this.parseErrorMissingFields(uri, parser, e); + } + } } diff --git a/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Requests.java b/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Requests.java index 467c95b..9e01a24 100644 --- a/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Requests.java +++ b/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1Requests.java @@ -20,6 +20,7 @@ import com.io7m.adoptopenjdk.spi.AOAPIVersionProviderType; import com.io7m.adoptopenjdk.spi.AOException; import com.io7m.adoptopenjdk.spi.AORelease; +import com.io7m.adoptopenjdk.spi.AOVariant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -158,6 +159,25 @@ public int rateLimitRemaining() return this.rate_limit.remaining; } + @Override + public List variants() + throws AOException + { + this.checkRateLimitRemaining(); + + final URI target = URI.create("https://api.adoptopenjdk.net/variants"); + try (AOv1HTTPConnectionType connection = + this.connections.get(target, standardProperties())) { + + this.rate_limit = rateLimitForConnection(connection); + try (InputStream stream = connection.input()) { + return this.parser.parseVariants(target, stream); + } + } catch (final IOException e) { + throw new AOException(e.getMessage(), e); + } + } + @Override public List releasesForVariant(final String variant) throws AOException diff --git a/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1RequestsType.java b/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1RequestsType.java index f8e3731..eb69855 100644 --- a/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1RequestsType.java +++ b/com.io7m.adoptopenjdk.v1/src/main/java/com/io7m/adoptopenjdk/v1/AOv1RequestsType.java @@ -20,6 +20,7 @@ import com.io7m.adoptopenjdk.spi.AOException; import com.io7m.adoptopenjdk.spi.AORelease; import com.io7m.adoptopenjdk.spi.AOReleases; +import com.io7m.adoptopenjdk.spi.AOVariant; import java.util.List; import java.util.Objects; @@ -35,6 +36,10 @@ public interface AOv1RequestsType extends AOAPIRequestsType @Override int rateLimitRemaining(); + @Override + List variants() + throws AOException; + @Override List releasesForVariant( String variant)