diff --git a/.github/actions/azure-login/action.yml b/.github/actions/azure-login/action.yml index a80cba492..7661301a5 100644 --- a/.github/actions/azure-login/action.yml +++ b/.github/actions/azure-login/action.yml @@ -10,31 +10,22 @@ inputs: AZURE_SUBSCRIPTION_ID: description: 'Azure Subscription ID' required: true - AZURE_CLIENT_ID: - description: 'Azure Client ID' - required: true - AZURE_CLIENT_SECRET: - description: 'Azure Client Secret' - required: true runs: using: composite steps: - name: Login to our Azure subscription. - uses: azure/login@6b2456866fc08b011acb422a92a4aa20e2c4de32 # v2.1.0 + uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0 with: client-id: ${{ inputs.AZURE_CLIENT_ID_OIDC }} tenant-id: ${{ inputs.AZURE_TENANT_ID }} subscription-id: ${{ inputs.AZURE_SUBSCRIPTION_ID }} - name: Login to Azure Container Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 - with: - registry: adoptopenjdkacr.azurecr.io - username: ${{ inputs.AZURE_CLIENT_ID }} - password: ${{ inputs.AZURE_CLIENT_SECRET }} + shell: bash + run: az acr login --name adoptopenjdkacr - name: Set the target Azure Kubernetes Service (AKS) cluster. - uses: azure/aks-set-context@37037e33d3a2fc08abe40c887d81c3f6e1eb93b9 # v4.0.0 + uses: azure/aks-set-context@1cf43fa609aaef0617c6a12deda238b920e926b0 # v4.0.1 with: - resource-group: adopt-api - cluster-name: aksff92 + resource-group: adoptium-api + cluster-name: adoptium-api diff --git a/.github/actions/do-login/action.yml b/.github/actions/do-login/action.yml deleted file mode 100644 index 7a74f00a1..000000000 --- a/.github/actions/do-login/action.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: DigitalOcean Login -description: Logs in to DigitalOcean -inputs: - DIGITALOCEAN_ACCESS_TOKEN: - description: 'DigitalOcean access token' - required: true -runs: - using: composite - steps: - - name: Login to DigitalOcean - uses: digitalocean/action-doctl@135ac0aa0eed4437d547c6f12c364d3006b42824 # v2.5.1 - with: - token: ${{ inputs.DIGITALOCEAN_ACCESS_TOKEN }} - - - name: Set the target Azure Kubernetes Service (AKS) cluster. - shell: bash - run: doctl kubernetes cluster kubeconfig save k8s-1-27-4-do-0-blr1-1693486270021 diff --git a/.github/actions/docker-build/action.yml b/.github/actions/docker-build/action.yml index 01d768ae8..a9ac87727 100644 --- a/.github/actions/docker-build/action.yml +++ b/.github/actions/docker-build/action.yml @@ -14,7 +14,7 @@ runs: using: composite steps: - name: Build container image and push to Azure - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 with: file: ${{ inputs.DOCKER_FILE }} tags: ${{ inputs.DOCKER_REPO }}:latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07bd82df1..0e562b2c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,11 +30,11 @@ jobs: adoptopenjdk) echo "ARGS=-Padoptopenjdk,-adoptium" >> $GITHUB_ENV ;; esac - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 with: java-version: '21' distribution: 'temurin' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7e9af0293..86e90210f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,11 +36,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,7 +48,7 @@ jobs: # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main - - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 with: java-version: '21' distribution: 'temurin' @@ -56,9 +56,9 @@ jobs: - if: matrix.build-mode == 'manual' run: | - ./mvnw --batch-mode clean -Dmaven.test.skip=true install -Padoptium,-adoptopenjdk + ./mvnw --batch-mode clean install -Padoptium,-adoptopenjdk - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/deploy-adoptium.yml b/.github/workflows/deploy-adoptium.yml index c1e5b6bfb..66c0de838 100644 --- a/.github/workflows/deploy-adoptium.yml +++ b/.github/workflows/deploy-adoptium.yml @@ -14,7 +14,7 @@ jobs: if: startsWith(github.repository, 'adoptium/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to Azure uses: ./.github/actions/azure-login @@ -22,8 +22,6 @@ jobs: AZURE_CLIENT_ID_OIDC: ${{ secrets.AZURE_CLIENT_ID_OIDC }} AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - name: Detect Branch run: | diff --git a/.github/workflows/deploy-adoptopenjdk.yml b/.github/workflows/deploy-adoptopenjdk.yml index 6a25abc13..2dea0662e 100644 --- a/.github/workflows/deploy-adoptopenjdk.yml +++ b/.github/workflows/deploy-adoptopenjdk.yml @@ -14,7 +14,7 @@ jobs: if: startsWith(github.repository, 'adoptium/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to Azure uses: ./.github/actions/azure-login @@ -22,8 +22,6 @@ jobs: AZURE_CLIENT_ID_OIDC: ${{ secrets.AZURE_CLIENT_ID_OIDC }} AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - name: Detect Branch run: | @@ -43,15 +41,3 @@ jobs: uses: ./.github/actions/kubectl-redeploy with: NAMESPACE: ${{ env.NAMESPACE }} - - # if the branch is production, redeploy the k8s service on digitalocean too - - name: Login to DigitalOcean - uses: ./.github/actions/do-login - with: - DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - - - name: Redeploy Kubernetes Updater and Frontend - if: startsWith(github.ref, 'refs/heads/production') - uses: ./.github/actions/kubectl-redeploy - with: - NAMESPACE: adoptopenjdk-do diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 93a5f9e7f..59611eb63 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,9 +14,9 @@ jobs: contents: write # for Git to git push steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 with: java-version: '21' distribution: 'temurin' diff --git a/.github/workflows/semgrep_diff.yml b/.github/workflows/semgrep_diff.yml new file mode 100644 index 000000000..8fa4884a8 --- /dev/null +++ b/.github/workflows/semgrep_diff.yml @@ -0,0 +1,25 @@ +# ******************************************************************************** +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made +# available under the terms of the Apache Software License 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************** + +--- +name: Semgrep Differential Scan +on: + pull_request: + +permissions: + contents: read + statuses: write + +jobs: + semgrep-diff: + uses: adoptium/.github/.github/workflows/semgrep_diff.yml@main diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 346d645fd..d58ddb0c7 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -14,5 +14,5 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar diff --git a/adoptium-api-v3-persistence/pom.xml b/adoptium-api-v3-persistence/pom.xml index 9968b9275..776a3eabe 100644 --- a/adoptium-api-v3-persistence/pom.xml +++ b/adoptium-api-v3-persistence/pom.xml @@ -13,8 +13,12 @@ - org.litote.kmongo - kmongo-coroutine + org.mongodb + mongodb-driver-kotlin-coroutine + + + com.fasterxml.jackson.datatype + jackson-datatype-jakarta-jsonp net.adoptium.api @@ -41,6 +45,17 @@ jakarta.enterprise jakarta.enterprise.cdi-api + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/DownloadStatsInterface.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/DownloadStatsInterface.kt index d563d92cd..038f85ab7 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/DownloadStatsInterface.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/DownloadStatsInterface.kt @@ -3,6 +3,7 @@ package net.adoptium.api.v3 import jakarta.enterprise.context.ApplicationScoped import jakarta.inject.Inject import net.adoptium.api.v3.config.APIConfig +import net.adoptium.api.v3.dataSources.VersionSupplier import net.adoptium.api.v3.dataSources.persitence.ApiPersistence import net.adoptium.api.v3.models.DbStatsEntry import net.adoptium.api.v3.models.DockerDownloadStatsDbEntry @@ -13,7 +14,6 @@ import net.adoptium.api.v3.models.JvmImpl import net.adoptium.api.v3.models.MonthlyDownloadDiff import net.adoptium.api.v3.models.StatsSource import net.adoptium.api.v3.models.TotalStats -import net.adoptium.api.v3.models.Versions import org.eclipse.microprofile.openapi.annotations.media.Schema import java.time.ZonedDateTime import java.time.temporal.ChronoUnit @@ -29,12 +29,16 @@ class StatEntry( @ApplicationScoped class DownloadStatsInterface { + @Schema(hidden = true) + private var versionSupplier: VersionSupplier + @Schema(hidden = true) private val dataStore: ApiPersistence @Inject - constructor(dataStore: ApiPersistence) { + constructor(dataStore: ApiPersistence, versionSupplier: VersionSupplier) { this.dataStore = dataStore + this.versionSupplier = versionSupplier } suspend fun getTrackingStats( @@ -115,8 +119,8 @@ class DownloadStatsInterface { return stats.groupBy { it.dateTime.toLocalDate() } .map { grouped -> StatEntry( - grouped.value.map { it.dateTime }.maxOrNull()!!, - grouped.value.map { it.count }.sum() + grouped.value.maxOf { it.dateTime }, + grouped.value.sumOf { it.count } ) } .sortedBy { it.dateTime } @@ -157,7 +161,7 @@ class DownloadStatsInterface { private fun calculateMonthlyDiff( stats: Collection ): List { - val toTwoChar = { value: Int -> if (value < 10) "0" + value else value.toString() } // Returns in MM format + val toTwoChar = { value: Int -> if (value < 10) "0$value" else value.toString() } // Returns in MM format return stats .windowed(2, 1, false) { @@ -240,16 +244,14 @@ class DownloadStatsInterface { return stats .groupBy { it.getId() } .map { grouped -> grouped.value.maxByOrNull { it.date } } - .map { it!!.getMetric() } - .sum() + .sumOf { it!!.getMetric() } } private fun formTotalDownloads(stats: List, jvmImpl: JvmImpl): Long { return stats .groupBy { it.getId() } .map { grouped -> grouped.value.maxByOrNull { it.date } } - .map { (it!!.jvmImplDownloads?.get(jvmImpl) ?: 0) } - .sum() + .sumOf { (it!!.jvmImplDownloads?.get(jvmImpl) ?: 0) } } suspend fun getTotalDownloadStats(): DownloadStats { @@ -257,21 +259,13 @@ class DownloadStatsInterface { val githubStats = getGithubStats() - val dockerPulls = dockerStats - .map { it.pulls } - .sum() + val dockerPulls = dockerStats.sumOf { it.pulls } - val githubDownloads = githubStats - .map { it.downloads } - .sum() + val githubDownloads = githubStats.sumOf { it.downloads } - val dockerBreakdown = dockerStats - .map { Pair(it.repo, it.pulls) } - .toMap() + val dockerBreakdown = dockerStats.associate { Pair(it.repo, it.pulls) } - val githubBreakdown = githubStats - .map { Pair(it.feature_version, it.downloads) } - .toMap() + val githubBreakdown = githubStats.associate { Pair(it.feature_version, it.downloads) } val totalStats = TotalStats(dockerPulls, githubDownloads, dockerPulls + githubDownloads) return DownloadStats(TimeSource.now(), totalStats, githubBreakdown, dockerBreakdown) @@ -284,7 +278,7 @@ class DownloadStatsInterface { } private suspend fun getGithubStats(): List { - return Versions.versions + return versionSupplier.getAllVersions() .mapNotNull { featureVersion -> dataStore.getLatestGithubStatsForFeatureVersion(featureVersion) } diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/JsonMapper.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/JsonMapper.kt index a4a0db970..592d15052 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/JsonMapper.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/JsonMapper.kt @@ -3,11 +3,9 @@ package net.adoptium.api.v3 import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.KotlinModule object JsonMapper { - val mapper: ObjectMapper = ObjectMapper() + val mapper: ObjectMapper = com.fasterxml.jackson.module.kotlin.jacksonObjectMapper() .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .registerModule(KotlinModule.Builder().build()) .registerModule(JavaTimeModule()) } diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStore.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStore.kt index b1a0f0940..596ffacbb 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStore.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStore.kt @@ -1,6 +1,7 @@ package net.adoptium.api.v3.dataSources import net.adoptium.api.v3.dataSources.models.AdoptRepos +import net.adoptium.api.v3.dataSources.persitence.mongo.UpdatedInfo import net.adoptium.api.v3.models.ReleaseInfo interface APIDataStore { @@ -9,4 +10,5 @@ interface APIDataStore { fun setAdoptRepos(binaryRepos: AdoptRepos) fun getReleaseInfo(): ReleaseInfo fun loadDataFromDb(forceUpdate: Boolean): AdoptRepos + fun getUpdateInfo(): UpdatedInfo } diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStoreImpl.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStoreImpl.kt index aa81d5da7..2fcbe6335 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStoreImpl.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/APIDataStoreImpl.kt @@ -11,7 +11,6 @@ import net.adoptium.api.v3.dataSources.persitence.mongo.UpdatedInfo import net.adoptium.api.v3.models.JvmImpl import net.adoptium.api.v3.models.ReleaseInfo import net.adoptium.api.v3.models.Vendor -import net.adoptium.api.v3.models.Versions import org.slf4j.LoggerFactory import java.time.ZonedDateTime import java.util.concurrent.Executors @@ -25,20 +24,106 @@ open class APIDataStoreImpl : APIDataStore { private var updatedAt: UpdatedInfo private var binaryRepos: AdoptRepos private var releaseInfo: ReleaseInfo - open var schedule: ScheduledFuture<*>? + private var schedule: ScheduledFuture<*>? + + // required as injection objects to the final field + open fun getSchedule() = schedule companion object { @JvmStatic private val LOGGER = LoggerFactory.getLogger(this::class.java) + private val MAX_VERSION_TO_LOAD = (System.getenv("MAX_VERSION_TO_LOAD") ?: "100").toInt() + + + fun loadDataFromDb( + dataStore: ApiPersistence, + previousUpdateInfo: UpdatedInfo, + forceUpdate: Boolean, + previousRepo: AdoptRepos?, + versions: List): Pair { + + return runBlocking { + val updated = dataStore.getUpdatedAt() + + if (previousRepo == null || forceUpdate || updated != previousUpdateInfo) { + val data = versions + .map { version -> + dataStore.readReleaseData(version) + } + .filter { it.releases.nodes.isNotEmpty() } + .toList() + val updatedAt = dataStore.getUpdatedAt() + + LOGGER.info("Loaded Version: $updatedAt") + val newData = filterValidAssets(data) + + showStats(previousRepo, newData) + Pair(newData, updatedAt) + } else { + Pair(previousRepo, previousUpdateInfo) + } + } + } + + private fun filterValidAssets(data: List): AdoptRepos { + // Ensure that we filter out valid releases/binaries for this ecosystem + val filtered = AdoptRepos(data) + .getFilteredReleases( + { release -> + Vendor.validVendor(release.vendor) + }, + { binary -> + JvmImpl.validJvmImpl(binary.jvm_impl) + }, + SortOrder.ASC, + SortMethod.DEFAULT + ) + .groupBy { it.version_data.major } + .map { FeatureRelease(it.key, Releases(it.value)) } + + return AdoptRepos(filtered) + } + + private fun showStats(binaryRepos: AdoptRepos?, newData: AdoptRepos) { + newData.allReleases.getReleases() + .forEach { release -> + val oldRelease = binaryRepos?.allReleases?.nodes?.get(release.id) + if (oldRelease == null) { + LOGGER.info("New release: ${release.release_name} ${release.binaries.size}") + } else if (oldRelease.binaries.size != release.binaries.size) { + LOGGER.info("Binary count changed ${release.release_name} ${oldRelease.binaries.size} -> ${release.binaries.size}") + } + } + + binaryRepos?.allReleases?.getReleases() + ?.forEach { oldRelease -> + val newRelease = binaryRepos.allReleases.nodes[oldRelease.id] + if (newRelease == null) { + LOGGER.info("Removed release: ${oldRelease.release_name} ${oldRelease.binaries.size}") + } + } + } + + } @Inject constructor(dataStore: ApiPersistence) { this.dataStore = dataStore + updatedAt = UpdatedInfo(ZonedDateTime.now().minusYears(10), "111", 0) schedule = null + binaryRepos = try { - loadDataFromDb(true) + val update = loadDataFromDb( + dataStore, + updatedAt, + true, + null, + (8..MAX_VERSION_TO_LOAD).toList() + ) + updatedAt = update.second + update.first } catch (e: Exception) { LOGGER.error("Failed to read db", e) AdoptRepos(listOf()) @@ -47,14 +132,6 @@ open class APIDataStoreImpl : APIDataStore { releaseInfo = loadReleaseInfo() } - constructor(binaryRepos: AdoptRepos, dataStore: ApiPersistence) { - this.dataStore = dataStore - updatedAt = UpdatedInfo(ZonedDateTime.now().minusYears(10), "111", 0) - schedule = null - this.binaryRepos = binaryRepos - this.releaseInfo = loadReleaseInfo() - } - override fun schedulePeriodicUpdates() { if (schedule == null) { schedule = Executors @@ -91,51 +168,28 @@ open class APIDataStoreImpl : APIDataStore { } override fun loadDataFromDb(forceUpdate: Boolean): AdoptRepos { - val previousRepo: AdoptRepos = binaryRepos - - binaryRepos = runBlocking { - val updated = dataStore.getUpdatedAt() - - if (forceUpdate || updated != updatedAt) { - val data = Versions - .versions - .map { version -> - dataStore.readReleaseData(version) - } - .filter { it.releases.nodes.isNotEmpty() } - .toList() - updatedAt = dataStore.getUpdatedAt() - - LOGGER.info("Loaded Version: $updatedAt") - val newData = filterValidAssets(data) - - showStats(previousRepo, newData) - newData - } else { - binaryRepos - } - } + // Scan the currently available versions plus 5 + val versions = releaseInfo.available_releases.toList() + .plus((releaseInfo.available_releases.last()..releaseInfo.available_releases.last() + 5)) + .filter { it <= MAX_VERSION_TO_LOAD } + + val update = loadDataFromDb( + dataStore, + updatedAt, + forceUpdate, + binaryRepos, + versions + ) + + this.updatedAt = update.second + this.binaryRepos = update.first return binaryRepos - } - private fun filterValidAssets(data: List): AdoptRepos { - // Ensure that we filter out valid releases/binaries for this ecosystem - val filtered = AdoptRepos(data) - .getFilteredReleases( - { release -> - Vendor.validVendor(release.vendor) - }, - { binary -> - JvmImpl.validJvmImpl(binary.jvm_impl) - }, - SortOrder.ASC, - SortMethod.DEFAULT - ) - .groupBy { it.version_data.major } - .map { FeatureRelease(it.key, Releases(it.value)) } + } - return AdoptRepos(filtered) + override fun getUpdateInfo(): UpdatedInfo { + return updatedAt } // open for @@ -157,26 +211,6 @@ open class APIDataStoreImpl : APIDataStore { } } - private fun showStats(binaryRepos: AdoptRepos?, newData: AdoptRepos) { - newData.allReleases.getReleases() - .forEach { release -> - val oldRelease = binaryRepos?.allReleases?.nodes?.get(release.id) - if (oldRelease == null) { - LOGGER.info("New release: ${release.release_name} ${release.binaries.size}") - } else if (oldRelease.binaries.size != release.binaries.size) { - LOGGER.info("Binary count changed ${release.release_name} ${oldRelease.binaries.size} -> ${release.binaries.size}") - } - } - - binaryRepos?.allReleases?.getReleases() - ?.forEach { oldRelease -> - val newRelease = binaryRepos.allReleases.nodes[oldRelease.id] - if (newRelease == null) { - LOGGER.info("Removed release: ${oldRelease.release_name} ${oldRelease.binaries.size}") - } - } - } - override fun getReleaseInfo(): ReleaseInfo { return releaseInfo } diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/VersionSupplier.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/VersionSupplier.kt new file mode 100644 index 000000000..1959d418d --- /dev/null +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/VersionSupplier.kt @@ -0,0 +1,7 @@ +package net.adoptium.api.v3.dataSources + +interface VersionSupplier { + fun getTipVersion(): Int? + fun getLtsVersions(): Array + fun getAllVersions(): Array +} diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoApiPersistence.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoApiPersistence.kt index c97b21482..db5db6dab 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoApiPersistence.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoApiPersistence.kt @@ -1,10 +1,13 @@ package net.adoptium.api.v3.dataSources.persitence.mongo import com.mongodb.client.model.InsertManyOptions -import com.mongodb.client.model.UpdateOptions +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.kotlin.client.coroutine.MongoCollection import jakarta.enterprise.context.ApplicationScoped -import jakarta.enterprise.inject.Model import jakarta.inject.Inject +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.toList import net.adoptium.api.v3.TimeSource import net.adoptium.api.v3.dataSources.models.AdoptRepos import net.adoptium.api.v3.dataSources.models.FeatureRelease @@ -24,19 +27,18 @@ import org.bson.BsonDateTime import org.bson.BsonDocument import org.bson.BsonString import org.bson.Document -import org.litote.kmongo.coroutine.CoroutineCollection import org.slf4j.LoggerFactory import java.time.ZonedDateTime @ApplicationScoped open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : MongoInterface(), ApiPersistence { - private val githubReleaseMetadataCollection: CoroutineCollection = createCollection(mongoClient.database, GH_RELEASE_METADATA) - private val releasesCollection: CoroutineCollection = createCollection(mongoClient.database, RELEASE_DB) - private val gitHubStatsCollection: CoroutineCollection = createCollection(mongoClient.database, GITHUB_STATS_DB) - private val dockerStatsCollection: CoroutineCollection = createCollection(mongoClient.database, DOCKER_STATS_DB) - private val releaseInfoCollection: CoroutineCollection = createCollection(mongoClient.database, RELEASE_INFO_DB) - private val updateTimeCollection: CoroutineCollection = createCollection(mongoClient.database, UPDATE_TIME_DB) - private val githubReleaseNotesCollection: CoroutineCollection = createCollection(mongoClient.database, GH_RELEASE_NOTES) + private val githubReleaseMetadataCollection: MongoCollection = createCollection(mongoClient.getDatabase(), GH_RELEASE_METADATA) + private val releasesCollection: MongoCollection = createCollection(mongoClient.getDatabase(), RELEASE_DB) + private val gitHubStatsCollection: MongoCollection = createCollection(mongoClient.getDatabase(), GITHUB_STATS_DB) + private val dockerStatsCollection: MongoCollection = createCollection(mongoClient.getDatabase(), DOCKER_STATS_DB) + private val releaseInfoCollection: MongoCollection = createCollection(mongoClient.getDatabase(), RELEASE_INFO_DB) + private val updateTimeCollection: MongoCollection = createCollection(mongoClient.getDatabase(), UPDATE_TIME_DB) + private val githubReleaseNotesCollection: MongoCollection = createCollection(mongoClient.getDatabase(), GH_RELEASE_NOTES) companion object { @JvmStatic @@ -119,7 +121,7 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M val repoNames = dockerStatsCollection.distinct("repo").toList() return repoNames - .mapNotNull { + .map { dockerStatsCollection .find(Document("repo", it)) .sort(Document("date", -1)) @@ -137,31 +139,31 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M override suspend fun setReleaseInfo(releaseInfo: ReleaseInfo) { releaseInfoCollection.deleteMany(releaseVersionDbEntryMatcher()) - releaseInfoCollection.updateOne( + releaseInfoCollection.replaceOne( releaseVersionDbEntryMatcher(), releaseInfo, - UpdateOptions().upsert(true) + ReplaceOptions().upsert(true) ) } // visible for testing open suspend fun updateUpdatedTime(dateTime: ZonedDateTime, checksum: String, hashCode: Int) { - updateTimeCollection.updateOne( + updateTimeCollection.replaceOne( Document(), UpdatedInfo(dateTime, checksum, hashCode), - UpdateOptions().upsert(true) + ReplaceOptions().upsert(true) ) updateTimeCollection.deleteMany(Document("time", BsonDocument("\$lt", BsonDateTime(dateTime.toInstant().toEpochMilli())))) } override suspend fun getUpdatedAt(): UpdatedInfo { - val info = updateTimeCollection.findOne() + val info = updateTimeCollection.find().firstOrNull() // if we have no existing time, make it 5 mins ago, should only happen on first time the db is used return info ?: UpdatedInfo(TimeSource.now().minusMinutes(5), "000", 0) } override suspend fun getReleaseInfo(): ReleaseInfo? { - return releaseInfoCollection.findOne(releaseVersionDbEntryMatcher()) + return releaseInfoCollection.find(releaseVersionDbEntryMatcher()).firstOrNull() } private fun releaseVersionDbEntryMatcher() = Document("tip_version", BsonDocument("\$exists", BsonBoolean(true))) @@ -181,20 +183,20 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M private fun majorVersionMatcher(featureVersion: Int) = Document("version_data.major", featureVersion) override suspend fun getGhReleaseMetadata(gitHubId: GitHubId): GHReleaseMetadata? { - return githubReleaseMetadataCollection.findOne(matchGithubId(gitHubId)) + return githubReleaseMetadataCollection.find(matchGithubId(gitHubId)).firstOrNull() } override suspend fun setGhReleaseMetadata(ghReleaseMetadata: GHReleaseMetadata) { githubReleaseMetadataCollection - .updateOne( + .replaceOne( matchGithubId(ghReleaseMetadata.gitHubId), ghReleaseMetadata, - UpdateOptions().upsert(true) + ReplaceOptions().upsert(true) ) } override suspend fun hasReleaseNotesForGithubId(gitHubId: GitHubId): Boolean { - return githubReleaseNotesCollection.findOne(Document("id", gitHubId.id)) != null + return githubReleaseNotesCollection.find(Document("id", gitHubId.id)).firstOrNull() != null } override suspend fun putReleaseNote(releaseNotes: ReleaseNotes) { @@ -203,7 +205,7 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M } override suspend fun getReleaseNotes(vendor: Vendor, releaseName: String): ReleaseNotes? { - return githubReleaseNotesCollection.findOne(Document( + return githubReleaseNotesCollection.find(Document( "\$and", BsonArray( listOf( @@ -213,6 +215,7 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M ) ) ) + .firstOrNull() } private fun matchGithubId(gitHubId: GitHubId) = Document("gitHubId.id", gitHubId.id) diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoClient.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoClient.kt index 3090c3bc8..1bab7aa97 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoClient.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoClient.kt @@ -2,17 +2,21 @@ package net.adoptium.api.v3.dataSources.persitence.mongo import com.mongodb.ConnectionString import com.mongodb.MongoClientSettings +import com.mongodb.kotlin.client.coroutine.MongoDatabase import jakarta.enterprise.context.ApplicationScoped -import org.litote.kmongo.coroutine.CoroutineClient -import org.litote.kmongo.coroutine.CoroutineDatabase -import org.litote.kmongo.coroutine.coroutine -import org.litote.kmongo.reactivestreams.KMongo +import net.adoptium.api.v3.dataSources.persitence.mongo.codecs.JacksonCodecProvider +import net.adoptium.api.v3.dataSources.persitence.mongo.codecs.ZonedDateTimeCodecProvider +import org.bson.codecs.configuration.CodecRegistries import org.slf4j.LoggerFactory + @ApplicationScoped open class MongoClient { - open val database: CoroutineDatabase - open val client: CoroutineClient + private val database: MongoDatabase + private val client: com.mongodb.kotlin.client.coroutine.MongoClient + + // required as injection objects to the final field + open fun getDatabase() = database companion object { @JvmStatic @@ -65,14 +69,21 @@ open class MongoClient { serverSelectionTimeoutMills = System.getenv("MONGODB_SERVER_SELECTION_TIMEOUT_MILLIS") ) var settingsBuilder = MongoClientSettings.builder() + .codecRegistry(CodecRegistries.fromProviders( + MongoClientSettings.getDefaultCodecRegistry(), + ZonedDateTimeCodecProvider(), + JacksonCodecProvider() + ) + ) .applyConnectionString(ConnectionString(connectionString)) val sslEnabled = System.getenv("MONGODB_SSL")?.toBoolean() if (sslEnabled == true) { - val checkMongoHostName = System.getenv("DISABLE_MONGO_HOST_CHECK")?.toBoolean() ?: true + val checkMongoHostName = System.getenv("DISABLE_MONGO_HOST_CHECK")?.toBoolean() ?: false settingsBuilder = settingsBuilder.applyToSslSettings { it.enabled(true).invalidHostNameAllowed(checkMongoHostName) } } - client = KMongo.createClient(settingsBuilder.build()).coroutine + client = com.mongodb.kotlin.client.coroutine.MongoClient.create(settingsBuilder.build()) database = client.getDatabase(dbName) } + } diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoInterface.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoInterface.kt index 4b1253101..5b4e7d6f8 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoInterface.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/MongoInterface.kt @@ -1,18 +1,37 @@ package net.adoptium.api.v3.dataSources.persitence.mongo +import com.mongodb.MongoCommandException +import com.mongodb.kotlin.client.coroutine.MongoCollection +import com.mongodb.kotlin.client.coroutine.MongoDatabase import kotlinx.coroutines.runBlocking -import org.litote.kmongo.coroutine.CoroutineCollection -import org.litote.kmongo.coroutine.CoroutineDatabase +import net.adoptium.api.v3.config.APIConfig +import net.adoptium.api.v3.config.DeploymentType +import org.slf4j.LoggerFactory abstract class MongoInterface { - inline fun createCollection(database: CoroutineDatabase, collectionName: String): CoroutineCollection { - return runBlocking { - if (!database.listCollectionNames().contains(collectionName)) { - // TODO add indexes + companion object { + @JvmStatic + val LOGGER = LoggerFactory.getLogger(MongoInterface::class.java)!! + } + + inline fun createCollection(database: MongoDatabase, collectionName: String): MongoCollection { + + runBlocking { + try { database.createCollection(collectionName) + } catch (e: MongoCommandException) { + if (e.errorCode == 48) { + // collection already exists ... ignore + } else { + if (APIConfig.DEPLOYMENT_TYPE == DeploymentType.UPDATER) { + LOGGER.warn("User does not have permission to create collection $collectionName", e) + throw e + } + } } - return@runBlocking database.getCollection(collectionName) } + return database.getCollection(collectionName, T::class.java) + } } diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/UpdatedInfo.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/UpdatedInfo.kt index 98019ef80..0d8d63683 100644 --- a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/UpdatedInfo.kt +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/UpdatedInfo.kt @@ -1,8 +1,22 @@ package net.adoptium.api.v3.dataSources.persitence.mongo +import java.math.BigInteger +import java.time.ZoneId import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.util.* + +data class UpdatedInfo( + val time: ZonedDateTime, + val checksum: String, + val hashCode: Int, + val hexChecksum: String? = BigInteger(1, Base64.getDecoder().decode(checksum)).toString(16), + val lastModified: Date? = Date.from(time.toInstant()), + val lastModifiedFormatted: String? = lastModified + ?.toInstant() + ?.atZone(ZoneId.of("GMT")) + ?.format(DateTimeFormatter.RFC_1123_DATE_TIME)) { -data class UpdatedInfo(val time: ZonedDateTime, val checksum: String, val hashCode: Int) { override fun toString(): String { return "$time $checksum $hashCode" } diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/DateCodecs.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/DateCodecs.kt new file mode 100644 index 000000000..ece63ae92 --- /dev/null +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/DateCodecs.kt @@ -0,0 +1,53 @@ +package net.adoptium.api.v3.dataSources.persitence.mongo.codecs + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.node.LongNode +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.databind.node.TextNode +import net.adoptium.api.v3.TimeSource +import java.time.Instant +import java.time.format.DateTimeFormatter +import java.util.* + +object DateCodecs { + class DateSerializer : JsonSerializer() { + override fun serialize(p0: Date?, p1: JsonGenerator?, p2: SerializerProvider?) { + p1?.writeStartObject(); + p1?.writeStringField( + "\$date", + p0?.toInstant()?.atZone(TimeSource.ZONE)?.format(DateTimeFormatter.ISO_INSTANT) + ); + p1?.writeEndObject(); + } + } + + class DateDeserializer : JsonDeserializer() { + override fun deserialize(jsonParser: JsonParser?, context: DeserializationContext?): Date { + when (val value = jsonParser?.readValueAsTree()) { + is ObjectNode -> { + val datetime = DateTimeFormatter.ISO_INSTANT.parse(value.get("\$date").asText()) + return Date(Instant.from(datetime).toEpochMilli()) + } + + is TextNode -> { + val datetime = DateTimeFormatter.ISO_INSTANT.parse(value.asText()) + return Date(Instant.from(datetime).toEpochMilli()) + } + + is LongNode -> { + return Date(value.asLong()) + } + } + + throw IllegalArgumentException("Could not parse ZonedDateTime") + } + + } + +} diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/JacksonCodecProvider.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/JacksonCodecProvider.kt new file mode 100644 index 000000000..31de4700f --- /dev/null +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/JacksonCodecProvider.kt @@ -0,0 +1,70 @@ +package net.adoptium.api.v3.dataSources.persitence.mongo.codecs + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import org.bson.BsonReader +import org.bson.BsonWriter +import org.bson.RawBsonDocument +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecProvider +import org.bson.codecs.configuration.CodecRegistry +import java.io.IOException +import java.io.UncheckedIOException +import java.time.ZonedDateTime +import java.util.* + +class JacksonCodecProvider : CodecProvider { + companion object { + private val objectMapper: ObjectMapper = com.fasterxml.jackson.module.kotlin.jacksonObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .registerModule(JavaTimeModule()) + .registerModule(object : SimpleModule() { + init { + addDeserializer(ZonedDateTime::class.java, ZonedDateTimeCodecs.ZonedDateTimeDeserializer()) + addSerializer(ZonedDateTime::class.java, ZonedDateTimeCodecs.ZonedDateTimeSerializer()) + addDeserializer(Date::class.java, DateCodecs.DateDeserializer()) + addSerializer(Date::class.java, DateCodecs.DateSerializer()) + } + }) + } + + override fun get(type: Class, registry: CodecRegistry): Codec? { + if (type == RawBsonDocument::class.java) { + return null + } + return JacksonCodec(objectMapper, registry, type) + } +} + +class JacksonCodec(private val objectMapper: ObjectMapper, private val registry: CodecRegistry, val type: Class) : + Codec { + + private var rawBsonDocumentCodec: Codec = registry.get(RawBsonDocument::class.java) + + override fun encode(bsonWriter: BsonWriter?, value: T, encoderContext: EncoderContext?) { + val doc = RawBsonDocument.parse(objectMapper.writeValueAsString(value)) + + rawBsonDocumentCodec.encode(bsonWriter, doc, encoderContext) + } + + override fun getEncoderClass(): Class { + return type + } + + override fun decode(reader: BsonReader, decoderContext: DecoderContext): T { + try { + val codec = registry.get(RawBsonDocument::class.java) + val document: RawBsonDocument? = codec?.decode(reader, decoderContext) + val json = document?.toJson() + return objectMapper.readValue(json, type) + } catch (e: IOException) { + throw UncheckedIOException(e) + } + } +} diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/ZonedDateTimeCodecProvider.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/ZonedDateTimeCodecProvider.kt new file mode 100644 index 000000000..7528d8043 --- /dev/null +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/ZonedDateTimeCodecProvider.kt @@ -0,0 +1,44 @@ +package net.adoptium.api.v3.dataSources.persitence.mongo.codecs + +import net.adoptium.api.v3.TimeSource +import org.bson.BsonReader +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecProvider +import org.bson.codecs.configuration.CodecRegistry +import java.time.Instant +import java.time.ZonedDateTime + +class ZonedDateTimeCodecProvider : CodecProvider { + override fun get(type: Class?, registry: CodecRegistry?): Codec? { + if (type == ZonedDateTime::class.java) { + return ZonedDateTimeCodec() as Codec + } + return null + } +} + +class ZonedDateTimeCodec : Codec { + override fun encode(writer: BsonWriter?, value: ZonedDateTime?, encoder: EncoderContext?) { + if (value == null) { + writer?.writeNull() + } else { + writer?.writeDateTime(value.withZoneSameInstant(TimeSource.ZONE).toInstant().toEpochMilli()) + } + } + + override fun getEncoderClass(): Class { + return ZonedDateTime::class.java + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): ZonedDateTime? { + val date = reader?.readDateTime() + return if (date == null) { + null + } else { + ZonedDateTime.ofInstant(Instant.ofEpochMilli(date), TimeSource.ZONE) + } + } +} diff --git a/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/ZonedDateTimeCodecs.kt b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/ZonedDateTimeCodecs.kt new file mode 100644 index 000000000..875bc6eca --- /dev/null +++ b/adoptium-api-v3-persistence/src/main/kotlin/net/adoptium/api/v3/dataSources/persitence/mongo/codecs/ZonedDateTimeCodecs.kt @@ -0,0 +1,50 @@ +package net.adoptium.api.v3.dataSources.persitence.mongo.codecs + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.node.LongNode +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.databind.node.TextNode +import net.adoptium.api.v3.TimeSource +import java.time.Instant +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +object ZonedDateTimeCodecs { + class ZonedDateTimeDeserializer : JsonDeserializer() { + override fun deserialize(jsonParser: JsonParser?, context: DeserializationContext?): ZonedDateTime { + when (val value = jsonParser?.readValueAsTree()) { + is ObjectNode -> { + val datetime = DateTimeFormatter.ISO_INSTANT.parse(value.get("\$date").asText()) + return Instant.from(datetime).atZone(TimeSource.ZONE) + } + + is TextNode -> { + val datetime = DateTimeFormatter.ISO_INSTANT.parse(value.asText()) + return Instant.from(datetime).atZone(TimeSource.ZONE) + } + + is LongNode -> { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(value.asLong()), TimeSource.ZONE) + } + } + + throw IllegalArgumentException("Could not parse ZonedDateTime") + } + + } + + class ZonedDateTimeSerializer : JsonSerializer() { + override fun serialize(p0: ZonedDateTime?, p1: JsonGenerator?, p2: SerializerProvider?) { + p1?.writeStartObject(); + p1?.writeStringField("\$date", p0?.format(DateTimeFormatter.ISO_INSTANT)); + p1?.writeEndObject(); + } + + } +} diff --git a/adoptium-api-v3-telemetry/pom.xml b/adoptium-api-v3-telemetry/pom.xml index fa1e05e24..bf6b22e79 100644 --- a/adoptium-api-v3-telemetry/pom.xml +++ b/adoptium-api-v3-telemetry/pom.xml @@ -32,10 +32,20 @@ org.slf4j slf4j-api + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + src/main/kotlin - src/test/kotlin diff --git a/adoptium-api-versions/pom.xml b/adoptium-api-versions/pom.xml index 87f9ed9f3..34653d486 100644 --- a/adoptium-api-versions/pom.xml +++ b/adoptium-api-versions/pom.xml @@ -8,20 +8,20 @@ pom - 1.8.0 - 2.17.0 + 1.9.0 + 2.18.1 17 - 4.11.0 + 5.1.2 17 - 1.9.23 - 1.5.6 + 2.0.21 + 1.5.8 17 - 3.9.6 - 3.5.0 + 3.9.9 + 3.7.0 UTF-8 UTF-8 - 3.9.4 - 5.4.0 + 3.16.1 + 5.5.0 @@ -29,7 +29,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.4.1 + 3.5.0 enforce @@ -101,7 +101,7 @@ io.mockk mockk-jvm - 1.13.10 + 1.13.13 test @@ -164,43 +164,20 @@ - org.litote.kmongo - kmongo-coroutine - ${kmongo.version} - - - org.litote.kmongo - kmongo-flapdoodle - ${kmongo.version} - test - - - org.slf4j - slf4j-simple - - - - junit - junit - - - - org.jetbrains.kotlin - kotlin-test-junit - - + org.mongodb + mongodb-driver-kotlin-coroutine + 5.1.4 + org.jetbrains.kotlin kotlin-test-junit5 ${kotlin.version} - de.flapdoodle.embed de.flapdoodle.embed.mongo - 4.12.6 + 4.17.0 test @@ -226,13 +203,13 @@ org.assertj assertj-core - 3.25.3 + 3.26.3 test org.awaitility awaitility - 4.2.1 + 4.2.2 test @@ -240,30 +217,30 @@ org.codehaus.groovy groovy-xml - 3.0.21 + 3.0.22 org.jooq jooq - 3.19.7 + 3.19.15 org.slf4j slf4j-api - 2.0.13 + 2.0.16 org.skyscreamer jsonassert - 1.5.1 + 1.5.3 test com.microsoft.azure applicationinsights-runtime-attach - 3.5.2 + 3.6.2 io.opentelemetry.javaagent.instrumentation @@ -273,7 +250,7 @@ io.opentelemetry.javaagent.instrumentation opentelemetry-javaagent-netty-4.1 - 2.3.0-alpha + 2.9.0-alpha org.jboss.weld @@ -284,7 +261,7 @@ org.jboss.weld.se weld-se-core - 5.1.2.Final + 5.1.3.Final javax.enterprise @@ -295,7 +272,7 @@ org.codehaus.groovy groovy - 3.0.21 + 3.0.22 io.quarkiverse.logging.logback @@ -324,7 +301,7 @@ io.quarkus - quarkus-resteasy-reactive + quarkus-rest ${quarkus.version} @@ -337,31 +314,31 @@ org.junit.jupiter junit-jupiter - 5.10.2 + 5.11.3 test io.smallrye smallrye-open-api-core - 3.10.0 + 3.12.0 test io.smallrye smallrye-open-api-jaxrs - 3.10.0 + 3.12.0 test org.eclipse.jetty jetty-server - 12.0.8 + 12.0.12 test org.eclipse.jetty jetty-client - 12.0.8 + 12.0.14 org.bouncycastle @@ -378,11 +355,6 @@ logback-classic ${logback.version} - - org.litote.kmongo - kmongo-id-jackson - ${kmongo.version} - jakarta.json.bind jakarta.json.bind-api @@ -391,7 +363,7 @@ jakarta.ws.rs jakarta.ws.rs-api - 3.1.0 + 4.0.0 jakarta.inject @@ -411,7 +383,7 @@ com.expediagroup graphql-kotlin-ktor-client - 7.0.2 + 8.2.1 org.jetbrains.kotlinx @@ -422,7 +394,7 @@ com.expediagroup graphql-kotlin-client-jackson - 7.0.2 + 8.2.1 diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/pom.xml b/adoptium-frontend-parent/adoptium-api-v3-frontend/pom.xml index 252aa99f0..ecd0aa9f4 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/pom.xml +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/pom.xml @@ -14,7 +14,7 @@ io.quarkus - quarkus-resteasy-reactive + quarkus-rest io.quarkus @@ -22,7 +22,7 @@ io.quarkus - quarkus-resteasy-reactive-jsonb + quarkus-rest-jsonb io.smallrye @@ -80,17 +80,6 @@ jsonassert test - - org.litote.kmongo - kmongo-flapdoodle - test - - - org.slf4j - slf4j-simple - - - io.mockk mockk-jvm @@ -157,6 +146,21 @@ io.quarkus quarkus-smallrye-openapi + + io.quarkus + quarkus-smallrye-metrics + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test-junit5 + ${kotlin.version} + test + @@ -195,18 +199,33 @@ org.jetbrains.kotlin kotlin-maven-plugin + ${kotlin.version} compile + compile compile + + + src/main/kotlin + target/generated-sources/annotations + + test-compile + test-compile test-compile + + + src/test/kotlin + target/generated-test-sources/test-annotations + + diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/CacheControlService.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/CacheControlService.kt new file mode 100644 index 000000000..4d548f729 --- /dev/null +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/CacheControlService.kt @@ -0,0 +1,70 @@ +package net.adoptium.api.v3 + +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject +import jakarta.ws.rs.container.ContainerRequestContext +import jakarta.ws.rs.container.ContainerRequestFilter +import jakarta.ws.rs.container.ContainerResponseContext +import jakarta.ws.rs.core.EntityTag +import jakarta.ws.rs.core.Request +import jakarta.ws.rs.ext.Provider +import net.adoptium.api.v3.dataSources.APIDataStore +import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate +import org.jboss.resteasy.reactive.common.util.ExtendedCacheControl +import org.jboss.resteasy.reactive.server.ServerResponseFilter + + +@Provider +@ApplicationScoped +class CacheControlService @Inject constructor(private var apiDataStore: APIDataStore) : ContainerRequestFilter { + + private val CACHE_CONTROLLED_PATHS = listOf("/v3/info", "/v3/assets") + private val MAX_CACHE_AGE_IN_SEC = 120 + + private fun isCacheControlledPath(requestContext: ContainerRequestContext?): Boolean { + val path = requestContext?.uriInfo?.path + + if (path == null) return false + + return CACHE_CONTROLLED_PATHS.any { path.startsWith(it) } + } + + override fun filter(requestContext: ContainerRequestContext?) { + if (isCacheControlledPath(requestContext)) { + val etag = apiDataStore.getUpdateInfo().hexChecksum + val lastModified = apiDataStore.getUpdateInfo().lastModified + + if (lastModified == null || etag == null) { + return + } + + val builder = + requestContext!! + .request + .evaluatePreconditions(lastModified, EntityTag(etag)) + + if (builder != null) { + requestContext.abortWith(builder.build()) + } + } + } + + @ServerResponseFilter + fun responseFilter(requestContext: ContainerRequestContext?, responseContext: ContainerResponseContext?) { + if (isCacheControlledPath(requestContext)) { + val ecc = ExtendedCacheControl(); + ecc.isPublic = true + ecc.maxAge = MAX_CACHE_AGE_IN_SEC + ecc.sMaxAge = MAX_CACHE_AGE_IN_SEC + + if (apiDataStore.getUpdateInfo().hexChecksum == null || + apiDataStore.getUpdateInfo().lastModifiedFormatted == null) { + return + } + + responseContext?.headers?.add("ETag", apiDataStore.getUpdateInfo().hexChecksum) + responseContext?.headers?.add("Last-Modified", apiDataStore.getUpdateInfo().lastModifiedFormatted) + responseContext?.headers?.add("Cache-Control", CacheControlDelegate.INSTANCE.toString(ecc)) + } + } +} diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/FrontEndVersionSupplier.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/FrontEndVersionSupplier.kt new file mode 100644 index 000000000..c9ddd6cae --- /dev/null +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/FrontEndVersionSupplier.kt @@ -0,0 +1,23 @@ +package net.adoptium.api.v3 + +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject +import net.adoptium.api.v3.dataSources.APIDataStore +import net.adoptium.api.v3.dataSources.VersionSupplier + +@ApplicationScoped +class FrontEndVersionSupplier @Inject constructor( + val apiDataStore: APIDataStore +) : VersionSupplier { + override fun getTipVersion(): Int { + return apiDataStore.getReleaseInfo().tip_version + } + + override fun getLtsVersions(): Array { + return apiDataStore.getReleaseInfo().available_lts_releases + } + + override fun getAllVersions(): Array { + return apiDataStore.getReleaseInfo().available_releases + } +} diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Main.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Main.kt index 58b711181..d8d30d4ea 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Main.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Main.kt @@ -4,6 +4,8 @@ import io.quarkus.runtime.Quarkus import io.quarkus.runtime.annotations.QuarkusMain import net.adoptium.api.v3.ai.AppInsightsTelemetry +import net.adoptium.api.v3.config.APIConfig +import net.adoptium.api.v3.config.DeploymentType @QuarkusMain @@ -12,6 +14,8 @@ object Main { fun main(args: Array) { // force eager startup of AppInsights, must be done from the main thread AppInsightsTelemetry.enabled + APIConfig.DEPLOYMENT_TYPE = DeploymentType.FRONTEND + Quarkus.run(*args) } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Pagination.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Pagination.kt index dd8296720..48fd70f5d 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Pagination.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Pagination.kt @@ -59,7 +59,7 @@ object Pagination { return try { - var totalPages: Int? = null; + var totalPages: Int? = null val chunked = if (showPageCount) { val releasesList = releases.toList() diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Startup.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Startup.kt index b44c35691..d51386863 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Startup.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/Startup.kt @@ -4,6 +4,8 @@ import io.quarkus.runtime.Startup import jakarta.annotation.PostConstruct import jakarta.enterprise.context.ApplicationScoped import jakarta.inject.Inject +import net.adoptium.api.v3.config.APIConfig +import net.adoptium.api.v3.config.DeploymentType import net.adoptium.api.v3.dataSources.APIDataStore @ApplicationScoped diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/ReleaseFilter.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/ReleaseFilter.kt index 1363b4e8e..0a4f5f210 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/ReleaseFilter.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/ReleaseFilter.kt @@ -1,14 +1,43 @@ package net.adoptium.api.v3.filters +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject import net.adoptium.api.v3.config.Ecosystem +import net.adoptium.api.v3.dataSources.VersionSupplier import net.adoptium.api.v3.models.JvmImpl import net.adoptium.api.v3.models.Release import net.adoptium.api.v3.models.ReleaseType import net.adoptium.api.v3.models.Vendor -import net.adoptium.api.v3.models.Versions import java.util.function.Predicate -class ReleaseFilter( +@ApplicationScoped +class ReleaseFilterFactory @Inject constructor( + private val versionSupplier: VersionSupplier +) { + fun createFilter( + releaseType: ReleaseType? = null, + featureVersion: Int? = null, + releaseName: String? = null, + vendor: Vendor? = null, + versionRange: VersionRangeFilter? = null, + lts: Boolean? = null, + jvm_impl: JvmImpl? = null + ): Predicate { + return ReleaseFilter( + versionSupplier.getLtsVersions(), + releaseType, + featureVersion, + releaseName, + vendor, + versionRange, + lts, + jvm_impl + ) + } +} + +private class ReleaseFilter( + private val ltsVersions: Array, private val releaseType: ReleaseType? = null, private val featureVersion: Int? = null, private val releaseName: String? = null, @@ -19,7 +48,7 @@ class ReleaseFilter( ) : Predicate { override fun test(release: Release): Boolean { val ltsFilter = if (lts != null) { - val isLts = Versions.ltsVersions.contains(release.version_data.major) + val isLts = ltsVersions.contains(release.version_data.major) lts == isLts } else { true diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/VersionRangeFilter.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/VersionRangeFilter.kt index e8b940f24..4297aca52 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/VersionRangeFilter.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/filters/VersionRangeFilter.kt @@ -23,10 +23,10 @@ class VersionRangeFilter(range: String?, val semver: Boolean) : Predicate { val binaryVendor = vendor ?: Vendor.getDefault() - val releaseFilter = ReleaseFilter(ReleaseType.ga, featureVersion = version, vendor = binaryVendor, jvm_impl = jvm_impl) + val releaseFilter = releaseFilterFactory.createFilter(ReleaseType.ga, featureVersion = version, vendor = binaryVendor, jvm_impl = jvm_impl) val binaryFilter = BinaryFilter(os, arch, image_type, jvm_impl, null, null) val releases = apiDataStore .getAdoptRepos() diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/ReleaseEndpoint.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/ReleaseEndpoint.kt index 92ec6026c..5ce11b3e2 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/ReleaseEndpoint.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/ReleaseEndpoint.kt @@ -7,7 +7,7 @@ import net.adoptium.api.v3.dataSources.APIDataStore import net.adoptium.api.v3.dataSources.SortMethod import net.adoptium.api.v3.dataSources.SortOrder import net.adoptium.api.v3.filters.BinaryFilter -import net.adoptium.api.v3.filters.ReleaseFilter +import net.adoptium.api.v3.filters.ReleaseFilterFactory import net.adoptium.api.v3.filters.VersionRangeFilter import net.adoptium.api.v3.models.Architecture import net.adoptium.api.v3.models.CLib @@ -26,7 +26,8 @@ import net.adoptium.api.v3.parser.maven.InvalidVersionSpecificationException class ReleaseEndpoint @Inject constructor( - private val apiDataStore: APIDataStore + private val apiDataStore: APIDataStore, + private val releaseFilterFactory: ReleaseFilterFactory ) { fun getReleases( sortOrder: SortOrder?, @@ -56,7 +57,7 @@ constructor( throw BadRequestException("Invalid version string", e) } - val releaseFilter = ReleaseFilter(releaseType = release_type, vendor = vendorNonNull, versionRange = range, lts = lts, jvm_impl = jvm_impl) + val releaseFilter = releaseFilterFactory.createFilter(releaseType = release_type, vendor = vendorNonNull, versionRange = range, lts = lts, jvm_impl = jvm_impl) val binaryFilter = BinaryFilter(os = os, arch = arch, imageType = image_type, jvmImpl = jvm_impl, heapSize = heap_size, project = project, cLib = cLib) return apiDataStore diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/AvailableReleasesResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/AvailableReleasesResource.kt index 8367c79b9..71b360b10 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/AvailableReleasesResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/AvailableReleasesResource.kt @@ -1,17 +1,20 @@ package net.adoptium.api.v3.routes.info -import net.adoptium.api.v3.dataSources.APIDataStore -import net.adoptium.api.v3.models.ReleaseInfo -import org.eclipse.microprofile.openapi.annotations.Operation -import org.eclipse.microprofile.openapi.annotations.tags.Tag import jakarta.enterprise.context.ApplicationScoped import jakarta.inject.Inject import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces import jakarta.ws.rs.core.MediaType -import jakarta.ws.rs.core.Response -import jakarta.ws.rs.core.UriInfo +import net.adoptium.api.v3.dataSources.APIDataStore +import net.adoptium.api.v3.models.ReleaseInfo +import org.eclipse.microprofile.openapi.annotations.Operation +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType +import org.eclipse.microprofile.openapi.annotations.media.Content +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses +import org.eclipse.microprofile.openapi.annotations.tags.Tag @Tag(name = "Release Info") @Path("/v3/info") @@ -25,6 +28,15 @@ constructor( @GET @Path("/available_releases") + @APIResponses( + value = [ + APIResponse( + responseCode = "200", + description = "Available release information", + content = [Content(schema = Schema(type = SchemaType.OBJECT, implementation = ReleaseInfo::class))] + ) + ] + ) @Operation(summary = "Returns information about available releases", operationId = "getAvailableReleases") fun get(): ReleaseInfo { return apiDataStore.getReleaseInfo() diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/ReleaseListResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/ReleaseListResource.kt index 70a273a66..4f6ad521c 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/ReleaseListResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/ReleaseListResource.kt @@ -1,5 +1,15 @@ package net.adoptium.api.v3.routes.info +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject +import jakarta.ws.rs.GET +import jakarta.ws.rs.Path +import jakarta.ws.rs.Produces +import jakarta.ws.rs.QueryParam +import jakarta.ws.rs.core.Context +import jakarta.ws.rs.core.MediaType +import jakarta.ws.rs.core.Response +import jakarta.ws.rs.core.UriInfo import net.adoptium.api.v3.OpenApiDocs import net.adoptium.api.v3.Pagination import net.adoptium.api.v3.Pagination.formPagedResponse @@ -26,16 +36,6 @@ 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 jakarta.enterprise.context.ApplicationScoped -import jakarta.inject.Inject -import jakarta.ws.rs.GET -import jakarta.ws.rs.Path -import jakarta.ws.rs.Produces -import jakarta.ws.rs.QueryParam -import jakarta.ws.rs.core.Context -import jakarta.ws.rs.core.MediaType -import jakarta.ws.rs.core.Response -import jakarta.ws.rs.core.UriInfo @Tag(name = "Release Info") @Path("/v3/info") diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesArchitecturesResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesArchitecturesResource.kt index b31e9333d..6ba743a61 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesArchitecturesResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesArchitecturesResource.kt @@ -19,6 +19,6 @@ class TypesArchitecturesResource { @Path("/architectures") @Operation(summary = "Returns names of architectures", operationId = "getArchitectures") fun get(): List { - return Architecture.values().map { it.name }.toList() + return Architecture.entries.map { it.name }.toList() } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesOperatingSystemsResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesOperatingSystemsResource.kt index 79dca7c37..d338c7f92 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesOperatingSystemsResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/info/TypesOperatingSystemsResource.kt @@ -19,6 +19,6 @@ class TypesOperatingSystemsResource { @Path("/operating_systems") @Operation(summary = "Returns names of operating systems", operationId = "getOperatingSystems") fun get(): List { - return OperatingSystem.values().map { it.name }.toList() + return OperatingSystem.entries.map { it.name }.toList() } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/BinaryResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/BinaryResource.kt index 50fe7582f..4de114c3a 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/BinaryResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/BinaryResource.kt @@ -40,7 +40,7 @@ class BinaryResource @Inject constructor(private val packageEndpoint: PackageEnd @GET @Path("/version/{release_name}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") - @Produces("application/octet-stream") + @Produces(MediaType.APPLICATION_OCTET_STREAM) @Operation( operationId = "getBinaryByVersion", summary = "Redirects to the binary that matches your current query", @@ -172,7 +172,7 @@ class BinaryResource @Inject constructor(private val packageEndpoint: PackageEnd @GET @Path("/latest/{feature_version}/{release_type}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") - @Produces("application/octet-stream") + @Produces(MediaType.APPLICATION_OCTET_STREAM) @Operation( operationId = "getBinary", summary = "Redirects to the binary that matches your current query", @@ -236,7 +236,7 @@ class BinaryResource @Inject constructor(private val packageEndpoint: PackageEnd return formResponse(if (release == null) emptyList() else listOf(release)) } - protected fun formResponse( + private fun formResponse( releases: List, createResponse: (Package) -> Response = packageEndpoint.redirectToAsset() ): Response { diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/ChecksumResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/ChecksumResource.kt index 58756099a..4469c0002 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/ChecksumResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/ChecksumResource.kt @@ -37,7 +37,7 @@ class ChecksumResource @Inject constructor(private val packageEndpoint: PackageE @GET @Path("/version/{release_name}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") - @Produces("application/octet-stream") + @Produces(MediaType.APPLICATION_OCTET_STREAM) @Operation( operationId = "getChecksumByVersion", summary = "Redirects to the checksum of the release that matches your current query", diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/InstallerResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/InstallerResource.kt index cd1b81255..7e48b89c7 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/InstallerResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/InstallerResource.kt @@ -38,7 +38,7 @@ class InstallerResource @Inject constructor(private val packageEndpoint: Package @GET @Path("/version/{release_name}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") - @Produces("application/octet-stream") + @Produces(MediaType.APPLICATION_OCTET_STREAM) @Operation( operationId = "getInstallerByVersion", summary = "Redirects to the installer that matches your current query", @@ -97,7 +97,7 @@ class InstallerResource @Inject constructor(private val packageEndpoint: Package @GET @Path("/latest/{feature_version}/{release_type}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") - @Produces("application/octet-stream") + @Produces(MediaType.APPLICATION_OCTET_STREAM) @Operation( operationId = "getInstaller", summary = "Redirects to the installer that matches your current query", diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/PackageEndpoint.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/PackageEndpoint.kt index f650e367f..f96d04ed5 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/PackageEndpoint.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/PackageEndpoint.kt @@ -1,12 +1,15 @@ package net.adoptium.api.v3.routes.packages +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject +import jakarta.ws.rs.core.Response import net.adoptium.api.v3.JsonMapper import net.adoptium.api.v3.dataSources.APIDataStore import net.adoptium.api.v3.dataSources.SortMethod import net.adoptium.api.v3.dataSources.SortOrder import net.adoptium.api.v3.dataSources.models.Releases.Companion.RELEASE_COMPARATOR import net.adoptium.api.v3.filters.BinaryFilter -import net.adoptium.api.v3.filters.ReleaseFilter +import net.adoptium.api.v3.filters.ReleaseFilterFactory import net.adoptium.api.v3.models.APIError import net.adoptium.api.v3.models.Architecture import net.adoptium.api.v3.models.Asset @@ -21,12 +24,11 @@ import net.adoptium.api.v3.models.Release import net.adoptium.api.v3.models.ReleaseType import net.adoptium.api.v3.models.Vendor import java.net.URI -import jakarta.enterprise.context.ApplicationScoped -import jakarta.inject.Inject -import jakarta.ws.rs.core.Response @ApplicationScoped -open class PackageEndpoint @Inject constructor(private val apiDataStore: APIDataStore) { +open class PackageEndpoint @Inject constructor( + private val apiDataStore: APIDataStore, + private val releaseFilterFactory: ReleaseFilterFactory) { open fun getReleases( release_name: String?, @@ -39,7 +41,7 @@ open class PackageEndpoint @Inject constructor(private val apiDataStore: APIData project: Project?, cLib: CLib? ): List { - val releaseFilter = ReleaseFilter(releaseName = release_name, vendor = vendor, jvm_impl = jvm_impl) + val releaseFilter = releaseFilterFactory.createFilter(releaseName = release_name, vendor = vendor, jvm_impl = jvm_impl) val binaryFilter = BinaryFilter(os, arch, image_type, jvm_impl, heap_size, project, null, cLib) return apiDataStore.getAdoptRepos().getFilteredReleases(releaseFilter, binaryFilter, SortOrder.DESC, SortMethod.DEFAULT).toList() } @@ -86,7 +88,7 @@ open class PackageEndpoint @Inject constructor(private val apiDataStore: APIData } open fun getRelease(release_type: ReleaseType?, version: Int?, vendor: Vendor?, os: OperatingSystem?, arch: Architecture?, image_type: ImageType?, jvm_impl: JvmImpl?, heap_size: HeapSize?, project: Project?, cLib: CLib?): List { - val releaseFilter = ReleaseFilter(releaseType = release_type, featureVersion = version, vendor = vendor, jvm_impl = jvm_impl) + val releaseFilter = releaseFilterFactory.createFilter(releaseType = release_type, featureVersion = version, vendor = vendor, jvm_impl = jvm_impl) val binaryFilter = BinaryFilter(os, arch, image_type, jvm_impl, heap_size, project, null, cLib) val releases = apiDataStore.getAdoptRepos().getFilteredReleases(releaseFilter, binaryFilter, SortOrder.DESC, SortMethod.DEFAULT).toList() diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/SignatureResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/SignatureResource.kt index d29467fdb..5424424a2 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/SignatureResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/packages/SignatureResource.kt @@ -37,7 +37,7 @@ class SignatureResource @Inject constructor(private val packageEndpoint: Package @GET @Path("/version/{release_name}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}") - @Produces("application/octet-stream") + @Produces(MediaType.APPLICATION_OCTET_STREAM) @Operation( operationId = "getSignatureByVersion", summary = "Redirects to the signature of the release that matches your current query", diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/stats/DownloadStatsResource.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/stats/DownloadStatsResource.kt index c48d545b8..92bbfc20a 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/stats/DownloadStatsResource.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/main/kotlin/net/adoptium/api/v3/routes/stats/DownloadStatsResource.kt @@ -131,7 +131,7 @@ class DownloadStatsResource { .getReleases() .filter { it.vendor == Vendor.getDefault() } - if(releaseTypes != null && releaseTypes.isNotEmpty()) { + if(!releaseTypes.isNullOrEmpty()) { releases = releases.filter { releaseTypes.contains(it.release_type) } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/ApiDataStoreStub.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/ApiDataStoreStub.kt index acd80ee6c..040005870 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/ApiDataStoreStub.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/ApiDataStoreStub.kt @@ -5,7 +5,9 @@ import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.inject.Alternative import net.adoptium.api.v3.dataSources.APIDataStore import net.adoptium.api.v3.dataSources.models.AdoptRepos +import net.adoptium.api.v3.dataSources.persitence.mongo.UpdatedInfo import net.adoptium.api.v3.models.ReleaseInfo +import java.time.ZonedDateTime @Priority(1) @Alternative @@ -56,4 +58,12 @@ open class ApiDataStoreStub : APIDataStore { // nop return adoptRepo } + + override fun getUpdateInfo(): UpdatedInfo { + return UpdatedInfo( + ZonedDateTime.now(), + "1234567890", + 123 + ) + } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsPathTest.kt index f68c020f2..c34d22610 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsPathTest.kt @@ -12,6 +12,7 @@ import net.adoptium.api.v3.models.Vendor import org.hamcrest.Matchers import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.TestFactory +import java.util.* import java.util.stream.Stream abstract class AssetsPathTest : FrontendTest() { @@ -24,22 +25,22 @@ abstract class AssetsPathTest : FrontendTest() { @TestFactory fun filtersOs(): Stream { - return runFilterTest("os", OperatingSystem.values()) + return runFilterTest("os", OperatingSystem.entries.toTypedArray()) } @TestFactory fun filtersArchitecture(): Stream { - return runFilterTest("architecture", Architecture.values()) + return runFilterTest("architecture", Architecture.entries.toTypedArray()) } @TestFactory fun filtersImageType(): Stream { - return runFilterTest("image_type", ImageType.values()) + return runFilterTest("image_type", ImageType.entries.toTypedArray()) } @TestFactory fun `filters c_lib`(): Stream { - return runFilterTest("c_lib", CLib.values()) { _, query -> + return runFilterTest("c_lib", CLib.entries.toTypedArray()) { _, query -> "$query&image_type=staticlibs" } } @@ -48,7 +49,7 @@ abstract class AssetsPathTest : FrontendTest() { fun filtersJvmImpl(): Stream { return runFilterTest( "jvm_impl", - JvmImpl.values().filter { JvmImpl.validJvmImpl(it) }.toTypedArray() + JvmImpl.entries.filter { JvmImpl.validJvmImpl(it) }.toTypedArray() ) { value, query -> if (value == JvmImpl.dragonwell) { "$query&vendor=${Vendor.alibaba.name}" @@ -60,7 +61,7 @@ abstract class AssetsPathTest : FrontendTest() { @TestFactory fun filtersHeapSize(): Stream { - return runFilterTest("heap_size", HeapSize.values()) + return runFilterTest("heap_size", HeapSize.entries.toTypedArray()) } @TestFactory @@ -78,7 +79,7 @@ abstract class AssetsPathTest : FrontendTest() { return values .filter { !exclude(it) } .map { value -> - val path2 = customiseQuery(value, "$path?$filterParamName=${value.toString().toLowerCase()}") + val path2 = customiseQuery(value, "$path?$filterParamName=${value.toString().lowercase(Locale.getDefault())}") DynamicTest.dynamicTest(path2) { RestAssured.given() .`when`() diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathSortOrderTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathSortOrderTest.kt index 24a56a767..3e942984f 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathSortOrderTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathSortOrderTest.kt @@ -33,7 +33,7 @@ import java.time.ZonedDateTime class AssetsResourceFeatureReleasePathSortOrderTest : FrontendTest() { val apiDataStore = ApiDataStoreStub(createRepo()) - var assetResource: AssetsResource = AssetsResource(apiDataStore, ReleaseEndpoint(apiDataStore)) + var assetResource: AssetsResource = AssetsResource(apiDataStore, ReleaseEndpoint(apiDataStore, releaseFilterFactory), releaseFilterFactory) companion object { fun createRepo(): AdoptRepos { diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathTest.kt index b6ec4b1e4..bf1ea5040 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceFeatureReleasePathTest.kt @@ -22,6 +22,8 @@ import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.extension.ExtendWith +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter import java.util.stream.Stream @QuarkusTest @@ -35,7 +37,7 @@ class AssetsResourceFeatureReleasePathTest : AssetsPathTest() { .repos .keys .flatMap { version -> - ReleaseType.values() + ReleaseType.entries .map { "/v3/assets/feature_releases/$version/$it" } .map { DynamicTest.dynamicTest(it) { @@ -58,7 +60,7 @@ class AssetsResourceFeatureReleasePathTest : AssetsPathTest() { .repos .keys .flatMap { version -> - ReleaseType.values() + ReleaseType.entries .map { "/v3/assets/feature_releases/$version/$it?PAGE_SIZE=100" } .map { request -> DynamicTest.dynamicTest(request) { @@ -140,7 +142,7 @@ class AssetsResourceFeatureReleasePathTest : AssetsPathTest() { } override fun runFilterTest(filterParamName: String, values: Array, customiseQuery: (T, String) -> String): Stream { - return ReleaseType.values() + return ReleaseType.entries .flatMap { releaseType -> // test the ltses and 1 non-lts listOf(8, 11, 12) @@ -238,4 +240,60 @@ class AssetsResourceFeatureReleasePathTest : AssetsPathTest() { } .stream() } + + @Test + fun `cache control headers are present`() { + RestAssured.given() + .`when`() + .get("/v3/assets/feature_releases/8/ga") + .then() + .statusCode(200) + .assertThat() + .header("Cache-Control", Matchers.equalTo("public, no-transform, s-maxage=120, max-age=120")) + .header("ETag", Matchers.equalTo("d76df8e7aefcf7")) + .header("Last-Modified", Matchers.notNullValue()) + } + + @Test + fun `if none match applied`() { + RestAssured.given() + .`when`() + .header("If-None-Match", "d76df8e7aefcf7") + .get("/v3/assets/feature_releases/8/ga") + .then() + .statusCode(304) + } + + @Test + fun `etag applied match applied`() { + RestAssured.given() + .`when`() + .header("If-Match", "d76df8e7aefcf7") + .get("/v3/assets/feature_releases/8/ga") + .then() + .statusCode(200) + } + + @Test + fun `modified match applied`() { + RestAssured.given() + .`when`() + .header("If-Modified-Since", ZonedDateTime.now().plusDays(1).format(DateTimeFormatter.RFC_1123_DATE_TIME)) + .get("/v3/assets/feature_releases/8/ga") + .then() + .statusCode(304) + } + + @Test + fun `modified match applied2`() { + RestAssured.given() + .`when`() + .header("If-Modified-Since", ZonedDateTime.now().minusYears(100) + .format(DateTimeFormatter.RFC_1123_DATE_TIME) + ) + .get("/v3/assets/feature_releases/8/ga") + .then() + .statusCode(200) + } + } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceReleaseNamePathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceReleaseNamePathTest.kt index f59a7029f..06988b5db 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceReleaseNamePathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceReleaseNamePathTest.kt @@ -2,12 +2,13 @@ package net.adoptium.api import io.quarkus.test.junit.QuarkusTest import io.restassured.RestAssured +import jakarta.inject.Inject +import jakarta.ws.rs.BadRequestException import net.adoptium.api.v3.JsonMapper import net.adoptium.api.v3.dataSources.APIDataStore import net.adoptium.api.v3.dataSources.SortMethod import net.adoptium.api.v3.dataSources.SortOrder import net.adoptium.api.v3.filters.BinaryFilter -import net.adoptium.api.v3.filters.ReleaseFilter import net.adoptium.api.v3.models.Architecture import net.adoptium.api.v3.models.Release import net.adoptium.api.v3.models.ReleaseType @@ -22,8 +23,6 @@ import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import java.util.stream.Stream -import jakarta.inject.Inject -import jakarta.ws.rs.BadRequestException @QuarkusTest @ExtendWith(value = [DbExtension::class]) @@ -34,17 +33,15 @@ class AssetsResourceReleaseNamePathTest : FrontendTest() { @TestFactory fun filtersByReleaseNameCorrectly(): Stream { - return Vendor - .values() + return Vendor.entries .flatMap { vendor -> apiDataStore .getAdoptRepos() .allReleases - .getReleases(ReleaseFilter(vendor = vendor), SortOrder.DESC, SortMethod.DEFAULT) + .getReleases(releaseFilterFactory.createFilter(vendor = vendor), SortOrder.DESC, SortMethod.DEFAULT) .take(3) .flatMap { release -> - ReleaseType - .values() + ReleaseType.entries .map { "/v3/assets/release_name/$vendor/${release.release_name}" } .map { DynamicTest.dynamicTest(it) { @@ -86,7 +83,7 @@ class AssetsResourceReleaseNamePathTest : FrontendTest() { fun `for frontend requests x86 == x32`() { val releaseName = apiDataStore .getAdoptRepos() - .getFilteredReleases(ReleaseFilter(vendor = Vendor.getDefault()), BinaryFilter(arch = Architecture.x32), SortOrder.DESC, SortMethod.DEFAULT) + .getFilteredReleases(releaseFilterFactory.createFilter(vendor = Vendor.getDefault()), BinaryFilter(arch = Architecture.x32), SortOrder.DESC, SortMethod.DEFAULT) .first() .release_name @@ -103,8 +100,8 @@ class AssetsResourceReleaseNamePathTest : FrontendTest() { override fun matchesSafely(p0: String?): Boolean { val returnedRelease = JsonMapper.mapper.readValue(p0, Release::class.java) - return returnedRelease.binaries.filter { it.architecture == Architecture.x32 }.size > 0 && - returnedRelease.binaries.filter { it.architecture != Architecture.x32 }.size == 0 + return returnedRelease.binaries.filter { it.architecture == Architecture.x32 }.isNotEmpty() && + returnedRelease.binaries.filter { it.architecture != Architecture.x32 }.isEmpty() } }) } @@ -114,7 +111,7 @@ class AssetsResourceReleaseNamePathTest : FrontendTest() { val releaseName = apiDataStore .getAdoptRepos() .allReleases - .getReleases(ReleaseFilter(vendor = Vendor.getDefault()), SortOrder.DESC, SortMethod.DEFAULT) + .getReleases(releaseFilterFactory.createFilter(vendor = Vendor.getDefault()), SortOrder.DESC, SortMethod.DEFAULT) .first() .release_name @@ -128,7 +125,7 @@ class AssetsResourceReleaseNamePathTest : FrontendTest() { @Test fun `missing release_name 400s`() { assertThrows { - AssetsResource(apiDataStore, ReleaseEndpoint(apiDataStore)) + AssetsResource(apiDataStore, ReleaseEndpoint(apiDataStore, releaseFilterFactory), releaseFilterFactory) .get( Vendor.adoptopenjdk, null, @@ -146,7 +143,7 @@ class AssetsResourceReleaseNamePathTest : FrontendTest() { @Test fun `missing vendor 400s`() { assertThrows { - AssetsResource(apiDataStore, ReleaseEndpoint(apiDataStore)) + AssetsResource(apiDataStore, ReleaseEndpoint(apiDataStore, releaseFilterFactory), releaseFilterFactory) .get( null, "foo", diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceVersionPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceVersionPathTest.kt index 16d2c490d..5defa9ba1 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceVersionPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AssetsResourceVersionPathTest.kt @@ -96,7 +96,6 @@ class AssetsResourceVersionPathTest : AssetsPathTest() { .stream() } - @TestFactory fun `semver does not match out of range`(): Stream { return listOf( @@ -184,4 +183,5 @@ class AssetsResourceVersionPathTest : AssetsPathTest() { element == OperatingSystem.`alpine-linux` || (element == JvmImpl.dragonwell).xor(versionRange.equals(JAVA8_212) || versionRange.equals(JAVA11)) } + } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AvailableReleasesPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AvailableReleasesPathTest.kt index 0b86156bd..e9eed366d 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AvailableReleasesPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/AvailableReleasesPathTest.kt @@ -2,7 +2,6 @@ package net.adoptium.api import io.quarkus.test.junit.QuarkusTest import io.restassured.RestAssured -import io.restassured.config.RedirectConfig import net.adoptium.api.v3.JsonMapper import net.adoptium.api.v3.models.ReleaseInfo import org.hamcrest.Description diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/DownloadStatsPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/DownloadStatsPathTest.kt index 947d05586..7eb782ae4 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/DownloadStatsPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/DownloadStatsPathTest.kt @@ -21,6 +21,7 @@ import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import java.time.format.DateTimeFormatter import jakarta.ws.rs.BadRequestException +import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub import net.adoptium.api.v3.models.ReleaseType @ExtendWith(value = [DbExtension::class]) @@ -47,7 +48,7 @@ class DownloadStatsPathTest : FrontendTest() { createGithubData() ) - val downloadStatsResource = DownloadStatsResource(apiDataStore, DownloadStatsInterface(persistance)) + val downloadStatsResource = DownloadStatsResource(apiDataStore, DownloadStatsInterface(persistance, UpdatableVersionSupplierStub())) downloadStatsResource } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/FrontendTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/FrontendTest.kt index a928cb96e..fa0fd8dcc 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/FrontendTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/FrontendTest.kt @@ -2,17 +2,19 @@ package net.adoptium.api import net.adoptium.api.testDoubles.InMemoryApiPersistence import net.adoptium.api.testDoubles.InMemoryInternalDbStore +import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub import net.adoptium.api.v3.dataSources.APIDataStore import net.adoptium.api.v3.dataSources.SortMethod import net.adoptium.api.v3.dataSources.SortOrder import net.adoptium.api.v3.filters.BinaryFilter -import net.adoptium.api.v3.filters.ReleaseFilter +import net.adoptium.api.v3.filters.ReleaseFilterFactory import net.adoptium.api.v3.models.Binary import net.adoptium.api.v3.models.ImageType import net.adoptium.api.v3.models.Release import net.adoptium.api.v3.models.Vendor import org.jboss.weld.junit5.auto.AddPackages import org.jboss.weld.junit5.auto.EnableAutoWeld +import java.util.function.Predicate @EnableAutoWeld @AddPackages( @@ -24,10 +26,12 @@ import org.jboss.weld.junit5.auto.EnableAutoWeld ) open class FrontendTest { + var releaseFilterFactory: ReleaseFilterFactory = ReleaseFilterFactory(UpdatableVersionSupplierStub()) + protected fun getReleases(): Sequence { return BaseTest.adoptRepos .getFilteredReleases( - ReleaseFilter(featureVersion = 8, vendor = Vendor.getDefault()), + releaseFilterFactory.createFilter(featureVersion = 8, vendor = Vendor.getDefault()), BinaryFilter(), SortOrder.ASC, SortMethod.DEFAULT @@ -36,12 +40,12 @@ open class FrontendTest { protected fun getRandomBinary(): Pair { return getRandomBinary( - ReleaseFilter(featureVersion = 8, vendor = Vendor.getDefault()), + releaseFilterFactory.createFilter(featureVersion = 8, vendor = Vendor.getDefault()), BinaryFilter(imageType = ImageType.jdk) ) } - protected fun getRandomBinary(releaseFilter: ReleaseFilter, binaryFilter: BinaryFilter): Pair { + protected fun getRandomBinary(releaseFilter: Predicate, binaryFilter: BinaryFilter): Pair { val release = BaseTest.adoptRepos .getFilteredReleases( releaseFilter, diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/MonthlyStatsPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/MonthlyStatsPathTest.kt index 4a4f60a51..858177dba 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/MonthlyStatsPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/MonthlyStatsPathTest.kt @@ -2,6 +2,7 @@ package net.adoptium.api import kotlinx.coroutines.runBlocking import net.adoptium.api.testDoubles.InMemoryApiPersistence +import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub import net.adoptium.api.v3.DownloadStatsInterface import net.adoptium.api.v3.TimeSource import net.adoptium.api.v3.config.APIConfig @@ -12,10 +13,10 @@ import net.adoptium.api.v3.models.JvmImpl import net.adoptium.api.v3.models.MonthlyDownloadDiff import net.adoptium.api.v3.models.StatsSource import net.adoptium.api.v3.routes.stats.DownloadStatsResource -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(value = [DbExtension::class]) class MonthlyStatsPathTest : FrontendTest() { @@ -27,7 +28,7 @@ class MonthlyStatsPathTest : FrontendTest() { fun initDownloadStatsResource(): DownloadStatsResource { persistence = initApi() - return DownloadStatsResource(ApiDataStoreStub(), DownloadStatsInterface(persistence)) + return DownloadStatsResource(ApiDataStoreStub(), DownloadStatsInterface(persistence, UpdatableVersionSupplierStub())) } fun initApi(): ApiPersistence { diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TestRunner.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TestRunner.kt index 1b2050df5..1ae0284ad 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TestRunner.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TestRunner.kt @@ -31,6 +31,6 @@ class TestRunner : BaseTest() { @Test @Inject fun run() { - Awaitility.await().atMost(Long.MAX_VALUE, TimeUnit.NANOSECONDS).until({ 4 === 5 }) + Awaitility.await().atMost(Long.MAX_VALUE, TimeUnit.NANOSECONDS).until { 4 === 5 } } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesArchitecturesPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesArchitecturesPathTest.kt index c0f6bab2c..c3c39f77f 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesArchitecturesPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesArchitecturesPathTest.kt @@ -18,7 +18,7 @@ class TypesArchitecturesPathTest : FrontendTest() { @Test fun getArchitecturesAreCorrect() { - var body = RestAssured.given() + val body = RestAssured.given() .`when`() .get("/v3/types/architectures") .body @@ -26,7 +26,7 @@ class TypesArchitecturesPathTest : FrontendTest() { val architectures = parseArchitectures(body.asString()) assert(architectures.contains(Architecture.x64.name)) - assert(architectures.size == Architecture.values().size) + assert(architectures.size == Architecture.entries.size) } private fun parseArchitectures(json: String?): List = diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesOperatingSystemsPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesOperatingSystemsPathTest.kt index d27d0ca19..b16c12280 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesOperatingSystemsPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/TypesOperatingSystemsPathTest.kt @@ -18,7 +18,7 @@ class TypesOperatingSystemsPathTest : FrontendTest() { @Test fun getOperatingSystemsAreCorrect() { - var body = RestAssured.given() + val body = RestAssured.given() .`when`() .get("/v3/types/operating_systems") .body @@ -26,7 +26,7 @@ class TypesOperatingSystemsPathTest : FrontendTest() { val operatingSystems = parseOperatingSystems(body.asString()) assert(operatingSystems.contains(OperatingSystem.linux.name)) - assert(operatingSystems.size == OperatingSystem.values().size) + assert(operatingSystems.size == OperatingSystem.entries.size) } private fun parseOperatingSystems(json: String?): List = diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V1RouteTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V1RouteTest.kt index 1ba19ee57..d247ed019 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V1RouteTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V1RouteTest.kt @@ -17,12 +17,12 @@ class V1RouteTest : FrontendTest() { "/v1/foo", "/v1/foo/bar" ) - .forEach({ route -> + .forEach { route -> RestAssured.given() .`when`() .get(route) .then() .statusCode(Response.Status.GONE.statusCode) - }) + } } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V3Test.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V3Test.kt index a9e11eb89..81674bb6d 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V3Test.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/V3Test.kt @@ -2,11 +2,10 @@ package net.adoptium.api import net.adoptium.api.v3.Startup import net.adoptium.api.v3.Startup.Companion.ENABLE_PERIODIC_UPDATES -import net.adoptium.api.v3.dataSources.APIDataStore import org.jboss.weld.junit5.auto.AddPackages -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith @AddPackages(value=[ApiDataStoreStub::class]) @ExtendWith(value = [DbExtension::class]) diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/BinaryPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/BinaryPathTest.kt index 7365c358d..b7b7e9bbb 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/BinaryPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/BinaryPathTest.kt @@ -37,7 +37,7 @@ class BinaryPathTest : PackageEndpointTest() { performRequest(path) .then() .statusCode(307) - .header("Location", Matchers.startsWith("https://github.com/adoptium/temurin11-binaries/releases/download/")) + .header("location", Matchers.startsWith("https://github.com/adoptium/temurin11-binaries/releases/download/")) } @Test diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/InstallerPathTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/InstallerPathTest.kt index 7004790c1..417bdddda 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/InstallerPathTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/InstallerPathTest.kt @@ -28,7 +28,7 @@ class InstallerPathTest : PackageEndpointTest() { performRequest(path) .then() .statusCode(307) - .header("Location", Matchers.startsWith("https://github.com/adoptium/temurin11-binaries/releases/download/")) + .header("location", Matchers.startsWith("https://github.com/adoptium/temurin11-binaries/releases/download/")) } @Test @@ -37,7 +37,7 @@ class InstallerPathTest : PackageEndpointTest() { performRequest(path) .then() .statusCode(307) - .header("Location", Matchers.startsWith("https://github.com/adoptium/temurin11-binaries/releases/download/")) + .header("location", Matchers.startsWith("https://github.com/adoptium/temurin11-binaries/releases/download/")) } @Test diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/PackageEndpointTest.kt b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/PackageEndpointTest.kt index c91f5c7b2..bea82b06b 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/PackageEndpointTest.kt +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/kotlin/net/adoptium/api/packages/PackageEndpointTest.kt @@ -3,8 +3,9 @@ package net.adoptium.api.packages import io.restassured.RestAssured import io.restassured.response.Response import net.adoptium.api.FrontendTest +import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub import net.adoptium.api.v3.filters.BinaryFilter -import net.adoptium.api.v3.filters.ReleaseFilter +import net.adoptium.api.v3.filters.ReleaseFilterFactory import net.adoptium.api.v3.models.Architecture import net.adoptium.api.v3.models.Binary import net.adoptium.api.v3.models.CLib @@ -79,7 +80,7 @@ abstract class PackageEndpointTest : FrontendTest() { } protected fun getClibBinary() = getRandomBinary( - ReleaseFilter( + ReleaseFilterFactory(UpdatableVersionSupplierStub()).createFilter( featureVersion = 11, releaseType = ReleaseType.ea, vendor = Vendor.getDefault(), @@ -106,7 +107,7 @@ abstract class PackageEndpointTest : FrontendTest() { performRequest(path) .then() .statusCode(307) - .header("Location", Matchers.startsWith(binary.`package`.link)) + .header("location", Matchers.startsWith(binary.`package`.link)) } protected fun requestSignatureExpecting307( @@ -120,7 +121,7 @@ abstract class PackageEndpointTest : FrontendTest() { performRequest(path) .then() .statusCode(307) - .header("Location", Matchers.startsWith(binary.`package`.signature_link)) + .header("location", Matchers.startsWith(binary.`package`.signature_link)) } protected fun requestChecksumExpecting307( @@ -134,6 +135,6 @@ abstract class PackageEndpointTest : FrontendTest() { performRequest(path) .then() .statusCode(307) - .header("Location", Matchers.startsWith(binary.`package`.checksum_link)) + .header("location", Matchers.startsWith(binary.`package`.checksum_link)) } } diff --git a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/resources/application.properties b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/resources/application.properties index 3b9c12ebd..f5f7eff76 100644 --- a/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/resources/application.properties +++ b/adoptium-frontend-parent/adoptium-api-v3-frontend/src/test/resources/application.properties @@ -1,3 +1,4 @@ quarkus.log.category."org.jboss".level=WARN -quarkus.class-loading.removed-resources."net.adoptium.api\:adoptium-api-v3-updater\:classes"=net/adoptium/api/v3/V3UpdaterApp.class \ No newline at end of file +quarkus.class-loading.removed-resources."net.adoptium.api\:adoptium-api-v3-updater\:classes"=net/adoptium/api/v3/V3UpdaterApp.class +quarkus.arc.exclude-types=net.adoptium.api.v3.dataSources.UpdatableVersionSupplierImpl diff --git a/adoptium-models-parent/adoptium-api-v3-models/pom.xml b/adoptium-models-parent/adoptium-api-v3-models/pom.xml index 2e3515db1..ffa0a3c27 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/pom.xml +++ b/adoptium-models-parent/adoptium-api-v3-models/pom.xml @@ -38,6 +38,17 @@ org.slf4j slf4j-api + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/config/APIConfig.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/config/APIConfig.kt index 1256d7792..dc7baf616 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/config/APIConfig.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/config/APIConfig.kt @@ -8,5 +8,10 @@ class APIConfig { var DISABLE_UPDATER: Boolean = System.getenv("DISABLE_UPDATER")?.toBoolean() ?: false var UPDATE_ADOPTOPENJDK: Boolean = System.getenv("UPDATE_ADOPTOPENJDK")?.toBoolean() ?: false + + // We will only update pre-releases if they are less than n days old + var UPDATE_DAY_CUTOFF: Int = System.getenv("UPDATE_DAY_CUTOFF")?.toInt() ?: 90 + + var DEPLOYMENT_TYPE: DeploymentType = DeploymentType.valueOf((System.getenv("DEPLOYMENT_TYPE") ?: "FRONTEND").uppercase()) } } diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/config/DeploymentType.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/config/DeploymentType.kt new file mode 100644 index 000000000..0acebdf6b --- /dev/null +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/config/DeploymentType.kt @@ -0,0 +1,5 @@ +package net.adoptium.api.v3.config + +enum class DeploymentType { + FRONTEND, UPDATER +} diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/AdoptRepos.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/AdoptRepos.kt index 3e91e8216..fd20b556a 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/AdoptRepos.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/AdoptRepos.kt @@ -27,7 +27,6 @@ class AdoptRepos { val releases = repos .asSequence() - .filterNotNull() .map { it.value.releases } .flatMap { it.getReleases() } .toList() @@ -40,9 +39,7 @@ class AdoptRepos { } constructor(list: List) : this( - list - .map { Pair(it.featureVersion, it) } - .toMap() + list.associateBy { it.featureVersion } ) fun getReleases( @@ -99,9 +96,7 @@ class AdoptRepos { other as AdoptRepos - if (repos != other.repos) return false - - return true + return repos == other.repos } override fun hashCode(): Int { diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/GitHubId.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/GitHubId.kt index 96ba5ecd1..f4d9ec8fb 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/GitHubId.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/GitHubId.kt @@ -20,9 +20,7 @@ class GitHubId { other as GitHubId - if (id != other.id) return false - - return true + return id == other.id } override fun hashCode(): Int { diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/Releases.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/Releases.kt index e446498c4..ecb051a78 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/Releases.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/dataSources/models/Releases.kt @@ -97,9 +97,7 @@ class Releases { other as Releases - if (nodes != other.nodes) return false - - return true + return nodes == other.nodes } override fun hashCode(): Int { diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/Architecture.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/Architecture.kt index c9ff0032e..40f2c82df 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/Architecture.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/Architecture.kt @@ -31,9 +31,7 @@ enum class Architecture : FileNameMatcher { @JvmStatic @JsonCreator fun forValue(value: String): Architecture { - return values() - .filter { it.names.contains(value) } - .first() + return entries.first { it.names.contains(value) } } fun getValue(value: String): Architecture { diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/DateTime.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/DateTime.kt index 0b66d12e4..db574735b 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/DateTime.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/DateTime.kt @@ -73,9 +73,7 @@ class DateTime { other as DateTime - if (dateTime != other.dateTime) return false - - return true + return dateTime == other.dateTime } override fun hashCode(): Int { diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/SourcePackage.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/SourcePackage.kt index 5968e72a9..a5682ac9a 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/SourcePackage.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/SourcePackage.kt @@ -5,12 +5,12 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema class SourcePackage( name: String, link: String, - size: Long) : FileAsset(name, link, size); + size: Long) : FileAsset(name, link, size) class ReleaseNotesPackage( name: String, link: String, - size: Long) : FileAsset(name, link, size); + size: Long) : FileAsset(name, link, size) open class FileAsset { diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/VersionData.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/VersionData.kt index 252f72843..8b7937637 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/VersionData.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/VersionData.kt @@ -46,7 +46,7 @@ class VersionData : Comparable { // i.e 11.0.1+11.1 fun formSemver(): String { - var semver = major.toString() + "." + minor + "." + security + var semver = "$major.$minor.$security" if (pre?.isNotEmpty() == true) { semver += "-$pre" diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/Versions.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/Versions.kt deleted file mode 100644 index c05300d6f..000000000 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/models/Versions.kt +++ /dev/null @@ -1,16 +0,0 @@ -package net.adoptium.api.v3.models - -object Versions { - - private const val DEFAULT_LATEST_JAVA_VERSION = 21 - private const val LATEST_JAVA_VERSION_PROPERTY = "LATEST_JAVA_VERSION" - - private val latestJavaVersion: Int - val versions: Array - val ltsVersions: Array = arrayOf(8, 11, 17, 21) - - init { - latestJavaVersion = Integer.parseInt(System.getProperty(LATEST_JAVA_VERSION_PROPERTY, DEFAULT_LATEST_JAVA_VERSION.toString())) - versions = (8..latestJavaVersion).toList().toTypedArray() - } -} diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/VersionParser.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/VersionParser.kt index 4ec0813e7..069e5e95d 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/VersionParser.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/VersionParser.kt @@ -1,10 +1,9 @@ package net.adoptium.api.v3.parser -import net.adoptium.api.v3.models.VersionData -import org.slf4j.LoggerFactory /* ktlint-disable no-wildcard-imports */ -import java.util.* /* ktlint-enable no-wildcard-imports */ +import net.adoptium.api.v3.models.VersionData +import org.slf4j.LoggerFactory import java.util.regex.Matcher import java.util.regex.Pattern @@ -52,7 +51,7 @@ object VersionParser { private fun jep223WithAdoptBuildNum(): List { val buildRegex = "(?[0-9]+)(\\.(?[0-9]+))?" - return Arrays.asList( + return listOf( "(?:jdk\\-)?(?$VNUM_REGEX(\\-$PRE_REGEX)?\\+$buildRegex(\\-$OPT_REGEX)?)", "(?:jdk\\-)?(?$VNUM_REGEX\\-$PRE_REGEX(\\-$OPT_REGEX)?)", "(?:jdk\\-)?(?$VNUM_REGEX(\\+\\-$OPT_REGEX)?)" @@ -60,7 +59,7 @@ object VersionParser { } private fun jep223(): List { - return Arrays.asList( + return listOf( "(?:jdk\\-)?(?$VNUM_REGEX(\\-$PRE_REGEX)?\\+$BUILD_REGEX(\\-$OPT_REGEX)?)", "(?:jdk\\-)?(?$VNUM_REGEX\\-$PRE_REGEX(\\-$OPT_REGEX)?)", "(?:jdk\\-)?(?$VNUM_REGEX(\\+\\-$OPT_REGEX)?)" @@ -104,7 +103,7 @@ object VersionParser { if (version != null) { return version } - } catch (e: Exception) { + } catch (_: Exception) { } throw FailedToParse("Failed to parse $publishName") } @@ -126,7 +125,7 @@ object VersionParser { if (!sanityCheck || sanityCheck(parsed)) { return parsed } - } catch (e: Exception) { + } catch (_: Exception) { } return null } @@ -185,7 +184,7 @@ object VersionParser { private fun sanityCheck(parsed: VersionData): Boolean { - if (!(parsed.major in 101 downTo 7)) { + if (parsed.major !in 101 downTo 7) { // Sanity check as javas parser can match a single number // sane range is 8 to 100 // TODO update me before 2062 and java 100 is released diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/maven/SemverParser.kt b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/maven/SemverParser.kt index 3aadc2618..69722c602 100644 --- a/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/maven/SemverParser.kt +++ b/adoptium-models-parent/adoptium-api-v3-models/src/main/kotlin/net/adoptium/api/v3/parser/maven/SemverParser.kt @@ -1,14 +1,14 @@ -package net.adoptium.api.v3.parser.maven; +package net.adoptium.api.v3.parser.maven import net.adoptium.api.v3.models.VersionData import net.adoptium.api.v3.parser.FailedToParse import java.util.regex.Pattern object SemverParser { - val PRE = """(\-(?
[\.A-Za-z0-9]+))?"""
-    val BUILD = """(\+(?[\.A-Za-z0-9]+))?"""
-    val VERSION_CORE = """(?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)$PRE$BUILD"""
-    val MATCHER = Pattern.compile("^$VERSION_CORE$")
+    private const val PRE = """(\-(?
[\.A-Za-z0-9]+))?"""
+    private const val BUILD = """(\+(?[\.A-Za-z0-9]+))?"""
+    private const val VERSION_CORE = """(?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)$PRE$BUILD"""
+    private val MATCHER = Pattern.compile("^$VERSION_CORE$")
 
     fun parseAdoptSemverNonNull(version: String): VersionData {
         return parseAdoptSemver(version) ?: throw FailedToParse("Failed to parse $version")
@@ -49,7 +49,7 @@ object SemverParser {
 
                         optional = if (parts.size > 2) parts[2] else null
                         adoptBuildNum = if (parts.size > 1) parts[1].toInt() else null
-                        build = if (parts.size > 0) parts[0].toInt() else null
+                        build = if (parts.isNotEmpty()) parts[0].toInt() else null
 
                         if (build != null) {
                             patch = build / 100
diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/api/SemverParserTest.kt b/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/net/adoptium/api/SemverParserTest.kt
similarity index 99%
rename from adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/api/SemverParserTest.kt
rename to adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/net/adoptium/api/SemverParserTest.kt
index 5bb85b1ff..df1b28c60 100644
--- a/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/api/SemverParserTest.kt
+++ b/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/net/adoptium/api/SemverParserTest.kt
@@ -1,4 +1,4 @@
-package api;
+package net.adoptium.api
 
 import net.adoptium.api.v3.models.VersionData
 import net.adoptium.api.v3.parser.VersionParser
diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/api/VersionParserTest.kt b/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/net/adoptium/api/VersionParserTest.kt
similarity index 100%
rename from adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/api/VersionParserTest.kt
rename to adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/net/adoptium/api/VersionParserTest.kt
diff --git a/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/api/VersionRangeTest.kt b/adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/net/adoptium/api/VersionRangeTest.kt
similarity index 100%
rename from adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/api/VersionRangeTest.kt
rename to adoptium-models-parent/adoptium-api-v3-models/src/test/kotlin/net/adoptium/api/VersionRangeTest.kt
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/pom.xml b/adoptium-updater-parent/adoptium-api-v3-updater/pom.xml
index b4fbf4f96..5a2291db6 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/pom.xml
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/pom.xml
@@ -21,7 +21,11 @@
     
         
             io.quarkus
-            quarkus-resteasy-reactive
+            quarkus-rest
+        
+        
+            io.quarkus
+            quarkus-smallrye-metrics
         
         
             jakarta.enterprise
@@ -74,17 +78,6 @@
             weld-junit5
             test
         
-        
-            org.litote.kmongo
-            kmongo-flapdoodle
-            test
-            
-                
-                    org.slf4j
-                    slf4j-simple
-                
-            
-        
         
             io.mockk
             mockk-jvm
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptReposBuilder.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptReposBuilder.kt
index 2b1040e93..6a6a1bf4b 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptReposBuilder.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptReposBuilder.kt
@@ -1,6 +1,8 @@
 package net.adoptium.api.v3
 
 import jakarta.enterprise.context.ApplicationScoped
+import jakarta.inject.Inject
+import net.adoptium.api.v3.dataSources.VersionSupplier
 import net.adoptium.api.v3.dataSources.github.graphql.models.summary.GHReleaseSummary
 import net.adoptium.api.v3.dataSources.github.graphql.models.summary.GHRepositorySummary
 import net.adoptium.api.v3.dataSources.models.AdoptRepos
@@ -12,11 +14,13 @@ import net.adoptium.api.v3.models.GHReleaseMetadata
 import net.adoptium.api.v3.models.Release
 import org.slf4j.LoggerFactory
 import java.time.temporal.ChronoUnit
-import jakarta.inject.Inject
 import kotlin.math.absoluteValue
 
 @ApplicationScoped
-class AdoptReposBuilder @Inject constructor(private var adoptRepository: AdoptRepository) {
+class AdoptReposBuilder @Inject constructor(
+    private var adoptRepository: AdoptRepository,
+    private var versionSupplier: VersionSupplier
+    ) {
 
     companion object {
         @JvmStatic
@@ -156,13 +160,14 @@ class AdoptReposBuilder @Inject constructor(private var adoptRepository: AdoptRe
         }
     }
 
-    suspend fun build(versions: Array): AdoptRepos {
+    suspend fun build(filter: ReleaseIncludeFilter): AdoptRepos {
         excluded.clear()
         // Fetch repos in parallel
-        val reposMap = versions
+        val reposMap = versionSupplier
+            .getAllVersions()
             .reversed()
             .mapNotNull { version ->
-                adoptRepository.getRelease(version)
+                adoptRepository.getRelease(version, filter)
             }
             .associateBy { it.featureVersion }
         LOGGER.info("DONE")
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptRepository.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptRepository.kt
index 2618b0e0d..91e39fa09 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptRepository.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/AdoptRepository.kt
@@ -1,10 +1,10 @@
 package net.adoptium.api.v3
 
 import jakarta.enterprise.context.ApplicationScoped
+import jakarta.inject.Inject
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.async
-import net.adoptium.api.v3.config.APIConfig
 import net.adoptium.api.v3.dataSources.github.GitHubApi
 import net.adoptium.api.v3.dataSources.github.graphql.models.GHAsset
 import net.adoptium.api.v3.dataSources.github.graphql.models.PageInfo
@@ -20,17 +20,13 @@ import net.adoptium.api.v3.models.Release
 import net.adoptium.api.v3.models.ReleaseType
 import net.adoptium.api.v3.models.Vendor
 import org.slf4j.LoggerFactory
-import jakarta.inject.Inject
 
 interface AdoptRepository {
-    suspend fun getRelease(version: Int): FeatureRelease?
+    suspend fun getRelease(version: Int, filter: ReleaseIncludeFilter): FeatureRelease?
     suspend fun getSummary(version: Int): GHRepositorySummary
     suspend fun getReleaseById(gitHubId: GitHubId): ReleaseResult?
     suspend fun getReleaseFilesForId(gitHubId: GitHubId): List?
 
-    companion object {
-        val VENDORS_EXCLUDED_FROM_FULL_UPDATE = setOf(Vendor.adoptopenjdk)
-    }
 }
 
 @ApplicationScoped
@@ -79,7 +75,7 @@ open class AdoptRepositoryImpl @Inject constructor(
     override suspend fun getReleaseById(gitHubId: GitHubId): ReleaseResult? {
         val release = client.getReleaseById(gitHubId)
 
-        if (release == null) return null;
+        if (release == null) return null
 
         return getMapperForRepo(release.url)
             .toAdoptRelease(release)
@@ -92,31 +88,25 @@ open class AdoptRepositoryImpl @Inject constructor(
             ?.assets
     }
 
-    override suspend fun getRelease(version: Int): FeatureRelease {
+    override suspend fun getRelease(version: Int, filter: ReleaseIncludeFilter): FeatureRelease {
+
         val repo = getDataForEachRepo(
             version,
-            ::getRepository,
-            getFullUpdateFilter()
+            filter,
+            getRepository(filter)
         )
             .await()
             .filterNotNull()
             .map { AdoptRepo(it) }
-        return FeatureRelease(version, repo)
-    }
 
-    // If not explicitly updating AdoptOpenJDK exclude them
-    private fun getFullUpdateFilter(): (Vendor) -> Boolean = if (APIConfig.UPDATE_ADOPTOPENJDK) {
-        { true } // include all vendors
-    } else {
-        { vendor -> !AdoptRepository.VENDORS_EXCLUDED_FROM_FULL_UPDATE.contains(vendor) } // exclude AdoptOpenjdk
+        return FeatureRelease(version, repo)
     }
 
     override suspend fun getSummary(version: Int): GHRepositorySummary {
         val releaseSummaries = getDataForEachRepo(
             version,
-            { owner: String, repoName: String -> client.getRepositorySummary(owner, repoName) },
-            { true } // include all vendors in summary update
-        )
+            ReleaseIncludeFilter.INCLUDE_ALL
+        ) { _: Vendor, owner: String, repoName: String -> client.getRepositorySummary(owner, repoName) }
             .await()
             .filterNotNull()
             .flatMap { it.releases.releases }
@@ -124,9 +114,13 @@ open class AdoptRepositoryImpl @Inject constructor(
         return GHRepositorySummary(GHReleasesSummary(releaseSummaries, PageInfo(false, "")))
     }
 
-    private suspend fun getRepository(owner: String, repoName: String): List {
+    private fun getRepository(filter: ReleaseIncludeFilter): suspend (Vendor, String, String) -> List {
+        return { vendor: Vendor, owner: String, repoName: String -> getRepository(filter, vendor, owner, repoName) }
+    }
+
+    private suspend fun getRepository(filter: ReleaseIncludeFilter, vendor: Vendor, owner: String, repoName: String): List {
         return client
-            .getRepository(owner, repoName)
+            .getRepository(owner, repoName) { updatedAt, isPrerelease -> filter.filter(vendor, updatedAt, isPrerelease) }
             .getReleases()
             .flatMap {
                 try {
@@ -152,10 +146,10 @@ open class AdoptRepositoryImpl @Inject constructor(
         }
     }
 
-    private suspend fun  getDataForEachRepo(
+    private fun  getDataForEachRepo(
         version: Int,
-        getFun: suspend (String, String) -> E,
-        filter: (Vendor) -> Boolean
+        filter: ReleaseIncludeFilter,
+        getFun: suspend (Vendor, String, String) -> E
     ): Deferred> {
         LOGGER.info("getting $version")
         return GlobalScope.async {
@@ -182,15 +176,15 @@ open class AdoptRepositoryImpl @Inject constructor(
         owner: String,
         vendor: Vendor,
         repoName: String,
-        getFun: suspend (String, String) -> E,
-        filter: (Vendor) -> Boolean
+        getFun: suspend (Vendor, String, String) -> E,
+        filter: ReleaseIncludeFilter
     ): Deferred {
         return GlobalScope.async {
-            if (!Vendor.validVendor(vendor) || !filter.invoke(vendor)) {
+            if (!Vendor.validVendor(vendor) || !filter.filterVendor(vendor)) {
                 return@async null
             }
             LOGGER.info("getting $owner $repoName")
-            val releases = getFun(owner, repoName)
+            val releases = getFun(vendor, owner, repoName)
             LOGGER.info("Done getting $owner $repoName")
             return@async releases
         }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/Main.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/Main.kt
index 0c08e89ad..e3e917337 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/Main.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/Main.kt
@@ -1,5 +1,7 @@
 package net.adoptium.api.v3
 
+import net.adoptium.api.v3.config.APIConfig
+import net.adoptium.api.v3.config.DeploymentType
 import org.jboss.weld.environment.se.Weld
 
 class Main {
@@ -8,6 +10,7 @@ class Main {
         fun main(args: Array) {
             // Force eager App insights loading
             // AppInsightsTelemetry.enabled
+            APIConfig.DEPLOYMENT_TYPE = DeploymentType.UPDATER
 
             val container = Weld().containerId("STATIC_INSTANCE").initialize()
             val v3Updater = container.select(V3Updater::class.java).get()
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/ReleaseIncludeFilter.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/ReleaseIncludeFilter.kt
new file mode 100644
index 000000000..949040caa
--- /dev/null
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/ReleaseIncludeFilter.kt
@@ -0,0 +1,60 @@
+package net.adoptium.api.v3
+
+import net.adoptium.api.v3.config.APIConfig
+import net.adoptium.api.v3.mapping.ReleaseMapper
+import net.adoptium.api.v3.models.Vendor
+import java.time.Duration
+import java.time.ZonedDateTime
+
+enum class ReleaseFilterType {
+    RELEASES_ONLY,
+    SNAPSHOTS_ONLY,
+    ALL
+}
+
+class ReleaseIncludeFilter(
+    private val now: ZonedDateTime,
+    private val filterType: ReleaseFilterType,
+    private val includeAll: Boolean = false,
+    private val excludedVendors: Set = VENDORS_EXCLUDED_FROM_FULL_UPDATE
+) {
+    companion object {
+        val VENDORS_EXCLUDED_FROM_FULL_UPDATE = setOf(Vendor.adoptopenjdk)
+
+        val INCLUDE_ALL = ReleaseIncludeFilter(TimeSource.now(), ReleaseFilterType.ALL, true)
+    }
+
+    fun filterVendor(vendor: Vendor): Boolean {
+        return if (includeAll || APIConfig.UPDATE_ADOPTOPENJDK) {
+            true // include all vendors
+        } else {
+            !excludedVendors.contains(vendor)
+        }
+    }
+
+    fun filter(vendor: Vendor, startTime: String, isPrerelease: Boolean): Boolean {
+        return filter(vendor, ReleaseMapper.parseDate(startTime), isPrerelease)
+    }
+
+    fun filter(vendor: Vendor, startTime: ZonedDateTime, isPrerelease: Boolean): Boolean {
+        if (includeAll || APIConfig.UPDATE_ADOPTOPENJDK) {
+            return true // include all vendors
+        } else {
+            if (excludedVendors.contains(vendor)) {
+                return false
+            }
+
+            var include = true
+
+            if (filterType == ReleaseFilterType.RELEASES_ONLY) {
+                return !isPrerelease
+            } else if (filterType == ReleaseFilterType.SNAPSHOTS_ONLY) {
+                include = isPrerelease
+            }
+
+            return include && Duration.between(startTime, now).toDays() < APIConfig.UPDATE_DAY_CUTOFF
+            // exclude AdoptOpenjdk
+            // Don't Update releases more than a year old
+        }
+    }
+}
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/V3Updater.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/V3Updater.kt
index 98984eb56..1764c6ed8 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/V3Updater.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/V3Updater.kt
@@ -13,10 +13,11 @@ import net.adoptium.api.v3.config.APIConfig
 import net.adoptium.api.v3.dataSources.APIDataStore
 import net.adoptium.api.v3.dataSources.ReleaseVersionResolver
 import net.adoptium.api.v3.dataSources.UpdaterJsonMapper
+import net.adoptium.api.v3.dataSources.UpdatableVersionSupplier
 import net.adoptium.api.v3.dataSources.models.AdoptRepos
 import net.adoptium.api.v3.dataSources.persitence.ApiPersistence
 import net.adoptium.api.v3.models.Release
-import net.adoptium.api.v3.models.Versions
+import net.adoptium.api.v3.models.ReleaseType
 import net.adoptium.api.v3.releaseNotes.AdoptReleaseNotes
 import net.adoptium.api.v3.stats.StatsInterface
 import org.slf4j.LoggerFactory
@@ -26,6 +27,7 @@ import java.util.*
 import java.util.concurrent.ConcurrentSkipListSet
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.concurrent.timerTask
 
 @UnlessBuildProfile("test")
@@ -41,7 +43,8 @@ class V3Updater @Inject constructor(
     private val database: ApiPersistence,
     private val statsInterface: StatsInterface,
     private val releaseVersionResolver: ReleaseVersionResolver,
-    private val adoptReleaseNotes: AdoptReleaseNotes
+    private val adoptReleaseNotes: AdoptReleaseNotes,
+    private val updatableVersionSupplier: UpdatableVersionSupplier
 ) : Updater {
 
     private val mutex = Mutex()
@@ -63,18 +66,14 @@ class V3Updater @Inject constructor(
             return String(Base64.getEncoder().encode(md.digest()))
         }
 
-        fun copyOldReleasesIntoNewRepo(currentRepo: AdoptRepos, newRepoData: AdoptRepos) = newRepoData
+        fun copyOldReleasesIntoNewRepo(currentRepo: AdoptRepos, newRepoData: AdoptRepos, filter: ReleaseIncludeFilter) = newRepoData
             .addAll(currentRepo
                 .allReleases
                 .getReleases()
-                .filter { AdoptRepository.VENDORS_EXCLUDED_FROM_FULL_UPDATE.contains(it.vendor) }
+                .filter { !filter.filter(it.vendor, it.updated_at.dateTime, it.release_type == ReleaseType.ea) }
                 .toList())
     }
 
-    init {
-        //AppInsightsTelemetry.start()
-    }
-
     override fun addToUpdate(toUpdate: String): List {
         val repo = apiDataStore.loadDataFromDb(true)
         val toUpdateList = repo
@@ -140,10 +139,10 @@ class V3Updater @Inject constructor(
             .forEach { releaseA ->
                 val releaseB = repoB.allReleases.getReleaseById(releaseA.id)
                 if (releaseB == null) {
-                    LOGGER.debug("Release disapeared ${releaseA.id} ${releaseA.version_data.semver}")
+                    LOGGER.debug("Release disappeared ${releaseA.id} ${releaseA.version_data.semver}")
                 } else if (releaseA != releaseB) {
-                    LOGGER.debug("Release changedA $releaseA")
-                    LOGGER.debug("Release changedB $releaseB")
+                    LOGGER.debug("Release changedA {}", releaseA)
+                    LOGGER.debug("Release changedB {}", releaseB)
                     releaseA
                         .binaries
                         .forEach { binaryA ->
@@ -208,6 +207,7 @@ class V3Updater @Inject constructor(
     fun run(instantFullUpdate: Boolean) {
         val executor = Executors.newScheduledThreadPool(2)
 
+
         val delay = if (instantFullUpdate) 0L else 1L
 
         var repo: AdoptRepos = try {
@@ -217,30 +217,44 @@ class V3Updater @Inject constructor(
             AdoptRepos(emptyList())
         }
 
-        executor.scheduleWithFixedDelay(
-            timerTask {
-                repo = fullUpdate(repo) ?: repo
-            },
-            delay, 1, TimeUnit.DAYS
-        )
+        val incrementalUpdateScheduled = AtomicBoolean(false)
 
         executor.scheduleWithFixedDelay(
             timerTask {
-                repo = incrementalUpdate(repo) ?: repo
+                repo = fullUpdate(repo, true) ?: repo
+                if (!incrementalUpdateScheduled.getAndSet(true)) {
+                    executor.scheduleWithFixedDelay(
+                        timerTask {
+                            repo = incrementalUpdate(repo) ?: repo
+                        },
+                        1, 6, TimeUnit.MINUTES
+                    )
+                }
+                repo = fullUpdate(repo, false) ?: repo
             },
-            1, 6, TimeUnit.MINUTES
+            delay, 1, TimeUnit.DAYS
         )
     }
 
-    fun fullUpdate(currentRepo: AdoptRepos): AdoptRepos? {
+    private fun fullUpdate(currentRepo: AdoptRepos, releasesOnly: Boolean): AdoptRepos? {
         // Must catch errors or may kill the scheduler
         try {
             return runBlocking {
-                LOGGER.info("Starting Full update")
+                LOGGER.info("Starting Full update {}", releasesOnly)
+
+                updatableVersionSupplier.updateVersions()
+
+                val filterType: ReleaseFilterType = if (releasesOnly) {
+                    ReleaseFilterType.RELEASES_ONLY
+                } else {
+                    ReleaseFilterType.ALL
+                }
 
-                val newRepoData = adoptReposBuilder.build(Versions.versions)
+                val filter = ReleaseIncludeFilter(TimeSource.now(), filterType)
 
-                val repo = carryOverExcludedReleases(currentRepo, newRepoData)
+                val newRepoData = adoptReposBuilder.build(filter)
+
+                val repo = copyOldReleasesIntoNewRepo(currentRepo, newRepoData, filter)
 
                 val checksum = calculateChecksum(repo)
 
@@ -268,13 +282,4 @@ class V3Updater @Inject constructor(
         return null
     }
 
-    // Releases that were excluded from the update due to being archived, copy them over from existing data
-    private fun carryOverExcludedReleases(currentRepo: AdoptRepos, newRepoData: AdoptRepos) = if (!APIConfig.UPDATE_ADOPTOPENJDK) {
-        // AdoptOpenJdk were excluded from full update so copy them from previous
-        copyOldReleasesIntoNewRepo(currentRepo, newRepoData)
-    } else {
-        newRepoData
-    }
-
-
 }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/ReleaseVersionResolver.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/ReleaseVersionResolver.kt
index e849aee71..913c8dba3 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/ReleaseVersionResolver.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/ReleaseVersionResolver.kt
@@ -1,31 +1,17 @@
 package net.adoptium.api.v3.dataSources
 
 import jakarta.enterprise.context.ApplicationScoped
+import jakarta.inject.Inject
 import net.adoptium.api.v3.dataSources.models.AdoptRepos
 import net.adoptium.api.v3.models.ReleaseInfo
 import net.adoptium.api.v3.models.ReleaseType
-import net.adoptium.api.v3.models.Versions
-import jakarta.inject.Inject
 
 @ApplicationScoped
 class ReleaseVersionResolver @Inject constructor(
-    private val updaterHtmlClient: UpdaterHtmlClient
+    private val versionSupplier: VersionSupplier
 ) {
 
-    private val VERSION_FILE_URL = "https://raw.githubusercontent.com/openjdk/jdk/master/make/conf/version-numbers.conf"
-
-    private suspend fun getTipVersion(): Int? {
-        val versionFile = updaterHtmlClient.get(VERSION_FILE_URL)
-
-        return if (versionFile != null) {
-            Regex(""".*DEFAULT_VERSION_FEATURE=(?\d+).*""", setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL))
-                .matchEntire(versionFile)?.groups?.get("num")?.value?.toInt()
-        } else {
-            null
-        }
-    }
-
-    suspend fun formReleaseInfo(repo: AdoptRepos): ReleaseInfo {
+    fun formReleaseInfo(repo: AdoptRepos): ReleaseInfo {
         val gaReleases = repo
             .allReleases
             .getReleases()
@@ -40,9 +26,11 @@ class ReleaseVersionResolver @Inject constructor(
             .toTypedArray()
         val mostRecentFeatureRelease: Int = availableReleases.lastOrNull() ?: 0
 
+        val ltsVersions = versionSupplier.getLtsVersions()
+
         val availableLtsReleases: Array = gaReleases
             .asSequence()
-            .filter { Versions.ltsVersions.contains(it.version_data.major) }
+            .filter { ltsVersions.contains(it.version_data.major) }
             .map { it.version_data.major }
             .distinct()
             .sorted()
@@ -58,7 +46,7 @@ class ReleaseVersionResolver @Inject constructor(
             .sorted()
             .lastOrNull() ?: 0
 
-        val tip = getTipVersion() ?: mostRecentFeatureVersion
+        val tip = versionSupplier.getTipVersion() ?: mostRecentFeatureVersion
 
         return ReleaseInfo(
             availableReleases,
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdatableVersionSupplier.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdatableVersionSupplier.kt
new file mode 100644
index 000000000..0f01490ac
--- /dev/null
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdatableVersionSupplier.kt
@@ -0,0 +1,5 @@
+package net.adoptium.api.v3.dataSources
+
+interface UpdatableVersionSupplier : VersionSupplier {
+    suspend fun updateVersions()
+}
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdatableVersionSupplierImpl.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdatableVersionSupplierImpl.kt
new file mode 100644
index 000000000..6d51b22f9
--- /dev/null
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdatableVersionSupplierImpl.kt
@@ -0,0 +1,66 @@
+package net.adoptium.api.v3.dataSources
+
+import jakarta.enterprise.context.ApplicationScoped
+import jakarta.inject.Inject
+import kotlinx.coroutines.runBlocking
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+@ApplicationScoped
+class UpdatableVersionSupplierImpl @Inject constructor(val updaterHtmlClient: UpdaterHtmlClient) : VersionSupplier, UpdatableVersionSupplier {
+    companion object {
+        private val LOGGER: Logger = LoggerFactory.getLogger(UpdatableVersionSupplierImpl::class.java)
+    }
+
+    private val DEFAULT_LATEST_JAVA_VERSION = 24
+    private val LATEST_JAVA_VERSION_PROPERTY = "LATEST_JAVA_VERSION"
+
+    private val VERSION_FILE_URL = "https://raw.githubusercontent.com/openjdk/jdk/master/make/conf/version-numbers.conf"
+
+    private var tipVersion: Int? = null
+    private var latestJavaVersion: Int
+    private var versions: Array
+    private var ltsVersions: Array = arrayOf(8, 11, 17, 21)
+
+    init {
+        latestJavaVersion = Integer.parseInt(System.getProperty(LATEST_JAVA_VERSION_PROPERTY, DEFAULT_LATEST_JAVA_VERSION.toString()))
+        versions = (8..latestJavaVersion).toList().toTypedArray()
+        runBlocking {
+            updateVersions()
+        }
+    }
+
+    override suspend fun updateVersions() {
+        try {
+            val versionFile = updaterHtmlClient.get(VERSION_FILE_URL)
+
+            if (versionFile != null) {
+                tipVersion = Regex(""".*DEFAULT_VERSION_FEATURE=(?\d+).*""", setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL))
+                    .matchEntire(versionFile)?.groups?.get("num")?.value?.toInt()
+            } else {
+                LOGGER.warn("Failed to get tip version")
+            }
+
+
+            if (tipVersion != null && tipVersion!! > latestJavaVersion) {
+                latestJavaVersion = tipVersion as Int
+                versions = (8..latestJavaVersion).toList().toTypedArray()
+            }
+
+        } catch (e: Exception) {
+            LOGGER.warn("Failed to get tip version", e)
+        }
+    }
+
+    override fun getTipVersion(): Int? {
+        return this.tipVersion
+    }
+
+    override fun getLtsVersions(): Array {
+        return ltsVersions
+    }
+
+    override fun getAllVersions(): Array {
+        return versions
+    }
+}
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/stats/GitHubDownloadStatsCalculator.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/stats/GitHubDownloadStatsCalculator.kt
index f56aa7eb6..552f1b1a7 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/stats/GitHubDownloadStatsCalculator.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/main/kotlin/net/adoptium/api/v3/stats/GitHubDownloadStatsCalculator.kt
@@ -1,6 +1,7 @@
 package net.adoptium.api.v3.stats
 
 import jakarta.enterprise.context.ApplicationScoped
+import jakarta.inject.Inject
 import net.adoptium.api.v3.TimeSource
 import net.adoptium.api.v3.dataSources.models.AdoptRepos
 import net.adoptium.api.v3.dataSources.persitence.ApiPersistence
@@ -9,7 +10,6 @@ import net.adoptium.api.v3.models.JvmImpl
 import net.adoptium.api.v3.models.Vendor
 import org.slf4j.LoggerFactory
 import java.time.ZonedDateTime
-import jakarta.inject.Inject
 
 @ApplicationScoped
 open class GitHubDownloadStatsCalculator @Inject constructor(private val database: ApiPersistence) {
@@ -32,7 +32,7 @@ open class GitHubDownloadStatsCalculator @Inject constructor(private val databas
         val stats = repos
             .repos
             .values
-            .map { featureRelease ->
+            .sumOf { featureRelease ->
                 val total = featureRelease
                     .releases
                     .getReleases()
@@ -48,7 +48,6 @@ open class GitHubDownloadStatsCalculator @Inject constructor(private val databas
                 LOGGER.info("Stats ${featureRelease.featureVersion} $total")
                 total
             }
-            .sum()
         LOGGER.info("Stats total $stats")
     }
 
@@ -67,21 +66,21 @@ open class GitHubDownloadStatsCalculator @Inject constructor(private val databas
                     }
 
                 // Tally up jvmImpl download stats
-                val jvmImplMap: Map = JvmImpl.values().map { jvmImpl ->
-                    jvmImpl to
-                            featureRelease
-                                .releases
-                                .getReleases()
-                                .filter { it.vendor == Vendor.getDefault() }
-                                .sumOf {
-                                    it.binaries
-                                        .filter { binary -> binary.jvm_impl == jvmImpl }
-                                        .sumOf { binary ->
-                                            binary.download_count.toInt()
-                                        }
-                                }
+                val jvmImplMap: Map = JvmImpl.entries
+                    .associateWith { jvmImpl ->
+                        featureRelease
+                            .releases
+                            .getReleases()
+                            .filter { it.vendor == Vendor.getDefault() }
+                            .sumOf {
+                                it.binaries
+                                    .filter { binary -> binary.jvm_impl == jvmImpl }
+                                    .sumOf { binary ->
+                                        binary.download_count.toInt()
+                                    }
+                            }
                             .toLong()
-                }.toMap()
+                    }
 
                 GitHubDownloadStatsDbEntry(
                     date,
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/APIDataStoreTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/APIDataStoreTest.kt
index 86b6ac46a..32859f170 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/APIDataStoreTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/APIDataStoreTest.kt
@@ -15,6 +15,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 import org.skyscreamer.jsonassert.JSONAssert
 import org.slf4j.LoggerFactory
+import java.util.*
 
 class APIDataStoreTest : MongoTest() {
 
@@ -27,7 +28,7 @@ class APIDataStoreTest : MongoTest() {
     fun reposHasElements() {
         runBlocking {
             val repo = BaseTest.adoptRepos
-            assert(repo.getFeatureRelease(8)!!.releases.getReleases().toList().size > 0)
+            assert(repo.getFeatureRelease(8)!!.releases.getReleases().toList().isNotEmpty())
         }
     }
 
@@ -63,20 +64,20 @@ class APIDataStoreTest : MongoTest() {
     @Test
     fun `updated at is set`(apiPersistence: ApiPersistence) {
         runBlocking {
-            apiPersistence.updateAllRepos(BaseTest.adoptRepos, "")
+            apiPersistence.updateAllRepos(BaseTest.adoptRepos, Base64.getEncoder().encodeToString("1234".toByteArray()))
             val time = TimeSource.now()
             delay(1000)
-            apiPersistence.updateAllRepos(BaseTest.adoptRepos, "a-checksum")
+            apiPersistence.updateAllRepos(BaseTest.adoptRepos, Base64.getEncoder().encodeToString("a-checksum".toByteArray()))
 
             val updatedTime = apiPersistence.getUpdatedAt()
 
             assertTrue(updatedTime.time.isAfter(time))
-            assertEquals("a-checksum", updatedTime.checksum)
+            assertEquals(Base64.getEncoder().encodeToString("a-checksum".toByteArray()), updatedTime.checksum)
         }
     }
 
     @Test
     fun `update is not scheduled by default`(apiDataStore: APIDataStoreImpl) {
-        assertNull(apiDataStore.schedule)
+        assertNull(apiDataStore.getSchedule())
     }
 }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptMetadataVersionParsingTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptMetadataVersionParsingTest.kt
index d225eee13..b890a3c2a 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptMetadataVersionParsingTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptMetadataVersionParsingTest.kt
@@ -23,7 +23,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
 class AdoptMetadataVersionParsingTest : BaseTest() {
 
     val client: UpdaterHtmlClient = object : UpdaterHtmlClient {
-        override suspend fun get(url: String): String? {
+        override suspend fun get(url: String): String {
             return """
                         {
                             "WARNING": "THIS METADATA FILE IS STILL IN ALPHA DO NOT USE ME",
@@ -49,10 +49,10 @@ class AdoptMetadataVersionParsingTest : BaseTest() {
             """.trimIndent()
         }
 
-        override suspend fun getFullResponse(request: UrlRequest): HttpResponse? {
+        override suspend fun getFullResponse(request: UrlRequest): HttpResponse {
             val metadataResponse = mockk()
             val entity = mockk()
-            every { entity.content } returns get(request.url)?.byteInputStream()
+            every { entity.content } returns get(request.url).byteInputStream()
             every { metadataResponse.statusLine } returns BasicStatusLine(ProtocolVersion("", 1, 1), 200, "")
             every { metadataResponse.entity } returns entity
             every { metadataResponse.getFirstHeader("Last-Modified") } returns BasicHeader("Last-Modified", "Thu, 01 Jan 1970 00:00:00 GMT")
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseMapperTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseMapperTest.kt
index 79e579bfd..1b88224ef 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseMapperTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseMapperTest.kt
@@ -197,11 +197,11 @@ class AdoptReleaseMapperTest : BaseTest() {
 
     private fun buildGhAssets(assetNames: List>) =
         GHAssets(assetNames
-            .map { it ->
+            .map {
                 GHAsset(
                     it.first,
                     1L,
-                    "${it.second}",
+                    it.second,
                     1L,
                     "2013-02-27T19:35:32Z"
                 )
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseNotesTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseNotesTest.kt
index 24f25e449..8699ad6ca 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseNotesTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReleaseNotesTest.kt
@@ -1,5 +1,6 @@
 package net.adoptium.api
 
+import net.adoptium.api.v3.ReleaseIncludeFilter
 import kotlinx.coroutines.runBlocking
 import net.adoptium.api.testDoubles.InMemoryApiPersistence
 import net.adoptium.api.v3.AdoptRepository
@@ -56,15 +57,15 @@ class AdoptReleaseNotesTest : BaseTest() {
                                 ]
                             """.trimIndent()
             } else {
-                return null;
+                return null
             }
         }
 
     }
 
     private fun addReleaseNotesFiles(adoptRepository: AdoptRepository) = object : AdoptRepository {
-        override suspend fun getRelease(version: Int): FeatureRelease? {
-            return adoptRepository.getRelease(version)
+        override suspend fun getRelease(version: Int, filter: ReleaseIncludeFilter): FeatureRelease? {
+            return adoptRepository.getRelease(version, filter)
         }
 
         override suspend fun getSummary(version: Int): GHRepositorySummary {
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposBuilderTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposBuilderTest.kt
index 52048444c..8e69ef132 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposBuilderTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposBuilderTest.kt
@@ -5,15 +5,16 @@ import io.mockk.coVerify
 import io.mockk.spyk
 import kotlinx.coroutines.runBlocking
 import net.adoptium.api.testDoubles.AdoptRepositoryStub
+import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub
 import net.adoptium.api.v3.AdoptReposBuilder
 import net.adoptium.api.v3.AdoptRepository
 import net.adoptium.api.v3.ReleaseResult
+import net.adoptium.api.v3.dataSources.VersionSupplier
 import net.adoptium.api.v3.dataSources.models.AdoptRepos
 import net.adoptium.api.v3.dataSources.models.GitHubId
 import net.adoptium.api.v3.models.GHReleaseMetadata
 import org.jboss.weld.junit5.auto.EnableAutoWeld
 import org.junit.jupiter.api.Assertions.assertTrue
-import org.junit.jupiter.api.Disabled
 import org.junit.jupiter.api.Test
 
 @EnableAutoWeld
@@ -21,7 +22,7 @@ class AdoptReposBuilderTest : BaseTest() {
 
     companion object {
         private val stub = AdoptRepositoryStub()
-        private val adoptReposBuilder: AdoptReposBuilder = AdoptReposBuilder(stub)
+        private val adoptReposBuilder: AdoptReposBuilder = AdoptReposBuilder(stub, UpdatableVersionSupplierStub())
         private var before: AdoptRepos = stub.repo
         private var updated: AdoptRepos = runBlocking {
             adoptReposBuilder.incrementalUpdate(emptySet(), before) { null }
@@ -29,11 +30,11 @@ class AdoptReposBuilderTest : BaseTest() {
     }
 
     @Test
-    @Disabled("FIX ME, pending spyk fix from mockk")
-    fun addReleaseIsUpdatedExplicitly(adoptRepository: AdoptRepository) {
+    fun addReleaseIsUpdatedExplicitly() {
         runBlocking {
+            val adoptRepository = AdoptRepositoryStub()
             val adoptRepo = spyk(adoptRepository)
-            val adoptReposBuilder = AdoptReposBuilder(adoptRepo)
+            val adoptReposBuilder = AdoptReposBuilder(adoptRepo, UpdatableVersionSupplierStub())
 
             adoptReposBuilder.incrementalUpdate(setOf(before.repos[8]?.releases?.nodeList?.first()?.release_name!!), before) { null }
 
@@ -83,12 +84,9 @@ class AdoptReposBuilderTest : BaseTest() {
     fun updatedReleaseIsNotUpdatedWhenThingsDontChange() {
         runBlocking {
 
-            val updated2 = runBlocking {
-                adoptReposBuilder.incrementalUpdate(emptySet(), before) { null }
-            }
-            val updated3 = runBlocking {
-                adoptReposBuilder.incrementalUpdate(emptySet(), before) { null }
-            }
+            val updated2 = adoptReposBuilder.incrementalUpdate(emptySet(), before) { null }
+
+            val updated3 = adoptReposBuilder.incrementalUpdate(emptySet(), before) { null }
 
             assertTrue { updated == updated2 }
             assertTrue { updated2 == updated3 }
@@ -96,11 +94,12 @@ class AdoptReposBuilderTest : BaseTest() {
     }
 
     @Test
-    @Disabled("FIX ME, pending spyk fix from mockk")
-    fun `young releases continue to be pulled`(repo: AdoptRepos, adoptRepository: AdoptRepository) {
+    fun `young releases continue to be pulled`() {
         runBlocking {
+            val repo = stub.repo
+            val adoptRepository = AdoptRepositoryStub()
             val adoptRepo = spyk(adoptRepository)
-            val adoptReposBuilder = AdoptReposBuilder(adoptRepo)
+            val adoptReposBuilder = AdoptReposBuilder(adoptRepo, UpdatableVersionSupplierStub())
 
             coEvery { adoptRepo.getReleaseById(GitHubId(AdoptRepositoryStub.toAddSemiYoungRelease.id)) } returns ReleaseResult(listOf(AdoptRepositoryStub.toAddSemiYoungRelease))
 
@@ -112,11 +111,11 @@ class AdoptReposBuilderTest : BaseTest() {
     }
 
     @Test
-    @Disabled("FIX ME, pending spyk fix from mockk")
-    fun `release is updated when binary count changes`(adoptRepository: AdoptRepository) {
+    fun `release is updated when binary count changes`() {
         runBlocking {
+            val adoptRepository = AdoptRepositoryStub()
             val adoptRepo = spyk(adoptRepository)
-            val adoptReposBuilder = AdoptReposBuilder(adoptRepo)
+            val adoptReposBuilder = AdoptReposBuilder(adoptRepo, UpdatableVersionSupplierStub())
 
             adoptReposBuilder.incrementalUpdate(emptySet(), before) {
                 if (before.repos[11]?.releases?.nodeList?.first()?.id == it.id) {
@@ -137,10 +136,10 @@ class AdoptReposBuilderTest : BaseTest() {
     }
 
     @Test
-    fun `release is not updated when binary count does not change`(adoptRepository: AdoptRepository) {
+    fun `release is not updated when binary count does not change`(adoptRepository: AdoptRepository, versionSupplier: VersionSupplier) {
         runBlocking {
             val adoptRepo = spyk(adoptRepository)
-            val adoptReposBuilder = AdoptReposBuilder(adoptRepo)
+            val adoptReposBuilder = AdoptReposBuilder(adoptRepo, versionSupplier)
 
             adoptReposBuilder.incrementalUpdate(emptySet(), before) {
                 GHReleaseMetadata(
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposTestDataGenerator.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposTestDataGenerator.kt
index 2144e8b24..831553cfc 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposTestDataGenerator.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/AdoptReposTestDataGenerator.kt
@@ -28,7 +28,7 @@ object AdoptReposTestDataGenerator {
     private val TEST_VERSIONS = listOf(8, 10, 11, 12)
     private val TEST_RESOURCES = listOf(
         PermittedValues(
-            ReleaseType.values().asList(),
+            ReleaseType.entries,
             listOf(Vendor.adoptopenjdk),
             listOf(Project.jdk),
             listOf(JvmImpl.hotspot),
@@ -38,17 +38,17 @@ object AdoptReposTestDataGenerator {
             listOf(HeapSize.normal),
         ),
         PermittedValues(
-            ReleaseType.values().asList(),
+            ReleaseType.entries,
             listOf(Vendor.adoptopenjdk),
             listOf(Project.jdk),
             listOf(JvmImpl.openj9),
             listOf(ImageType.jre, ImageType.jdk),
             listOf(Architecture.x64, Architecture.x32, Architecture.arm),
             listOf(OperatingSystem.linux, OperatingSystem.mac, OperatingSystem.windows),
-            HeapSize.values().asList()
+            HeapSize.entries
         ),
         PermittedValues(
-            ReleaseType.values().asList(),
+            ReleaseType.entries,
             listOf(Vendor.openjdk),
             listOf(Project.jdk),
             listOf(JvmImpl.hotspot),
@@ -59,7 +59,7 @@ object AdoptReposTestDataGenerator {
             listOf(8, 11)
         ),
         PermittedValues(
-            ReleaseType.values().asList(),
+            ReleaseType.entries,
             listOf(Vendor.alibaba),
             listOf(Project.jdk),
             listOf(JvmImpl.dragonwell),
@@ -70,7 +70,7 @@ object AdoptReposTestDataGenerator {
             listOf(8, 11)
         ),
         PermittedValues(
-            ReleaseType.values().asList(),
+            ReleaseType.entries,
             listOf(Vendor.eclipse),
             listOf(Project.jdk),
             listOf(JvmImpl.hotspot),
@@ -81,7 +81,7 @@ object AdoptReposTestDataGenerator {
             listOf(8, 11, 12)
         ),
         PermittedValues(
-            ReleaseType.values().asList(),
+            ReleaseType.entries,
             listOf(Vendor.eclipse),
             listOf(Project.jdk),
             listOf(JvmImpl.hotspot),
@@ -232,7 +232,7 @@ object AdoptReposTestDataGenerator {
         }
 
         private fun exhaustiveBinaryList(): List {
-            return HeapSize.values()
+            return HeapSize.entries
                 .map {
                     Binary(
                         createPackage(),
@@ -250,7 +250,7 @@ object AdoptReposTestDataGenerator {
                     )
                 }
                 .union(
-                    OperatingSystem.values()
+                    OperatingSystem.entries
                         .map {
                             Binary(
                                 createPackage(),
@@ -269,7 +269,7 @@ object AdoptReposTestDataGenerator {
                         }
                 )
                 .union(
-                    Architecture.values()
+                    Architecture.entries
                         .map {
                             Binary(
                                 createPackage(),
@@ -288,7 +288,7 @@ object AdoptReposTestDataGenerator {
                         }
                 )
                 .union(
-                    ImageType.values()
+                    ImageType.entries
                         .map {
                             Binary(
                                 createPackage(),
@@ -307,7 +307,7 @@ object AdoptReposTestDataGenerator {
                         }
                 )
                 .union(
-                    JvmImpl.values()
+                    JvmImpl.entries
                         .map {
                             Binary(
                                 createPackage(),
@@ -326,7 +326,7 @@ object AdoptReposTestDataGenerator {
                         }
                 )
                 .union(
-                    Project.values()
+                    Project.entries
                         .map {
                             Binary(
                                 createPackage(),
@@ -383,6 +383,7 @@ object AdoptReposTestDataGenerator {
                 return emptyList()
             }
             return releaseType
+                .asSequence()
                 .map { releaseBuilder()(it) }
                 .flatMap { builder -> vendor.map { builder(it) } }
                 .flatMap { builder -> getVersions(majorVersion).map { builder(it) } }
@@ -390,6 +391,7 @@ object AdoptReposTestDataGenerator {
                 .filter {
                     Vendor.validVendor(it.vendor)
                 }
+                .toList()
         }
 
         private fun getBinaries(): Array {
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/BaseTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/BaseTest.kt
index 76572aa1f..d9a556d96 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/BaseTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/BaseTest.kt
@@ -5,6 +5,7 @@ import io.mockk.junit5.MockKExtension
 import io.mockk.mockk
 import net.adoptium.api.testDoubles.InMemoryApiPersistence
 import net.adoptium.api.testDoubles.InMemoryInternalDbStore
+import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub
 import net.adoptium.api.v3.dataSources.APIDataStoreImpl
 import net.adoptium.api.v3.dataSources.UpdaterHtmlClient
 import net.adoptium.api.v3.dataSources.UrlRequest
@@ -26,7 +27,7 @@ import org.junit.jupiter.api.extension.ExtendWith
 
 @EnableAutoWeld
 @ExtendWith(MockKExtension::class)
-@AddPackages(value = [InMemoryApiPersistence::class, InMemoryInternalDbStore::class, APIDataStoreImpl::class])
+@AddPackages(value = [InMemoryApiPersistence::class, InMemoryInternalDbStore::class, APIDataStoreImpl::class, UpdatableVersionSupplierStub::class])
 @EnableAlternatives
 abstract class BaseTest {
 
@@ -51,7 +52,7 @@ abstract class BaseTest {
                 return null
             }
 
-            override suspend fun getFullResponse(request: UrlRequest): HttpResponse? {
+            override suspend fun getFullResponse(request: UrlRequest): HttpResponse {
                 val metadataResponse = mockk()
 
                 val entity = mockk()
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/CachedGitHubHtmlClientTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/CachedGitHubHtmlClientTest.kt
index 23cda7411..c8718de7c 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/CachedGitHubHtmlClientTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/CachedGitHubHtmlClientTest.kt
@@ -1,6 +1,5 @@
 package net.adoptium.api
 
-import io.mockk.Called
 import io.mockk.coEvery
 import io.mockk.coVerify
 import io.mockk.confirmVerified
@@ -15,14 +14,14 @@ import net.adoptium.api.v3.dataSources.UpdaterHtmlClient
 import net.adoptium.api.v3.dataSources.UrlRequest
 import net.adoptium.api.v3.dataSources.github.CachedGitHubHtmlClient
 import net.adoptium.api.v3.dataSources.mongo.CacheDbEntry
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNull
 import org.junit.jupiter.api.DynamicTest
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestFactory
 import org.junit.jupiter.api.extension.ExtendWith
 import java.time.ZonedDateTime
 import java.util.stream.Stream
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Assertions.assertNull
 
 @ExtendWith(MockKExtension::class)
 class CachedGitHubHtmlClientTest {
@@ -115,9 +114,11 @@ class CachedGitHubHtmlClientTest {
                 while (client.getQueueLength() > 0) {
                     delay(1000)
                 }
-                coVerify(timeout = 3000) {
-                    updaterHtmlClient.getFullResponse(request)?.wasNot(Called)
+
+                coVerify(timeout = 3000, exactly = 0) {
+                    updaterHtmlClient.getFullResponse(request)
                 }
+
                 confirmVerified(updaterHtmlClient)
             }
         }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DateTimeMigrationTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DateTimeMigrationTest.kt
index 8064d0178..567d0598f 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DateTimeMigrationTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DateTimeMigrationTest.kt
@@ -1,23 +1,29 @@
 package net.adoptium.api
 
+import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.runBlocking
 import net.adoptium.api.v3.JsonMapper
 import net.adoptium.api.v3.TimeSource
 import net.adoptium.api.v3.dataSources.persitence.mongo.MongoClient
 import net.adoptium.api.v3.models.DateTime
+import net.adoptium.api.v3.models.GitHubDownloadStatsDbEntry
+import org.bson.Document
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Test
 import java.time.Instant
 import java.time.ZoneOffset
 import java.time.ZonedDateTime
-import java.util.UUID
+import java.util.*
 import java.util.concurrent.TimeUnit
 
-class DateTimeMigrationTest : MongoTest() {
+data class HasZonedDateTime(
+    val zdt: ZonedDateTime)
+
+data class HasDateTime(
+    val zdt: DateTime)
 
-    class HasZonedDateTime(val zdt: ZonedDateTime)
+class DateTimeMigrationTest : MongoTest() {
 
-    class HasDateTime(val zdt: DateTime)
 
     @Test
     fun `can serialize as zdt and deserialize as DateTime`() {
@@ -39,19 +45,41 @@ class DateTimeMigrationTest : MongoTest() {
                     .minus(1, TimeUnit.MILLISECONDS.toChronoUnit())
 
                 val hzdt = HasZonedDateTime(date)
-                val client1 = mongoClient.database.getCollection(collectionName)
+                val client1 = mongoClient.getDatabase().getCollection(collectionName)
                 client1.insertOne(hzdt)
 
-                val hzdt2 = client1.findOne()
+                val hzdt2 = client1.find().firstOrNull()
                 assertEquals(hzdt.zdt, hzdt2?.zdt)
 
-                val client2 = mongoClient.database.getCollection(collectionName)
-                val fromDb = client2.findOne("{}")
-                assertEquals(hzdt.zdt, fromDb?.zdt?.dateTime)
+                val client2 = mongoClient.getDatabase().getCollection(collectionName)
+                val fromDb = client2.find().firstOrNull()
+                assertEquals(hzdt.zdt, fromDb?.zdt)
             } finally {
-                mongoClient.database.dropCollection(collectionName)
+                mongoClient.getDatabase().getCollection(collectionName).drop()
+            }
+        }
+    }
+
+    @Test
+    fun `github stats`(mongoClient: MongoClient) {
+        runBlocking {
+            val collectionName = UUID.randomUUID().toString()
+            val client1 = mongoClient.getDatabase().getCollection(collectionName)
+
+            client1.insertOne(
+                GitHubDownloadStatsDbEntry(
+                    TimeSource.now(),
+                    1,
+                    mapOf(),
+                    1
+                )
+            )
+
+            client1.find().firstOrNull()?.let {
+                assertEquals(1, it.downloads)
             }
         }
+
     }
 
     @Test
@@ -67,22 +95,22 @@ class DateTimeMigrationTest : MongoTest() {
         runBlocking {
             try {
                 val hzdt = HasZonedDateTime(TimeSource.now())
-                val client1 = mongoClient.database.getCollection(collectionName)
+                val client1 = mongoClient.getDatabase().getCollection(collectionName)
                 client1.insertOne(hzdt)
 
-                val hzdt2 = client1.findOne()
+                val hzdt2 = client1.find().firstOrNull()
                 assertEquals(hzdt.zdt, hzdt2?.zdt)
 
-                val client2 = mongoClient.database.getCollection(collectionName)
-                val fromDb = client2.findOne("{}")
-                assertEquals(hzdt.zdt, fromDb?.zdt?.dateTime)
+                val client2 = mongoClient.getDatabase().getCollection(collectionName)
+                val fromDb = client2.find(Document.parse("{}")).firstOrNull()
+                assertEquals(hzdt.zdt, fromDb?.zdt)
 
-                client2.deleteMany()
+                client2.deleteMany(Document.parse("{}"))
                 client2.insertOne(fromDb!!)
-                val fromDb2 = client2.findOne("{}")
-                assertEquals(fromDb.zdt.dateTime, fromDb2?.zdt?.dateTime)
+                val fromDb2 = client2.find(Document.parse("{}")).firstOrNull()
+                assertEquals(fromDb.zdt, fromDb2?.zdt)
             } finally {
-                mongoClient.database.dropCollection(collectionName)
+                mongoClient.getDatabase().getCollection(collectionName).drop()
             }
         }
     }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DockerStatsInterfaceTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DockerStatsInterfaceTest.kt
index 077f921ed..04cd682e7 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DockerStatsInterfaceTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/DockerStatsInterfaceTest.kt
@@ -4,6 +4,7 @@ import io.mockk.coEvery
 import io.mockk.mockk
 import kotlinx.coroutines.runBlocking
 import net.adoptium.api.testDoubles.InMemoryApiPersistence
+import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub
 import net.adoptium.api.v3.DownloadStatsInterface
 import net.adoptium.api.v3.TimeSource
 import net.adoptium.api.v3.dataSources.DefaultUpdaterHtmlClient
@@ -33,7 +34,7 @@ class DockerStatsInterfaceTest : BaseTest() {
             dockerStatsInterface.updateDb()
 
             val stats = apiPersistence.getLatestAllDockerStats()
-            Assertions.assertTrue(stats.size > 0)
+            Assertions.assertTrue(stats.isNotEmpty())
         }
     }
 
@@ -61,7 +62,7 @@ class DockerStatsInterfaceTest : BaseTest() {
                 DockerDownloadStatsDbEntry(baseTime, 100, "a-stats-repo", 8, JvmImpl.hotspot)
             )
 
-            val downloadStatsInterface = DownloadStatsInterface(apiPersistanceMock)
+            val downloadStatsInterface = DownloadStatsInterface(apiPersistanceMock, UpdatableVersionSupplierStub())
 
             var stats = downloadStatsInterface.getTrackingStats(10, source = StatsSource.github, featureVersion = 8)
             assertEquals(70, stats[0].total)
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/GraphQLGitHubReleaseClientTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/GraphQLGitHubReleaseClientTest.kt
index 40627f4e6..59ff296bb 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/GraphQLGitHubReleaseClientTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/GraphQLGitHubReleaseClientTest.kt
@@ -1,15 +1,24 @@
 package net.adoptium.api
 
+import net.adoptium.api.v3.ReleaseFilterType
+import net.adoptium.api.v3.ReleaseIncludeFilter
 import com.expediagroup.graphql.client.types.GraphQLClientRequest
 import com.expediagroup.graphql.client.types.GraphQLClientResponse
+import io.mockk.coEvery
 import io.mockk.every
 import io.mockk.mockk
 import kotlinx.coroutines.runBlocking
 import net.adoptium.api.v3.AdoptRepositoryImpl
+import net.adoptium.api.v3.ReleaseResult
+import net.adoptium.api.v3.TimeSource
+import net.adoptium.api.v3.config.Ecosystem
+import net.adoptium.api.v3.dataSources.github.GitHubHtmlClient
+import net.adoptium.api.v3.dataSources.github.graphql.GraphQLGitHubClient
 import net.adoptium.api.v3.dataSources.github.graphql.clients.GraphQLGitHubInterface
 import net.adoptium.api.v3.dataSources.github.graphql.clients.GraphQLGitHubReleaseClient
 import net.adoptium.api.v3.dataSources.github.graphql.clients.GraphQLGitHubReleaseRequest
 import net.adoptium.api.v3.dataSources.github.graphql.clients.GraphQLGitHubRepositoryClient
+import net.adoptium.api.v3.dataSources.github.graphql.clients.GraphQLGitHubRepositoryClient.GetQueryData
 import net.adoptium.api.v3.dataSources.github.graphql.clients.GraphQLGitHubSummaryClient
 import net.adoptium.api.v3.dataSources.github.graphql.clients.GraphQLRequest
 import net.adoptium.api.v3.dataSources.github.graphql.models.GHAsset
@@ -27,8 +36,20 @@ import net.adoptium.api.v3.dataSources.github.graphql.models.summary.GHReleaseSu
 import net.adoptium.api.v3.dataSources.github.graphql.models.summary.GHReleasesSummary
 import net.adoptium.api.v3.dataSources.github.graphql.models.summary.GHRepositorySummary
 import net.adoptium.api.v3.dataSources.models.GitHubId
+import net.adoptium.api.v3.mapping.ReleaseMapper
+import net.adoptium.api.v3.mapping.ReleaseMapper.Companion.parseDate
+import net.adoptium.api.v3.mapping.adopt.AdoptReleaseMapperFactory
+import net.adoptium.api.v3.models.DateTime
+import net.adoptium.api.v3.models.Release
+import net.adoptium.api.v3.models.ReleaseType
+import net.adoptium.api.v3.models.Vendor
+import net.adoptium.api.v3.models.VersionData
 import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.DynamicTest
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestFactory
+import java.time.ZonedDateTime
+import java.util.stream.Stream
 
 class GraphQLGitHubReleaseClientTest : BaseTest() {
     companion object {
@@ -103,14 +124,14 @@ class GraphQLGitHubReleaseClientTest : BaseTest() {
                     every { builder.errors } returns null
                     return builder
                 }
-            };
+            }
 
             val graphQLGitHubInterface = GraphQLGitHubInterface(graphQLRequest, mockkHttpClient())
             val graphQLGitHubReleaseRequest = GraphQLGitHubReleaseRequest(graphQLGitHubInterface)
 
             val client = GraphQLGitHubRepositoryClient(graphQLGitHubInterface, graphQLGitHubReleaseRequest)
 
-            val repo = client.getRepository(AdoptRepositoryImpl.ADOPT_ORG, "a-repo-name")
+            val repo = client.getRepository(AdoptRepositoryImpl.ADOPT_ORG, "a-repo-name") { _, _ -> true }
 
             assertEquals(Companion.repo, repo)
         }
@@ -182,15 +203,147 @@ class GraphQLGitHubReleaseClientTest : BaseTest() {
                 }
             }
 
-
             val graphQLGitHubInterface = GraphQLGitHubInterface(graphQLRequest, mockkHttpClient())
             val graphQLGitHubReleaseRequest = GraphQLGitHubReleaseRequest(graphQLGitHubInterface)
 
             val client = GraphQLGitHubRepositoryClient(graphQLGitHubInterface, graphQLGitHubReleaseRequest)
 
-            val repo = client.getRepository(AdoptRepositoryImpl.ADOPT_ORG, "a-repo-name")
+            val repo = client.getRepository(AdoptRepositoryImpl.ADOPT_ORG, "a-repo-name") { _, _ -> true }
 
             assertEquals(2, repo.releases.releases.size)
         }
     }
+
+    @TestFactory
+    fun filtersReleases(): Stream {
+        val releaseDate = parseDate("2013-02-27T19:35:32Z")
+        return listOf(
+            Triple(
+                "prereleases more than 90 days old are ignored",
+                ReleaseIncludeFilter(
+                    releaseDate.plusDays(91),
+                    ReleaseFilterType.ALL,
+                    excludedVendors = setOf()
+                ),
+                0
+            ),
+            Triple(
+                "prereleases less than 90 days old are not ignored",
+                ReleaseIncludeFilter(
+                    releaseDate.plusDays(89),
+                    ReleaseFilterType.ALL,
+                    excludedVendors = setOf()
+                ),
+                1
+            ),
+            Triple(
+                "release type filter is applied",
+                ReleaseIncludeFilter(
+                    releaseDate.plusDays(89),
+                    ReleaseFilterType.RELEASES_ONLY,
+                    excludedVendors = setOf()
+                ),
+                0
+            ),
+            Triple(
+                "date filter is applied to non-excluded vendor",
+                ReleaseIncludeFilter(
+                    releaseDate.plusDays(91),
+                    ReleaseFilterType.RELEASES_ONLY,
+                    excludedVendors = setOf()
+                ),
+                0
+            ),
+            Triple(
+                "Excluded vendor more than 90 days is ignored",
+                ReleaseIncludeFilter(
+                    releaseDate.plusDays(91),
+                    ReleaseFilterType.ALL,
+                    excludedVendors = setOf(Vendor.getDefault())
+                ),
+                0
+            ),
+            Triple(
+                "Excluded vendor less than 90 days is ignored",
+                ReleaseIncludeFilter(
+                    releaseDate.plusDays(89),
+                    ReleaseFilterType.ALL,
+                    excludedVendors = setOf(Vendor.getDefault())
+                ),
+                0
+            ),
+        )
+            .map {
+                return@map DynamicTest.dynamicTest(it.first) {
+                    runBlocking {
+                        val repository = setupFilterTest()
+                        val repo = repository.getRelease(8, it.second)
+                        assertEquals(it.third, repo.releases.getReleases().count())
+                    }
+                }
+            }
+            .stream()
+    }
+
+    private fun setupFilterTest(): AdoptRepositoryImpl {
+        val graphQLRequest = object : GraphQLRequest {
+            override suspend fun  request(query: GraphQLClientRequest): GraphQLClientResponse {
+                val builder = mockk>()
+
+                val match = if (Ecosystem.CURRENT == Ecosystem.adoptopenjdk) {
+                    (query as GetQueryData).owner.lowercase() == "adoptopenjdk" &&
+                        (query as GetQueryData).repoName == "openjdk8-binaries"
+                } else {
+                    (query as GetQueryData).owner.lowercase() == "adoptium" &&
+                        (query as GetQueryData).repoName == "temurin8-binaries"
+                }
+
+                if (match) {
+                    every { builder.data } returns QueryData(repo, RateLimit(0, 5000)) as F
+                } else {
+                    every { builder.data } returns QueryData(GHRepository(GHReleases(listOf(), PageInfo(false, null))), RateLimit(0, 5000)) as F
+                }
+
+                every { builder.errors } returns null
+                return builder
+            }
+        }
+
+        val graphQLGitHubInterface = GraphQLGitHubInterface(graphQLRequest, mockkHttpClient())
+        val graphQLGitHubReleaseRequest = GraphQLGitHubReleaseRequest(graphQLGitHubInterface)
+
+        val client = GraphQLGitHubRepositoryClient(graphQLGitHubInterface, graphQLGitHubReleaseRequest)
+
+
+        val htmlClient = mockk()
+        coEvery { htmlClient.getUrl(any()) }.returns(null)
+
+        val adoptReleaseMapperFactory = mockk()
+        every { adoptReleaseMapperFactory.get(any()) }.returns(
+            object : ReleaseMapper() {
+                override suspend fun toAdoptRelease(ghRelease: GHRelease): ReleaseResult {
+                    return ReleaseResult(
+                        listOf(
+                            Release(
+                                "foo", ReleaseType.ga, "a", "foo",
+                                DateTime(ZonedDateTime.of(2010, 1, 1, 1, 1, 0, 0, TimeSource.ZONE)),
+                                DateTime(ZonedDateTime.of(2010, 1, 1, 1, 1, 0, 0, TimeSource.ZONE)),
+                                arrayOf(), 2, Vendor.getDefault(),
+                                VersionData(8, 0, 242, "b", null, 4, "b", "")
+                            ),
+                        )
+                    )
+                }
+            })
+
+        val repository = AdoptRepositoryImpl(
+            GraphQLGitHubClient(
+                mockk(),
+                mockk(),
+                client
+            ),
+            adoptReleaseMapperFactory
+        )
+        return repository
+    }
 }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/InternalDbStoreTests.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/InternalDbStoreTests.kt
index a51196edc..639431528 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/InternalDbStoreTests.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/InternalDbStoreTests.kt
@@ -5,16 +5,15 @@ import net.adoptium.api.v3.TimeSource
 import net.adoptium.api.v3.dataSources.mongo.CacheDbEntry
 import net.adoptium.api.v3.dataSources.mongo.InternalDbStoreImpl
 import net.adoptium.api.v3.dataSources.persitence.mongo.MongoClient
-import org.junit.jupiter.api.Assertions
-import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
 
 class InternalDbStoreTests : MongoTest() {
 
     @Test
-    fun `checked time is set`(mongoClient: MongoClient) {
+    fun `checked time is set`() {
         runBlocking {
-            val internalDbStore = InternalDbStoreImpl(mongoClient)
+            val internalDbStore = InternalDbStoreImpl(MongoClient())
 
             val now = TimeSource.now()
             val data = CacheDbEntry("foo", "bar", now, "some data")
@@ -30,7 +29,7 @@ class InternalDbStoreTests : MongoTest() {
             internalDbStore.updateCheckedTime("foo", newTime)
             val updated = internalDbStore.getCachedWebpage("foo")
 
-            Assertions.assertEquals(CacheDbEntry(data.url, data.lastModified, newTime, data.data), updated)
+            assertEquals(CacheDbEntry(data.url, data.lastModified, newTime, data.data), updated)
         }
     }
 }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MetadataSerializationTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MetadataSerializationTest.kt
index d7e5aa52b..f56b4f692 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MetadataSerializationTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MetadataSerializationTest.kt
@@ -55,7 +55,7 @@ class MetadataSerializationTest {
         val metadata = generateMetadata()
 
         val serialized = UpdaterJsonMapper.mapper.writeValueAsString(metadata)
-        var json = UpdaterJsonMapper.mapper.readValue(serialized, ObjectNode::class.java)
+        val json = UpdaterJsonMapper.mapper.readValue(serialized, ObjectNode::class.java)
         json.remove("WARNING")
 
         val noWarning = GHMetaData(null, metadata.os, metadata.arch, metadata.variant, metadata.version, metadata.scmRef, metadata.version_data, metadata.binary_type, metadata.sha256)
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoAPIPersistenceTests.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoAPIPersistenceTests.kt
index 3cd0ee542..fc8082866 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoAPIPersistenceTests.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoAPIPersistenceTests.kt
@@ -4,16 +4,15 @@ import kotlinx.coroutines.runBlocking
 import net.adoptium.api.v3.TimeSource
 import net.adoptium.api.v3.dataSources.models.GitHubId
 import net.adoptium.api.v3.dataSources.persitence.mongo.MongoApiPersistence
+import net.adoptium.api.v3.dataSources.persitence.mongo.MongoClient
 import net.adoptium.api.v3.models.GHReleaseMetadata
-import org.junit.jupiter.api.Assertions
-import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
 
 class MongoAPIPersistenceTests : MongoTest() {
     @Test
-    fun `update time is set`(apiPersistence: MongoApiPersistence) {
+    fun `update time is set`(api: MongoApiPersistence) {
         runBlocking {
-            val api = apiPersistence
             api.updateUpdatedTime(TimeSource.now(), "", 0)
             api.updateUpdatedTime(TimeSource.now(), "", 0)
             api.updateUpdatedTime(TimeSource.now(), "", 0)
@@ -22,19 +21,21 @@ class MongoAPIPersistenceTests : MongoTest() {
 
             val stored = api.getUpdatedAt()
 
-            Assertions.assertEquals(time, stored.time)
+            assertEquals(time, stored.time)
         }
     }
 
     @Test
-    fun `metadata is persisted`(apiPersistence: MongoApiPersistence) {
+    fun `metadata is persisted`() {
         runBlocking {
+            val apiPersistence = MongoApiPersistence(MongoClient())
 
             val metadata = GHReleaseMetadata(10, GitHubId("foo"))
             apiPersistence.setGhReleaseMetadata(metadata)
 
             val saved = apiPersistence.getGhReleaseMetadata(GitHubId("foo"))
 
+
             assertEquals(metadata, saved)
         }
     }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoTest.kt
index 6c1f57900..794c536b8 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/MongoTest.kt
@@ -1,11 +1,12 @@
 package net.adoptium.api
 
+import de.flapdoodle.embed.mongo.config.ImmutableNet
 import de.flapdoodle.embed.mongo.config.Net
 import de.flapdoodle.embed.mongo.distribution.Version
 import de.flapdoodle.embed.mongo.transitions.Mongod
 import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess
-import de.flapdoodle.embed.process.runtime.Network
 import de.flapdoodle.reverse.transitions.Start
+import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub
 import net.adoptium.api.v3.dataSources.APIDataStoreImpl
 import org.jboss.weld.junit5.auto.AddPackages
 import org.jboss.weld.junit5.auto.EnableAutoWeld
@@ -14,7 +15,7 @@ import org.junit.jupiter.api.BeforeAll
 import org.slf4j.LoggerFactory
 
 @EnableAutoWeld
-@AddPackages(value = [APIDataStoreImpl::class])
+@AddPackages(value = [APIDataStoreImpl::class, UpdatableVersionSupplierStub::class])
 abstract class MongoTest {
 
     companion object {
@@ -33,12 +34,13 @@ abstract class MongoTest {
         @JvmStatic
         fun startFongo() {
             val bindIp = "localhost"
-            val net = Net.of("localhost",
-                Network.freeServerPort(Network.getLocalHost()),
-                Network.localhostIsIPv6()
-            )
 
-            val mongodbTestConnectionString = "mongodb://$bindIp:${net.port}"
+            val net = ImmutableNet.defaults()
+
+            val port = net.port
+
+            val mongodbTestConnectionString = "mongodb://$bindIp:$port"
+
             LOGGER.info("Mongo test connection string - $mongodbTestConnectionString")
             System.setProperty("MONGODB_TEST_CONNECTION_STRING", mongodbTestConnectionString)
 
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/ReleaseVersionResolverTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/ReleaseVersionResolverTest.kt
index b7f62f1f2..ba9ddf803 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/ReleaseVersionResolverTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/ReleaseVersionResolverTest.kt
@@ -1,37 +1,16 @@
 package net.adoptium.api
 
 import kotlinx.coroutines.runBlocking
+import net.adoptium.api.testDoubles.UpdatableVersionSupplierStub
 import net.adoptium.api.v3.dataSources.ReleaseVersionResolver
-import net.adoptium.api.v3.dataSources.UpdaterHtmlClient
-import net.adoptium.api.v3.dataSources.UrlRequest
 import net.adoptium.api.v3.models.ReleaseInfo
-import org.apache.http.HttpResponse
 import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 
 class ReleaseVersionResolverTest : BaseTest() {
 
     private fun getReleaseVersionResolver(): ReleaseVersionResolver {
-        return ReleaseVersionResolver(
-
-            object : UpdaterHtmlClient {
-                override suspend fun get(url: String): String? {
-                    return getTipMetadata()
-                }
-
-                fun getTipMetadata(): String {
-                    return """
-                        DEFAULT_VERSION_FEATURE=15
-                        DEFAULT_VERSION_INTERIM=0
-                    """.trimIndent()
-                }
-
-                override suspend fun getFullResponse(request: UrlRequest): HttpResponse? {
-                    return null
-                }
-            }
-
-        )
+        return ReleaseVersionResolver(UpdatableVersionSupplierStub())
     }
 
     @Test
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/UpdateRunner.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/UpdateRunner.kt
index d75cb90b6..7f5c56121 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/UpdateRunner.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/UpdateRunner.kt
@@ -3,6 +3,7 @@ package net.adoptium.api
 import kotlinx.coroutines.runBlocking
 import net.adoptium.api.testDoubles.InMemoryInternalDbStore
 import net.adoptium.api.v3.AdoptRepositoryImpl
+import net.adoptium.api.v3.ReleaseIncludeFilter
 import net.adoptium.api.v3.V3Updater
 import net.adoptium.api.v3.dataSources.DefaultUpdaterHtmlClient
 import net.adoptium.api.v3.dataSources.HttpClientFactory
@@ -35,7 +36,7 @@ class UpdateRunner {
     fun run(updater: V3Updater) {
         System.clearProperty("GITHUB_TOKEN")
         updater.run(false)
-        Awaitility.await().atMost(Long.MAX_VALUE, TimeUnit.NANOSECONDS).until({ 4 == 5 })
+        Awaitility.await().atMost(Long.MAX_VALUE, TimeUnit.NANOSECONDS).until { 4 == 5 }
     }
 
     @Test
@@ -76,7 +77,7 @@ class UpdateRunner {
             )
 
             val repo = AdoptRepositoryImpl(client, AdoptReleaseMapperFactory(AdoptBinaryMapper(gitHubHtmlClient), gitHubHtmlClient))
-            repo.getRelease(8)
+            repo.getRelease(8, ReleaseIncludeFilter.INCLUDE_ALL)
         }
     }
 }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/V3UpdaterTest.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/V3UpdaterTest.kt
index b4e0a8035..75997d588 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/V3UpdaterTest.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/V3UpdaterTest.kt
@@ -1,8 +1,12 @@
 package net.adoptium.api
 
+import net.adoptium.api.v3.ReleaseIncludeFilter
 import kotlinx.coroutines.runBlocking
+import net.adoptium.api.v3.ReleaseFilterType
+import net.adoptium.api.v3.TimeSource
 import net.adoptium.api.v3.V3Updater
 import net.adoptium.api.v3.dataSources.models.AdoptRepos
+import net.adoptium.api.v3.models.ReleaseType
 import net.adoptium.api.v3.models.Vendor
 import org.junit.jupiter.api.Assertions
 import org.junit.jupiter.api.Assertions.assertTrue
@@ -30,23 +34,30 @@ class V3UpdaterTest {
     fun `adoptOpenJdk releases are copied over`() {
 
         runBlocking {
-            val repo = AdoptReposTestDataGenerator.generate();
+            val repo = AdoptReposTestDataGenerator.generate()
+
+            val filter = ReleaseIncludeFilter(
+                TimeSource.now(),
+                ReleaseFilterType.ALL,
+                false,
+                setOf(Vendor.adoptopenjdk)
+            )
 
             val adoptopenjdk = AdoptRepos(emptyList()).addAll(repo
                 .allReleases
                 .getReleases()
-                .filter { it.vendor == Vendor.adoptopenjdk }
+                .filter { filter.filter(it.vendor, it.updated_at.dateTime, it.release_type == ReleaseType.ea) }
                 .toList()
             )
 
             val notAdoptopenjdk = AdoptRepos(emptyList()).addAll(repo
                 .allReleases
                 .getReleases()
-                .filter { it.vendor != Vendor.adoptopenjdk }
+                .filter { !filter.filter(it.vendor, it.updated_at.dateTime, it.release_type == ReleaseType.ea) }
                 .toList()
             )
 
-            val concated = V3Updater.copyOldReleasesIntoNewRepo(adoptopenjdk, notAdoptopenjdk)
+            val concated = V3Updater.copyOldReleasesIntoNewRepo(adoptopenjdk, notAdoptopenjdk, filter)
 
             Assertions.assertEquals(repo, concated)
         }
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/testDoubles/AdoptRepositoryStub.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/testDoubles/AdoptRepositoryStub.kt
index 0b942761e..b08bd2477 100644
--- a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/testDoubles/AdoptRepositoryStub.kt
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/testDoubles/AdoptRepositoryStub.kt
@@ -1,5 +1,6 @@
 package net.adoptium.api.testDoubles
 
+import net.adoptium.api.v3.ReleaseIncludeFilter
 import jakarta.annotation.Priority
 import jakarta.enterprise.context.ApplicationScoped
 import jakarta.enterprise.inject.Alternative
@@ -48,7 +49,7 @@ open class AdoptRepositoryStub : AdoptRepository {
         .addRelease(8, toAddSemiYoungRelease)
 
     companion object {
-        val unchangedIndex = 3
+        const val unchangedIndex = 3
 
         val toAdd = Release(
             "foo", ReleaseType.ga, "openjdk-8u", "jdk8u-2018-09-27-08-50",
@@ -74,7 +75,7 @@ open class AdoptRepositoryStub : AdoptRepository {
         )
     }
 
-    override suspend fun getRelease(version: Int): FeatureRelease? {
+    override suspend fun getRelease(version: Int, filter: ReleaseIncludeFilter): FeatureRelease? {
         return updated.getFeatureRelease(version)
     }
 
diff --git a/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/testDoubles/UpdatableVersionSupplierStub.kt b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/testDoubles/UpdatableVersionSupplierStub.kt
new file mode 100644
index 000000000..5e5db25c3
--- /dev/null
+++ b/adoptium-updater-parent/adoptium-api-v3-updater/src/test/kotlin/net/adoptium/api/testDoubles/UpdatableVersionSupplierStub.kt
@@ -0,0 +1,27 @@
+package net.adoptium.api.testDoubles
+
+import jakarta.annotation.Priority
+import jakarta.enterprise.context.ApplicationScoped
+import jakarta.enterprise.inject.Alternative
+import net.adoptium.api.v3.dataSources.UpdatableVersionSupplier
+
+@Priority(1)
+@Alternative
+@ApplicationScoped
+class UpdatableVersionSupplierStub : UpdatableVersionSupplier {
+    override suspend fun updateVersions() {
+        // NOP
+    }
+
+    override fun getTipVersion(): Int {
+        return 15
+    }
+
+    override fun getLtsVersions(): Array {
+        return arrayOf(8, 11)
+    }
+
+    override fun getAllVersions(): Array {
+        return listOf(8, 10, 11, 12, 18).toList().toTypedArray()
+    }
+}
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/pom.xml b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/pom.xml
index 4fe3a3957..7d32ca974 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/pom.xml
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/pom.xml
@@ -19,18 +19,18 @@
         
             io.jsonwebtoken
             jjwt-api
-            0.12.5
+            0.12.6
         
         
             io.jsonwebtoken
             jjwt-impl
-            0.12.5
+            0.12.6
             runtime
         
         
             io.jsonwebtoken
             jjwt-jackson
-            0.12.5
+            0.12.6
             runtime
         
         
@@ -50,12 +50,27 @@
         
             org.kohsuke
             github-api
-            1.321
+            1.326
         
         
             net.adoptium.api
             adoptium-api-v3-persistence
         
+        
+            org.jetbrains.kotlin
+            kotlin-stdlib-jdk8
+            ${kotlin.version}
+        
+        
+            org.jetbrains.kotlin
+            kotlin-test
+            ${kotlin.version}
+            test
+        
+        
+            jakarta.ws.rs
+            jakarta.ws.rs-api
+        
     
 
     
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdaterJsonMapper.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdaterJsonMapper.kt
index 1a6bbaa20..27afdb0da 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdaterJsonMapper.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/UpdaterJsonMapper.kt
@@ -5,11 +5,21 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.datatype.jsonp.JSONPModule
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
 import com.fasterxml.jackson.module.kotlin.KotlinModule
+import jakarta.ws.rs.Produces
+import jakarta.ws.rs.ext.Provider
 
-object UpdaterJsonMapper {
-    val mapper: ObjectMapper = ObjectMapper()
-        .setSerializationInclusion(JsonInclude.Include.NON_NULL)
-        .registerModule(KotlinModule.Builder().build())
-        .registerModule(JavaTimeModule())
-        .registerModule(JSONPModule())
+@Provider
+class UpdaterJsonMapper {
+    companion object {
+        val mapper: ObjectMapper = ObjectMapper()
+            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+            .registerModule(KotlinModule.Builder().build())
+            .registerModule(JavaTimeModule())
+            .registerModule(JSONPModule())
+    }
+
+    @Produces
+    fun getObjectMapper(): ObjectMapper {
+        return mapper
+    }
 }
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubApi.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubApi.kt
index 5144c9bd5..f27d1f49f 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubApi.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubApi.kt
@@ -6,7 +6,7 @@ import net.adoptium.api.v3.dataSources.github.graphql.models.summary.GHRepositor
 import net.adoptium.api.v3.dataSources.models.GitHubId
 
 interface GitHubApi {
-    suspend fun getRepository(owner: String, repoName: String): GHRepository
+    suspend fun getRepository(owner: String, repoName: String, filter: (updatedAt: String, isPrerelease: Boolean) -> Boolean): GHRepository
     suspend fun getRepositorySummary(owner: String, repoName: String): GHRepositorySummary
     suspend fun getReleaseById(id: GitHubId): GHRelease?
 }
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubAuth.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubAuth.kt
index 354b9187b..c4b129b08 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubAuth.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/GitHubAuth.kt
@@ -75,7 +75,7 @@ class GitHubAuth {
             return token
         }
 
-        private suspend fun authenticateAsGitHubApp(appId: String, privateKey: String, installationId: String): GHAppInstallationToken {
+        private fun authenticateAsGitHubApp(appId: String, privateKey: String, installationId: String): GHAppInstallationToken {
             try {
                 // Remove the first and last lines
                 val sanitizedKey = privateKey
@@ -101,7 +101,7 @@ class GitHubAuth {
                     .compact()
 
                 val gitHubApp: GitHub = GitHubBuilder().withJwtToken(jwtToken).build()
-                val appInstallation: GHAppInstallation = gitHubApp.getApp().getInstallationById(installationId.toLong())
+                val appInstallation: GHAppInstallation = gitHubApp.app.getInstallationById(installationId.toLong())
                 return appInstallation.createToken().create()
             } catch (e: Exception) {
                 LOGGER.error("Error authenticating as GitHub App", e)
@@ -110,5 +110,5 @@ class GitHubAuth {
         }
     }
 
-    class FailedToAuthenticateException : Exception("Failed to authenticate to GitHub") {}
+    class FailedToAuthenticateException : Exception("Failed to authenticate to GitHub")
 }
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/GraphQLGitHubClient.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/GraphQLGitHubClient.kt
index ae9772c88..15166684c 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/GraphQLGitHubClient.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/GraphQLGitHubClient.kt
@@ -26,7 +26,7 @@ open class GraphQLGitHubClient @Inject constructor(
         return releaseClient.getReleaseById(id)
     }
 
-    override suspend fun getRepository(owner: String, repoName: String): GHRepository {
-        return repositoryClientClient.getRepository(owner, repoName)
+    override suspend fun getRepository(owner: String, repoName: String, filter: (updatedAt: String, isPrerelease: Boolean) -> Boolean): GHRepository {
+        return repositoryClientClient.getRepository(owner, repoName, filter)
     }
 }
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubInterface.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubInterface.kt
index 11ada77b3..d17f0b0e1 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubInterface.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubInterface.kt
@@ -188,7 +188,7 @@ open class GraphQLGitHubInterface @Inject constructor(
                 }
 
             } catch (e: MismatchedInputException) {
-                return null;
+                return null
             } catch (e: Exception) {
                 LOGGER.error("Query failed", e)
                 throw e
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubReleaseRequest.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubReleaseRequest.kt
index eff1d4cc1..05678ccad 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubReleaseRequest.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubReleaseRequest.kt
@@ -3,7 +3,6 @@ package net.adoptium.api.v3.dataSources.github.graphql.clients
 import com.expediagroup.graphql.client.types.GraphQLClientRequest
 import jakarta.enterprise.context.ApplicationScoped
 import jakarta.inject.Inject
-import net.adoptium.api.v3.dataSources.UpdaterHtmlClient
 import net.adoptium.api.v3.dataSources.github.graphql.models.GHAssets
 import net.adoptium.api.v3.dataSources.github.graphql.models.GHRelease
 import net.adoptium.api.v3.dataSources.github.graphql.models.PageInfo
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubRepositoryClient.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubRepositoryClient.kt
index c011b6414..f110a88f0 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubRepositoryClient.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLGitHubRepositoryClient.kt
@@ -23,14 +23,17 @@ open class GraphQLGitHubRepositoryClient @Inject constructor(
         private val LOGGER = LoggerFactory.getLogger(this::class.java)
     }
 
-    open suspend fun getRepository(owner: String, repoName: String): GHRepository {
+    open suspend fun getRepository(
+        owner: String,
+        repoName: String,
+        filter: (updateTime: String, isPrerelease: Boolean) -> Boolean): GHRepository {
         val query = GetQueryData(owner, repoName)
 
         LOGGER.info("Getting repo $repoName")
 
         val releases = graphQLGitHubInterface.getAll(
             query::withCursor,
-            { request -> getAllAssets(request) },
+            { request -> getAllAssets(request, filter) },
             { it.repository!!.releases.pageInfo.hasNextPage },
             { it.repository!!.releases.pageInfo.endCursor }
         )
@@ -40,11 +43,18 @@ open class GraphQLGitHubRepositoryClient @Inject constructor(
         return GHRepository(GHReleases(releases, PageInfo(false, null)))
     }
 
-    private suspend fun getAllAssets(request: QueryData): List {
+    private suspend fun getAllAssets(request: QueryData, filter: (updateTime: String, isPrerelease: Boolean) -> Boolean): List {
         if (request.repository == null) return listOf()
 
         // nested releases based on how we deserialise githubs data
         return request.repository.releases.releases
+            .filter {
+                val include = filter(it.updatedAt, it.isPrerelease)
+                if (!include) {
+                    LOGGER.debug("Excluding " + it.url)
+                }
+                return@filter include
+            }
             .map { release ->
                 if (release.releaseAssets.pageInfo.hasNextPage) {
                     graphQLGitHubReleaseRequest.getAllReleaseAssets(release)
@@ -54,7 +64,7 @@ open class GraphQLGitHubRepositoryClient @Inject constructor(
             }
     }
 
-    private class GetQueryData(private val owner: String, private val repoName: String, override val variables: Any = mapOf()) :
+    class GetQueryData(val owner: String, val repoName: String, override val variables: Any = mapOf()) :
         GraphQLClientRequest {
 
         fun withCursor(cursor: String?): GetQueryData {
@@ -75,7 +85,7 @@ open class GraphQLGitHubRepositoryClient @Inject constructor(
                                         updatedAt,
                                         isPrerelease,
                                         resourcePath,
-                                        releaseAssets(first:50) {
+                                        releaseAssets(first:1) {
                                             totalCount,
                                             nodes {
                                                 downloadCount,
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLRequestImpl.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLRequestImpl.kt
index ac2ea79f3..f044e55b6 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLRequestImpl.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/github/graphql/clients/GraphQLRequestImpl.kt
@@ -15,11 +15,10 @@ import java.net.URL
 open class GraphQLRequestImpl : GraphQLRequest {
 
     private val client: GraphQLKtorClient
-    private val httpClient: HttpClient
+    private val httpClient: HttpClient = HttpClient()
     val BASE_URL = "https://api.github.com/graphql"
 
     init {
-        httpClient = HttpClient()
         client = GraphQLKtorClient(
             url = URL(BASE_URL),
             httpClient = httpClient,
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/mongo/InternalDbStore.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/mongo/InternalDbStore.kt
index dc9779039..13f3f1f48 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/mongo/InternalDbStore.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-github-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/mongo/InternalDbStore.kt
@@ -1,11 +1,13 @@
 package net.adoptium.api.v3.dataSources.mongo
 
 import com.mongodb.client.model.IndexOptions
-import com.mongodb.client.model.UpdateOptions
+import com.mongodb.client.model.ReplaceOptions
+import com.mongodb.kotlin.client.coroutine.MongoCollection
 import jakarta.enterprise.context.ApplicationScoped
 import jakarta.inject.Inject
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import net.adoptium.api.v3.dataSources.persitence.mongo.MongoClient
@@ -13,7 +15,6 @@ import net.adoptium.api.v3.dataSources.persitence.mongo.MongoInterface
 import org.bson.BsonDateTime
 import org.bson.BsonDocument
 import org.bson.Document
-import org.litote.kmongo.coroutine.CoroutineCollection
 import java.time.ZonedDateTime
 
 interface InternalDbStore {
@@ -24,27 +25,26 @@ interface InternalDbStore {
 
 @ApplicationScoped
 open class InternalDbStoreImpl @Inject constructor(mongoClient: MongoClient) : MongoInterface(), InternalDbStore {
-    private val webCache: CoroutineCollection = createCollection(mongoClient.database, "web-cache")
+    private val webCache: MongoCollection = createCollection(mongoClient.getDatabase(), "web-cache")
 
     init {
         runBlocking {
-            webCache.createIndex("""{"url":1}""", IndexOptions().background(true))
+            webCache.createIndex(Document.parse("""{"url":1}"""), IndexOptions().background(true))
         }
     }
 
     override fun putCachedWebpage(url: String, lastModified: String?, date: ZonedDateTime, data: String?): Job {
         return GlobalScope.launch {
-            webCache.updateOne(
+            webCache.replaceOne(
                 Document("url", url),
                 CacheDbEntry(url, lastModified, date, data),
-                UpdateOptions().upsert(true),
-                false
+                ReplaceOptions().upsert(true)
             )
         }
     }
 
     override suspend fun getCachedWebpage(url: String): CacheDbEntry? {
-        return webCache.findOne(Document("url", url))
+        return webCache.find(Document("url", url)).firstOrNull()
     }
 
     override suspend fun updateCheckedTime(url: String, dateTime: ZonedDateTime) {
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/pom.xml b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/pom.xml
index bce43c779..813146b4f 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/pom.xml
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/pom.xml
@@ -51,24 +51,35 @@
         
             io.jsonwebtoken
             jjwt-api
-            0.12.5
+            0.12.6
         
         
             io.jsonwebtoken
             jjwt-impl
-            0.12.5
+            0.12.6
             runtime
         
         
             io.jsonwebtoken
             jjwt-jackson
-            0.12.5
+            0.12.6
             runtime
         
         
             org.kohsuke
             github-api
-            1.321
+            1.326
+        
+        
+            org.jetbrains.kotlin
+            kotlin-stdlib-jdk8
+            ${kotlin.version}
+        
+        
+            org.jetbrains.kotlin
+            kotlin-test
+            ${kotlin.version}
+            test
         
     
 
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/DefaultUpdaterHtmlClient.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/DefaultUpdaterHtmlClient.kt
index 07e75b73e..9ca335361 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/DefaultUpdaterHtmlClient.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/DefaultUpdaterHtmlClient.kt
@@ -141,7 +141,7 @@ open class DefaultUpdaterHtmlClient @Inject constructor(
             try {
                 LOGGER.debug("Getting ${request.url} ${request.lastModified}")
                 val response: HttpResponse = withTimeout(REQUEST_TIMEOUT) {
-                    suspendCoroutine { continuation ->
+                    suspendCoroutine { continuation ->
                         getData(request, continuation, authInfo?.token)
                     }
                 }
diff --git a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/GitHubAuth.kt b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/GitHubAuth.kt
index c2cc24a74..0f5532943 100644
--- a/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/GitHubAuth.kt
+++ b/adoptium-updater-parent/adoptium-datasources-parent/adoptium-http-client-datasource/src/main/kotlin/net/adoptium/api/v3/dataSources/GitHubAuth.kt
@@ -73,7 +73,7 @@ class GitHubAuth {
             return token
         }
 
-        private suspend fun authenticateAsGitHubApp(appId: String, privateKey: String, installationId: String): GHAppInstallationToken {
+        private fun authenticateAsGitHubApp(appId: String, privateKey: String, installationId: String): GHAppInstallationToken {
             try {
                 // Remove the first and last lines
                 val sanitizedKey = privateKey
@@ -99,7 +99,7 @@ class GitHubAuth {
                     .compact()
 
                 val gitHubApp: GitHub = GitHubBuilder().withJwtToken(jwtToken).build()
-                val appInstallation: GHAppInstallation = gitHubApp.getApp().getInstallationById(installationId.toLong())
+                val appInstallation: GHAppInstallation = gitHubApp.app.getInstallationById(installationId.toLong())
                 return appInstallation.createToken().create()
             } catch (e: Exception) {
                 LOGGER.error("Error authenticating as GitHub App", e)
@@ -108,5 +108,5 @@ class GitHubAuth {
         }
     }
 
-    class FailedToAuthenticateException : Exception("Failed to authenticate as GitHub App") {}
+    class FailedToAuthenticateException : Exception("Failed to authenticate as GitHub App")
 }
diff --git a/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/pom.xml b/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/pom.xml
index 0cf0cc5c1..2e63a2388 100644
--- a/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/pom.xml
+++ b/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/pom.xml
@@ -22,11 +22,21 @@
             adoptium-github-datasource
             ${project.version}
         
+        
+            org.jetbrains.kotlin
+            kotlin-stdlib-jdk8
+            ${kotlin.version}
+        
+        
+            org.jetbrains.kotlin
+            kotlin-test
+            ${kotlin.version}
+            test
+        
     
 
     
         src/main/kotlin
-        src/test/kotlin
     
 
 
diff --git a/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptBinaryMapper.kt b/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptBinaryMapper.kt
index adcac54d4..ab9dafe71 100644
--- a/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptBinaryMapper.kt
+++ b/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptBinaryMapper.kt
@@ -28,12 +28,13 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
 
     companion object {
         @JvmStatic
-        private val LOGGER = LoggerFactory.getLogger(this::class.java)
+        private val LOGGER = LoggerFactory.getLogger(AdoptBinaryMapper::class.java)
         private const val HOTSPOT_JFR = "hotspot-jfr"
         private const val TEMURIN = "temurin"
+
+        private val EXCLUDED = listOf("corretto")
     }
 
-    private val EXCLUDED = listOf("corretto")
 
     suspend fun toBinaryList(ghBinaryAssets: List, allGhAssets: List, ghAssetsWithMetadata: Map): List {
         // probably whitelist rather than black list
@@ -55,9 +56,9 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
 
                 val binaryMetadata = ghAssetsWithMetadata[ghBinaryAsset]
 
-                val heapSize = getEnumFromFileName(ghBinaryAsset.name, HeapSize.values(), HeapSize.normal)
+                val heapSize = getEnumFromFileName(ghBinaryAsset.name, HeapSize.entries.toTypedArray(), HeapSize.normal)
 
-                val cLib = getEnumFromFileNameNullable(ghBinaryAsset.name, CLib.values(), null)
+                val cLib = getEnumFromFileNameNullable(ghBinaryAsset.name, CLib.entries.toTypedArray(), null)
 
                 val installer = getInstaller(ghBinaryAsset, allGhAssets)
                 val `package` = getPackage(allGhAssets, ghBinaryAsset, binaryMetadata)
@@ -90,13 +91,13 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
         val binarySize = binaryAsset.size
         val binaryChecksumLink = getCheckSumLink(fullAssetList, binaryName)
         val signatureLink = getSignatureLink(fullAssetList, binaryAsset.name)
-        val binaryChecksum: String?
 
-        binaryChecksum = if (binaryMetadata != null && binaryMetadata.sha256.isNotEmpty()) {
-            binaryMetadata.sha256
-        } else {
-            getChecksum(binaryChecksumLink)
-        }
+        val binaryChecksum: String? =
+            if (binaryMetadata != null && binaryMetadata.sha256.isNotEmpty()) {
+                binaryMetadata.sha256
+            } else {
+                getChecksum(binaryChecksumLink)
+            }
 
         val metadataLink = getMetadataLink(fullAssetList, binaryName)
 
@@ -115,7 +116,7 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
     private suspend fun getInstaller(binaryAsset: GHAsset, fullAssetList: List): Installer? {
 
         val nameWithoutExtension =
-            BINARY_ASSET_WHITELIST.fold(binaryAsset.name, { assetName, extension -> assetName.replace(extension, "") })
+            BINARY_ASSET_WHITELIST.fold(binaryAsset.name) { assetName, extension -> assetName.replace(extension, "") }
 
         val installer = fullAssetList
             .filter { it.name.startsWith(nameWithoutExtension) }
@@ -171,10 +172,10 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
     }
 
     private fun removeExtensionFromName(binary_name: String): String {
-        return BINARY_ASSET_WHITELIST.foldRight(binary_name, { extension, name -> name.removeSuffix(extension) })
+        return BINARY_ASSET_WHITELIST.foldRight(binary_name) { extension, name -> name.removeSuffix(extension) }
     }
 
-    private fun isBinaryAsset(asset: GHAsset) = ARCHIVE_WHITELIST.any { asset.name.endsWith(it) } || ( asset.name.endsWith(".json") && asset.name.contains("-sbom_") )
+    private fun isBinaryAsset(asset: GHAsset) = ARCHIVE_WHITELIST.any { asset.name.endsWith(it) } || (asset.name.endsWith(".json") && asset.name.contains("-sbom_"))
 
     private fun binaryFromName(
         asset: GHAsset,
@@ -186,11 +187,11 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
         cLib: CLib?
     ): Binary {
         val scmRef = null
-        val os = getEnumFromFileName(asset.name, OperatingSystem.values())
-        val architecture = getEnumFromFileName(asset.name, Architecture.values())
-        val binaryType = getEnumFromFileName(asset.name, ImageType.values(), ImageType.jdk)
-        val jvmImpl = getEnumFromFileName(asset.name, JvmImpl.values(), JvmImpl.hotspot)
-        val project = getEnumFromFileName(asset.name, Project.values(), Project.jdk)
+        val os = getEnumFromFileName(asset.name, OperatingSystem.entries.toTypedArray())
+        val architecture = getEnumFromFileName(asset.name, Architecture.entries.toTypedArray())
+        val binaryType = getEnumFromFileName(asset.name, ImageType.entries.toTypedArray(), ImageType.jdk)
+        val jvmImpl = getEnumFromFileName(asset.name, JvmImpl.entries.toTypedArray(), JvmImpl.hotspot)
+        val project = getEnumFromFileName(asset.name, Project.entries.toTypedArray(), Project.jdk)
 
         return Binary(
             pack,
@@ -242,7 +243,7 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
 
     private fun getImageType(asset: GHAsset, binaryMetadata: GHMetaData): ImageType {
         // static-libs incorrectly labeled as JDK, if the name tells us it's a static-lib, trust that over the metadata file
-        val binaryTypeFromName = getEnumFromFileName(asset.name, ImageType.values(), ImageType.jdk)
+        val binaryTypeFromName = getEnumFromFileName(asset.name, ImageType.entries.toTypedArray(), ImageType.jdk)
         return if (binaryTypeFromName == ImageType.staticlibs) {
             ImageType.staticlibs
         } else {
@@ -271,7 +272,7 @@ class AdoptBinaryMapper @Inject constructor(private val gitHubHtmlClient: GitHub
 
     private suspend fun getChecksum(binary_checksum_link: String?): String? {
         try {
-            if (!(binary_checksum_link == null || binary_checksum_link.isEmpty())) {
+            if (!binary_checksum_link.isNullOrEmpty()) {
                 LOGGER.debug("Pulling checksum for $binary_checksum_link")
                 val checksum = gitHubHtmlClient.getUrl(binary_checksum_link)
                 if (checksum != null) {
diff --git a/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptReleaseMapper.kt b/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptReleaseMapper.kt
index ca6341ff6..036198742 100644
--- a/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptReleaseMapper.kt
+++ b/adoptium-updater-parent/adoptium-mappers-parent/adopt-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/adopt/AdoptReleaseMapper.kt
@@ -1,7 +1,7 @@
 package net.adoptium.api.v3.mapping.adopt
 
 import jakarta.enterprise.context.ApplicationScoped
-import jakarta.enterprise.inject.Model
+import jakarta.inject.Inject
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import net.adoptium.api.v3.ReleaseResult
@@ -29,8 +29,6 @@ import java.security.MessageDigest
 import java.time.ZonedDateTime
 import java.util.*
 import java.util.regex.Pattern
-import jakarta.inject.Inject
-import jakarta.inject.Singleton
 
 @ApplicationScoped
 open class AdoptReleaseMapperFactory @Inject constructor(
@@ -39,7 +37,7 @@ open class AdoptReleaseMapperFactory @Inject constructor(
 ) {
     private val mappers: MutableMap = EnumMap(Vendor::class.java)
 
-    fun get(vendor: Vendor): ReleaseMapper {
+    open fun get(vendor: Vendor): ReleaseMapper {
         return if (mappers.containsKey(vendor)) {
             mappers[vendor]!!
         } else {
@@ -50,7 +48,7 @@ open class AdoptReleaseMapperFactory @Inject constructor(
     }
 }
 
-private class AdoptReleaseMapper constructor(
+private class AdoptReleaseMapper(
     val adoptBinaryMapper: AdoptBinaryMapper,
     val htmlClient: GitHubHtmlClient,
     val vendor: Vendor
@@ -196,7 +194,7 @@ private class AdoptReleaseMapper constructor(
             .filter { asset ->
                 BinaryMapper.BINARY_EXTENSIONS.any { asset.name.endsWith(it) }
             }
-            .map { it.downloadCount }.sum()
+            .sumOf { it.downloadCount }
 
         return Release(
             id,
diff --git a/adoptium-updater-parent/adoptium-mappers-parent/mappers-common/pom.xml b/adoptium-updater-parent/adoptium-mappers-parent/mappers-common/pom.xml
index a417251cf..7e38b04d3 100644
--- a/adoptium-updater-parent/adoptium-mappers-parent/mappers-common/pom.xml
+++ b/adoptium-updater-parent/adoptium-mappers-parent/mappers-common/pom.xml
@@ -25,6 +25,17 @@
             net.adoptium.api
             adoptium-api-v3-persistence
         
+        
+            org.jetbrains.kotlin
+            kotlin-stdlib-jdk8
+            ${kotlin.version}
+        
+        
+            org.jetbrains.kotlin
+            kotlin-test
+            ${kotlin.version}
+            test
+        
     
 
     
diff --git a/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/pom.xml b/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/pom.xml
index 2f37b8434..947da21dd 100644
--- a/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/pom.xml
+++ b/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/pom.xml
@@ -22,11 +22,21 @@
             adoptium-github-datasource
             ${project.version}
         
+        
+            org.jetbrains.kotlin
+            kotlin-stdlib-jdk8
+            ${kotlin.version}
+        
+        
+            org.jetbrains.kotlin
+            kotlin-test
+            ${kotlin.version}
+            test
+        
     
 
     
         src/main/kotlin
-        src/test/kotlin
     
 
 
diff --git a/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamBinaryMapper.kt b/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamBinaryMapper.kt
index e86868d2e..b7f427dcc 100644
--- a/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamBinaryMapper.kt
+++ b/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamBinaryMapper.kt
@@ -39,11 +39,11 @@ object UpstreamBinaryMapper : BinaryMapper() {
                 val signatureLink = getSignatureLink(assets, asset.name)
                 val pack = Package(asset.name, asset.downloadUrl, asset.size, null, null, asset.downloadCount, signatureLink, null)
 
-                val os = getEnumFromFileName(asset.name, OperatingSystem.values())
-                val architecture = getEnumFromFileName(asset.name, Architecture.values())
-                val imageType = getEnumFromFileName(asset.name, ImageType.values(), ImageType.jdk)
+                val os = getEnumFromFileName(asset.name, OperatingSystem.entries.toTypedArray())
+                val architecture = getEnumFromFileName(asset.name, Architecture.entries.toTypedArray())
+                val imageType = getEnumFromFileName(asset.name, ImageType.entries.toTypedArray(), ImageType.jdk)
                 val updatedAt = getUpdatedTime(asset)
-                val projectType = getEnumFromFileName(asset.name, Project.values(), Project.jdk)
+                val projectType = getEnumFromFileName(asset.name, Project.entries.toTypedArray(), Project.jdk)
 
                 Binary(
                     pack,
diff --git a/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamReleaseMapper.kt b/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamReleaseMapper.kt
index cc44380b8..77ce74687 100644
--- a/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamReleaseMapper.kt
+++ b/adoptium-updater-parent/adoptium-mappers-parent/upstream-mappers/src/main/kotlin/net/adoptium/api/v3/mapping/upstream/UpstreamReleaseMapper.kt
@@ -31,7 +31,7 @@ object UpstreamReleaseMapper : ReleaseMapper() {
             .filter { asset ->
                 BinaryMapper.BINARY_EXTENSIONS.any { asset.name.endsWith(it) }
             }
-            .map { it.downloadCount }.sum()
+            .sumOf { it.downloadCount }
 
         val vendor = Vendor.openjdk
 
diff --git a/deploy/Dockerfile b/deploy/Dockerfile
index 680c99a3d..8243e657f 100644
--- a/deploy/Dockerfile
+++ b/deploy/Dockerfile
@@ -1,4 +1,6 @@
-FROM eclipse-temurin:21
+######################################################
+## Build
+FROM eclipse-temurin:21-jammy as build
 
 # BUILDS FOR ADOPTIUM, NOT ADOPTOPENJDK
 ARG MAVEN_FLAGS="-Padoptium,-adoptopenjdk"
@@ -10,32 +12,35 @@ ENV REPOSITORY=$REPOSITORY
 ARG BRANCH="main"
 ENV BRANCH=$BRANCH
 
-RUN     useradd -ms /bin/bash api && \
-        mkdir -p /home/api/deployment/ && \
-        mkdir -p /logs && \
-        mkdir -p /home/api/deployment/lib && \
-        mkdir -p /home/api/build && \
-        mkdir /tmp/build && \
-        chown -R api: /home/api/ && \
-        apt-get update && apt-get -y install openssl
+RUN     mkdir /tmp/build
 
 WORKDIR /tmp/build
 
 COPY . /tmp/build
 
-RUN chown -R api: /tmp/build
-
-USER api
-
 RUN     ./mvnw clean install $MAVEN_FLAGS && \
-        cp adoptium-updater-parent/adoptium-api-v3-updater/target/adoptium-api-v3-updater-*-jar-with-dependencies.jar /home/api/deployment/updater.jar && \
-        cp -r adoptium-frontend-parent/adoptium-api-v3-frontend/target/quarkus-app/* /home/api/deployment/ && \
-        mv /home/api/deployment/quarkus-run.jar /home/api/deployment/frontend.jar && \
-        cp deploy/run.sh /home/api/deployment/ && \
-        chmod +x /home/api/deployment/run.sh && \
+        mkdir -p /api/deployment && \
+        cp adoptium-updater-parent/adoptium-api-v3-updater/target/adoptium-api-v3-updater-*-jar-with-dependencies.jar /api/deployment/updater.jar && \
+        cp -r adoptium-frontend-parent/adoptium-api-v3-frontend/target/quarkus-app/* /api/deployment/ && \
+        mv /api/deployment/quarkus-run.jar /api/deployment/frontend.jar && \
+        cp deploy/run.sh /api/deployment/ && \
+        chmod +x /api/deployment/run.sh && \
         cd /tmp && \
         rm -rf /tmp/build ~/.m2 && \
-        cd /home/api/ && find
+        cd /api/ && find
+
+
+######################################################
+## Build Deployment
+FROM eclipse-temurin:21-jammy
+
+RUN     apt-get update && apt-get -y install openssl adduser && \
+        useradd -ms /bin/bash api && \
+        mkdir -p /logs
+
+COPY --chown=api:api --from=build /api/deployment /home/api/deployment
+
+USER api
 
 WORKDIR /home/api/deployment/
 
@@ -43,5 +48,5 @@ ENV JAVA_OPTS=""
 ARG type=frontend
 ENV typeEnv=$type
 
-CMD cd /home/api/deployment/ && ./run.sh ${typeEnv}
+CMD ./run.sh ${typeEnv}
 
diff --git a/deploy/run.sh b/deploy/run.sh
index cc5e87f0b..14dd7f9b8 100644
--- a/deploy/run.sh
+++ b/deploy/run.sh
@@ -35,4 +35,6 @@ then
   export MONGODB_SSL="true"
 fi
 
+export DEPLOYMENT_TYPE="${deploymentType}"
+
 java $JAVA_OPTS -jar ${deploymentType}.jar
diff --git a/pom.xml b/pom.xml
index 482193172..f96dc64dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
 
     
     
-        3.9.6
+        3.9.9
     
 
     
@@ -19,61 +19,61 @@
         0.8.12
         17
         17
-        1.9.23
+        2.0.21
         3.1.0
         3.7.1
-        3.5.0
+        3.6.0
         2.12.1
         3.3.0
-        3.3.2
+        3.4.0
         3.13.0
         17
         17
         17
         17
         17
-        3.6.1
+        3.8.1
         2.0.4
-        3.1.1
+        3.1.3
         1.0
-        3.4.1
-        3.2.0
-        3.3.2
-        3.4.0
+        3.5.0
+        3.4.1
+        3.4.0
+        3.5.0
         
-        2.5.2
+        3.1.3
         
         0.8.10
-        3.4.1
-        3.6.3
+        3.4.2
+        3.8.0
         
-        2.0
+        2.1
         3.0.0-alpha-1
         3.1.2
-        3.3.2
+        3.5.0
         2.4.0
-        7.5.0
-        1.16.0
-        3.22.0
-        3.5.0
+        7.8.0
+        1.16.3
+        3.25.0
+        3.7.0
         0.16.1
-        3.0.1
+        3.1.1
         3.3.1
         2.1.0
-        4.0.0-M13
+        4.0.0-M16
         3.3.1
-        4.8.4.0
-        3.2.5
+        4.8.6.5
+        3.5.2
         1.0.10
-        1.2.0
+        1.3.0
         3.2.0
         3.9.6
-        2.16.2
+        2.17.1
         3.4.0
         0.9.1
         6.55.0
@@ -82,7 +82,7 @@
         4.7.3
         UTF-8
         UTF-8
-        3.9.4
+        3.16.1