diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index 00ed95801d..3d64732fac 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -64,16 +64,22 @@ runs: run: | echo "JDK_CI=$JAVA_HOME" >> $GITHUB_ENV echo "JDK_EA=${{ inputs.early-access == inputs.java }}" >> $GITHUB_ENV - - name: Set up JDK 17 + - name: Read Gradle JDK toolchain version + id: read-jdk-version + shell: bash + run: | + toolchainVersion=$(grep -oP '(?<=^toolchainVersion=).*' gradle/gradle-daemon-jvm.properties) + echo "toolchainVersion=${toolchainVersion}" >> $GITHUB_ENV + - name: Set up JDK ${{ env.toolchainVersion }} id: setup-gradle-jdk uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 if: inputs.java != 'GraalVM' with: - java-version: 17 + java-version: ${{ env.toolchainVersion }} distribution: temurin - name: Setup Gradle id: setup-gradle - uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + uses: gradle/actions/setup-gradle@d9336dac04dea2507a617466bc058a3def92b18b # v3.4.0 env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} ORG_GRADLE_PROJECT_org.gradle.java.installations.auto-download: 'false' diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a0618b4141..064f3b5b80 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -46,7 +46,7 @@ updates: patterns: - "*" - package-ecosystem: gradle - directory: examples/write-behind-rxjava + directory: examples/hibernate schedule: interval: monthly groups: @@ -55,7 +55,7 @@ updates: patterns: - "*" - package-ecosystem: gradle - directory: examples/hibernate + directory: examples/indexable schedule: interval: monthly groups: @@ -81,3 +81,12 @@ updates: applies-to: version-updates patterns: - "*" + - package-ecosystem: gradle + directory: examples/write-behind-rxjava + schedule: + interval: monthly + groups: + gradle-dependencies: + applies-to: version-updates + patterns: + - "*" diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index a9f82489fc..ea46e1b879 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -14,7 +14,7 @@ jobs: allowed-endpoints: > api.github.com:443 github.com:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: actionlint uses: reviewdog/action-actionlint@fd627997c9688c2f39e13917aed23873c031b834 # v1.48.0 env: diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index f8de507dc7..f6cb8d41e9 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -31,7 +31,7 @@ jobs: disable-sudo: true egress-policy: block allowed-endpoints: ${{ env.ALLOWED_ENDPOINTS }} - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Forbidden Apis uses: ./.github/actions/run-gradle with: @@ -49,7 +49,7 @@ jobs: disable-sudo: true egress-policy: block allowed-endpoints: ${{ env.ALLOWED_ENDPOINTS }} - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Pmd uses: ./.github/actions/run-gradle with: @@ -67,7 +67,7 @@ jobs: disable-sudo: true egress-policy: block allowed-endpoints: ${{ env.ALLOWED_ENDPOINTS }} - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Spotbugs uses: ./.github/actions/run-gradle with: diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 935c62c393..cbfc71e225 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -39,7 +39,7 @@ jobs: raw.githubusercontent.com:443 services.gradle.org:443 www.graalvm.org:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Compute JMH Benchmark uses: ./.github/actions/run-gradle with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa166edc16..242bd6f0a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: egress-policy: block allowed-endpoints: ${{ env.ALLOWED_ENDPOINTS }} - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Prepare GraalVM if: env.JAVA_VERSION == 'GraalVM' shell: bash @@ -72,7 +72,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} arguments: check -x test ${{ env.GRADLE_ARGS }} - name: Cancel if failed - uses: andymckay/cancel-action@271cfbfa11ca9222f7be99a47e8f929574549e0a # 0.4 + uses: andymckay/cancel-action@a955d435292c0d409d104b57d8e78435a93a6ef1 # 0.5 continue-on-error: true if: failure() @@ -176,7 +176,7 @@ jobs: egress-policy: block allowed-endpoints: ${{ env.ALLOWED_ENDPOINTS }} - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run tests (${{ env.JAVA_VERSION }}) uses: ./.github/actions/run-gradle with: @@ -202,7 +202,7 @@ jobs: name: ${{ env.ARTIFACT_NAME }}-results path: ${{ env.ARTIFACT_NAME }}.tar.gz - name: Cancel if failed - uses: andymckay/cancel-action@271cfbfa11ca9222f7be99a47e8f929574549e0a # 0.4 + uses: andymckay/cancel-action@a955d435292c0d409d104b57d8e78435a93a6ef1 # 0.5 continue-on-error: true if: failure() @@ -231,7 +231,7 @@ jobs: storage.googleapis.com:443 uploader.codecov.io:443 - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Download Tests Results @@ -252,7 +252,7 @@ jobs: java: ${{ env.PUBLISH_JDK }} continue-on-error: true - name: Publish to Codecov - uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Publish to Codacy @@ -342,7 +342,7 @@ jobs: errorprone.info:443 lightbend.github.io:443 guava.dev:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Publish Snapshot uses: ./.github/actions/run-gradle env: diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml index ad102ebefa..67acd7326e 100644 --- a/.github/workflows/codacy.yml +++ b/.github/workflows/codacy.yml @@ -29,7 +29,7 @@ jobs: registry-1.docker.io:443 *.blob.core.windows.net:443 - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run Codacy Analysis uses: codacy/codacy-analysis-cli-action@master continue-on-error: true @@ -47,7 +47,7 @@ jobs: if: steps.check_files.outputs.files_exists == 'true' run: jq -c '.runs |= unique_by({tool, invocations, results})' < results.sarif > codacy.sarif - name: Upload result to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 if: steps.check_files.outputs.files_exists == 'true' continue-on-error: true with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 03fae5d78d..9e3af0389f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,17 +50,17 @@ jobs: uploads.github.com:443 services.gradle.org:443 - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Setup Gradle uses: ./.github/actions/run-gradle with: java: ${{ env.JAVA_VERSION }} token: ${{ secrets.GITHUB_TOKEN }} - name: Initialize CodeQL - uses: github/codeql-action/init@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 14e010c6e7..d2b5826b25 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -42,7 +42,7 @@ jobs: raw.githubusercontent.com:443 services.gradle.org:443 www.cisa.gov:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run dependency-check uses: ./.github/actions/run-gradle continue-on-error: true @@ -57,7 +57,7 @@ jobs: with: files: build/reports/dependency-check-report.sarif - name: Upload result to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 if: steps.check_files.outputs.files_exists == 'true' with: sarif_file: build/reports/dependency-check-report.sarif diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c1a0a0b67a..2230950fff 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,7 +19,7 @@ jobs: api.github.com:443 github.com:443 - name: Checkout Repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Dependency Review uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3 with: diff --git a/.github/workflows/dependency-submission-pr-retreive.yml b/.github/workflows/dependency-submission-pr-retreive.yml index 93d35afa19..2e021421f7 100644 --- a/.github/workflows/dependency-submission-pr-retreive.yml +++ b/.github/workflows/dependency-submission-pr-retreive.yml @@ -35,6 +35,6 @@ jobs: repo1.maven.org:443 services.gradle.org:443 - name: Retrieve and submit dependency graph - uses: gradle/actions/dependency-submission@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + uses: gradle/actions/dependency-submission@d9336dac04dea2507a617466bc058a3def92b18b # v3.4.0 with: dependency-graph: download-and-submit diff --git a/.github/workflows/dependency-submission-pr-submit.yml b/.github/workflows/dependency-submission-pr-submit.yml index 8e5d98d603..ed572aae6c 100644 --- a/.github/workflows/dependency-submission-pr-submit.yml +++ b/.github/workflows/dependency-submission-pr-submit.yml @@ -31,14 +31,14 @@ jobs: repo.maven.apache.org:443 repo1.maven.org:443 services.gradle.org:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: java-version: ${{ env.JAVA_VERSION }} distribution: temurin - name: Submit Dependency Graph - uses: gradle/actions/dependency-submission@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + uses: gradle/actions/dependency-submission@d9336dac04dea2507a617466bc058a3def92b18b # v3.4.0 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} dependency-graph: generate-and-upload diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml index a506db91cc..5256d8ae34 100644 --- a/.github/workflows/dependency-submission.yml +++ b/.github/workflows/dependency-submission.yml @@ -31,13 +31,13 @@ jobs: repo.maven.apache.org:443 repo1.maven.org:443 services.gradle.org:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: java-version: ${{ env.JAVA_VERSION }} distribution: temurin - name: Submit Dependency Graph - uses: gradle/actions/dependency-submission@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + uses: gradle/actions/dependency-submission@d9336dac04dea2507a617466bc058a3def92b18b # v3.4.0 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} diff --git a/.github/workflows/devskim.yml b/.github/workflows/devskim.yml index 7ae616ff29..196d7853ea 100644 --- a/.github/workflows/devskim.yml +++ b/.github/workflows/devskim.yml @@ -27,10 +27,10 @@ jobs: api.github.com:443 github.com:443 - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run DevSkim scanner uses: microsoft/DevSkim-Action@914fa647b406c387000300b2f09bb28691be2b6d # v1.0.14 - name: Upload DevSkim scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 with: sarif_file: devskim-results.sarif diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index f9c630ddc6..b915ca3d42 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -32,14 +32,14 @@ jobs: repo1.maven.org:443 services.gradle.org:443 www.graalvm.org:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: java-version: ${{ env.JAVA_VERSION }} distribution: temurin - name: Setup Gradle - uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + uses: gradle/actions/setup-gradle@d9336dac04dea2507a617466bc058a3def92b18b # v3.4.0 with: add-job-summary: never cache-read-only: false @@ -84,6 +84,9 @@ jobs: - name: Hibernate (jcache) working-directory: examples/hibernate run: ./gradlew build + - name: Indexable + working-directory: examples/indexable + run: ./gradlew build - name: Resilience (failsafe) working-directory: examples/resilience-failsafe run: ./gradlew build diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml index d8820c169e..83c93dd673 100644 --- a/.github/workflows/gitleaks.yml +++ b/.github/workflows/gitleaks.yml @@ -23,7 +23,7 @@ jobs: egress-policy: block allowed-endpoints: ${{ env.ALLOWED_ENDPOINTS }} - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Run gitleaks diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index a642a6a20f..aea993e18a 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -17,5 +17,5 @@ jobs: downloads.gradle-dn.com:443 github.com:443 services.gradle.org:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: gradle/actions/wrapper-validation@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: gradle/actions/wrapper-validation@d9336dac04dea2507a617466bc058a3def92b18b # v3.4.0 diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml index d52d5620e7..269a473301 100644 --- a/.github/workflows/qodana.yml +++ b/.github/workflows/qodana.yml @@ -55,7 +55,7 @@ jobs: resources.jetbrains.com:443 services.gradle.org:443 - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Build uses: ./.github/actions/run-gradle with: @@ -68,6 +68,6 @@ jobs: with: upload-result: true - name: Upload SARIF file for GitHub Advanced Security Dashboard - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 with: sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4c75e8ea17..a1a66e140d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: with: disable-sudo: true egress-policy: audit - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Releasing uses: ./.github/actions/run-gradle env: diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index e96a709e73..c9379a337d 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -40,7 +40,7 @@ jobs: tuf-repo-cdn.sigstore.dev:443 www.bestpractices.dev:443 - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: persist-credentials: false - name: Run analysis @@ -57,6 +57,6 @@ jobs: path: results.sarif retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 with: sarif_file: results.sarif diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 2296cc6b4b..afa2d8cdc3 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -17,7 +17,7 @@ jobs: # Incompatible with Harden Runner image: returntocorp/semgrep steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - run: semgrep scan --sarif --output=results.sarif env: SEMGREP_RULES: >- @@ -34,7 +34,7 @@ jobs: if: steps.check_files.outputs.files_exists == 'true' run: jq -c '.runs[0].tool.driver.rules |= unique_by(.id)' < results.sarif > semgrep.sarif - name: Upload SARIF file for GitHub Advanced Security Dashboard - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 if: steps.check_files.outputs.files_exists == 'true' continue-on-error: true with: diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index e6301a5fda..dc628ff588 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -37,7 +37,7 @@ jobs: repo.maven.apache.org:443 repo1.maven.org:443 services.gradle.org:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run Snyk test uses: snyk/actions/gradle-jdk17@master continue-on-error: true @@ -52,7 +52,7 @@ jobs: with: files: snyk.sarif - name: Upload result to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 if: steps.check_files.outputs.files_exists == 'true' with: sarif_file: snyk.sarif diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 4722a19b0b..eaa264b350 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -14,7 +14,7 @@ jobs: allowed-endpoints: > api.github.com:443 github.com:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Misspell uses: reviewdog/action-misspell@8cd4a880dd86b1b175092c18c23cdec31283d654 # v1.19.0 with: @@ -32,6 +32,6 @@ jobs: allowed-endpoints: > github.com:443 objects.githubusercontent.com:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Typos - uses: crate-ci/typos@8382594ee09667379b652553cf57daebb8176a3f # v1.22.3 + uses: crate-ci/typos@cfe759ac8dd421e203cc293a373396fbc6fe0d4b # v1.22.7 diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 4356732209..c921c77078 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -21,7 +21,7 @@ jobs: ghcr.io:443 github.com:443 pkg-containers.githubusercontent.com:443 - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@595be6a0f6560a0a8fc419ddf630567fc623531d # v0.22.0 with: @@ -29,6 +29,6 @@ jobs: format: sarif output: trivy-results.sarif - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 with: sarif_file: trivy-results.sarif diff --git a/.java-version b/.java-version deleted file mode 100644 index 03b6389f32..0000000000 --- a/.java-version +++ /dev/null @@ -1 +0,0 @@ -17.0 diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 91abe9545c..0000000000 --- a/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -java 17 diff --git a/examples/coalescing-bulkloader-reactor/build.gradle.kts b/examples/coalescing-bulkloader-reactor/build.gradle.kts index a816509fc0..3430c9c8d6 100644 --- a/examples/coalescing-bulkloader-reactor/build.gradle.kts +++ b/examples/coalescing-bulkloader-reactor/build.gradle.kts @@ -17,11 +17,4 @@ testing.suites { } } -java.toolchain.languageVersion = JavaLanguageVersion.of( - System.getenv("JAVA_VERSION")?.toIntOrNull() ?: 17) - -tasks.withType().configureEach { - javaCompiler = javaToolchains.compilerFor { - languageVersion = java.toolchain.languageVersion - } -} +java.toolchain.languageVersion = JavaLanguageVersion.of(21) diff --git a/examples/coalescing-bulkloader-reactor/gradle/gradle-daemon-jvm.properties b/examples/coalescing-bulkloader-reactor/gradle/gradle-daemon-jvm.properties index 858feb7e38..63e5bbdf48 100644 --- a/examples/coalescing-bulkloader-reactor/gradle/gradle-daemon-jvm.properties +++ b/examples/coalescing-bulkloader-reactor/gradle/gradle-daemon-jvm.properties @@ -1,2 +1,2 @@ #This file is generated by updateDaemonJvm -toolchainVersion=17 +toolchainVersion=21 diff --git a/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml b/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml index 850f3750e9..cb45473fce 100644 --- a/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml +++ b/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] caffeine = "3.1.8" junit = "5.11.0-M2" -reactor = "3.6.6" +reactor = "3.6.7" truth = "1.4.2" versions = "0.51.0" diff --git a/examples/coalescing-bulkloader-reactor/settings.gradle.kts b/examples/coalescing-bulkloader-reactor/settings.gradle.kts index ee91160124..e488722571 100644 --- a/examples/coalescing-bulkloader-reactor/settings.gradle.kts +++ b/examples/coalescing-bulkloader-reactor/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.17.4" + id("com.gradle.develocity") version "3.17.5" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/graal-native/settings.gradle.kts b/examples/graal-native/settings.gradle.kts index 3211bf6276..7c5d22b6d7 100644 --- a/examples/graal-native/settings.gradle.kts +++ b/examples/graal-native/settings.gradle.kts @@ -5,7 +5,7 @@ pluginManagement { } } plugins { - id("com.gradle.develocity") version "3.17.4" + id("com.gradle.develocity") version "3.17.5" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/hibernate/build.gradle.kts b/examples/hibernate/build.gradle.kts index 7708b7a02f..85b2448d36 100644 --- a/examples/hibernate/build.gradle.kts +++ b/examples/hibernate/build.gradle.kts @@ -25,15 +25,6 @@ testing.suites { } } -java.toolchain.languageVersion = JavaLanguageVersion.of( - System.getenv("JAVA_VERSION")?.toIntOrNull() ?: 17) - -tasks.withType().configureEach { - javaCompiler = javaToolchains.compilerFor { - languageVersion = java.toolchain.languageVersion - } -} - eclipse.classpath.file.beforeMerged { if (this is Classpath) { val absolutePath = layout.buildDirectory.dir("generated/sources/annotationProcessor/java/main") diff --git a/examples/hibernate/gradle/gradle-daemon-jvm.properties b/examples/hibernate/gradle/gradle-daemon-jvm.properties index 858feb7e38..63e5bbdf48 100644 --- a/examples/hibernate/gradle/gradle-daemon-jvm.properties +++ b/examples/hibernate/gradle/gradle-daemon-jvm.properties @@ -1,2 +1,2 @@ #This file is generated by updateDaemonJvm -toolchainVersion=17 +toolchainVersion=21 diff --git a/examples/hibernate/settings.gradle.kts b/examples/hibernate/settings.gradle.kts index c956e3da81..8789da6487 100644 --- a/examples/hibernate/settings.gradle.kts +++ b/examples/hibernate/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.17.4" + id("com.gradle.develocity") version "3.17.5" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/indexable/README.md b/examples/indexable/README.md new file mode 100644 index 0000000000..f5815d2f21 --- /dev/null +++ b/examples/indexable/README.md @@ -0,0 +1,82 @@ +In some scenarios, it can be useful to associate a single cache value with alternative keys. Similar +to a relational database, the cache acts as a table with a primary key, where the value is a row of +data, and unique hash indexes allow for fast retrieval using secondary keys. When the value is +updated or deleted, either explicitly or by eviction, the changes should be reflected in the key +associations. An _Indexable Cache_ provides a straightforward solution for achieving multiple unique +key lookups to a single value. + +### A simple example +In the schema below, the application needs to find a user by the row id for direct queries, by the +username during login, and by the email during a password recovery flow. + +```sql +CREATE TABLE user_info ( + id bigserial primary key, + first_name varchar(255) NOT NULL, + last_name varchar(255) NOT NULL, + email varchar(255) NOT NULL, + username varchar(255) NOT NULL, + password_hash varchar(255) NOT NULL, + created_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + modified_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP, +); +CREATE UNIQUE INDEX user_info_email_idx ON user_info (email); +CREATE UNIQUE INDEX user_info_username_idx ON user_info (username); +``` + +Java's [Data-Oriented Programming][] approach represents each lookup key as a distinct type and uses +pattern matching when loading an entry on a cache miss. + +```java +sealed interface UserKey permits UserById, UserByLogin, UserByEmail { + record UserByLogin(String login) implements UserKey {} + record UserByEmail(String email) implements UserKey {} + record UserById(long id) implements UserKey {} +} + +private User findUser(UserKey key) { + Condition condition = switch (key) { + case UserById(var id) -> USER_INFO.ID.eq(id); + case UserByLogin(var login) -> USER_INFO.USERNAME.eq(login); + case UserByEmail(var email) -> USER_INFO.EMAIL.eq(email.toLowerCase()); + }; + return db.selectFrom(USER_INFO).where(condition).fetchOneInto(User.class); +} +``` + +The cache is constructed with functions to build the indexes, the data loader, and the bounding +constraints. The value can then be queried using the typed key. + +```java +var cache = new IndexedCache.Builder() + .primaryKey(user -> new UserById(user.id())) + .addSecondaryKey(user -> new UserByLogin(user.login())) + .addSecondaryKey(user -> new UserByEmail(user.email())) + .expireAfterWrite(Duration.ofMinutes(5)) + .maximumSize(10_000) + .build(this::findUser); + +var userByEmail = cache.get(new UserByEmail("john.doe@example.com")); +var userByLogin = cache.get(new UserByLogin("john.doe")); +assertThat(userByEmail).isSameInstanceAs(userByLogin); +``` + +### How it works +The sample [IndexedCache][] combines a key-value cache with an associated mapping from the +individual keys to the entry's complete set. Consistency is maintained by acquiring the write lock +through the cache using the primary key before updating the index. This prevents race conditions +when the entry is concurrently updated and evicted, which could otherwise lead to missing or +non-resident key associations in the index. On eviction, a listener discards the keys while holding +the cache's entry lock. + +When a value is not found and must be loaded, an important performance optimization is to avoid a +cache stampede of redundant queries by performing that work once for all callers. This is +challenging when there are alternative lookup keys, as the cache is unaware of a canonical key to +lock against until the value is loaded. While using a single shared lock across all keys could solve +this, it would also penalize distinct entries by the slow loading time of preceding calls. A +[StripedLock][] provides a balanced solution by memoizing per key and using a last-write-wins policy +if the same value is loaded by concurrent calls using different keys. + +[Data-Oriented Programming]: https://inside.java/2024/05/23/dop-v1-1-introduction +[IndexedCache]: src/main/java/com/github/benmanes/caffeine/examples/indexable/IndexedCache.java +[StripedLock]: https://guava.dev/releases/snapshot-jre/api/docs/com/google/common/util/concurrent/Striped.html diff --git a/examples/indexable/build.gradle.kts b/examples/indexable/build.gradle.kts new file mode 100644 index 0000000000..4e67b3e05a --- /dev/null +++ b/examples/indexable/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + `java-library` + alias(libs.plugins.versions) +} + +dependencies { + implementation(libs.caffeine) + implementation(libs.guava) + + testImplementation(libs.junit.jupiter) + testImplementation(libs.guava.testlib) + testImplementation(libs.truth) +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +testing.suites { + val test by getting(JvmTestSuite::class) { + useJUnitJupiter() + } +} diff --git a/examples/indexable/gradle/gradle-daemon-jvm.properties b/examples/indexable/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000000..63e5bbdf48 --- /dev/null +++ b/examples/indexable/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,2 @@ +#This file is generated by updateDaemonJvm +toolchainVersion=21 diff --git a/examples/indexable/gradle/libs.versions.toml b/examples/indexable/gradle/libs.versions.toml new file mode 100644 index 0000000000..dac0a28fac --- /dev/null +++ b/examples/indexable/gradle/libs.versions.toml @@ -0,0 +1,16 @@ +[versions] +caffeine = "3.1.8" +guava = "33.2.1-jre" +junit-jupiter = "5.11.0-M2" +truth = "1.4.2" +versions = "0.51.0" + +[libraries] +caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +guava-testlib = { module = "com.google.guava:guava-testlib", version.ref = "guava" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } +truth = { module = "com.google.truth:truth", version.ref = "truth" } + +[plugins] +versions = { id = "com.github.ben-manes.versions", version.ref = "versions" } diff --git a/examples/indexable/gradle/wrapper/gradle-wrapper.jar b/examples/indexable/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e6441136f3 Binary files /dev/null and b/examples/indexable/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/indexable/gradle/wrapper/gradle-wrapper.properties b/examples/indexable/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..c9e6332d32 --- /dev/null +++ b/examples/indexable/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +validateDistributionUrl=true +zipStorePath=wrapper/dists +networkTimeout=10000 diff --git a/examples/indexable/gradlew b/examples/indexable/gradlew new file mode 100755 index 0000000000..b740cf1339 --- /dev/null +++ b/examples/indexable/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/indexable/gradlew.bat b/examples/indexable/gradlew.bat new file mode 100644 index 0000000000..25da30dbde --- /dev/null +++ b/examples/indexable/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/indexable/settings.gradle.kts b/examples/indexable/settings.gradle.kts new file mode 100644 index 0000000000..0ea2063460 --- /dev/null +++ b/examples/indexable/settings.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("com.gradle.develocity") version "3.17.5" + id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + } +} + +apply(from = "../../gradle/develocity.gradle") + +rootProject.name = "indexable" diff --git a/examples/indexable/src/main/java/com/github/benmanes/caffeine/examples/indexable/IndexedCache.java b/examples/indexable/src/main/java/com/github/benmanes/caffeine/examples/indexable/IndexedCache.java new file mode 100644 index 0000000000..67606bbacd --- /dev/null +++ b/examples/indexable/src/main/java/com/github/benmanes/caffeine/examples/indexable/IndexedCache.java @@ -0,0 +1,180 @@ +/* + * Copyright 2024 Ben Manes. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.benmanes.caffeine.examples.indexable; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static java.util.Objects.requireNonNull; + +import java.time.Duration; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.function.Function; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.Ticker; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Striped; + +/** + * A cache abstraction that allows the entry to looked up by alternative keys. This approach mirrors + * a database table where a row is stored by its primary key, it contains all of the columns that + * identify it, and the unique indexes are additional mappings defined by the column mappings. This + * class similarly stores the in the value once in the cache by its primary key and maintains a + * secondary mapping for lookups by using indexing functions to derive the keys. + * + * @author ben.manes@gmail.com (Ben Manes) + */ +public final class IndexedCache { + final ConcurrentMap> indexes; + final Function mappingFunction; + final Function> indexer; + final Striped locks; + final Cache store; + + private IndexedCache(Caffeine cacheBuilder, Function mappingFunction, + Function primary, Set> secondaries) { + this.locks = Striped.lock(1_000); + this.mappingFunction = mappingFunction; + this.indexes = new ConcurrentHashMap<>(); + this.store = cacheBuilder + .evictionListener((key, value, cause) -> + indexes.keySet().removeAll(indexes.get(key).allKeys())) + .build(); + this.indexer = value -> new Index<>(primary.apply(value), + secondaries.stream().map(indexer -> indexer.apply(value)).collect(toImmutableSet())); + } + + /** Returns the value associated with the key or {@code null} if not found. */ + public V getIfPresent(K key) { + var index = indexes.get(key); + return (index == null) ? null : store.getIfPresent(index.primaryKey()); + } + + /** + * Returns the value associated with the key, obtaining that value from the + * {@code mappingFunction} if necessary. The entire method invocation is performed atomically, so + * the function is applied at most once per key. As the value may be looked up by alternative + * keys, those function invocations may be executed in parallel and will replace any existing + * mappings when completed. + */ + public V get(K key) { + var value = getIfPresent(key); + if (value != null) { + return value; + } + + var lock = locks.get(key); + lock.lock(); + try { + value = getIfPresent(key); + if (value != null) { + return value; + } + + value = mappingFunction.apply(key); + if (value == null) { + return null; + } + + put(value); + return value; + } finally { + lock.unlock(); + } + } + + /** Associates the {@code value} with its keys, replacing the old value and keys if present. */ + public V put(V value) { + requireNonNull(value); + var index = indexer.apply(value); + return store.asMap().compute(index.primaryKey(), (key, oldValue) -> { + if (oldValue != null) { + indexes.keySet().removeAll(Sets.difference( + indexes.get(index.primaryKey()).allKeys(), index.allKeys())); + } + for (var indexKey : index.allKeys()) { + indexes.put(indexKey, index); + } + return value; + }); + } + + /** Discards any cached value and its keys. */ + public void invalidate(K key) { + var index = indexes.get(key); + if (index == null) { + return; + } + + store.asMap().computeIfPresent(index.primaryKey(), (k, v) -> { + indexes.keySet().removeAll(index.allKeys()); + return null; + }); + } + + private record Index(K primaryKey, Set secondaryKeys) { + public Set allKeys() { + return Sets.union(Set.of(primaryKey), secondaryKeys); + } + } + + /** This builder could be extended to support most cache options, but not weak keys. */ + public static final class Builder { + final Caffeine cacheBuilder; + final ImmutableSet.Builder> secondaries; + + Function primary; + + public Builder() { + cacheBuilder = Caffeine.newBuilder(); + secondaries = ImmutableSet.builder(); + } + + /** See {@link Caffeine#expireAfterWrite(Duration)}. */ + public Builder expireAfterWrite(Duration duration) { + cacheBuilder.expireAfterWrite(duration); + return this; + } + + /** See {@link Caffeine#ticker(Duration)}. */ + public Builder ticker(Ticker ticker) { + cacheBuilder.ticker(ticker); + return this; + } + + /** Adds the functions to extract the primary key. */ + public Builder primaryKey(Function primary) { + this.primary = requireNonNull(primary); + return this; + } + + /** Adds the functions to extract a secondary key. */ + public Builder addSecondaryKey(Function secondary) { + secondaries.add(requireNonNull(secondary)); + return this; + } + + public IndexedCache build(Function mappingFunction) { + requireNonNull(primary); + requireNonNull(mappingFunction); + return new IndexedCache(cacheBuilder, mappingFunction, primary, secondaries.build()); + } + } +} diff --git a/examples/indexable/src/test/java/com/github/benmanes/caffeine/examples/indexable/IndexedCacheTest.java b/examples/indexable/src/test/java/com/github/benmanes/caffeine/examples/indexable/IndexedCacheTest.java new file mode 100644 index 0000000000..2a26b3e08a --- /dev/null +++ b/examples/indexable/src/test/java/com/github/benmanes/caffeine/examples/indexable/IndexedCacheTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2024 Ben Manes. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.benmanes.caffeine.examples.indexable; + +import static com.google.common.truth.Truth.assertThat; + +import java.time.Duration; +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import com.github.benmanes.caffeine.examples.indexable.IndexedCacheTest.UserKey.UserById; +import com.github.benmanes.caffeine.examples.indexable.IndexedCacheTest.UserKey.UserByLogin; +import com.github.benmanes.caffeine.examples.indexable.IndexedCacheTest.UserKey.UserByPhone; +import com.google.common.testing.FakeTicker; + +/** + * @author ben.manes@gmail.com (Ben Manes) + */ +public final class IndexedCacheTest { + private final Set users = Set.of( + new User(1, "john.doe", "+1 (555) 555-5555"), + new User(2, "jane.doe", "+1 (777) 777-7777")); + + @Test + public void basicUsage() { + var ticker = new FakeTicker(); + var cache = new IndexedCache.Builder() + .addSecondaryKey(user -> new UserByLogin(user.login())) + .addSecondaryKey(user -> new UserByPhone(user.phone())) + .primaryKey(user -> new UserById(user.id())) + .expireAfterWrite(Duration.ofMinutes(1)) + .ticker(ticker::read) + .build(this::findUser); + + var johnById = cache.get(new UserById(1)); + assertThat(johnById).isNotNull(); + + var johnByLogin = cache.get(new UserByLogin("john.doe")); + assertThat(johnByLogin).isSameInstanceAs(johnById); + + var janeByLogin = cache.get(new UserByLogin("jane.doe")); + assertThat(janeByLogin).isNotSameInstanceAs(johnById); + + assertThat(cache.store.asMap()).hasSize(2); + assertThat(cache.indexes).hasSize(6); + + cache.invalidate(new UserByPhone("+1 (555) 555-5555")); + assertThat(cache.getIfPresent(new UserByLogin("john.doe"))).isNull(); + + assertThat(cache.store.asMap()).hasSize(1); + assertThat(cache.indexes).hasSize(3); + + ticker.advance(Duration.ofHours(1)); + cache.store.cleanUp(); + + assertThat(cache.store.asMap()).isEmpty(); + assertThat(cache.indexes).isEmpty(); + } + + /** Returns the user found in the system of record. */ + private User findUser(UserKey key) { + Predicate predicate = switch (key) { + case UserById(int id) -> user -> user.id() == id; + case UserByLogin(var login) -> user -> user.login().equals(login); + case UserByPhone(var phone) -> user -> user.phone().equals(phone); + }; + return users.stream().filter(predicate).findAny().orElse(null); + } + + sealed interface UserKey permits UserById, UserByLogin, UserByPhone { + record UserByLogin(String login) implements UserKey {} + record UserByPhone(String phone) implements UserKey {} + record UserById(int id) implements UserKey {} + } + record User(int id, String login, String phone) {} +} diff --git a/examples/resilience-failsafe/build.gradle.kts b/examples/resilience-failsafe/build.gradle.kts index ddf8b54df4..d73d73b2b3 100644 --- a/examples/resilience-failsafe/build.gradle.kts +++ b/examples/resilience-failsafe/build.gradle.kts @@ -16,12 +16,3 @@ testing.suites { useJUnitJupiter() } } - -java.toolchain.languageVersion = JavaLanguageVersion.of( - System.getenv("JAVA_VERSION")?.toIntOrNull() ?: 17) - -tasks.withType().configureEach { - javaCompiler = javaToolchains.compilerFor { - languageVersion = java.toolchain.languageVersion - } -} diff --git a/examples/resilience-failsafe/gradle/gradle-daemon-jvm.properties b/examples/resilience-failsafe/gradle/gradle-daemon-jvm.properties index 858feb7e38..63e5bbdf48 100644 --- a/examples/resilience-failsafe/gradle/gradle-daemon-jvm.properties +++ b/examples/resilience-failsafe/gradle/gradle-daemon-jvm.properties @@ -1,2 +1,2 @@ #This file is generated by updateDaemonJvm -toolchainVersion=17 +toolchainVersion=21 diff --git a/examples/resilience-failsafe/settings.gradle.kts b/examples/resilience-failsafe/settings.gradle.kts index 5fa0e06224..5e04180ef8 100644 --- a/examples/resilience-failsafe/settings.gradle.kts +++ b/examples/resilience-failsafe/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.17.4" + id("com.gradle.develocity") version "3.17.5" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/write-behind-rxjava/build.gradle.kts b/examples/write-behind-rxjava/build.gradle.kts index facadf7abb..4d63ae2538 100644 --- a/examples/write-behind-rxjava/build.gradle.kts +++ b/examples/write-behind-rxjava/build.gradle.kts @@ -16,12 +16,3 @@ testing.suites { useJUnitJupiter() } } - -java.toolchain.languageVersion = JavaLanguageVersion.of( - System.getenv("JAVA_VERSION")?.toIntOrNull() ?: 17) - -tasks.withType().configureEach { - javaCompiler = javaToolchains.compilerFor { - languageVersion = java.toolchain.languageVersion - } -} diff --git a/examples/write-behind-rxjava/gradle/gradle-daemon-jvm.properties b/examples/write-behind-rxjava/gradle/gradle-daemon-jvm.properties index 858feb7e38..63e5bbdf48 100644 --- a/examples/write-behind-rxjava/gradle/gradle-daemon-jvm.properties +++ b/examples/write-behind-rxjava/gradle/gradle-daemon-jvm.properties @@ -1,2 +1,2 @@ #This file is generated by updateDaemonJvm -toolchainVersion=17 +toolchainVersion=21 diff --git a/examples/write-behind-rxjava/settings.gradle.kts b/examples/write-behind-rxjava/settings.gradle.kts index 60f29a4afa..5cc38c5d2e 100644 --- a/examples/write-behind-rxjava/settings.gradle.kts +++ b/examples/write-behind-rxjava/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.17.4" + id("com.gradle.develocity") version "3.17.5" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties index 858feb7e38..63e5bbdf48 100644 --- a/gradle/gradle-daemon-jvm.properties +++ b/gradle/gradle-daemon-jvm.properties @@ -1,2 +1,2 @@ #This file is generated by updateDaemonJvm -toolchainVersion=17 +toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6d19ace364..44fb3b5b94 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ expiring-map = "0.5.11" fast-filter = "1.0.2" fastutil = "8.5.13" felix-framework = "7.0.5" -felix-scr = "2.2.10" +felix-scr = "2.2.12" findsecbugs = "1.13.0" flip-tables = "1.1.1" forbidden-apis = "3.7" @@ -53,7 +53,7 @@ jcache = "1.1.1" jcommander = "1.82" jctools = "4.0.5" jfreechart = "1.5.4" -jgit = "6.9.0.202403050737-r" +jgit = "6.10.0.202406032230-r" jmh-core = "1.37" jmh-plugin = "0.7.2" jmh-report = "0.9.6" @@ -86,7 +86,7 @@ snakeyaml = "2.2" sonarqube = "5.0.0.4638" spotbugs-contrib = "7.6.4" spotbugs-core = "4.8.5" -spotbugs-plugin = "6.0.15" +spotbugs-plugin = "6.0.16" stream = "2.9.8" tcache = "2.0.1" testng = "7.10.2" diff --git a/gradle/plugins/gradle/gradle-daemon-jvm.properties b/gradle/plugins/gradle/gradle-daemon-jvm.properties index 858feb7e38..63e5bbdf48 100644 --- a/gradle/plugins/gradle/gradle-daemon-jvm.properties +++ b/gradle/plugins/gradle/gradle-daemon-jvm.properties @@ -1,2 +1,2 @@ #This file is generated by updateDaemonJvm -toolchainVersion=17 +toolchainVersion=21 diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts index 0d5c5fbdc4..411c840413 100644 --- a/gradle/plugins/settings.gradle.kts +++ b/gradle/plugins/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.17.4" + id("com.gradle.develocity") version "3.17.5" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/jitpack.yml b/jitpack.yml index f57d7fe8e2..b4da4048f1 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,5 +1,5 @@ before_install: - source "$HOME/.sdkman/bin/sdkman-init.sh" - sdk update - - sdk install java 17.0.10-zulu - - sdk use java 17.0.10-zulu + - sdk install java 21.0.3-zulu + - sdk use java 21.0.3-zulu diff --git a/qodana.yaml b/qodana.yaml index 1a9455ea5c..705621c19b 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -1,4 +1,5 @@ version: "1.0" +projectJDK: "21" profile: name: qodana.recommended licenseRules: diff --git a/settings.gradle.kts b/settings.gradle.kts index cbf1fd8747..e55bfb2032 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { includeBuild("gradle/plugins") } plugins { - id("com.gradle.develocity") version "3.17.4" + id("com.gradle.develocity") version "3.17.5" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" }