diff --git a/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/V3.kt b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/V3.kt index 6de66c5c..77507173 100644 --- a/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/V3.kt +++ b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/V3.kt @@ -2,13 +2,14 @@ package net.adoptopenjdk.api.v3 import net.adoptopenjdk.api.v3.dataSources.APIDataStore import net.adoptopenjdk.api.v3.routes.AssetsResource -import net.adoptopenjdk.api.v3.routes.BinaryResource +import net.adoptopenjdk.api.v3.routes.packages.BinaryResource import net.adoptopenjdk.api.v3.routes.V1Route import net.adoptopenjdk.api.v3.routes.VersionResource import net.adoptopenjdk.api.v3.routes.info.AvailableReleasesResource import net.adoptopenjdk.api.v3.routes.info.PlatformsResource import net.adoptopenjdk.api.v3.routes.info.ReleaseListResource import net.adoptopenjdk.api.v3.routes.info.VariantsResource +import net.adoptopenjdk.api.v3.routes.packages.InstallerResource import net.adoptopenjdk.api.v3.routes.stats.DownloadStatsResource import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition import org.eclipse.microprofile.openapi.annotations.info.Info @@ -65,7 +66,8 @@ class V3 : Application() { ReleaseListResource::class.java, VariantsResource::class.java, VersionResource::class.java, - DownloadStatsResource::class.java + DownloadStatsResource::class.java, + InstallerResource::class.java ) } diff --git a/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/BinaryResource.kt b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/BinaryResource.kt similarity index 59% rename from adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/BinaryResource.kt rename to adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/BinaryResource.kt index 74a055b0..4361c4a3 100644 --- a/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/BinaryResource.kt +++ b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/BinaryResource.kt @@ -1,17 +1,13 @@ -package net.adoptopenjdk.api.v3.routes +package net.adoptopenjdk.api.v3.routes.packages -import net.adoptopenjdk.api.v3.JsonMapper import net.adoptopenjdk.api.v3.OpenApiDocs -import net.adoptopenjdk.api.v3.dataSources.APIDataStore -import net.adoptopenjdk.api.v3.dataSources.SortOrder -import net.adoptopenjdk.api.v3.filters.BinaryFilter -import net.adoptopenjdk.api.v3.filters.ReleaseFilter -import net.adoptopenjdk.api.v3.models.APIError import net.adoptopenjdk.api.v3.models.Architecture +import net.adoptopenjdk.api.v3.models.Binary import net.adoptopenjdk.api.v3.models.HeapSize import net.adoptopenjdk.api.v3.models.ImageType import net.adoptopenjdk.api.v3.models.JvmImpl import net.adoptopenjdk.api.v3.models.OperatingSystem +import net.adoptopenjdk.api.v3.models.Package import net.adoptopenjdk.api.v3.models.Project import net.adoptopenjdk.api.v3.models.Release import net.adoptopenjdk.api.v3.models.ReleaseType @@ -25,7 +21,6 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses import org.eclipse.microprofile.openapi.annotations.tags.Tag import org.jboss.resteasy.annotations.jaxrs.PathParam import org.jboss.resteasy.annotations.jaxrs.QueryParam -import java.net.URI import javax.ws.rs.GET import javax.ws.rs.HEAD import javax.ws.rs.HeaderParam @@ -39,7 +34,7 @@ import javax.ws.rs.core.Response @Tag(name = "Binary") @Path("/v3/binary/") @Produces(MediaType.APPLICATION_JSON) -class BinaryResource { +class BinaryResource : PackageEndpoint() { @GET @HEAD @@ -94,68 +89,29 @@ class BinaryResource { @HeaderParam("User-Agent") userAgent: String ): Response { + val releases = getReleases(release_name, vendor, os, arch, image_type, jvm_impl, heap_size, project) return when (request.method) { - "HEAD" -> versionHead(userAgent, release_name, vendor, os, arch, image_type, jvm_impl, heap_size, project) - else -> versionGet(release_name, vendor, os, arch, image_type, jvm_impl, heap_size, project) - } - } - - private fun versionGet( - release_name: String?, - vendor: Vendor?, - os: OperatingSystem?, - arch: Architecture?, - image_type: ImageType?, - jvm_impl: JvmImpl?, - heap_size: HeapSize?, - project: Project? - ): Response { - return getBinary(release_name, vendor, os, arch, image_type, jvm_impl, heap_size, project) { `package` -> - Response.temporaryRedirect(URI.create(`package`.link)).build() + "HEAD" -> versionHead(userAgent, releases) + else -> formResponse(releases) } } private fun versionHead( userAgent: String, - release_name: String?, - vendor: Vendor?, - os: OperatingSystem?, - arch: Architecture?, - image_type: ImageType?, - jvm_impl: JvmImpl?, - heap_size: HeapSize?, - project: Project? + releases: List ): Response { return if (userAgent.contains("Gradle")) { - getBinary(release_name, vendor, os, arch, image_type, jvm_impl, heap_size, project) { `package` -> + return formResponse(releases) { `package` -> Response.status(200) .header("size", `package`.size) .header("content-disposition", """attachment; filename="${`package`.name}"; filename*=UTF-8''${`package`.name}""") .build() } } else { - versionGet(release_name, vendor, os, arch, image_type, jvm_impl, heap_size, project) + formResponse(releases) } } - private fun getBinary( - release_name: String?, - vendor: Vendor?, - os: OperatingSystem?, - arch: Architecture?, - image_type: ImageType?, - jvm_impl: JvmImpl?, - heap_size: HeapSize?, - project: Project?, - createResponse: (net.adoptopenjdk.api.v3.models.Package) -> Response - ): Response { - val releaseFilter = ReleaseFilter(releaseName = release_name, vendor = vendor) - val binaryFilter = BinaryFilter(os, arch, image_type, jvm_impl, heap_size, project) - val releases = APIDataStore.getAdoptRepos().getFilteredReleases(releaseFilter, binaryFilter, SortOrder.DESC).toList() - - return formResponse(releases, createResponse) - } - @GET @Path("/latest/{feature_version}/{release_type}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") @Produces("application/octet-stream") @@ -205,53 +161,21 @@ class BinaryResource { @QueryParam("project") project: Project? ): Response { - val releaseFilter = ReleaseFilter(releaseType = release_type, featureVersion = version, vendor = vendor) - val binaryFilter = BinaryFilter(os, arch, image_type, jvm_impl, heap_size, project) - val releases = APIDataStore.getAdoptRepos().getFilteredReleases(releaseFilter, binaryFilter, SortOrder.DESC).toList() - - val comparator = compareBy { it.version_data.major } - .thenBy { it.version_data.minor } - .thenBy { it.version_data.security } - .thenBy { it.version_data.pre } - .thenBy { it.version_data.build } - .thenBy { it.version_data.adopt_build_number } - .thenBy { it.version_data.optional } + val releaseList = getRelease(release_type, version, vendor, os, arch, image_type, jvm_impl, heap_size, project) - val release = releases.sortedWith(comparator).lastOrNull() + val release = releaseList.lastOrNull() - return formResponse(if (release == null) emptyList() else listOf(release)) { `package` -> - Response.temporaryRedirect(URI.create(`package`.link)).build() - } + return formResponse(if (release == null) emptyList() else listOf(release)) } - private fun formResponse(releases: List, createResponse: (net.adoptopenjdk.api.v3.models.Package) -> Response): Response { - if (releases.size == 0) { - return formErrorResponse(Response.Status.NOT_FOUND, "No releases match the request") - } else if (releases.size > 1) { - val versions = releases - .map { it.release_name } - return formErrorResponse(Response.Status.BAD_REQUEST, "Multiple releases match request: $versions") - } else { - val binaries = releases.get(0).binaries - val packages = binaries - .map { it.`package` } - .filterNotNull() - - if (packages.size == 0) { - return formErrorResponse(Response.Status.NOT_FOUND, "No binaries match the request") - } else if (packages.size > 1) { - val names = packages.map { it.name } - return formErrorResponse(Response.Status.BAD_REQUEST, "Multiple binaries match request: $names") - } else { - return createResponse(packages.first()) - } - } + protected fun formResponse( + releases: List, + createResponse: (Package) -> Response = redirectToAsset() + ): Response { + return formResponse(releases, extractPackage(), createResponse) } - private fun formErrorResponse(status: Response.Status, message: String): Response { - return Response - .status(status) - .entity(JsonMapper.mapper.writeValueAsString(APIError(message))) - .build() + private fun extractPackage(): (binary: Binary) -> Package { + return { binary -> binary.`package` } } } diff --git a/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/InstallerResource.kt b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/InstallerResource.kt new file mode 100644 index 00000000..0549ac75 --- /dev/null +++ b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/InstallerResource.kt @@ -0,0 +1,150 @@ +package net.adoptopenjdk.api.v3.routes.packages + +import net.adoptopenjdk.api.v3.OpenApiDocs +import net.adoptopenjdk.api.v3.models.Architecture +import net.adoptopenjdk.api.v3.models.Binary +import net.adoptopenjdk.api.v3.models.HeapSize +import net.adoptopenjdk.api.v3.models.ImageType +import net.adoptopenjdk.api.v3.models.Installer +import net.adoptopenjdk.api.v3.models.JvmImpl +import net.adoptopenjdk.api.v3.models.OperatingSystem +import net.adoptopenjdk.api.v3.models.Project +import net.adoptopenjdk.api.v3.models.Release +import net.adoptopenjdk.api.v3.models.ReleaseType +import net.adoptopenjdk.api.v3.models.Vendor +import org.eclipse.microprofile.openapi.annotations.Operation +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses +import org.eclipse.microprofile.openapi.annotations.tags.Tag +import org.jboss.resteasy.annotations.jaxrs.PathParam +import org.jboss.resteasy.annotations.jaxrs.QueryParam +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response + +@Tag(name = "Installer") +@Path("/v3/installer/") +@Produces(MediaType.APPLICATION_JSON) +class InstallerResource : PackageEndpoint() { + + @GET + @Path("/version/{release_name}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") + @Produces("application/octet-stream") + @Operation(summary = "Redirects to the installer that matches your current query", description = "Redirects to the installer that matches your current query") + @APIResponses(value = [ + APIResponse(responseCode = "307", description = "link to installer that matches your current query"), + APIResponse(responseCode = "400", description = "bad input parameter"), + APIResponse(responseCode = "404", description = "No matching installer found") + ] + ) + fun returnInstallerByVersion( + @Parameter(name = "os", description = "Operating System", required = true) + @PathParam("os") + os: OperatingSystem?, + + @Parameter(name = "arch", description = "Architecture", required = true) + @PathParam("arch") + arch: Architecture?, + + @Parameter(name = "release_name", description = OpenApiDocs.RELASE_NAME, required = true, + schema = Schema(defaultValue = "jdk-11.0.6+10", type = SchemaType.STRING) + ) + @PathParam("release_name") + release_name: String?, + + @Parameter(name = "image_type", description = "Image Type", required = true) + @PathParam("image_type") + image_type: ImageType?, + + @Parameter(name = "jvm_impl", description = "JVM Implementation", required = true) + @PathParam("jvm_impl") + jvm_impl: JvmImpl?, + + @Parameter(name = "heap_size", description = "Heap Size", required = true) + @PathParam("heap_size") + heap_size: HeapSize?, + + @Parameter(name = "vendor", description = OpenApiDocs.VENDOR, required = true) + @PathParam("vendor") + vendor: Vendor?, + + @Parameter(name = "project", description = "Project", schema = Schema(defaultValue = "jdk", enumeration = ["jdk", "valhalla", "metropolis", "jfr"], required = false), required = false) + @QueryParam("project") + project: Project? + ): Response { + val releases = getReleases(release_name, vendor, os, arch, image_type, jvm_impl, heap_size, project) + return formResponseInstaller(releases) + } + + @GET + @Path("/latest/{feature_version}/{release_type}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") + @Produces("application/octet-stream") + @Operation(summary = "Redirects to the installer that matches your current query", description = "Redirects to the installer that matches your current query") + @APIResponses(value = [ + APIResponse(responseCode = "307", description = "link to installer that matches your current query"), + APIResponse(responseCode = "400", description = "bad input parameter"), + APIResponse(responseCode = "404", description = "No matching installer found") + ] + ) + fun returnInstaller( + @Parameter(name = "feature_version", description = OpenApiDocs.FEATURE_RELEASE, required = true, + schema = Schema(defaultValue = "8", type = SchemaType.INTEGER) + ) + @PathParam("feature_version") + version: Int?, + + @Parameter(name = "release_type", description = OpenApiDocs.RELEASE_TYPE, required = true) + @PathParam("release_type") + release_type: ReleaseType?, + + @Parameter(name = "os", description = "Operating System", required = true) + @PathParam("os") + os: OperatingSystem?, + + @Parameter(name = "arch", description = "Architecture", required = true) + @PathParam("arch") + arch: Architecture?, + + @Parameter(name = "image_type", description = "Image Type", required = true) + @PathParam("image_type") + image_type: ImageType?, + + @Parameter(name = "jvm_impl", description = "JVM Implementation", required = true) + @PathParam("jvm_impl") + jvm_impl: JvmImpl?, + + @Parameter(name = "heap_size", description = "Heap Size", required = true) + @PathParam("heap_size") + heap_size: HeapSize?, + + @Parameter(name = "vendor", description = OpenApiDocs.VENDOR, required = true) + @PathParam("vendor") + vendor: Vendor?, + + @Parameter(name = "project", description = "Project", schema = Schema(defaultValue = "jdk", enumeration = ["jdk", "valhalla", "metropolis", "jfr"], required = false), required = false) + @QueryParam("project") + project: Project? + ): Response { + val releaseList = getRelease(release_type, version, vendor, os, arch, image_type, jvm_impl, heap_size, project) + + val release = releaseList + .lastOrNull { release -> + release.binaries.any { it.installer != null } + } + + return formResponseInstaller(if (release == null) emptyList() else listOf(release)) + } + + private fun formResponseInstaller(releases: List): Response { + return formResponse(releases, extractInstaller(), redirectToAsset()) + } + + private fun extractInstaller(): (Binary) -> Installer? { + return { binary -> binary.installer } + } +} diff --git a/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/PackageEndpoint.kt b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/PackageEndpoint.kt new file mode 100644 index 00000000..f6fa1a03 --- /dev/null +++ b/adoptopenjdk-api-v3-frontend/src/main/kotlin/net/adoptopenjdk/api/v3/routes/packages/PackageEndpoint.kt @@ -0,0 +1,100 @@ +package net.adoptopenjdk.api.v3.routes.packages + +import net.adoptopenjdk.api.v3.JsonMapper +import net.adoptopenjdk.api.v3.dataSources.APIDataStore +import net.adoptopenjdk.api.v3.dataSources.SortOrder +import net.adoptopenjdk.api.v3.dataSources.models.Releases.Companion.VERSION_COMPARATOR +import net.adoptopenjdk.api.v3.filters.BinaryFilter +import net.adoptopenjdk.api.v3.filters.ReleaseFilter +import net.adoptopenjdk.api.v3.models.APIError +import net.adoptopenjdk.api.v3.models.Architecture +import net.adoptopenjdk.api.v3.models.Asset +import net.adoptopenjdk.api.v3.models.Binary +import net.adoptopenjdk.api.v3.models.HeapSize +import net.adoptopenjdk.api.v3.models.ImageType +import net.adoptopenjdk.api.v3.models.JvmImpl +import net.adoptopenjdk.api.v3.models.OperatingSystem +import net.adoptopenjdk.api.v3.models.Project +import net.adoptopenjdk.api.v3.models.Release +import net.adoptopenjdk.api.v3.models.ReleaseType +import net.adoptopenjdk.api.v3.models.Vendor +import java.net.URI +import javax.ws.rs.core.Response + +open class PackageEndpoint { + + fun getReleases( + release_name: String?, + vendor: Vendor?, + os: OperatingSystem?, + arch: Architecture?, + image_type: ImageType?, + jvm_impl: JvmImpl?, + heap_size: HeapSize?, + project: Project? + ): List { + val releaseFilter = ReleaseFilter(releaseName = release_name, vendor = vendor) + val binaryFilter = BinaryFilter(os, arch, image_type, jvm_impl, heap_size, project) + return APIDataStore.getAdoptRepos().getFilteredReleases(releaseFilter, binaryFilter, SortOrder.DESC).toList() + } + + protected fun formResponse( + releases: List, + extractAsset: (Binary) -> T?, + createResponse: (T) -> Response + ): Response { + when { + releases.isEmpty() -> { + return formErrorResponse(Response.Status.NOT_FOUND, "No releases match the request") + } + releases.size > 1 -> { + val versions = releases + .map { it.release_name } + return formErrorResponse(Response.Status.BAD_REQUEST, "Multiple releases match request: $versions") + } + else -> { + val binaries = releases[0].binaries + val packages = binaries.mapNotNull { extractAsset(it) } + + return when { + packages.isEmpty() -> { + formErrorResponse(Response.Status.NOT_FOUND, "No binaries match the request") + } + packages.size > 1 -> { + val names = packages.map { it.name } + formErrorResponse(Response.Status.BAD_REQUEST, "Multiple binaries match request: $names") + } + else -> { + createResponse(packages.first()) + } + } + } + } + } + + private fun formErrorResponse(status: Response.Status, message: String): Response { + return Response + .status(status) + .entity(JsonMapper.mapper.writeValueAsString(APIError(message))) + .build() + } + + fun getRelease(release_type: ReleaseType?, version: Int?, vendor: Vendor?, os: OperatingSystem?, arch: Architecture?, image_type: ImageType?, jvm_impl: JvmImpl?, heap_size: HeapSize?, project: Project?): List { + val releaseFilter = ReleaseFilter(releaseType = release_type, featureVersion = version, vendor = vendor) + val binaryFilter = BinaryFilter(os, arch, image_type, jvm_impl, heap_size, project) + val releases = APIDataStore.getAdoptRepos().getFilteredReleases(releaseFilter, binaryFilter, SortOrder.DESC).toList() + + // We use updated_at and timestamp as well JIC we've made a mistake and respun the same version number twice, in which case newest wins. + val comparator = VERSION_COMPARATOR.thenBy { it.version_data.optional } + .thenBy { it.updated_at } + .thenBy { it.timestamp } + + return releases.sortedWith(comparator) + } + + protected fun redirectToAsset(): (Asset) -> Response { + return { asset -> + Response.temporaryRedirect(URI.create(asset.link)).build() + } + } +} diff --git a/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/BinaryPathTest.kt b/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/BinaryPathTest.kt similarity index 55% rename from adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/BinaryPathTest.kt rename to adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/BinaryPathTest.kt index 70b053b0..7bc19ba7 100644 --- a/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/BinaryPathTest.kt +++ b/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/BinaryPathTest.kt @@ -1,12 +1,7 @@ -package net.adoptopenjdk.api +package net.adoptopenjdk.api.packages import io.quarkus.test.junit.QuarkusTest import io.restassured.RestAssured -import io.restassured.response.Response -import kotlinx.coroutines.runBlocking -import net.adoptopenjdk.api.v3.AdoptReposBuilder -import net.adoptopenjdk.api.v3.dataSources.APIDataStore -import net.adoptopenjdk.api.v3.dataSources.ApiPersistenceFactory import net.adoptopenjdk.api.v3.models.Architecture import net.adoptopenjdk.api.v3.models.HeapSize import net.adoptopenjdk.api.v3.models.ImageType @@ -16,104 +11,64 @@ import net.adoptopenjdk.api.v3.models.Project import net.adoptopenjdk.api.v3.models.ReleaseType import net.adoptopenjdk.api.v3.models.Vendor import org.hamcrest.Matchers -import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test @QuarkusTest -class BinaryPathTest : BaseTest() { +class BinaryPathTest : PackageEndpointTest() { - companion object { - @JvmStatic - @BeforeAll - fun populateDb() { - runBlocking { - val repo = AdoptReposBuilder.build(APIDataStore.variants.versions) - // Reset connection - ApiPersistenceFactory.set(null) - ApiPersistenceFactory.get().updateAllRepos(repo) - APIDataStore.loadDataFromDb() - } - } - } - - val path = "/v3/binary" - - fun getLatestPath( - featureVersion: Int, - releaseType: ReleaseType, - os: OperatingSystem, - arch: Architecture, - imageType: ImageType, - jvmImpl: JvmImpl, - heapSize: HeapSize, - vendor: Vendor, - project: Project - ): String { - return "$path/latest/$featureVersion/$releaseType/$os/$arch/$imageType/$jvmImpl/$heapSize/$vendor?project=$project" - } - - fun getVersionPath( - releaseName: String, - os: OperatingSystem, - arch: Architecture, - imageType: ImageType, - jvmImpl: JvmImpl, - heapSize: HeapSize, - vendor: Vendor, - project: Project - ): String { - return "$path/version/$releaseName/$os/$arch/$imageType/$jvmImpl/$heapSize/$vendor?project=$project" + override fun getPath(): String { + return "/v3/binary" } @Test fun latestDoesRedirectToBinary() { val path = getLatestPath(8, ReleaseType.ga, OperatingSystem.linux, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.jdk) performRequest(path) - .then() - .statusCode(307) - .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/")) + .then() + .statusCode(307) + .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/")) } @Test fun latestDoesRedirectToBinaryNoProject() { - val path = "$path/latest/11/ga/linux/x64/jdk/openj9/normal/adoptopenjdk" + val path = "${getPath()}/latest/11/ga/linux/x64/jdk/openj9/normal/adoptopenjdk" performRequest(path) - .then() - .statusCode(307) - .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/")) + .then() + .statusCode(307) + .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/")) } @Test fun latestDoesNotRedirectToBinary() { val path = getLatestPath(8, ReleaseType.ga, OperatingSystem.linux, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.valhalla) performRequest(path) - .then() - .statusCode(404) + .then() + .statusCode(404) } @Test - fun noExistantLatestRequestGives404() { + fun nonExistantLatestRequestGives404() { val path = getLatestPath(4, ReleaseType.ga, OperatingSystem.linux, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.valhalla) performRequest(path) - .then() - .statusCode(404) + .then() + .statusCode(404) } @Test - fun nonExistantVersionRequestRedirects() { + fun versionRequestRedirects() { val path = getVersionPath("jdk8u212-b04", OperatingSystem.linux, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.jdk) performRequest(path) - .then() - .statusCode(307) - .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u212-b04")) + .then() + .statusCode(307) + .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u212-b04")) } @Test fun nonExistantVersionRequestGives404() { val path = getVersionPath("fooBar", OperatingSystem.linux, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.jdk) performRequest(path) - .then() - .statusCode(404) + .then() + .statusCode(404) } @Test @@ -153,11 +108,4 @@ class BinaryPathTest : BaseTest() { .then() .statusCode(404) } - - private fun performRequest(path: String): Response { - return RestAssured.given() - .`when`() - .redirects().follow(false) - .get(path) - } } diff --git a/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/InstallerPathTest.kt b/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/InstallerPathTest.kt new file mode 100644 index 00000000..c072e086 --- /dev/null +++ b/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/InstallerPathTest.kt @@ -0,0 +1,72 @@ +package net.adoptopenjdk.api.packages + +import io.quarkus.test.junit.QuarkusTest +import net.adoptopenjdk.api.v3.models.Architecture +import net.adoptopenjdk.api.v3.models.HeapSize +import net.adoptopenjdk.api.v3.models.ImageType +import net.adoptopenjdk.api.v3.models.JvmImpl +import net.adoptopenjdk.api.v3.models.OperatingSystem +import net.adoptopenjdk.api.v3.models.Project +import net.adoptopenjdk.api.v3.models.ReleaseType +import net.adoptopenjdk.api.v3.models.Vendor +import org.hamcrest.Matchers +import org.junit.jupiter.api.Test + +@QuarkusTest +class InstallerPathTest : PackageEndpointTest() { + + override fun getPath(): String { + return "/v3/installer" + } + + @Test + fun latestDoesRedirectToBinary() { + val path = getLatestPath(11, ReleaseType.ga, OperatingSystem.windows, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.jdk) + performRequest(path) + .then() + .statusCode(307) + .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/")) + } + + @Test + fun latestDoesRedirectToBinaryNoProject() { + val path = "${getPath()}/latest/11/ga/windows/x64/jdk/openj9/normal/adoptopenjdk" + performRequest(path) + .then() + .statusCode(307) + .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/")) + } + + @Test + fun latestDoesNotRedirectToBinary() { + val path = getLatestPath(8, ReleaseType.ga, OperatingSystem.windows, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.valhalla) + performRequest(path) + .then() + .statusCode(404) + } + + @Test + fun noExistantLatestRequestGives404() { + val path = getLatestPath(4, ReleaseType.ga, OperatingSystem.windows, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.valhalla) + performRequest(path) + .then() + .statusCode(404) + } + + @Test + fun versionRequestRedirects() { + val path = getVersionPath("jdk8u212-b04", OperatingSystem.windows, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.jdk) + performRequest(path) + .then() + .statusCode(307) + .header("Location", Matchers.startsWith("https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u212-b04")) + } + + @Test + fun nonExistantVersionRequestGives404() { + val path = getVersionPath("fooBar", OperatingSystem.windows, Architecture.x64, ImageType.jdk, JvmImpl.hotspot, HeapSize.normal, Vendor.adoptopenjdk, Project.jdk) + performRequest(path) + .then() + .statusCode(404) + } +} diff --git a/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/PackageEndpointTest.kt b/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/PackageEndpointTest.kt new file mode 100644 index 00000000..aa79147a --- /dev/null +++ b/adoptopenjdk-api-v3-frontend/src/test/kotlin/net/adoptopenjdk/api/packages/PackageEndpointTest.kt @@ -0,0 +1,73 @@ +package net.adoptopenjdk.api.packages + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.RestAssured +import io.restassured.response.Response +import kotlinx.coroutines.runBlocking +import net.adoptopenjdk.api.BaseTest +import net.adoptopenjdk.api.v3.AdoptReposBuilder +import net.adoptopenjdk.api.v3.dataSources.APIDataStore +import net.adoptopenjdk.api.v3.dataSources.ApiPersistenceFactory +import net.adoptopenjdk.api.v3.models.Architecture +import net.adoptopenjdk.api.v3.models.HeapSize +import net.adoptopenjdk.api.v3.models.ImageType +import net.adoptopenjdk.api.v3.models.JvmImpl +import net.adoptopenjdk.api.v3.models.OperatingSystem +import net.adoptopenjdk.api.v3.models.Project +import net.adoptopenjdk.api.v3.models.ReleaseType +import net.adoptopenjdk.api.v3.models.Vendor +import org.junit.jupiter.api.BeforeAll + +@QuarkusTest +abstract class PackageEndpointTest : BaseTest() { + + companion object { + @JvmStatic + @BeforeAll + fun populateDb() { + runBlocking { + val repo = AdoptReposBuilder.build(APIDataStore.variants.versions) + // Reset connection + ApiPersistenceFactory.set(null) + ApiPersistenceFactory.get().updateAllRepos(repo) + APIDataStore.loadDataFromDb() + } + } + } + + abstract fun getPath(): String + + fun getLatestPath( + featureVersion: Int, + releaseType: ReleaseType, + os: OperatingSystem, + arch: Architecture, + imageType: ImageType, + jvmImpl: JvmImpl, + heapSize: HeapSize, + vendor: Vendor, + project: Project + ): String { + return "${getPath()}/latest/$featureVersion/$releaseType/$os/$arch/$imageType/$jvmImpl/$heapSize/$vendor?project=$project" + } + + fun getVersionPath( + releaseName: String, + os: OperatingSystem, + arch: Architecture, + imageType: ImageType, + jvmImpl: JvmImpl, + heapSize: HeapSize, + vendor: Vendor, + project: Project + ): String { + return "${getPath()}/version/$releaseName/$os/$arch/$imageType/$jvmImpl/$heapSize/$vendor?project=$project" + } + + protected fun performRequest(path: String): Response { + return RestAssured.given() + .`when`() + .redirects().follow(false) + .get(path) + } +} diff --git a/adoptopenjdk-api-v3-updater/src/main/kotlin/net/adoptopenjdk/api/v3/stats/GithubDownloadStatsCalculator.kt b/adoptopenjdk-api-v3-updater/src/main/kotlin/net/adoptopenjdk/api/v3/stats/GithubDownloadStatsCalculator.kt index 27f3e472..77be4f9c 100644 --- a/adoptopenjdk-api-v3-updater/src/main/kotlin/net/adoptopenjdk/api/v3/stats/GithubDownloadStatsCalculator.kt +++ b/adoptopenjdk-api-v3-updater/src/main/kotlin/net/adoptopenjdk/api/v3/stats/GithubDownloadStatsCalculator.kt @@ -6,37 +6,70 @@ import net.adoptopenjdk.api.v3.dataSources.models.AdoptRepos import net.adoptopenjdk.api.v3.dataSources.persitence.ApiPersistence import net.adoptopenjdk.api.v3.models.GithubDownloadStatsDbEntry import net.adoptopenjdk.api.v3.models.Vendor +import org.slf4j.LoggerFactory import java.time.ZonedDateTime class GithubDownloadStatsCalculator { private val database: ApiPersistence = ApiPersistenceFactory.get() + companion object { + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(this::class.java) + } + suspend fun saveStats(repos: AdoptRepos) { val stats = getStats(repos) database.addGithubDownloadStatsEntries(stats) + + printSizeStats(repos) + } + + fun printSizeStats(repos: AdoptRepos) { + val stats = repos + .repos + .values + .map { featureRelease -> + val total = featureRelease + .releases + .getReleases() + .filter { it.vendor == Vendor.adoptopenjdk } + .flatMap { release -> + release + .binaries + .map { it.`package`.size + if (it.installer == null) 0 else it.installer!!.size } + .asSequence() + } + .sum() + + LOGGER.info("Stats ${featureRelease.featureVersion} $total") + total + } + .sum() + LOGGER.info("Stats total $stats") } public fun getStats(repos: AdoptRepos): List { val date: ZonedDateTime = TimeSource.now() val stats = repos - .repos - .values - .map { featureRelease -> - val total = featureRelease - .releases - .getReleases() - .filter { it.vendor == Vendor.adoptopenjdk } - .sumBy { - it.download_count.toInt() - } - - GithubDownloadStatsDbEntry(date, - total.toLong(), - featureRelease.featureVersion) - } - .toList() + .repos + .values + .map { featureRelease -> + val total = featureRelease + .releases + .getReleases() + .filter { it.vendor == Vendor.adoptopenjdk } + .sumBy { + it.download_count.toInt() + } + + GithubDownloadStatsDbEntry(date, + total.toLong(), + featureRelease.featureVersion + ) + } + .toList() return stats } } diff --git a/adoptopenjdk-api-v3-updater/src/test/kotlin/net/adoptopenjdk/api/UpstreamBinaryMapperTest.kt b/adoptopenjdk-api-v3-updater/src/test/kotlin/net/adoptopenjdk/api/UpstreamBinaryMapperTest.kt index cfc37106..69824184 100644 --- a/adoptopenjdk-api-v3-updater/src/test/kotlin/net/adoptopenjdk/api/UpstreamBinaryMapperTest.kt +++ b/adoptopenjdk-api-v3-updater/src/test/kotlin/net/adoptopenjdk/api/UpstreamBinaryMapperTest.kt @@ -60,14 +60,15 @@ class UpstreamBinaryMapperTest { fun correctlyClassifiesImageType() { val assets = getAssetList(listOf( - "OpenJDK11U-x64_linux_11.0.3_7.tar.gz", - "OpenJDK11U-x64_windows_11.0.3_6_ea.zip", - "OpenJDK11U-testimage_x64_linux_11.0.5_10.tar.gz", - "OpenJDK11U-static-libs_x64_linux_11.0.5_10.tar.gz", - "OpenJDK11U-jre_aarch64_linux_11.0.5_10.tar.gz", - "OpenJDK11U-jdk_aarch64_linux_11.0.5_10-debuginfo.tar.gz", - "OpenJDK11U-sources_11.0.5_10.tar.gz" - )) + "OpenJDK11U-x64_linux_11.0.3_7.tar.gz", + "OpenJDK11U-x64_windows_11.0.3_6_ea.zip", + "OpenJDK11U-testimage_x64_linux_11.0.5_10.tar.gz", + "OpenJDK11U-static-libs_x64_linux_11.0.5_10.tar.gz", + "OpenJDK11U-jre_aarch64_linux_11.0.5_10.tar.gz", + "OpenJDK11U-jdk_aarch64_linux_11.0.5_10-debuginfo.tar.gz", + "OpenJDK11U-sources_11.0.5_10.tar.gz" + ) + ) runBlocking { val binaryList = UpstreamBinaryMapper.toBinaryList(assets)