Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Builds 1827776d2e Release v5.3.32 2024-02-15 13:29:32 +00:00
91 changed files with 768 additions and 2377 deletions
@@ -1,33 +0,0 @@
name: Send notification
description: Sends a Google Chat message as a notification of the job's outcome
inputs:
webhook-url:
description: 'Google Chat Webhook URL'
required: true
status:
description: 'Status of the job'
required: true
build-scan-url:
description: 'URL of the build scan to include in the notification'
run-name:
description: 'Name of the run to include in the notification'
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
runs:
using: composite
steps:
- shell: bash
run: |
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
- shell: bash
if: ${{ inputs.status == 'success' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'failure' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'cancelled' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
-34
View File
@@ -1,34 +0,0 @@
name: Backport Bot
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
push:
branches:
- '*.x'
permissions:
contents: read
jobs:
build:
permissions:
contents: read
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT: ${{ toJSON(github.event) }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
@@ -1,64 +0,0 @@
name: Build and deploy snapshot
on:
push:
branches:
- 5.3.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-deploy-snapshot:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and deploy snapshot
runs-on: ubuntu-latest
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 8
- name: Check out code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Build and publish
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Deploy
uses: spring-io/artifactory-deploy-action@v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
repository: 'libs-snapshot-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
- name: Send notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 8', github.ref_name) }}
-82
View File
@@ -1,82 +0,0 @@
name: CI
on:
push:
branches:
- 5.3.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
os:
- id: ubuntu-latest
name: Linux
java:
- version: 8
toolchain: false
- version: 17
toolchain: true
- version: 21
toolchain: true
exclude:
- os:
name: Linux
java:
version: 8
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
runs-on: ${{ matrix.os.id }}
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: |
${{ matrix.java.version }}
${{ matrix.java.toolchain && '8' || '' }}
- name: Prepare Windows runner
if: ${{ runner.os == 'Windows' }}
run: |
git config --global core.autocrlf true
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check out code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure toolchain properties
if: ${{ matrix.java.toolchain }}
shell: bash
run: |
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
- name: Build
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
run: ./gradlew check
- name: Send notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
@@ -9,5 +9,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
+1 -1
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=5.3.x)](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A5.3.x) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
+5 -7
View File
@@ -28,8 +28,8 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
mavenBom "io.netty:netty-bom:4.1.108.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.43"
mavenBom "io.netty:netty-bom:4.1.107.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.41"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.54.v20240208"
@@ -375,9 +375,8 @@ configure([rootProject] + javaProjects) { project ->
"https://tiles.apache.org/tiles-request/apidocs/",
"https://tiles.apache.org/framework/apidocs/",
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
// Temporarily commenting out Ehcache and Quartz since javadoc on JDK 8 cannot access them.
// "https://www.ehcache.org/apidocs/2.10.4/",
// "https://www.quartz-scheduler.org/api/2.3.0/",
"https://www.ehcache.org/apidocs/2.10.4/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.12.7/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.12.7/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.12.7/",
@@ -389,8 +388,7 @@ configure([rootProject] + javaProjects) { project ->
// "https://junit.org/junit5/docs/5.8.2/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
// Temporarily commenting out R2DBC since javadoc on JDK 8 cannot access it.
// "https://r2dbc.io/spec/0.8.5.RELEASE/api/",
"https://r2dbc.io/spec/0.8.5.RELEASE/api/",
// The external Javadoc link for JSR 305 must come last to ensure that types from
// JSR 250 (such as @PostConstruct) are still supported. This is due to the fact
// that JSR 250 and JSR 305 both define types in javax.annotation, which results
-2
View File
@@ -1,7 +1,5 @@
== Spring Framework Concourse pipeline
NOTE: CI is being migrated to GitHub Actions.
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x[Spring Framework 5.3.x].
+1 -1
View File
@@ -17,4 +17,4 @@ changelog:
- "type: dependency-upgrade"
contributors:
exclude:
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll"]
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:jammy-20240125
FROM ubuntu:jammy-20240111
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+1
View File
@@ -8,3 +8,4 @@ milestone: "5.3.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
task-timeout: 1h00m
+119 -13
View File
@@ -23,6 +23,14 @@ anchors:
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
[$TEXT_FILE_CONTENT]
text_file: git-repo/build/build-scan-uri.txt
silent: true
icon_emoji: ":concourse:"
username: concourse-ci
changelog-task-params: &changelog-task-params
name: generated-changelog/tag
tag: generated-changelog/tag
@@ -37,7 +45,7 @@ resource_types:
source:
<<: *docker-resource-source
repository: concourse/registry-image-resource
tag: 1.8.0
tag: 1.7.1
- name: artifactory-resource
type: registry-image
source:
@@ -56,12 +64,25 @@ resource_types:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
- name: slack-notification
type: registry-image
source:
<<: *docker-resource-source
repository: cfcommunity/slack-notification-resource
tag: latest
resources:
- name: git-repo
type: git
icon: github
source:
<<: *git-repo-resource-source
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: ci-images-git-repo
type: git
icon: github
@@ -84,6 +105,27 @@ resources:
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: repo-status-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk17-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk17-build
- name: slack-alert
type: slack-notification
icon: slack
source:
url: ((slack-webhook-url))
- name: github-pre-release
type: github-release
icon: briefcase-download-outline
@@ -118,23 +160,37 @@ jobs:
- put: ci-image
params:
image: ci-image/image.tar
- name: stage-milestone
- name: build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
trigger: true
- put: repo-status-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: build-project
image: ci-image
file: git-repo/ci/tasks/build-project.yml
privileged: true
timeout: ((task-timeout))
params:
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-build
params: { state: "success", commit: "git-repo" }
- put: artifactory-repo
params: &artifactory-params
signing_key: ((signing-key))
signing_passphrase: ((signing-passphrase))
repo: libs-staging-local
repo: libs-snapshot-local
folder: distribution-repository
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
@@ -159,9 +215,55 @@ jobs:
- "/**/spring-*-schema.zip"
properties:
"zip.type": "schema"
- put: git-repo
params:
repository: stage-git-repo
get_params:
threads: 8
- name: jdk17-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk17-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 17
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk17-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk17-build
params: { state: "success", commit: "git-repo" }
- name: stage-milestone
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
- name: promote-milestone
serial: true
plan:
@@ -202,6 +304,7 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -245,6 +348,7 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -287,6 +391,8 @@ jobs:
<<: *changelog-task-params
groups:
- name: "builds"
jobs: ["build", "jdk17-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
popd > /dev/null
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
popd > /dev/null
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check
popd > /dev/null
+1
View File
@@ -1,5 +1,6 @@
#!/bin/bash
source $(dirname $0)/common.sh
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
+1 -2
View File
@@ -26,5 +26,4 @@ run:
cat > /root/.docker/config.json <<EOF
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
EOF
build
build
+19
View File
@@ -0,0 +1,19 @@
---
platform: linux
inputs:
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-pr.sh
+22
View File
@@ -0,0 +1,22 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-project.sh
+24
View File
@@ -0,0 +1,24 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
MAIN_TOOLCHAIN:
TEST_TOOLCHAIN:
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/check-project.sh
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.8'
tag: '0.0.7'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/concourse-release-scripts
tag: '0.4.0'
tag: '0.3.4'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.34
version=5.3.32
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
+1 -1
View File
@@ -5,7 +5,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
sourceRoots.setFrom(file("src/main/kotlin"))
classpath.from(sourceSets["main"].runtimeClasspath)
externalDocumentationLink {
url.set(new URL("https://docs.spring.io/spring-framework/docs/5.3.x/javadoc-api/"))
url.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/"))
}
externalDocumentationLink {
url.set(new URL("https://projectreactor.io/docs/core/release/api/"))
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -186,10 +186,10 @@ public abstract class AopUtils {
* this method resolves bridge methods in order to retrieve attributes from
* the <i>original</i> method definition.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation
* (can be {@code null} or may not even implement the method)
* @param targetClass the target class for the current invocation.
* May be {@code null} or may not even implement the method.
* @return the specific target method, or the original method if the
* {@code targetClass} does not implement it
* {@code targetClass} doesn't implement it or is {@code null}
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,35 +17,29 @@
package org.springframework.aop.support;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.ResolvableType;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rod Johnson
* @author Chris Beams
* @author Sebastien Deleuze
* @author Juergen Hoeller
*/
class AopUtilsTests {
public class AopUtilsTests {
@Test
void testPointcutCanNeverApply() {
public void testPointcutCanNeverApply() {
class TestPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, @Nullable Class<?> clazzy) {
@@ -58,13 +52,13 @@ class AopUtilsTests {
}
@Test
void testPointcutAlwaysApplies() {
public void testPointcutAlwaysApplies() {
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), Object.class)).isTrue();
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), TestBean.class)).isTrue();
}
@Test
void testPointcutAppliesToOneMethodOnObject() {
public void testPointcutAppliesToOneMethodOnObject() {
class TestPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, @Nullable Class<?> clazz) {
@@ -84,7 +78,7 @@ class AopUtilsTests {
* that's subverted the singleton construction limitation.
*/
@Test
void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
public void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
assertThat(SerializationTestUtils.serializeAndDeserialize(MethodMatcher.TRUE)).isSameAs(MethodMatcher.TRUE);
assertThat(SerializationTestUtils.serializeAndDeserialize(ClassFilter.TRUE)).isSameAs(ClassFilter.TRUE);
assertThat(SerializationTestUtils.serializeAndDeserialize(Pointcut.TRUE)).isSameAs(Pointcut.TRUE);
@@ -94,45 +88,4 @@ class AopUtilsTests {
assertThat(SerializationTestUtils.serializeAndDeserialize(ExposeInvocationInterceptor.INSTANCE)).isSameAs(ExposeInvocationInterceptor.INSTANCE);
}
@Test
void testInvokeJoinpointUsingReflection() throws Throwable {
String name = "foo";
TestBean testBean = new TestBean(name);
Method method = ReflectionUtils.findMethod(TestBean.class, "getName");
Object result = AopUtils.invokeJoinpointUsingReflection(testBean, method, new Object[0]);
assertThat(result).isEqualTo(name);
}
@Test // gh-32365
void mostSpecificMethodBetweenJdkProxyAndTarget() throws Exception {
Class<?> proxyClass = new ProxyFactory(new WithInterface()).getProxy(getClass().getClassLoader()).getClass();
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithInterface.class);
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
}
@Test // gh-32365
void mostSpecificMethodBetweenCglibProxyAndTarget() throws Exception {
Class<?> proxyClass = new ProxyFactory(new WithoutInterface()).getProxy(getClass().getClassLoader()).getClass();
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithoutInterface.class);
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
}
interface ProxyInterface {
void handle(List<String> list);
}
static class WithInterface implements ProxyInterface {
public void handle(List<String> list) {
}
}
static class WithoutInterface {
public void handle(List<String> list) {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -166,9 +166,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
/** Map from scope identifier String to corresponding Scope. */
private final Map<String, Scope> scopes = new LinkedHashMap<>(8);
/** Application startup metrics. **/
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
/** Security context used when running with a SecurityManager. */
@Nullable
private SecurityContextProvider securityContextProvider;
@@ -183,6 +180,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<>("Prototype beans currently in creation");
/** Application startup metrics. **/
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
/**
* Create a new AbstractBeanFactory.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -600,10 +600,13 @@ class ConstructorResolver {
String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes);
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"No matching factory method found on class [" + factoryClass.getName() + "]: " +
(mbd.getFactoryBeanName() != null ? "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
(mbd.getFactoryBeanName() != null ?
"factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
"factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " +
"Check that a method with the specified name " + (minNrOfArgs > 0 ? "and arguments " : "") +
"exists and that it is " + (isStatic ? "static" : "non-static") + ".");
"Check that a method with the specified name " +
(minNrOfArgs > 0 ? "and arguments " : "") +
"exists and that it is " +
(isStatic ? "static" : "non-static") + ".");
}
else if (void.class == factoryMethodToUse.getReturnType()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -567,16 +567,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
*/
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
// Trigger destruction of dependent beans first...
Set<String> dependentBeanNames;
Set<String> dependencies;
synchronized (this.dependentBeanMap) {
// Within full synchronization in order to guarantee a disconnected Set
dependentBeanNames = this.dependentBeanMap.remove(beanName);
dependencies = this.dependentBeanMap.remove(beanName);
}
if (dependentBeanNames != null) {
if (dependencies != null) {
if (logger.isTraceEnabled()) {
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependentBeanNames);
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);
}
for (String dependentBeanName : dependentBeanNames) {
for (String dependentBeanName : dependencies) {
destroySingleton(dependentBeanName);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,6 +72,7 @@ final class ConfigurationClass {
* Create a new {@link ConfigurationClass} with the given name.
* @param metadataReader reader used to parse the underlying {@link Class}
* @param beanName must not be {@code null}
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
*/
ConfigurationClass(MetadataReader metadataReader, String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
@@ -85,10 +86,10 @@ final class ConfigurationClass {
* using the {@link Import} annotation or automatically processed as a nested
* configuration class (if importedBy is not {@code null}).
* @param metadataReader reader used to parse the underlying {@link Class}
* @param importedBy the configuration class importing this one
* @param importedBy the configuration class importing this one or {@code null}
* @since 3.1.1
*/
ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
ConfigurationClass(MetadataReader metadataReader, @Nullable ConfigurationClass importedBy) {
this.metadata = metadataReader.getAnnotationMetadata();
this.resource = metadataReader.getResource();
this.importedBy.add(importedBy);
@@ -98,6 +99,7 @@ final class ConfigurationClass {
* Create a new {@link ConfigurationClass} with the given name.
* @param clazz the underlying {@link Class} to represent
* @param beanName name of the {@code @Configuration} class bean
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
*/
ConfigurationClass(Class<?> clazz, String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
@@ -111,10 +113,10 @@ final class ConfigurationClass {
* using the {@link Import} annotation or automatically processed as a nested
* configuration class (if imported is {@code true}).
* @param clazz the underlying {@link Class} to represent
* @param importedBy the configuration class importing this one
* @param importedBy the configuration class importing this one (or {@code null})
* @since 3.1.1
*/
ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
ConfigurationClass(Class<?> clazz, @Nullable ConfigurationClass importedBy) {
this.metadata = AnnotationMetadata.introspect(clazz);
this.resource = new DescriptiveResource(clazz.getName());
this.importedBy.add(importedBy);
@@ -124,6 +126,7 @@ final class ConfigurationClass {
* Create a new {@link ConfigurationClass} with the given name.
* @param metadata the metadata for the underlying class to represent
* @param beanName name of the {@code @Configuration} class bean
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
*/
ConfigurationClass(AnnotationMetadata metadata, String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
@@ -145,12 +148,12 @@ final class ConfigurationClass {
return ClassUtils.getShortName(getMetadata().getClassName());
}
void setBeanName(@Nullable String beanName) {
void setBeanName(String beanName) {
this.beanName = beanName;
}
@Nullable
String getBeanName() {
public String getBeanName() {
return this.beanName;
}
@@ -160,7 +163,7 @@ final class ConfigurationClass {
* @since 3.1.1
* @see #getImportedBy()
*/
boolean isImported() {
public boolean isImported() {
return !this.importedBy.isEmpty();
}
@@ -194,10 +197,6 @@ final class ConfigurationClass {
this.importedResources.put(importedResource, readerClass);
}
Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
return this.importedResources;
}
void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
@@ -206,6 +205,10 @@ final class ConfigurationClass {
return this.importBeanDefinitionRegistrars;
}
Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
return this.importedResources;
}
void validate(ProblemReporter problemReporter) {
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -136,6 +136,17 @@ import org.springframework.util.ReflectionUtils;
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
/**
* The name of the {@link LifecycleProcessor} bean in the context.
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
* @since 3.0
* @see org.springframework.context.LifecycleProcessor
* @see org.springframework.context.support.DefaultLifecycleProcessor
* @see #start()
* @see #stop()
*/
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
/**
* The name of the {@link MessageSource} bean in the context.
* If none is supplied, message resolution is delegated to the parent.
@@ -156,18 +167,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
/**
* The name of the {@link LifecycleProcessor} bean in the context.
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
* @since 3.0
* @see org.springframework.context.LifecycleProcessor
* @see org.springframework.context.support.DefaultLifecycleProcessor
* @see #start()
* @see #stop()
*/
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
/**
* Boolean flag controlled by a {@code spring.spel.ignore} system property that
* instructs Spring to ignore SpEL, i.e. to not initialize the SpEL infrastructure.
@@ -571,6 +570,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
@@ -774,9 +774,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
/**
* Initialize the {@link MessageSource}.
* <p>Uses parent's {@code MessageSource} if none defined in this context.
* @see #MESSAGE_SOURCE_BEAN_NAME
* Initialize the MessageSource.
* Use parent's if none defined in this context.
*/
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
@@ -808,9 +807,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
/**
* Initialize the {@link ApplicationEventMulticaster}.
* <p>Uses {@link SimpleApplicationEventMulticaster} if none defined in the context.
* @see #APPLICATION_EVENT_MULTICASTER_BEAN_NAME
* Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context.
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
protected void initApplicationEventMulticaster() {
@@ -833,16 +831,15 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
/**
* Initialize the {@link LifecycleProcessor}.
* <p>Uses {@link DefaultLifecycleProcessor} if none defined in the context.
* @since 3.0
* @see #LIFECYCLE_PROCESSOR_BEAN_NAME
* Initialize the LifecycleProcessor.
* Uses DefaultLifecycleProcessor if none defined in the context.
* @see org.springframework.context.support.DefaultLifecycleProcessor
*/
protected void initLifecycleProcessor() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
this.lifecycleProcessor = beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
this.lifecycleProcessor =
beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
if (logger.isTraceEnabled()) {
logger.trace("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,6 +133,11 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
* execution callback (which may be a wrapper around the user-supplied task).
* <p>The primary use case is to set some execution context around the task's
* invocation, or to provide some monitoring/statistics for task execution.
* <p><b>NOTE:</b> Exception handling in {@code TaskDecorator} implementations
* is limited to plain {@code Runnable} execution via {@code execute} calls.
* In case of {@code #submit} calls, the exposed {@code Runnable} will be a
* {@code FutureTask} which does not propagate any exceptions; you might
* have to cast it and call {@code Future#get} to evaluate exceptions.
* @since 4.3
*/
public final void setTaskDecorator(TaskDecorator taskDecorator) {
@@ -173,10 +178,11 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
}
private TaskExecutorAdapter getAdaptedExecutor(Executor originalExecutor) {
TaskExecutorAdapter adapter =
(managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(originalExecutor) ?
new ManagedTaskExecutorAdapter(originalExecutor) : new TaskExecutorAdapter(originalExecutor));
private TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
if (managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(concurrentExecutor)) {
return new ManagedTaskExecutorAdapter(concurrentExecutor);
}
TaskExecutorAdapter adapter = new TaskExecutorAdapter(concurrentExecutor);
if (this.taskDecorator != null) {
adapter.setTaskDecorator(this.taskDecorator);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -177,7 +177,6 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "Clock must not be null");
this.clock = clock;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,9 +45,8 @@ import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
/**
* A standard implementation of Spring's {@link TaskScheduler} interface, wrapping
* a native {@link java.util.concurrent.ScheduledThreadPoolExecutor} and providing
* all applicable configuration options for it.
* Implementation of Spring's {@link TaskScheduler} interface, wrapping
* a native {@link java.util.concurrent.ScheduledThreadPoolExecutor}.
*
* @author Juergen Hoeller
* @author Mark Fisher
@@ -155,7 +154,6 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "Clock must not be null");
this.clock = clock;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -268,7 +268,6 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
scanner.scan("org.springframework.context.annotation2");
assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
.withMessageContaining("myNamedDao")
.withMessageContaining(NamedStubDao.class.getName())
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,8 @@
package org.springframework.scheduling.concurrent;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -52,8 +52,8 @@ class ConcurrentTaskExecutorTests extends AbstractSchedulingTaskExecutorTests {
@AfterEach
void shutdownExecutor() {
for (Runnable task : concurrentExecutor.shutdownNow()) {
if (task instanceof Future) {
((Future<?>) task).cancel(true);
if (task instanceof RunnableFuture) {
((RunnableFuture<?>) task).cancel(true);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Sam Brannen
* @since 3.0
*/
class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests {
public class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests {
private final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
@@ -97,7 +97,7 @@ class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests {
}
@Test
void scheduleOneTimeFailingTaskWithoutErrorHandler() {
void scheduleOneTimeFailingTaskWithoutErrorHandler() throws Exception {
TestTask task = new TestTask(this.testName, 0);
Future<?> future = scheduler.schedule(task, new Date());
assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> future.get(1000, TimeUnit.MILLISECONDS));
@@ -149,7 +149,7 @@ class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests {
catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
assertThat(latch.getCount()).as("latch did not count down").isEqualTo(0);
assertThat(latch.getCount()).as("latch did not count down,").isEqualTo(0);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,6 @@ import org.springframework.util.ReflectionUtils;
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.2.3
*/
public final class MethodIntrospector {
@@ -76,7 +75,6 @@ public final class MethodIntrospector {
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || bridgedMethod == method ||
bridgedMethod.equals(specificMethod) || bridgedMethod.equals(method) ||
metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -98,9 +98,7 @@ public final class ReactiveTypeDescriptor {
*/
public Object getEmptyValue() {
Assert.state(this.emptySupplier != null, "Empty values not supported");
Object emptyValue = this.emptySupplier.get();
Assert.notNull(emptyValue, "Invalid null return value from emptySupplier");
return emptyValue;
return this.emptySupplier.get();
}
/**
@@ -132,7 +130,7 @@ public final class ReactiveTypeDescriptor {
/**
* Descriptor for a reactive type that can produce {@code 0..N} values.
* Descriptor for a reactive type that can produce 0..N values.
* @param type the reactive type
* @param emptySupplier a supplier of an empty-value instance of the reactive type
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -750,7 +750,7 @@ public class ResolvableType implements Serializable {
* Convenience method that will {@link #getGenerics() get} and
* {@link #resolve() resolve} generic parameters.
* @return an array of resolved generic parameters (the resulting array
* will never be {@code null}, but it may contain {@code null} elements)
* will never be {@code null}, but it may contain {@code null} elements})
* @see #getGenerics()
* @see #resolve()
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -95,19 +95,20 @@ public interface Decoder<T> {
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) throws DecodingException {
CompletableFuture<T> future = decodeToMono(Mono.just(buffer), targetType, mimeType, hints).toFuture();
Assert.state(future.isDone(), "DataBuffer decoding should have completed");
Assert.state(future.isDone(), "DataBuffer decoding should have completed.");
Throwable failure;
try {
return future.get();
}
catch (ExecutionException ex) {
Throwable cause = ex.getCause();
throw (cause instanceof CodecException ? (CodecException) cause :
new DecodingException("Failed to decode: " + (cause != null ? cause.getMessage() : ex), cause));
failure = ex.getCause();
}
catch (InterruptedException ex) {
throw new DecodingException("Interrupted during decode", ex);
failure = ex;
}
throw (failure instanceof CodecException ? (CodecException) failure :
new DecodingException("Failed to decode: " + failure.getMessage(), failure));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,11 +76,10 @@ public class ResourceDecoder extends AbstractDataBufferDecoder<Resource> {
}
Class<?> clazz = elementType.toClass();
String filename = (hints != null ? (String) hints.get(FILENAME_HINT) : null);
String filename = hints != null ? (String) hints.get(FILENAME_HINT) : null;
if (clazz == InputStreamResource.class) {
return new InputStreamResource(new ByteArrayInputStream(bytes)) {
@Override
@Nullable
public String getFilename() {
return filename;
}
@@ -93,7 +92,6 @@ public class ResourceDecoder extends AbstractDataBufferDecoder<Resource> {
else if (Resource.class.isAssignableFrom(clazz)) {
return new ByteArrayResource(bytes) {
@Override
@Nullable
public String getFilename() {
return filename;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,6 +52,8 @@ import org.springframework.util.ObjectUtils;
@SuppressWarnings("serial")
public class TypeDescriptor implements Serializable {
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
private static final Map<Class<?>, TypeDescriptor> commonTypesCache = new HashMap<>(32);
private static final Class<?>[] CACHED_COMMON_TYPES = {
@@ -82,7 +84,7 @@ public class TypeDescriptor implements Serializable {
public TypeDescriptor(MethodParameter methodParameter) {
this.resolvableType = ResolvableType.forMethodParameter(methodParameter);
this.type = this.resolvableType.resolve(methodParameter.getNestedParameterType());
this.annotatedElement = AnnotatedElementAdapter.from(methodParameter.getParameterIndex() == -1 ?
this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ?
methodParameter.getMethodAnnotations() : methodParameter.getParameterAnnotations());
}
@@ -94,7 +96,7 @@ public class TypeDescriptor implements Serializable {
public TypeDescriptor(Field field) {
this.resolvableType = ResolvableType.forField(field);
this.type = this.resolvableType.resolve(field.getType());
this.annotatedElement = AnnotatedElementAdapter.from(field.getAnnotations());
this.annotatedElement = new AnnotatedElementAdapter(field.getAnnotations());
}
/**
@@ -107,7 +109,7 @@ public class TypeDescriptor implements Serializable {
Assert.notNull(property, "Property must not be null");
this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter());
this.type = this.resolvableType.resolve(property.getType());
this.annotatedElement = AnnotatedElementAdapter.from(property.getAnnotations());
this.annotatedElement = new AnnotatedElementAdapter(property.getAnnotations());
}
/**
@@ -123,7 +125,7 @@ public class TypeDescriptor implements Serializable {
public TypeDescriptor(ResolvableType resolvableType, @Nullable Class<?> type, @Nullable Annotation[] annotations) {
this.resolvableType = resolvableType;
this.type = (type != null ? type : resolvableType.toClass());
this.annotatedElement = AnnotatedElementAdapter.from(annotations);
this.annotatedElement = new AnnotatedElementAdapter(annotations);
}
@@ -732,26 +734,18 @@ public class TypeDescriptor implements Serializable {
* @see AnnotatedElementUtils#isAnnotated(AnnotatedElement, Class)
* @see AnnotatedElementUtils#getMergedAnnotation(AnnotatedElement, Class)
*/
private static final class AnnotatedElementAdapter implements AnnotatedElement, Serializable {
private static final AnnotatedElementAdapter EMPTY = new AnnotatedElementAdapter(new Annotation[0]);
private class AnnotatedElementAdapter implements AnnotatedElement, Serializable {
@Nullable
private final Annotation[] annotations;
private AnnotatedElementAdapter(Annotation[] annotations) {
public AnnotatedElementAdapter(@Nullable Annotation[] annotations) {
this.annotations = annotations;
}
private static AnnotatedElementAdapter from(@Nullable Annotation[] annotations) {
if (annotations == null || annotations.length == 0) {
return EMPTY;
}
return new AnnotatedElementAdapter(annotations);
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
for (Annotation annotation : this.annotations) {
for (Annotation annotation : getAnnotations()) {
if (annotation.annotationType() == annotationClass) {
return true;
}
@@ -763,7 +757,7 @@ public class TypeDescriptor implements Serializable {
@Nullable
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
for (Annotation annotation : this.annotations) {
for (Annotation annotation : getAnnotations()) {
if (annotation.annotationType() == annotationClass) {
return (T) annotation;
}
@@ -773,7 +767,7 @@ public class TypeDescriptor implements Serializable {
@Override
public Annotation[] getAnnotations() {
return (isEmpty() ? this.annotations : this.annotations.clone());
return (this.annotations != null ? this.annotations.clone() : EMPTY_ANNOTATION_ARRAY);
}
@Override
@@ -782,7 +776,7 @@ public class TypeDescriptor implements Serializable {
}
public boolean isEmpty() {
return (this.annotations.length == 0);
return ObjectUtils.isEmpty(this.annotations);
}
@Override
@@ -798,7 +792,7 @@ public class TypeDescriptor implements Serializable {
@Override
public String toString() {
return Arrays.toString(this.annotations);
return TypeDescriptor.this.toString();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -138,7 +138,7 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
@Nullable
private static Executable getValidatedExecutable(Class<?> targetClass, Class<?> sourceClass) {
Executable executable = conversionExecutableCache.get(targetClass);
if (executable != null && isApplicable(executable, sourceClass)) {
if (isApplicable(executable, sourceClass)) {
return executable;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ import org.springframework.util.Assert;
*/
public abstract class DataBufferUtils {
private static final Log logger = LogFactory.getLog(DataBufferUtils.class);
private final static Log logger = LogFactory.getLog(DataBufferUtils.class);
private static final Consumer<DataBuffer> RELEASE_CONSUMER = DataBufferUtils::release;
@@ -728,7 +728,7 @@ public abstract class DataBufferUtils {
*/
private static class SingleByteMatcher implements NestedMatcher {
static final SingleByteMatcher NEWLINE_MATCHER = new SingleByteMatcher(new byte[] {10});
static SingleByteMatcher NEWLINE_MATCHER = new SingleByteMatcher(new byte[] {10});
private final byte[] delimiter;
@@ -767,7 +767,7 @@ public abstract class DataBufferUtils {
/**
* Base class for a {@link NestedMatcher}.
*/
private abstract static class AbstractNestedMatcher implements NestedMatcher {
private static abstract class AbstractNestedMatcher implements NestedMatcher {
private final byte[] delimiter;
@@ -1005,11 +1005,11 @@ public abstract class DataBufferUtils {
}
@Override
public void failed(Throwable ex, DataBuffer dataBuffer) {
public void failed(Throwable exc, DataBuffer dataBuffer) {
release(dataBuffer);
closeChannel(this.channel);
this.state.set(State.DISPOSED);
this.sink.error(ex);
this.sink.error(exc);
}
private enum State {
@@ -1064,6 +1064,7 @@ public abstract class DataBufferUtils {
public Context currentContext() {
return Context.of(this.sink.contextView());
}
}
@@ -1144,9 +1145,9 @@ public abstract class DataBufferUtils {
}
@Override
public void failed(Throwable ex, ByteBuffer byteBuffer) {
public void failed(Throwable exc, ByteBuffer byteBuffer) {
sinkDataBuffer();
this.sink.error(ex);
this.sink.error(exc);
}
private void sinkDataBuffer() {
@@ -1160,6 +1161,7 @@ public abstract class DataBufferUtils {
public Context currentContext() {
return Context.of(this.sink.contextView());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -496,7 +496,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
String rootDirPath = determineRootDir(locationPattern);
String subPattern = locationPattern.substring(rootDirPath.length());
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<>(64);
Set<Resource> result = new LinkedHashSet<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
@@ -648,7 +648,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
Set<Resource> result = new LinkedHashSet<>(64);
Set<Resource> result = new LinkedHashSet<>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
@@ -864,7 +864,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private final String rootPath;
private final Set<Resource> resources = new LinkedHashSet<>(64);
private final Set<Resource> resources = new LinkedHashSet<>();
public PatternVirtualFileVisitor(String rootPath, String subPattern, PathMatcher pathMatcher) {
this.subPattern = subPattern;
@@ -895,6 +895,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
else if ("toString".equals(methodName)) {
return toString();
}
throw new IllegalStateException("Unexpected method invocation: " + method);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,6 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.function.Supplier;
import org.springframework.lang.Nullable;
/**
* Default "no op" {@code ApplicationStartup} implementation.
*
@@ -54,7 +52,6 @@ class DefaultApplicationStartup implements ApplicationStartup {
}
@Override
@Nullable
public Long getParentId() {
return null;
}
@@ -76,6 +73,7 @@ class DefaultApplicationStartup implements ApplicationStartup {
@Override
public void end() {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,10 +21,10 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import org.springframework.core.metrics.StartupStep;
import org.springframework.lang.NonNull;
/**
* {@link StartupStep} implementation for the Java Flight Recorder.
*
* <p>This variant delegates to a {@link FlightRecorderStartupEvent JFR event extension}
* to collect and record data in Java Flight Recorder.
*
@@ -114,12 +114,12 @@ class FlightRecorderStartupStep implements StartupStep {
add(key, value.get());
}
@NonNull
@Override
public Iterator<Tag> iterator() {
return new TagsIterator();
}
private class TagsIterator implements Iterator<Tag> {
private int idx = 0;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -73,20 +73,20 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
// Optimization to avoid creating ClassReader for superclass.
Boolean superClassMatch = matchSuperClass(superClassName);
if (superClassMatch != null) {
if (superClassMatch) {
if (superClassMatch.booleanValue()) {
return true;
}
}
else {
// Need to read superclass to determine a match...
try {
if (match(superClassName, metadataReaderFactory)) {
if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
return true;
}
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not read superclass [" + superClassName +
logger.debug("Could not read superclass [" + metadata.getSuperClassName() +
"] of type-filtered class [" + metadata.getClassName() + "]");
}
}
@@ -99,7 +99,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
// Optimization to avoid creating ClassReader for superclass
Boolean interfaceMatch = matchInterface(ifc);
if (interfaceMatch != null) {
if (interfaceMatch) {
if (interfaceMatch.booleanValue()) {
return true;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,8 +44,7 @@ import org.springframework.lang.Nullable;
/**
* Miscellaneous {@code java.lang.Class} utility methods.
*
* <p>Mainly for internal use within the framework.
* Mainly for internal use within the framework.
*
* @author Juergen Hoeller
* @author Keith Donald
@@ -244,7 +243,7 @@ public abstract class ClassUtils {
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
* @param name the name of the Class
* @param classLoader the class loader to use
* (can be {@code null}, which indicates the default class loader)
* (may be {@code null}, which indicates the default class loader)
* @return a class instance for the supplied name
* @throws ClassNotFoundException if the class was not found
* @throws LinkageError if the class file could not be loaded
@@ -315,7 +314,7 @@ public abstract class ClassUtils {
* the exceptions thrown in case of class loading failure.
* @param className the name of the Class
* @param classLoader the class loader to use
* (can be {@code null}, which indicates the default class loader)
* (may be {@code null}, which indicates the default class loader)
* @return a class instance for the supplied name
* @throws IllegalArgumentException if the class name was not resolvable
* (that is, the class could not be found or the class file could not be loaded)
@@ -349,7 +348,7 @@ public abstract class ClassUtils {
* one of its dependencies is not present or cannot be loaded.
* @param className the name of the class to check
* @param classLoader the class loader to use
* (can be {@code null} which indicates the default class loader)
* (may be {@code null} which indicates the default class loader)
* @return whether the specified class is present (including all of its
* superclasses and interfaces)
* @throws IllegalStateException if the corresponding class is resolvable but
@@ -376,7 +375,7 @@ public abstract class ClassUtils {
* Check whether the given class is visible in the given ClassLoader.
* @param clazz the class to check (typically an interface)
* @param classLoader the ClassLoader to check against
* (can be {@code null} in which case this method will always return {@code true})
* (may be {@code null} in which case this method will always return {@code true})
*/
public static boolean isVisible(Class<?> clazz, @Nullable ClassLoader classLoader) {
if (classLoader == null) {
@@ -400,7 +399,7 @@ public abstract class ClassUtils {
* i.e. whether it is loaded by the given ClassLoader or a parent of it.
* @param clazz the class to analyze
* @param classLoader the ClassLoader to potentially cache metadata in
* (can be {@code null} which indicates the system class loader)
* (may be {@code null} which indicates the system class loader)
*/
public static boolean isCacheSafe(Class<?> clazz, @Nullable ClassLoader classLoader) {
Assert.notNull(clazz, "Class must not be null");
@@ -540,10 +539,9 @@ public abstract class ClassUtils {
* Check if the right-hand side type may be assigned to the left-hand side
* type, assuming setting by reflection. Considers primitive wrapper
* classes as assignable to the corresponding primitive types.
* @param lhsType the target type (left-hand side (LHS) type)
* @param rhsType the value type (right-hand side (RHS) type) that should
* be assigned to the target type
* @return {@code true} if {@code rhsType} is assignable to {@code lhsType}
* @param lhsType the target type
* @param rhsType the value type that should be assigned to the target type
* @return if the target type is assignable from the value type
* @see TypeUtils#isAssignable(java.lang.reflect.Type, java.lang.reflect.Type)
*/
public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
@@ -664,7 +662,7 @@ public abstract class ClassUtils {
* in the given collection.
* <p>Basically like {@code AbstractCollection.toString()}, but stripping
* the "class "/"interface " prefix before every class name.
* @param classes a Collection of Class objects (can be {@code null})
* @param classes a Collection of Class objects (may be {@code null})
* @return a String of form "[com.foo.Bar, com.foo.Baz]"
* @see java.util.AbstractCollection#toString()
*/
@@ -719,7 +717,7 @@ public abstract class ClassUtils {
* <p>If the class itself is an interface, it gets returned as sole interface.
* @param clazz the class to analyze for interfaces
* @param classLoader the ClassLoader that the interfaces need to be visible in
* (can be {@code null} when accepting all declared interfaces)
* (may be {@code null} when accepting all declared interfaces)
* @return all interfaces that the given object implements as an array
*/
public static Class<?>[] getAllInterfacesForClass(Class<?> clazz, @Nullable ClassLoader classLoader) {
@@ -754,7 +752,7 @@ public abstract class ClassUtils {
* <p>If the class itself is an interface, it gets returned as sole interface.
* @param clazz the class to analyze for interfaces
* @param classLoader the ClassLoader that the interfaces need to be visible in
* (can be {@code null} when accepting all declared interfaces)
* (may be {@code null} when accepting all declared interfaces)
* @return all interfaces that the given object implements as a Set
*/
public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz, @Nullable ClassLoader classLoader) {
@@ -868,9 +866,9 @@ public abstract class ClassUtils {
/**
* Check whether the given object is a CGLIB proxy.
* @param object the object to check
* @see #isCglibProxyClass(Class)
* @see org.springframework.aop.support.AopUtils#isCglibProxy(Object)
* @deprecated as of 5.2, in favor of custom (possibly narrower) checks
* such as for a Spring AOP proxy
*/
@Deprecated
public static boolean isCglibProxy(Object object) {
@@ -880,9 +878,8 @@ public abstract class ClassUtils {
/**
* Check whether the specified class is a CGLIB-generated class.
* @param clazz the class to check
* @see #getUserClass(Class)
* @see #isCglibProxyClassName(String)
* @deprecated as of 5.2, in favor of custom (possibly narrower) checks
* or simply a check for containing {@link #CGLIB_CLASS_SEPARATOR}
*/
@Deprecated
public static boolean isCglibProxyClass(@Nullable Class<?> clazz) {
@@ -892,9 +889,7 @@ public abstract class ClassUtils {
/**
* Check whether the specified class name is a CGLIB-generated class.
* @param className the class name to check
* @see #CGLIB_CLASS_SEPARATOR
* @deprecated as of 5.2, in favor of custom (possibly narrower) checks
* or simply a check for containing {@link #CGLIB_CLASS_SEPARATOR}
*/
@Deprecated
public static boolean isCglibProxyClassName(@Nullable String className) {
@@ -918,7 +913,6 @@ public abstract class ClassUtils {
* class, but the original class in case of a CGLIB-generated subclass.
* @param clazz the class to check
* @return the user-defined class
* @see #CGLIB_CLASS_SEPARATOR
*/
public static Class<?> getUserClass(Class<?> clazz) {
if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
@@ -1071,7 +1065,7 @@ public abstract class ClassUtils {
* fully qualified interface/class name + "." + method name.
* @param method the method
* @param clazz the clazz that the method is being invoked on
* (can be {@code null} to indicate the method's declaring class)
* (may be {@code null} to indicate the method's declaring class)
* @return the qualified name of the method
* @since 4.3.4
*/
@@ -1152,7 +1146,7 @@ public abstract class ClassUtils {
* @param clazz the clazz to analyze
* @param methodName the name of the method
* @param paramTypes the parameter types of the method
* (can be {@code null} to indicate any signature)
* (may be {@code null} to indicate any signature)
* @return the method (never {@code null})
* @throws IllegalStateException if the method has not been found
* @see Class#getMethod
@@ -1191,7 +1185,7 @@ public abstract class ClassUtils {
* @param clazz the clazz to analyze
* @param methodName the name of the method
* @param paramTypes the parameter types of the method
* (can be {@code null} to indicate any signature)
* (may be {@code null} to indicate any signature)
* @return the method, or {@code null} if not found
* @see Class#getMethod
*/
@@ -1267,27 +1261,26 @@ public abstract class ClassUtils {
/**
* Given a method, which may come from an interface, and a target class used
* in the current reflective invocation, find the corresponding target method
* if there is one &mdash; for example, the method may be {@code IFoo.bar()},
* and the target class may be {@code DefaultFoo}. In this case, the method may be
* if there is one. E.g. the method may be {@code IFoo.bar()} and the
* target class may be {@code DefaultFoo}. In this case, the method may be
* {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
* <p><b>NOTE:</b> In contrast to {@link org.springframework.aop.support.AopUtils#getMostSpecificMethod},
* this method does <i>not</i> resolve bridge methods automatically.
* Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod}
* if bridge method resolution is desirable &mdash; for example, to obtain
* metadata from the original method definition.
* <p><b>NOTE:</b> If Java security settings disallow reflective access &mdash;
* for example, calls to {@code Class#getDeclaredMethods}, etc. &mdash; this
* implementation will fall back to returning the originally provided method.
* if bridge method resolution is desirable (e.g. for obtaining metadata from
* the original method definition).
* <p><b>NOTE:</b> Since Spring 3.1.1, if Java security settings disallow reflective
* access (e.g. calls to {@code Class#getDeclaredMethods} etc, this implementation
* will fall back to returning the originally provided method.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation
* (can be {@code null} or may not even implement the method)
* (may be {@code null} or may not even implement the method)
* @return the specific target method, or the original method if the
* {@code targetClass} does not implement it
* @see #getInterfaceMethodIfPossible(Method, Class)
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
if (targetClass != null && targetClass != method.getDeclaringClass() &&
(isOverridable(method, targetClass) || !method.getDeclaringClass().isAssignableFrom(targetClass))) {
if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) {
try {
if (Modifier.isPublic(method.getModifiers())) {
try {
@@ -1,112 +0,0 @@
/*
* Copyright 2002-2024 the original author or 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.
*/
package org.springframework.core;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodIntrospector.MetadataLookup;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.TYPE_HIERARCHY;
/**
* Tests for {@link MethodIntrospector}.
*
* @author Sam Brannen
* @since 5.3.34
*/
class MethodIntrospectorTests {
@Test // gh-32586
void selectMethodsAndClearDeclaredMethodsCacheBetweenInvocations() {
Class<?> targetType = ActualController.class;
// Preconditions for this use case.
assertThat(targetType).isPublic();
assertThat(targetType.getSuperclass()).isPackagePrivate();
MetadataLookup<String> metadataLookup = (MetadataLookup<String>) method -> {
if (MergedAnnotations.from(method, TYPE_HIERARCHY).isPresent(Mapped.class)) {
return method.getName();
}
return null;
};
// Start with a clean slate.
ReflectionUtils.clearCache();
// Round #1
Map<Method, String> methods = MethodIntrospector.selectMethods(targetType, metadataLookup);
assertThat(methods.values()).containsExactlyInAnyOrder("update", "delete");
// Simulate ConfigurableApplicationContext#refresh() which clears the
// ReflectionUtils#declaredMethodsCache but NOT the BridgeMethodResolver#cache.
// As a consequence, ReflectionUtils.getDeclaredMethods(...) will return a
// new set of methods that are logically equivalent to but not identical
// to (in terms of object identity) any bridged methods cached in the
// BridgeMethodResolver cache.
ReflectionUtils.clearCache();
// Round #2
methods = MethodIntrospector.selectMethods(targetType, metadataLookup);
assertThat(methods.values()).containsExactlyInAnyOrder("update", "delete");
}
@Retention(RetentionPolicy.RUNTIME)
@interface Mapped {
}
interface Controller {
void unmappedMethod();
@Mapped
void update();
@Mapped
void delete();
}
// Must NOT be public.
abstract static class AbstractController implements Controller {
@Override
public void unmappedMethod() {
}
@Override
public void delete() {
}
}
// MUST be public.
public static class ActualController extends AbstractController {
@Override
public void update() {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,14 +48,14 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
class PathMatchingResourcePatternResolverTests {
private static final String[] CLASSES_IN_CORE_IO_SUPPORT = {"EncodedResource.class",
private static final String[] CLASSES_IN_CORE_IO_SUPPORT = { "EncodedResource.class",
"LocalizedResourceHelper.class", "PathMatchingResourcePatternResolver.class", "PropertiesLoaderSupport.class",
"PropertiesLoaderUtils.class", "ResourceArrayPropertyEditor.class", "ResourcePatternResolver.class",
"ResourcePatternUtils.class", "SpringFactoriesLoader.class"};
"ResourcePatternUtils.class", "SpringFactoriesLoader.class" };
private static final String[] TEST_CLASSES_IN_CORE_IO_SUPPORT = {"PathMatchingResourcePatternResolverTests.class"};
private static final String[] TEST_CLASSES_IN_CORE_IO_SUPPORT = { "PathMatchingResourcePatternResolverTests.class" };
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATION = {"NonNull.class", "NonNullApi.class", "Nullable.class"};
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATION = { "NonNull.class", "NonNullApi.class", "Nullable.class" };
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@@ -166,7 +166,7 @@ class PathMatchingResourcePatternResolverTests {
}
@Test
void usingFileProtocolWithoutWildcardInPatternAndEndingInSlashStarStar() {
void usingFileProtocolWithoutWildcardInPatternAndEndingInSlashStarStar() {
Path testResourcesDir = Paths.get("src/test/resources").toAbsolutePath();
String pattern = String.format("file:%s/scanned-resources/**", testResourcesDir);
String pathPrefix = ".+?resources/";
@@ -294,8 +294,8 @@ class PathMatchingResourcePatternResolverTests {
}
private String getPath(Resource resource) {
// Tests fail if we use resource.getURL().getPath(). They would also fail on macOS when
// using resource.getURI().getPath() if the resource paths are not Unicode normalized.
// Tests fail if we use resouce.getURL().getPath(). They would also fail on Mac OS when
// using resouce.getURI().getPath() if the resource paths are not Unicode normalized.
//
// On the JVM, all tests should pass when using resouce.getFile().getPath(); however,
// we use FileSystemResource#getPath since this test class is sometimes run within a
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import org.springframework.dao.DataRetrievalFailureException;
/**
* Data access exception thrown when a result set did not have the correct column count,
* for example when expecting a single column but getting 0 or more than 1 column.
* for example when expecting a single column but getting 0 or more than 1 columns.
*
* @author Juergen Hoeller
* @since 2.0
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
/**
* Exception thrown when a JDBC update affects an unexpected number of rows.
* Typically, we expect an update to affect a single row, meaning it is an
* Typically we expect an update to affect a single row, meaning it's an
* error if it affects multiple rows.
*
* @author Rod Johnson
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ package org.springframework.jdbc.core;
*
* <p>This interface allows you to signal the end of a batch rather than
* having to determine the exact batch size upfront. Batch size is still
* being honored, but it is now the maximum size of the batch.
* being honored but it is now the maximum size of the batch.
*
* <p>The {@link #isBatchExhausted} method is called after each call to
* {@link #setValues} to determine whether there were some values added,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -128,8 +128,8 @@ public abstract class RdbmsOperation implements InitializingBean {
/**
* Set the maximum number of rows for this RDBMS operation. This is important
* for processing subsets of large result sets, in order to avoid reading and
* holding the entire result set in the database or in the JDBC driver.
* for processing subsets of large result sets, avoiding to read and hold
* the entire result set in the database or in the JDBC driver.
* <p>Default is -1, indicating to use the driver's default.
* @see org.springframework.jdbc.core.JdbcTemplate#setMaxRows
*/
@@ -175,7 +175,7 @@ public abstract class RdbmsOperation implements InitializingBean {
public void setUpdatableResults(boolean updatableResults) {
if (isCompiled()) {
throw new InvalidDataAccessApiUsageException(
"The updatableResults flag must be set before the operation is compiled");
"The updateableResults flag must be set before the operation is compiled");
}
this.updatableResults = updatableResults;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.jdbc.core.SqlParameter;
/**
* Superclass for object abstractions of RDBMS stored procedures.
* This class is abstract, and it is intended that subclasses will provide a typed
* This class is abstract and it is intended that subclasses will provide a typed
* method for invocation that delegates to the supplied {@link #execute} method.
*
* <p>The inherited {@link #setSql sql} property is the name of the stored procedure
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,9 +25,9 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
/**
* Registry for custom {@link SQLExceptionTranslator} instances associated with
* specific databases allowing for overriding translation based on values
* contained in the configuration file named "sql-error-codes.xml".
* Registry for custom {@link org.springframework.jdbc.support.SQLExceptionTranslator} instances associated with
* specific databases allowing for overriding translation based on values contained in the configuration file
* named "sql-error-codes.xml".
*
* @author Thomas Risberg
* @since 3.1.1
@@ -38,7 +38,7 @@ public final class CustomSQLExceptionTranslatorRegistry {
private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistry.class);
/**
* Keep track of a single instance, so we can return it to classes that request it.
* Keep track of a single instance so we can return it to classes that request it.
*/
private static final CustomSQLExceptionTranslatorRegistry instance = new CustomSQLExceptionTranslatorRegistry();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -218,7 +218,7 @@ public abstract class JdbcUtils {
return NumberUtils.convertNumberToTargetClass((Number) obj, Integer.class);
}
else {
// e.g. on Postgres: getObject returns a PGObject, but we need a String
// e.g. on Postgres: getObject returns a PGObject but we need a String
return rs.getString(index);
}
}
@@ -228,14 +228,14 @@ public abstract class JdbcUtils {
try {
return rs.getObject(index, requiredType);
}
catch (SQLFeatureNotSupportedException | AbstractMethodError ex) {
catch (AbstractMethodError err) {
logger.debug("JDBC driver does not implement JDBC 4.1 'getObject(int, Class)' method", err);
}
catch (SQLFeatureNotSupportedException ex) {
logger.debug("JDBC driver does not support JDBC 4.1 'getObject(int, Class)' method", ex);
}
catch (SQLException ex) {
if (logger.isDebugEnabled()) {
logger.debug("JDBC driver has limited support for 'getObject(int, Class)' with column type: " +
requiredType.getName(), ex);
}
logger.debug("JDBC driver has limited support for JDBC 4.1 'getObject(int, Class)' method", ex);
}
// Corresponding SQL types for JSR-310 / Joda-Time types, left up
@@ -416,14 +416,14 @@ public abstract class JdbcUtils {
}
/**
* Return whether the given JDBC driver supports JDBC batch updates.
* Return whether the given JDBC driver supports JDBC 2.0 batch updates.
* <p>Typically invoked right before execution of a given set of statements:
* to decide whether the set of SQL statements should be executed through
* the JDBC batch mechanism or simply in a traditional one-by-one fashion.
* the JDBC 2.0 batch mechanism or simply in a traditional one-by-one fashion.
* <p>Logs a warning if the "supportsBatchUpdates" methods throws an exception
* and simply returns {@code false} in that case.
* @param con the Connection to check
* @return whether JDBC batch updates are supported
* @return whether JDBC 2.0 batch updates are supported
* @see java.sql.DatabaseMetaData#supportsBatchUpdates()
*/
public static boolean supportsBatchUpdates(Connection con) {
@@ -496,8 +496,8 @@ public abstract class JdbcUtils {
/**
* Determine the column name to use. The column name is determined based on a
* lookup using ResultSetMetaData.
* <p>This method's implementation takes into account clarifications expressed
* in the JDBC 4.0 specification:
* <p>This method implementation takes into account recent clarifications
* expressed in the JDBC 4.0 specification:
* <p><i>columnLabel - the label for the column specified with the SQL AS clause.
* If the SQL AS clause was not specified, then the label is the name of the column</i>.
* @param resultSetMetaData the current meta-data to use
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,7 +67,7 @@ public class SQLErrorCodesFactory {
private static final Log logger = LogFactory.getLog(SQLErrorCodesFactory.class);
/**
* Keep track of a single instance, so we can return it to classes that request it.
* Keep track of a single instance so we can return it to classes that request it.
*/
private static final SQLErrorCodesFactory instance = new SQLErrorCodesFactory();
@@ -101,7 +101,6 @@ public class SQLErrorCodesFactory {
* @see #loadResource(String)
*/
protected SQLErrorCodesFactory() {
Map<String, SQLErrorCodes> errorCodes;
try {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -217,7 +217,7 @@ public abstract class JmsUtils {
try {
session.commit();
}
catch (javax.jms.TransactionInProgressException ex) {
catch (javax.jms.TransactionInProgressException | javax.jms.IllegalStateException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
}
@@ -232,7 +232,7 @@ public abstract class JmsUtils {
try {
session.rollback();
}
catch (javax.jms.TransactionInProgressException ex) {
catch (javax.jms.TransactionInProgressException | javax.jms.IllegalStateException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
}
@@ -253,9 +253,8 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
* @see javax.persistence.spi.PersistenceUnitInfo#getNonJtaDataSource()
* @see #setPersistenceUnitManager
*/
public void setDataSource(@Nullable DataSource dataSource) {
this.internalPersistenceUnitManager.setDataSourceLookup(
dataSource != null ? new SingleDataSourceLookup(dataSource) : null);
public void setDataSource(DataSource dataSource) {
this.internalPersistenceUnitManager.setDataSourceLookup(new SingleDataSourceLookup(dataSource));
this.internalPersistenceUnitManager.setDefaultDataSource(dataSource);
}
@@ -271,9 +270,8 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
* @see javax.persistence.spi.PersistenceUnitInfo#getJtaDataSource()
* @see #setPersistenceUnitManager
*/
public void setJtaDataSource(@Nullable DataSource jtaDataSource) {
this.internalPersistenceUnitManager.setDataSourceLookup(
jtaDataSource != null ? new SingleDataSourceLookup(jtaDataSource) : null);
public void setJtaDataSource(DataSource jtaDataSource) {
this.internalPersistenceUnitManager.setDataSourceLookup(new SingleDataSourceLookup(jtaDataSource));
this.internalPersistenceUnitManager.setDefaultJtaDataSource(jtaDataSource);
}
@@ -418,7 +416,6 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
}
@Override
@Nullable
public DataSource getDataSource() {
if (this.persistenceUnitInfo != null) {
return (this.persistenceUnitInfo.getJtaDataSource() != null ?
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,18 +28,28 @@ import javax.persistence.spi.PersistenceProvider;
* shared JPA EntityManagerFactory in a Spring application context; the
* EntityManagerFactory can then be passed to JPA-based DAOs via
* dependency injection. Note that switching to a JNDI lookup or to a
* {@link LocalContainerEntityManagerFactoryBean} definition based on the
* JPA container contract is just a matter of configuration!
* {@link LocalContainerEntityManagerFactoryBean}
* definition is just a matter of configuration!
*
* <p>Configuration settings are usually read from a {@code META-INF/persistence.xml}
* config file, residing in the class path, according to the JPA standalone bootstrap
* contract. See the Java Persistence API specification and your persistence provider
* documentation for setup details. Additionally, JPA properties can also be added
* on this FactoryBean via {@link #setJpaProperties}/{@link #setJpaPropertyMap}.
* contract. Additionally, most JPA providers will require a special VM agent
* (specified on JVM startup) that allows them to instrument application classes.
* See the Java Persistence API specification and your provider documentation
* for setup details.
*
* <p>This EntityManagerFactory bootstrap is appropriate for standalone applications
* which solely use JPA for data access. If you want to set up your persistence
* provider for an external DataSource and/or for global transactions which span
* multiple resources, you will need to either deploy it into a full Java EE
* application server and access the deployed EntityManagerFactory via JNDI,
* or use Spring's {@link LocalContainerEntityManagerFactoryBean} with appropriate
* configuration for local setup according to JPA's container contract.
*
* <p><b>Note:</b> This FactoryBean has limited configuration power in terms of
* the configuration that it is able to pass to the JPA provider. If you need
* more flexible configuration options, consider using Spring's more powerful
* what configuration it is able to pass to the JPA provider. If you need more
* flexible configuration, for example passing a Spring-managed JDBC DataSource
* to the JPA provider, consider using Spring's more powerful
* {@link LocalContainerEntityManagerFactoryBean} instead.
*
* <p><b>NOTE: Spring's JPA support requires JPA 2.1 or higher, as of Spring 5.0.</b>
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,15 +70,13 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
private boolean suppressClose;
/** Override auto-commit state?. */
@Nullable
private Boolean autoCommit;
private @Nullable Boolean autoCommit;
/** Wrapped Connection. */
private final AtomicReference<Connection> target = new AtomicReference<>();
/** Proxy Connection. */
@Nullable
private Connection connection;
private @Nullable Connection connection;
private final Mono<? extends Connection> connectionEmitter;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,6 +60,7 @@ import javax.servlet.http.Part;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
@@ -989,7 +990,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
}
private static String encodeCookies(Cookie... cookies) {
private static String encodeCookies(@NonNull Cookie... cookies) {
return Arrays.stream(cookies)
.map(c -> c.getName() + '=' + (c.getValue() == null ? "" : c.getValue()))
.collect(Collectors.joining("; "));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestContextAnnotationUtils;
@@ -279,6 +280,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
}
}
@NonNull
private ResourceDatabasePopulator createDatabasePopulator(MergedSqlConfig mergedSqlConfig) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,11 +44,10 @@ public class IncorrectUpdateSemanticsDataAccessException extends InvalidDataAcce
super(msg, cause);
}
/**
* Return whether data was updated.
* If this method returns {@code false}, there is nothing to roll back.
* <p>The default implementation always returns {@code true}.
* If this method returns false, there's nothing to roll back.
* <p>The default implementation always returns true.
* This can be overridden in subclasses.
*/
public boolean wasDataUpdated() {
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.reactive.client.ReactiveResponse;
import reactor.core.publisher.Flux;
@@ -54,29 +55,23 @@ class JettyClientHttpResponse extends AbstractClientHttpResponse {
}
private static HttpHeaders adaptHeaders(ReactiveResponse response) {
MultiValueMap<String, String> headers = (Jetty10HttpFieldsHelper.jetty10Present() ?
Jetty10HttpFieldsHelper.getHttpHeaders(response.getResponse()) :
new JettyHeadersAdapter(response.getHeaders()));
MultiValueMap<String, String> headers = new JettyHeadersAdapter(response.getHeaders());
return HttpHeaders.readOnlyHttpHeaders(headers);
}
private static MultiValueMap<String, ResponseCookie> adaptCookies(ReactiveResponse response) {
MultiValueMap<String, ResponseCookie> result = new LinkedMultiValueMap<>();
MultiValueMap<String, String> headers = adaptHeaders(response);
List<String> cookieHeader = headers.get(HttpHeaders.SET_COOKIE);
if (!CollectionUtils.isEmpty(cookieHeader)) {
cookieHeader.forEach(header ->
HttpCookie.parse(header).forEach(cookie -> result.add(cookie.getName(),
List<HttpField> cookieHeaders = response.getHeaders().getFields(HttpHeaders.SET_COOKIE);
cookieHeaders.forEach(header ->
HttpCookie.parse(header.getValue()).forEach(cookie -> result.add(cookie.getName(),
ResponseCookie.fromClientResponse(cookie.getName(), cookie.getValue())
.domain(cookie.getDomain())
.path(cookie.getPath())
.maxAge(cookie.getMaxAge())
.secure(cookie.getSecure())
.httpOnly(cookie.isHttpOnly())
.sameSite(parseSameSite(header))
.sameSite(parseSameSite(header.getValue()))
.build()))
);
}
return CollectionUtils.unmodifiableMultiValueMap(result);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -112,7 +112,6 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
}
@Override
@Nullable
protected Long getContentLength(Resource resource, @Nullable MediaType contentType) throws IOException {
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -519,7 +519,6 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
}
@Override
@Nullable
protected Long getContentLength(Object object, @Nullable MediaType contentType) throws IOException {
if (object instanceof MappingJacksonValue) {
object = ((MappingJacksonValue) object).getValue();
@@ -1,44 +0,0 @@
/*
* Copyright 2002-2024 the original author or 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.
*/
package org.springframework.web.context.request.async;
import java.io.IOException;
/**
* Raised when the response for an asynchronous request becomes unusable as
* indicated by a write failure, or a Servlet container error notification, or
* after the async request has completed.
*
* <p>The exception relies on response wrapping, and on {@code AsyncListener}
* notifications, managed by {@link StandardServletAsyncWebRequest}.
*
* @author Rossen Stoyanchev
* @since 5.3.33
*/
@SuppressWarnings("serial")
public class AsyncRequestNotUsableException extends IOException {
public AsyncRequestNotUsableException(String message) {
super(message);
}
public AsyncRequestNotUsableException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,28 +17,22 @@
package org.springframework.web.context.request.async;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.context.request.ServletWebRequest;
/**
* A Servlet implementation of {@link AsyncWebRequest}.
* A Servlet 3.0 implementation of {@link AsyncWebRequest}.
*
* <p>The servlet and all filters involved in an async request must have async
* support enabled using the Servlet API or by adding an
@@ -50,22 +44,18 @@ import org.springframework.web.context.request.ServletWebRequest;
*/
public class StandardServletAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest, AsyncListener {
private Long timeout;
private AsyncContext asyncContext;
private AtomicBoolean asyncCompleted = new AtomicBoolean();
private final List<Runnable> timeoutHandlers = new ArrayList<>();
private final List<Consumer<Throwable>> exceptionHandlers = new ArrayList<>();
private final List<Runnable> completionHandlers = new ArrayList<>();
@Nullable
private Long timeout;
@Nullable
private AsyncContext asyncContext;
private State state;
private final ReentrantLock stateLock = new ReentrantLock();
/**
* Create a new instance for the given request/response pair.
@@ -73,26 +63,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
* @param response current HTTP response
*/
public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
this(request, response, null);
}
/**
* Constructor to wrap the request and response for the current dispatch that
* also picks up the state of the last (probably the REQUEST) dispatch.
* @param request current HTTP request
* @param response current HTTP response
* @param previousRequest the existing request from the last dispatch
* @since 5.3.33
*/
StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response,
@Nullable StandardServletAsyncWebRequest previousRequest) {
super(request, new LifecycleHttpServletResponse(response));
this.state = (previousRequest != null ? previousRequest.state : State.NEW);
//noinspection DataFlowIssue
((LifecycleHttpServletResponse) getResponse()).setAsyncWebRequest(this);
super(request, response);
}
@@ -133,7 +104,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
*/
@Override
public boolean isAsyncComplete() {
return (this.state == State.COMPLETED);
return this.asyncCompleted.get();
}
@Override
@@ -143,18 +114,11 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
"in async request processing. This is done in Java code using the Servlet API " +
"or by adding \"<async-supported>true</async-supported>\" to servlet and " +
"filter declarations in web.xml.");
Assert.state(!isAsyncComplete(), "Async processing has already completed");
if (isAsyncStarted()) {
return;
}
if (this.state == State.NEW) {
this.state = State.ASYNC;
}
else {
Assert.state(this.state == State.ASYNC, "Cannot start async: [" + this.state + "]");
}
this.asyncContext = getRequest().startAsync(getRequest(), getResponse());
this.asyncContext.addListener(this);
if (this.timeout != null) {
@@ -164,10 +128,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void dispatch() {
Assert.state(this.asyncContext != null, "AsyncContext not yet initialized");
if (!this.isAsyncComplete()) {
this.asyncContext.dispatch();
}
Assert.state(this.asyncContext != null, "Cannot dispatch without an AsyncContext");
this.asyncContext.dispatch();
}
@@ -186,516 +148,14 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void onError(AsyncEvent event) throws IOException {
this.stateLock.lock();
try {
this.state = State.ERROR;
Throwable ex = event.getThrowable();
this.exceptionHandlers.forEach(consumer -> consumer.accept(ex));
}
finally {
this.stateLock.unlock();
}
this.exceptionHandlers.forEach(consumer -> consumer.accept(event.getThrowable()));
}
@Override
public void onComplete(AsyncEvent event) throws IOException {
this.stateLock.lock();
try {
this.completionHandlers.forEach(Runnable::run);
this.asyncContext = null;
this.state = State.COMPLETED;
}
finally {
this.stateLock.unlock();
}
}
/**
* Package private access for testing only.
*/
ReentrantLock stateLock() {
return this.stateLock;
}
/**
* Response wrapper to wrap the output stream with {@link LifecycleServletOutputStream}.
* @since 5.3.33
*/
private static final class LifecycleHttpServletResponse extends HttpServletResponseWrapper {
@Nullable
private StandardServletAsyncWebRequest asyncWebRequest;
@Nullable
private ServletOutputStream outputStream;
@Nullable
private PrintWriter writer;
public LifecycleHttpServletResponse(HttpServletResponse response) {
super(response);
}
public void setAsyncWebRequest(StandardServletAsyncWebRequest asyncWebRequest) {
this.asyncWebRequest = asyncWebRequest;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
int level = obtainLockAndCheckState();
try {
if (this.outputStream == null) {
Assert.notNull(this.asyncWebRequest, "Not initialized");
ServletOutputStream delegate = getResponse().getOutputStream();
this.outputStream = new LifecycleServletOutputStream(delegate, this);
}
}
catch (IOException ex) {
handleIOException(ex, "Failed to get ServletResponseOutput");
}
finally {
releaseLock(level);
}
return this.outputStream;
}
@Override
public PrintWriter getWriter() throws IOException {
int level = obtainLockAndCheckState();
try {
if (this.writer == null) {
Assert.notNull(this.asyncWebRequest, "Not initialized");
this.writer = new LifecyclePrintWriter(getResponse().getWriter(), this.asyncWebRequest);
}
}
catch (IOException ex) {
handleIOException(ex, "Failed to get PrintWriter");
}
finally {
releaseLock(level);
}
return this.writer;
}
@Override
public void flushBuffer() throws IOException {
int level = obtainLockAndCheckState();
try {
getResponse().flushBuffer();
}
catch (IOException ex) {
handleIOException(ex, "ServletResponse failed to flushBuffer");
}
finally {
releaseLock(level);
}
}
/**
* Return 0 if checks passed and lock is not needed, 1 if checks passed
* and lock is held, or raise AsyncRequestNotUsableException.
*/
private int obtainLockAndCheckState() throws AsyncRequestNotUsableException {
Assert.notNull(this.asyncWebRequest, "Not initialized");
if (this.asyncWebRequest.state == State.NEW) {
return 0;
}
this.asyncWebRequest.stateLock.lock();
if (this.asyncWebRequest.state == State.ASYNC) {
return 1;
}
this.asyncWebRequest.stateLock.unlock();
throw new AsyncRequestNotUsableException("Response not usable after " +
(this.asyncWebRequest.state == State.COMPLETED ?
"async request completion" : "response errors") + ".");
}
void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
Assert.notNull(this.asyncWebRequest, "Not initialized");
this.asyncWebRequest.state = State.ERROR;
throw new AsyncRequestNotUsableException(msg + ": " + ex.getMessage(), ex);
}
void releaseLock(int level) {
Assert.notNull(this.asyncWebRequest, "Not initialized");
if (level > 0) {
this.asyncWebRequest.stateLock.unlock();
}
}
}
/**
* Wraps a ServletOutputStream to prevent use after Servlet container onError
* notifications, and after async request completion.
* @since 5.3.33
*/
private static final class LifecycleServletOutputStream extends ServletOutputStream {
private final ServletOutputStream delegate;
private final LifecycleHttpServletResponse response;
private LifecycleServletOutputStream(ServletOutputStream delegate, LifecycleHttpServletResponse response) {
this.delegate = delegate;
this.response = response;
}
@Override
public boolean isReady() {
return this.delegate.isReady();
}
@Override
public void setWriteListener(WriteListener writeListener) {
this.delegate.setWriteListener(writeListener);
}
@Override
public void write(int b) throws IOException {
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.write(b);
}
catch (IOException ex) {
this.response.handleIOException(ex, "ServletOutputStream failed to write");
}
finally {
this.response.releaseLock(level);
}
}
public void write(byte[] buf, int offset, int len) throws IOException {
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.write(buf, offset, len);
}
catch (IOException ex) {
this.response.handleIOException(ex, "ServletOutputStream failed to write");
}
finally {
this.response.releaseLock(level);
}
}
@Override
public void flush() throws IOException {
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.flush();
}
catch (IOException ex) {
this.response.handleIOException(ex, "ServletOutputStream failed to flush");
}
finally {
this.response.releaseLock(level);
}
}
@Override
public void close() throws IOException {
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.close();
}
catch (IOException ex) {
this.response.handleIOException(ex, "ServletOutputStream failed to close");
}
finally {
this.response.releaseLock(level);
}
}
}
/**
* Wraps a PrintWriter to prevent use after Servlet container onError
* notifications, and after async request completion.
* @since 5.3.33
*/
private static final class LifecyclePrintWriter extends PrintWriter {
private final PrintWriter delegate;
private final StandardServletAsyncWebRequest asyncWebRequest;
private LifecyclePrintWriter(PrintWriter delegate, StandardServletAsyncWebRequest asyncWebRequest) {
super(delegate);
this.delegate = delegate;
this.asyncWebRequest = asyncWebRequest;
}
@Override
public void flush() {
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.flush();
}
finally {
releaseLock(level);
}
}
}
@Override
public void close() {
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.close();
}
finally {
releaseLock(level);
}
}
}
@Override
public boolean checkError() {
return this.delegate.checkError();
}
@Override
public void write(int c) {
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.write(c);
}
finally {
releaseLock(level);
}
}
}
@Override
public void write(char[] buf, int off, int len) {
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.write(buf, off, len);
}
finally {
releaseLock(level);
}
}
}
@Override
public void write(char[] buf) {
this.delegate.write(buf);
}
@Override
public void write(String s, int off, int len) {
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.write(s, off, len);
}
finally {
releaseLock(level);
}
}
}
@Override
public void write(String s) {
this.delegate.write(s);
}
/**
* Return 0 if checks passed and lock is not needed, 1 if checks passed
* and lock is held, and -1 if checks did not pass.
*/
private int tryObtainLockAndCheckState() {
if (this.asyncWebRequest.state == State.NEW) {
return 0;
}
this.asyncWebRequest.stateLock.lock();
if (this.asyncWebRequest.state == State.ASYNC) {
return 1;
}
this.asyncWebRequest.stateLock.unlock();
return -1;
}
private void releaseLock(int level) {
if (level > 0) {
this.asyncWebRequest.stateLock.unlock();
}
}
// Plain delegates
@Override
public void print(boolean b) {
this.delegate.print(b);
}
@Override
public void print(char c) {
this.delegate.print(c);
}
@Override
public void print(int i) {
this.delegate.print(i);
}
@Override
public void print(long l) {
this.delegate.print(l);
}
@Override
public void print(float f) {
this.delegate.print(f);
}
@Override
public void print(double d) {
this.delegate.print(d);
}
@Override
public void print(char[] s) {
this.delegate.print(s);
}
@Override
public void print(String s) {
this.delegate.print(s);
}
@Override
public void print(Object obj) {
this.delegate.print(obj);
}
@Override
public void println() {
this.delegate.println();
}
@Override
public void println(boolean x) {
this.delegate.println(x);
}
@Override
public void println(char x) {
this.delegate.println(x);
}
@Override
public void println(int x) {
this.delegate.println(x);
}
@Override
public void println(long x) {
this.delegate.println(x);
}
@Override
public void println(float x) {
this.delegate.println(x);
}
@Override
public void println(double x) {
this.delegate.println(x);
}
@Override
public void println(char[] x) {
this.delegate.println(x);
}
@Override
public void println(String x) {
this.delegate.println(x);
}
@Override
public void println(Object x) {
this.delegate.println(x);
}
@Override
public PrintWriter printf(String format, Object... args) {
return this.delegate.printf(format, args);
}
@Override
public PrintWriter printf(Locale l, String format, Object... args) {
return this.delegate.printf(l, format, args);
}
@Override
public PrintWriter format(String format, Object... args) {
return this.delegate.format(format, args);
}
@Override
public PrintWriter format(Locale l, String format, Object... args) {
return this.delegate.format(l, format, args);
}
@Override
public PrintWriter append(CharSequence csq) {
return this.delegate.append(csq);
}
@Override
public PrintWriter append(CharSequence csq, int start, int end) {
return this.delegate.append(csq, start, end);
}
@Override
public PrintWriter append(char c) {
return this.delegate.append(c);
}
}
/**
* Represents a state for {@link StandardServletAsyncWebRequest} to be in.
* <p><pre>
* +------ NEW
* | |
* | v
* | ASYNC ----> +
* | | |
* | v |
* +----> ERROR |
* | |
* v |
* COMPLETED <---+
* </pre>
* @since 5.3.33
*/
private enum State {
/** New request (may not start async handling). */
NEW,
/** Async handling has started. */
ASYNC,
/** ServletOutputStream failed, or onError notification received. */
ERROR,
/** onComplete notification received. */
COMPLETED
this.completionHandlers.forEach(Runnable::run);
this.asyncContext = null;
this.asyncCompleted.set(true);
}
}
@@ -22,7 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.RejectedExecutionException;
import javax.servlet.http.HttpServletRequest;
@@ -89,7 +89,12 @@ public final class WebAsyncManager {
@Nullable
private volatile Object[] concurrentResultContext;
private final AtomicReference<State> state = new AtomicReference<>(State.NOT_STARTED);
/*
* Whether the concurrentResult is an error. If such errors remain unhandled, some
* Servlet containers will call AsyncListener#onError at the end, after the ASYNC
* and/or the ERROR dispatch (Boot's case), and we need to ignore those.
*/
private volatile boolean errorHandlingInProgress;
private final Map<Object, CallableProcessingInterceptor> callableInterceptors = new LinkedHashMap<>();
@@ -121,15 +126,6 @@ public final class WebAsyncManager {
WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST));
}
/**
* Return the current {@link AsyncWebRequest}.
* @since 5.3.33
*/
@Nullable
public AsyncWebRequest getAsyncWebRequest() {
return this.asyncWebRequest;
}
/**
* Configure an AsyncTaskExecutor for use with concurrent processing via
* {@link #startCallableProcessing(Callable, Object...)}.
@@ -140,8 +136,8 @@ public final class WebAsyncManager {
}
/**
* Return whether the selected handler for the current request chose to handle
* the request asynchronously. A return value of "true" indicates concurrent
* Whether the selected handler for the current request chose to handle the
* request asynchronously. A return value of "true" indicates concurrent
* handling is under way and the response will remain open. A return value
* of "false" means concurrent handling was either not started or possibly
* that it has completed and the request was dispatched for further
@@ -152,16 +148,16 @@ public final class WebAsyncManager {
}
/**
* Return whether a result value exists as a result of concurrent handling.
* Whether a result value exists as a result of concurrent handling.
*/
public boolean hasConcurrentResult() {
return (this.concurrentResult != RESULT_NONE);
}
/**
* Get the result from concurrent handling.
* Provides access to the result from concurrent handling.
* @return an Object, possibly an {@code Exception} or {@code Throwable} if
* concurrent handling raised one
* concurrent handling raised one.
* @see #clearConcurrentResult()
*/
@Nullable
@@ -170,7 +166,8 @@ public final class WebAsyncManager {
}
/**
* Get the additional processing context saved at the start of concurrent handling.
* Provides access to additional processing context saved at the start of
* concurrent handling.
* @see #clearConcurrentResult()
*/
@Nullable
@@ -211,7 +208,7 @@ public final class WebAsyncManager {
/**
* Register a {@link CallableProcessingInterceptor} without a key.
* The key is derived from the class name and hash code.
* The key is derived from the class name and hashcode.
* @param interceptors one or more interceptors to register
*/
public void registerCallableInterceptors(CallableProcessingInterceptor... interceptors) {
@@ -234,8 +231,8 @@ public final class WebAsyncManager {
}
/**
* Register one or more {@link DeferredResultProcessingInterceptor DeferredResultProcessingInterceptors}
* without a specified key. The default key is derived from the interceptor class name and hash code.
* Register one or more {@link DeferredResultProcessingInterceptor DeferredResultProcessingInterceptors} without a specified key.
* The default key is derived from the interceptor class name and hash code.
* @param interceptors one or more interceptors to register
*/
public void registerDeferredResultInterceptors(DeferredResultProcessingInterceptor... interceptors) {
@@ -251,12 +248,6 @@ public final class WebAsyncManager {
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
*/
public void clearConcurrentResult() {
if (!this.state.compareAndSet(State.RESULT_SET, State.NOT_STARTED)) {
if (logger.isDebugEnabled()) {
logger.debug("Unexpected call to clear: [" + this.state.get() + "]");
}
return;
}
synchronized (WebAsyncManager.this) {
this.concurrentResult = RESULT_NONE;
this.concurrentResultContext = null;
@@ -297,11 +288,6 @@ public final class WebAsyncManager {
Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
if (!this.state.compareAndSet(State.NOT_STARTED, State.ASYNC_PROCESSING)) {
throw new IllegalStateException(
"Unexpected call to startCallableProcessing: [" + this.state.get() + "]");
}
Long timeout = webAsyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
@@ -312,7 +298,7 @@ public final class WebAsyncManager {
this.taskExecutor = executor;
}
else {
logExecutorWarning(this.asyncWebRequest);
logExecutorWarning();
}
List<CallableProcessingInterceptor> interceptors = new ArrayList<>();
@@ -325,7 +311,7 @@ public final class WebAsyncManager {
this.asyncWebRequest.addTimeoutHandler(() -> {
if (logger.isDebugEnabled()) {
logger.debug("Servlet container timeout notification for " + formatUri(this.asyncWebRequest));
logger.debug("Async request timeout for " + formatRequestUri());
}
Object result = interceptorChain.triggerAfterTimeout(this.asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
@@ -334,12 +320,14 @@ public final class WebAsyncManager {
});
this.asyncWebRequest.addErrorHandler(ex -> {
if (logger.isDebugEnabled()) {
logger.debug("Servlet container error notification for " + formatUri(this.asyncWebRequest) + ": " + ex);
if (!this.errorHandlingInProgress) {
if (logger.isDebugEnabled()) {
logger.debug("Async request error for " + formatRequestUri() + ": " + ex);
}
Object result = interceptorChain.triggerAfterError(this.asyncWebRequest, callable, ex);
result = (result != CallableProcessingInterceptor.RESULT_NONE ? result : ex);
setConcurrentResultAndDispatch(result);
}
Object result = interceptorChain.triggerAfterError(this.asyncWebRequest, callable, ex);
result = (result != CallableProcessingInterceptor.RESULT_NONE ? result : ex);
setConcurrentResultAndDispatch(result);
});
this.asyncWebRequest.addCompletionHandler(() ->
@@ -364,13 +352,14 @@ public final class WebAsyncManager {
});
interceptorChain.setTaskFuture(future);
}
catch (Throwable ex) {
catch (RejectedExecutionException ex) {
Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);
setConcurrentResultAndDispatch(result);
throw ex;
}
}
private void logExecutorWarning(AsyncWebRequest asyncWebRequest) {
private void logExecutorWarning() {
if (taskExecutorWarning && logger.isWarnEnabled()) {
synchronized (DEFAULT_TASK_EXECUTOR) {
AsyncTaskExecutor executor = this.taskExecutor;
@@ -382,7 +371,7 @@ public final class WebAsyncManager {
"Please, configure a TaskExecutor in the MVC config under \"async support\".\n" +
"The " + executorTypeName + " currently in use is not suitable under load.\n" +
"-------------------------------\n" +
"Request URI: '" + formatUri(asyncWebRequest) + "'\n" +
"Request URI: '" + formatRequestUri() + "'\n" +
"!!!");
taskExecutorWarning = false;
}
@@ -390,35 +379,32 @@ public final class WebAsyncManager {
}
}
private String formatRequestUri() {
HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
return request != null ? request.getRequestURI() : "servlet container";
}
private void setConcurrentResultAndDispatch(@Nullable Object result) {
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
synchronized (WebAsyncManager.this) {
if (!this.state.compareAndSet(State.ASYNC_PROCESSING, State.RESULT_SET)) {
if (logger.isDebugEnabled()) {
logger.debug("Async result already set: " +
"[" + this.state.get() + "], ignored result: " + result +
" for " + formatUri(this.asyncWebRequest));
}
if (this.concurrentResult != RESULT_NONE) {
return;
}
this.concurrentResult = result;
if (logger.isDebugEnabled()) {
logger.debug("Async result set to: " + result + " for " + formatUri(this.asyncWebRequest));
}
if (this.asyncWebRequest.isAsyncComplete()) {
if (logger.isDebugEnabled()) {
logger.debug("Async request already completed for " + formatUri(this.asyncWebRequest));
}
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Performing async dispatch for " + formatUri(this.asyncWebRequest));
}
this.asyncWebRequest.dispatch();
this.errorHandlingInProgress = (result instanceof Throwable);
}
if (this.asyncWebRequest.isAsyncComplete()) {
if (logger.isDebugEnabled()) {
logger.debug("Async result set but request already complete: " + formatRequestUri());
}
return;
}
if (logger.isDebugEnabled()) {
boolean isError = result instanceof Throwable;
logger.debug("Async " + (isError ? "error" : "result set") + ", dispatch to " + formatRequestUri());
}
this.asyncWebRequest.dispatch();
}
/**
@@ -441,11 +427,6 @@ public final class WebAsyncManager {
Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
if (!this.state.compareAndSet(State.NOT_STARTED, State.ASYNC_PROCESSING)) {
throw new IllegalStateException(
"Unexpected call to startDeferredResultProcessing: [" + this.state.get() + "]");
}
Long timeout = deferredResult.getTimeoutValue();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
@@ -459,9 +440,6 @@ public final class WebAsyncManager {
final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);
this.asyncWebRequest.addTimeoutHandler(() -> {
if (logger.isDebugEnabled()) {
logger.debug("Servlet container timeout notification for " + formatUri(this.asyncWebRequest));
}
try {
interceptorChain.triggerAfterTimeout(this.asyncWebRequest, deferredResult);
}
@@ -471,22 +449,21 @@ public final class WebAsyncManager {
});
this.asyncWebRequest.addErrorHandler(ex -> {
if (logger.isDebugEnabled()) {
logger.debug("Servlet container error notification for " + formatUri(this.asyncWebRequest));
}
try {
if (!interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex)) {
return;
if (!this.errorHandlingInProgress) {
try {
if (!interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex)) {
return;
}
deferredResult.setErrorResult(ex);
}
catch (Throwable interceptorEx) {
setConcurrentResultAndDispatch(interceptorEx);
}
deferredResult.setErrorResult(ex);
}
catch (Throwable interceptorEx) {
setConcurrentResultAndDispatch(interceptorEx);
}
});
this.asyncWebRequest.addCompletionHandler(() ->
interceptorChain.triggerAfterCompletion(this.asyncWebRequest, deferredResult));
this.asyncWebRequest.addCompletionHandler(()
-> interceptorChain.triggerAfterCompletion(this.asyncWebRequest, deferredResult));
interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult);
startAsyncProcessing(processingContext);
@@ -507,46 +484,13 @@ public final class WebAsyncManager {
synchronized (WebAsyncManager.this) {
this.concurrentResult = RESULT_NONE;
this.concurrentResultContext = processingContext;
this.errorHandlingInProgress = false;
}
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Started async request for " + formatUri(this.asyncWebRequest));
}
this.asyncWebRequest.startAsync();
}
private static String formatUri(AsyncWebRequest asyncWebRequest) {
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
return (request != null ? "\"" + request.getRequestURI() + "\"" : "servlet container");
}
/**
* Represents a state for {@link WebAsyncManager} to be in.
* <p><pre>
* NOT_STARTED <------+
* | |
* v |
* ASYNC_PROCESSING |
* | |
* v |
* RESULT_SET -------+
* </pre>
* @since 5.3.33
*/
private enum State {
/** No async processing in progress. */
NOT_STARTED,
/** Async handling has started, but the result hasn't been set yet. */
ASYNC_PROCESSING,
/** The result is set, and an async dispatch was performed, unless there is a network error. */
RESULT_SET
if (logger.isDebugEnabled()) {
logger.debug("Started async request");
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@ import org.springframework.web.context.request.NativeWebRequest;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2
* @param <V> the value type
*/
@@ -38,25 +37,18 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
private final Callable<V> callable;
@Nullable
private final Long timeout;
private Long timeout;
@Nullable
private final AsyncTaskExecutor executor;
private AsyncTaskExecutor executor;
@Nullable
private final String executorName;
private String executorName;
@Nullable
private BeanFactory beanFactory;
@Nullable
private Callable<V> timeoutCallback;
@Nullable
private Callable<V> errorCallback;
@Nullable
private Runnable completionCallback;
@@ -67,9 +59,6 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
public WebAsyncTask(Callable<V> callable) {
Assert.notNull(callable, "Callable must not be null");
this.callable = callable;
this.timeout = null;
this.executor = null;
this.executorName = null;
}
/**
@@ -78,11 +67,8 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
* @param callable the callable for concurrent handling
*/
public WebAsyncTask(long timeout, Callable<V> callable) {
Assert.notNull(callable, "Callable must not be null");
this.callable = callable;
this(callable);
this.timeout = timeout;
this.executor = null;
this.executorName = null;
}
/**
@@ -92,12 +78,10 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
* @param callable the callable for concurrent handling
*/
public WebAsyncTask(@Nullable Long timeout, String executorName, Callable<V> callable) {
Assert.notNull(callable, "Callable must not be null");
this(callable);
Assert.notNull(executorName, "Executor name must not be null");
this.callable = callable;
this.timeout = timeout;
this.executor = null;
this.executorName = executorName;
this.timeout = timeout;
}
/**
@@ -107,12 +91,10 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
* @param callable the callable for concurrent handling
*/
public WebAsyncTask(@Nullable Long timeout, AsyncTaskExecutor executor, Callable<V> callable) {
Assert.notNull(callable, "Callable must not be null");
this(callable);
Assert.notNull(executor, "Executor must not be null");
this.callable = callable;
this.timeout = timeout;
this.executor = executor;
this.executorName = null;
this.timeout = timeout;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,10 +82,7 @@ public abstract class WebAsyncUtils {
* @return an AsyncWebRequest instance (never {@code null})
*/
public static AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
AsyncWebRequest prev = getAsyncManager(request).getAsyncWebRequest();
return (prev instanceof StandardServletAsyncWebRequest ?
new StandardServletAsyncWebRequest(request, response, (StandardServletAsyncWebRequest) prev) :
new StandardServletAsyncWebRequest(request, response));
return new StandardServletAsyncWebRequest(request, response);
}
}
@@ -21,10 +21,10 @@ import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.List;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
@@ -38,13 +38,12 @@ import org.springframework.util.FastByteArrayOutputStream;
/**
* {@link javax.servlet.http.HttpServletResponse} wrapper that caches all content written to
* the {@linkplain #getOutputStream() output stream} and {@linkplain #getWriter() writer},
* and allows this content to be retrieved via a {@linkplain #getContentAsByteArray() byte array}.
* and allows this content to be retrieved via a {@link #getContentAsByteArray() byte array}.
*
* <p>Used e.g. by {@link org.springframework.web.filter.ShallowEtagHeaderFilter}.
* Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 4.1.3
* @see ContentCachingRequestWrapper
*/
@@ -61,6 +60,9 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
@Nullable
private Integer contentLength;
@Nullable
private String contentType;
/**
* Create a new ContentCachingResponseWrapper for the given servlet response.
@@ -120,16 +122,9 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
return this.writer;
}
/**
* This method neither flushes content to the client nor commits the underlying
* response, since the content has not yet been copied to the response.
* <p>Invoke {@link #copyBodyToResponse()} to copy the cached body content to
* the wrapped response object and flush its buffer.
* @see javax.servlet.ServletResponseWrapper#flushBuffer()
*/
@Override
public void flushBuffer() throws IOException {
// no-op
// do not flush the underlying response as the content has not been copied to it yet
}
@Override
@@ -147,13 +142,31 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
throw new IllegalArgumentException("Content-Length exceeds ContentCachingResponseWrapper's maximum (" +
Integer.MAX_VALUE + "): " + len);
}
setContentLength((int) len);
int lenInt = (int) len;
if (lenInt > this.content.size()) {
this.content.resize(lenInt);
}
this.contentLength = lenInt;
}
@Override
public void setContentType(String type) {
this.contentType = type;
}
@Override
@Nullable
public String getContentType() {
return this.contentType;
}
@Override
public boolean containsHeader(String name) {
if (this.contentLength != null && HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
return true;
if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
return this.contentLength != null;
}
else if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
return this.contentType != null;
}
else {
return super.containsHeader(name);
@@ -165,6 +178,9 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
this.contentLength = Integer.valueOf(value);
}
else if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
this.contentType = value;
}
else {
super.setHeader(name, value);
}
@@ -175,6 +191,9 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
this.contentLength = Integer.valueOf(value);
}
else if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
this.contentType = value;
}
else {
super.addHeader(name, value);
}
@@ -203,8 +222,11 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
@Override
@Nullable
public String getHeader(String name) {
if (this.contentLength != null && HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
return this.contentLength.toString();
if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
return (this.contentLength != null) ? this.contentLength.toString() : null;
}
else if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
return this.contentType;
}
else {
return super.getHeader(name);
@@ -213,8 +235,12 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
@Override
public Collection<String> getHeaders(String name) {
if (this.contentLength != null && HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
return Collections.singleton(this.contentLength.toString());
if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) {
return this.contentLength != null ? Collections.singleton(this.contentLength.toString()) :
Collections.emptySet();
}
else if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
return this.contentType != null ? Collections.singleton(this.contentType) : Collections.emptySet();
}
else {
return super.getHeaders(name);
@@ -224,9 +250,14 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
@Override
public Collection<String> getHeaderNames() {
Collection<String> headerNames = super.getHeaderNames();
if (this.contentLength != null) {
Set<String> result = new LinkedHashSet<>(headerNames);
result.add(HttpHeaders.CONTENT_LENGTH);
if (this.contentLength != null || this.contentType != null) {
List<String> result = new ArrayList<>(headerNames);
if (this.contentLength != null) {
result.add(HttpHeaders.CONTENT_LENGTH);
}
if (this.contentType != null) {
result.add(HttpHeaders.CONTENT_TYPE);
}
return result;
}
else {
@@ -308,6 +339,10 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
}
this.contentLength = null;
}
if (complete || this.contentType != null) {
rawResponse.setContentType(this.contentType);
this.contentType = null;
}
}
this.content.writeTo(rawResponse.getOutputStream());
this.content.reset();
@@ -73,19 +73,19 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?");
private static final String SCHEME_PATTERN = "([^:/?#\\\\]+):";
private static final String SCHEME_PATTERN = "([^:/?#]+):";
private static final String HTTP_PATTERN = "(?i)(http|https):";
private static final String USERINFO_PATTERN = "([^/?#\\\\]*)";
private static final String USERINFO_PATTERN = "([^@/?#]*)";
private static final String HOST_IPV4_PATTERN = "[^/?#:\\\\]*";
private static final String HOST_IPV4_PATTERN = "[^\\[/?#:]*";
private static final String HOST_IPV6_PATTERN = "\\[[\\p{XDigit}:.]*[%\\p{Alnum}]*]";
private static final String HOST_PATTERN = "(" + HOST_IPV6_PATTERN + "|" + HOST_IPV4_PATTERN + ")";
private static final String PORT_PATTERN = "(\\{[^}]+\\}?|[^/?#\\\\]*)";
private static final String PORT_PATTERN = "(\\{[^}]+\\}?|[^/?#]*)";
private static final String PATH_PATTERN = "([^?#]*)";
@@ -252,7 +252,9 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
builder.schemeSpecificPart(ssp);
}
else {
checkSchemeAndHost(uri, scheme, host);
if (StringUtils.hasLength(scheme) && scheme.startsWith("http") && !StringUtils.hasLength(host)) {
throw new IllegalArgumentException("[" + uri + "] is not a valid HTTP URL");
}
builder.userInfo(userInfo);
builder.host(host);
if (StringUtils.hasLength(port)) {
@@ -294,7 +296,9 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
builder.scheme(scheme != null ? scheme.toLowerCase() : null);
builder.userInfo(matcher.group(4));
String host = matcher.group(5);
checkSchemeAndHost(httpUrl, scheme, host);
if (StringUtils.hasLength(scheme) && !StringUtils.hasLength(host)) {
throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
}
builder.host(host);
String port = matcher.group(7);
if (StringUtils.hasLength(port)) {
@@ -313,15 +317,6 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
}
}
private static void checkSchemeAndHost(String uri, @Nullable String scheme, @Nullable String host) {
if (StringUtils.hasLength(scheme) && scheme.startsWith("http") && !StringUtils.hasLength(host)) {
throw new IllegalArgumentException("[" + uri + "] is not a valid HTTP URL");
}
if (StringUtils.hasLength(host) && host.startsWith("[") && !host.endsWith("]")) {
throw new IllegalArgumentException("Invalid IPV6 host in [" + uri + "]");
}
}
/**
* Create a new {@code UriComponents} object from the URI associated with
* the given HttpRequest while also overlaying with values from the headers
@@ -407,7 +402,6 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
if (StringUtils.hasLength(port)) {
builder.port(port);
}
checkSchemeAndHost(origin, scheme, host);
return builder;
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,7 +66,7 @@ public class UriTemplate implements Serializable {
* @param uriTemplate the URI template string
*/
public UriTemplate(String uriTemplate) {
Assert.notNull(uriTemplate, "'uriTemplate' must not be null");
Assert.hasText(uriTemplate, "'uriTemplate' must not be null");
this.uriTemplate = uriTemplate;
this.uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).build();
@@ -1,358 +0,0 @@
/*
* Copyright 2002-2024 the original author or 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.
*/
package org.springframework.web.context.request.async;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.AsyncEvent;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.stubbing.Answer;
import org.springframework.web.testfixture.servlet.MockAsyncContext;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.doAnswer;
import static org.mockito.BDDMockito.doThrow;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.verify;
import static org.mockito.BDDMockito.verifyNoInteractions;
/**
* {@link StandardServletAsyncWebRequest} tests related to response wrapping in
* order to enforce thread safety and prevent use after errors.
*
* @author Rossen Stoyanchev
*/
public class AsyncRequestNotUsableTests {
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final HttpServletResponse response = mock(HttpServletResponse.class);
private final ServletOutputStream outputStream = mock(ServletOutputStream.class);
private final PrintWriter writer = mock(PrintWriter.class);
private StandardServletAsyncWebRequest asyncRequest;
@BeforeEach
void setup() throws IOException {
this.request.setAsyncSupported(true);
given(this.response.getOutputStream()).willReturn(this.outputStream);
given(this.response.getWriter()).willReturn(this.writer);
this.asyncRequest = new StandardServletAsyncWebRequest(this.request, this.response);
}
@AfterEach
void tearDown() {
assertThat(this.asyncRequest.stateLock().isLocked()).isFalse();
}
@SuppressWarnings("DataFlowIssue")
private ServletOutputStream getWrappedOutputStream() throws IOException {
return this.asyncRequest.getResponse().getOutputStream();
}
@SuppressWarnings("DataFlowIssue")
private PrintWriter getWrappedWriter() throws IOException {
return this.asyncRequest.getResponse().getWriter();
}
@Nested
class ResponseTests {
@Test
void notUsableAfterError() throws IOException {
asyncRequest.startAsync();
asyncRequest.onError(new AsyncEvent(new MockAsyncContext(request, response), new Exception()));
HttpServletResponse wrapped = asyncRequest.getResponse();
assertThat(wrapped).isNotNull();
assertThatThrownBy(wrapped::getOutputStream).hasMessage("Response not usable after response errors.");
assertThatThrownBy(wrapped::getWriter).hasMessage("Response not usable after response errors.");
assertThatThrownBy(wrapped::flushBuffer).hasMessage("Response not usable after response errors.");
}
@Test
void notUsableAfterCompletion() throws IOException {
asyncRequest.startAsync();
asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(request, response)));
HttpServletResponse wrapped = asyncRequest.getResponse();
assertThat(wrapped).isNotNull();
assertThatThrownBy(wrapped::getOutputStream).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(wrapped::getWriter).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(wrapped::flushBuffer).hasMessage("Response not usable after async request completion.");
}
@Test
void notUsableWhenRecreatedAfterCompletion() throws IOException {
asyncRequest.startAsync();
asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(request, response)));
StandardServletAsyncWebRequest newWebRequest =
new StandardServletAsyncWebRequest(request, response, asyncRequest);
HttpServletResponse wrapped = newWebRequest.getResponse();
assertThat(wrapped).isNotNull();
assertThatThrownBy(wrapped::getOutputStream).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(wrapped::getWriter).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(wrapped::flushBuffer).hasMessage("Response not usable after async request completion.");
}
}
@Nested
class OutputStreamTests {
@Test
void use() throws IOException {
testUseOutputStream();
}
@Test
void useInAsyncState() throws IOException {
asyncRequest.startAsync();
testUseOutputStream();
}
private void testUseOutputStream() throws IOException {
ServletOutputStream wrapped = getWrappedOutputStream();
wrapped.write('a');
wrapped.write(new byte[0], 1, 2);
wrapped.flush();
wrapped.close();
verify(outputStream).write('a');
verify(outputStream).write(new byte[0], 1, 2);
verify(outputStream).flush();
verify(outputStream).close();
}
@Test
void notUsableAfterCompletion() throws IOException {
asyncRequest.startAsync();
ServletOutputStream wrapped = getWrappedOutputStream();
asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(request, response)));
assertThatThrownBy(() -> wrapped.write('a')).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(() -> wrapped.write(new byte[0])).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(() -> wrapped.write(new byte[0], 0, 0)).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(wrapped::flush).hasMessage("Response not usable after async request completion.");
assertThatThrownBy(wrapped::close).hasMessage("Response not usable after async request completion.");
}
@Test
void lockingNotUsed() throws IOException {
AtomicInteger count = new AtomicInteger(-1);
doAnswer((Answer<Void>) invocation -> {
count.set(asyncRequest.stateLock().getHoldCount());
return null;
}).when(outputStream).write('a');
// Access ServletOutputStream in NEW state (no async handling) without locking
getWrappedOutputStream().write('a');
assertThat(count.get()).isEqualTo(0);
}
@Test
void lockingUsedInAsyncState() throws IOException {
AtomicInteger count = new AtomicInteger(-1);
doAnswer((Answer<Void>) invocation -> {
count.set(asyncRequest.stateLock().getHoldCount());
return null;
}).when(outputStream).write('a');
// Access ServletOutputStream in ASYNC state with locking
asyncRequest.startAsync();
getWrappedOutputStream().write('a');
assertThat(count.get()).isEqualTo(1);
}
}
@Nested
class OutputStreamErrorTests {
@Test
void writeInt() throws IOException {
asyncRequest.startAsync();
ServletOutputStream wrapped = getWrappedOutputStream();
doThrow(new IOException("Broken pipe")).when(outputStream).write('a');
assertThatThrownBy(() -> wrapped.write('a')).hasMessage("ServletOutputStream failed to write: Broken pipe");
}
@Test
void writeBytesFull() throws IOException {
asyncRequest.startAsync();
ServletOutputStream wrapped = getWrappedOutputStream();
byte[] bytes = new byte[0];
doThrow(new IOException("Broken pipe")).when(outputStream).write(bytes, 0, 0);
assertThatThrownBy(() -> wrapped.write(bytes)).hasMessage("ServletOutputStream failed to write: Broken pipe");
}
@Test
void writeBytes() throws IOException {
asyncRequest.startAsync();
ServletOutputStream wrapped = getWrappedOutputStream();
byte[] bytes = new byte[0];
doThrow(new IOException("Broken pipe")).when(outputStream).write(bytes, 0, 0);
assertThatThrownBy(() -> wrapped.write(bytes, 0, 0)).hasMessage("ServletOutputStream failed to write: Broken pipe");
}
@Test
void flush() throws IOException {
asyncRequest.startAsync();
ServletOutputStream wrapped = getWrappedOutputStream();
doThrow(new IOException("Broken pipe")).when(outputStream).flush();
assertThatThrownBy(wrapped::flush).hasMessage("ServletOutputStream failed to flush: Broken pipe");
}
@Test
void close() throws IOException {
asyncRequest.startAsync();
ServletOutputStream wrapped = getWrappedOutputStream();
doThrow(new IOException("Broken pipe")).when(outputStream).close();
assertThatThrownBy(wrapped::close).hasMessage("ServletOutputStream failed to close: Broken pipe");
}
@Test
void writeErrorPreventsFurtherWriting() throws IOException {
ServletOutputStream wrapped = getWrappedOutputStream();
doThrow(new IOException("Broken pipe")).when(outputStream).write('a');
assertThatThrownBy(() -> wrapped.write('a')).hasMessage("ServletOutputStream failed to write: Broken pipe");
assertThatThrownBy(() -> wrapped.write('a')).hasMessage("Response not usable after response errors.");
}
@Test
void writeErrorInAsyncStatePreventsFurtherWriting() throws IOException {
asyncRequest.startAsync();
ServletOutputStream wrapped = getWrappedOutputStream();
doThrow(new IOException("Broken pipe")).when(outputStream).write('a');
assertThatThrownBy(() -> wrapped.write('a')).hasMessage("ServletOutputStream failed to write: Broken pipe");
assertThatThrownBy(() -> wrapped.write('a')).hasMessage("Response not usable after response errors.");
}
}
@Nested
class WriterTests {
@Test
void useWriter() throws IOException {
testUseWriter();
}
@Test
void useWriterInAsyncState() throws IOException {
asyncRequest.startAsync();
testUseWriter();
}
private void testUseWriter() throws IOException {
PrintWriter wrapped = getWrappedWriter();
wrapped.write('a');
wrapped.write(new char[0], 1, 2);
wrapped.write("abc", 1, 2);
wrapped.flush();
wrapped.close();
verify(writer).write('a');
verify(writer).write(new char[0], 1, 2);
verify(writer).write("abc", 1, 2);
verify(writer).flush();
verify(writer).close();
}
@Test
void writerNotUsableAfterCompletion() throws IOException {
asyncRequest.startAsync();
PrintWriter wrapped = getWrappedWriter();
asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(request, response)));
char[] chars = new char[0];
wrapped.write('a');
wrapped.write(chars, 1, 2);
wrapped.flush();
wrapped.close();
verifyNoInteractions(writer);
}
@Test
void lockingNotUsed() throws IOException {
AtomicInteger count = new AtomicInteger(-1);
doAnswer((Answer<Void>) invocation -> {
count.set(asyncRequest.stateLock().getHoldCount());
return null;
}).when(writer).write('a');
// Use Writer in NEW state (no async handling) without locking
PrintWriter wrapped = getWrappedWriter();
wrapped.write('a');
assertThat(count.get()).isEqualTo(0);
}
@Test
void lockingUsedInAsyncState() throws IOException {
AtomicInteger count = new AtomicInteger(-1);
doAnswer((Answer<Void>) invocation -> {
count.set(asyncRequest.stateLock().getHoldCount());
return null;
}).when(writer).write('a');
// Use Writer in ASYNC state with locking
asyncRequest.startAsync();
PrintWriter wrapped = getWrappedWriter();
wrapped.write('a');
assertThat(count.get()).isEqualTo(1);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -96,8 +96,9 @@ public class StandardServletAsyncWebRequestTests {
@Test
public void startAsyncAfterCompleted() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(this.request, this.response)));
assertThatIllegalStateException().isThrownBy(this.asyncRequest::startAsync)
.withMessage("Cannot start async: [COMPLETED]");
assertThatIllegalStateException().isThrownBy(
this.asyncRequest::startAsync)
.withMessage("Async processing has already completed");
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,255 +16,55 @@
package org.springframework.web.filter;
import java.util.stream.Stream;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.MediaType;
import org.springframework.http.HttpHeaders;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import org.springframework.web.util.ContentCachingResponseWrapper;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Named.named;
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
import static org.springframework.http.HttpHeaders.TRANSFER_ENCODING;
/**
* Unit tests for {@link ContentCachingResponseWrapper}.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class ContentCachingResponseWrapperTests {
public class ContentCachingResponseWrapperTests {
@Test
void copyBodyToResponse() throws Exception {
byte[] responseBody = "Hello World".getBytes(UTF_8);
byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
MockHttpServletResponse response = new MockHttpServletResponse();
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
responseWrapper.setStatus(HttpServletResponse.SC_CREATED);
responseWrapper.setStatus(HttpServletResponse.SC_OK);
FileCopyUtils.copy(responseBody, responseWrapper.getOutputStream());
responseWrapper.copyBodyToResponse();
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_CREATED);
assertThat(response.getContentLength()).isGreaterThan(0);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentLength() > 0).isTrue();
assertThat(response.getContentAsByteArray()).isEqualTo(responseBody);
}
@Test
void copyBodyToResponseWithPresetHeaders() throws Exception {
String PUZZLE = "puzzle";
String ENIGMA = "enigma";
String NUMBER = "number";
String MAGIC = "42";
byte[] responseBody = "Hello World".getBytes(UTF_8);
int responseLength = responseBody.length;
int originalContentLength = 999;
String contentType = MediaType.APPLICATION_JSON_VALUE;
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentType(contentType);
response.setContentLength(originalContentLength);
response.setHeader(PUZZLE, ENIGMA);
response.setIntHeader(NUMBER, 42);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
responseWrapper.setStatus(HttpServletResponse.SC_CREATED);
assertThat(responseWrapper.getStatus()).isEqualTo(HttpServletResponse.SC_CREATED);
assertThat(responseWrapper.getContentSize()).isZero();
assertThat(responseWrapper.getHeaderNames())
.containsExactlyInAnyOrder(PUZZLE, NUMBER, CONTENT_TYPE, CONTENT_LENGTH);
assertHeader(responseWrapper, PUZZLE, ENIGMA);
assertHeader(responseWrapper, NUMBER, MAGIC);
assertHeader(responseWrapper, CONTENT_LENGTH, originalContentLength);
assertContentTypeHeader(responseWrapper, contentType);
FileCopyUtils.copy(responseBody, responseWrapper.getOutputStream());
assertThat(responseWrapper.getContentSize()).isEqualTo(responseLength);
responseWrapper.copyBodyToResponse();
assertThat(responseWrapper.getStatus()).isEqualTo(HttpServletResponse.SC_CREATED);
assertThat(responseWrapper.getContentSize()).isZero();
assertThat(responseWrapper.getHeaderNames())
.containsExactlyInAnyOrder(PUZZLE, NUMBER, CONTENT_TYPE, CONTENT_LENGTH);
assertHeader(responseWrapper, PUZZLE, ENIGMA);
assertHeader(responseWrapper, NUMBER, MAGIC);
assertHeader(responseWrapper, CONTENT_LENGTH, responseLength);
assertContentTypeHeader(responseWrapper, contentType);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_CREATED);
assertThat(response.getContentLength()).isEqualTo(responseLength);
assertThat(response.getContentAsByteArray()).isEqualTo(responseBody);
assertThat(response.getHeaderNames())
.containsExactlyInAnyOrder(PUZZLE, NUMBER, CONTENT_TYPE, CONTENT_LENGTH);
assertHeader(response, PUZZLE, ENIGMA);
assertHeader(response, NUMBER, MAGIC);
assertHeader(response, CONTENT_LENGTH, responseLength);
assertContentTypeHeader(response, contentType);
}
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("setContentLengthFunctions")
void copyBodyToResponseWithOverridingContentLength(SetContentLength setContentLength) throws Exception {
byte[] responseBody = "Hello World".getBytes(UTF_8);
int responseLength = responseBody.length;
int originalContentLength = 11;
int overridingContentLength = 22;
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentLength(originalContentLength);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
responseWrapper.setContentLength(overridingContentLength);
setContentLength.invoke(responseWrapper, overridingContentLength);
assertThat(responseWrapper.getContentSize()).isZero();
assertThat(responseWrapper.getHeaderNames()).containsExactlyInAnyOrder(CONTENT_LENGTH);
assertHeader(response, CONTENT_LENGTH, originalContentLength);
assertHeader(responseWrapper, CONTENT_LENGTH, overridingContentLength);
FileCopyUtils.copy(responseBody, responseWrapper.getOutputStream());
assertThat(responseWrapper.getContentSize()).isEqualTo(responseLength);
responseWrapper.copyBodyToResponse();
assertThat(responseWrapper.getContentSize()).isZero();
assertThat(responseWrapper.getHeaderNames()).containsExactlyInAnyOrder(CONTENT_LENGTH);
assertHeader(response, CONTENT_LENGTH, responseLength);
assertHeader(responseWrapper, CONTENT_LENGTH, responseLength);
assertThat(response.getContentLength()).isEqualTo(responseLength);
assertThat(response.getContentAsByteArray()).isEqualTo(responseBody);
assertThat(response.getHeaderNames()).containsExactlyInAnyOrder(CONTENT_LENGTH);
}
private static Stream<Named<SetContentLength>> setContentLengthFunctions() {
return Stream.of(
named("setContentLength()", HttpServletResponse::setContentLength),
named("setContentLengthLong()", HttpServletResponse::setContentLengthLong),
named("setIntHeader()", (response, contentLength) -> response.setIntHeader(CONTENT_LENGTH, contentLength)),
named("addIntHeader()", (response, contentLength) -> response.addIntHeader(CONTENT_LENGTH, contentLength)),
named("setHeader()", (response, contentLength) -> response.setHeader(CONTENT_LENGTH, "" + contentLength)),
named("addHeader()", (response, contentLength) -> response.addHeader(CONTENT_LENGTH, "" + contentLength))
);
}
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("setContentTypeFunctions")
void copyBodyToResponseWithOverridingContentType(SetContentType setContentType) throws Exception {
byte[] responseBody = "Hello World".getBytes(UTF_8);
int responseLength = responseBody.length;
String originalContentType = MediaType.TEXT_PLAIN_VALUE;
String overridingContentType = MediaType.APPLICATION_JSON_VALUE;
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentType(originalContentType);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
assertContentTypeHeader(response, originalContentType);
assertContentTypeHeader(responseWrapper, originalContentType);
setContentType.invoke(responseWrapper, overridingContentType);
assertThat(responseWrapper.getContentSize()).isZero();
assertThat(responseWrapper.getHeaderNames()).containsExactlyInAnyOrder(CONTENT_TYPE);
assertContentTypeHeader(response, overridingContentType);
assertContentTypeHeader(responseWrapper, overridingContentType);
FileCopyUtils.copy(responseBody, responseWrapper.getOutputStream());
assertThat(responseWrapper.getContentSize()).isEqualTo(responseLength);
responseWrapper.copyBodyToResponse();
assertThat(responseWrapper.getContentSize()).isZero();
assertThat(responseWrapper.getHeaderNames()).containsExactlyInAnyOrder(CONTENT_TYPE, CONTENT_LENGTH);
assertHeader(response, CONTENT_LENGTH, responseLength);
assertHeader(responseWrapper, CONTENT_LENGTH, responseLength);
assertContentTypeHeader(response, overridingContentType);
assertContentTypeHeader(responseWrapper, overridingContentType);
assertThat(response.getContentLength()).isEqualTo(responseLength);
assertThat(response.getContentAsByteArray()).isEqualTo(responseBody);
assertThat(response.getHeaderNames()).containsExactlyInAnyOrder(CONTENT_TYPE, CONTENT_LENGTH);
}
private static Stream<Named<SetContentType>> setContentTypeFunctions() {
return Stream.of(
named("setContentType()", HttpServletResponse::setContentType),
named("setHeader()", (response, contentType) -> response.setHeader(CONTENT_TYPE, contentType)),
named("addHeader()", (response, contentType) -> response.addHeader(CONTENT_TYPE, contentType))
);
}
@Test
void copyBodyToResponseWithTransferEncoding() throws Exception {
byte[] responseBody = "6\r\nHello 5\r\nWorld0\r\n\r\n".getBytes(UTF_8);
byte[] responseBody = "6\r\nHello 5\r\nWorld0\r\n\r\n".getBytes(StandardCharsets.UTF_8);
MockHttpServletResponse response = new MockHttpServletResponse();
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
responseWrapper.setStatus(HttpServletResponse.SC_CREATED);
responseWrapper.setHeader(TRANSFER_ENCODING, "chunked");
responseWrapper.setStatus(HttpServletResponse.SC_OK);
responseWrapper.setHeader(HttpHeaders.TRANSFER_ENCODING, "chunked");
FileCopyUtils.copy(responseBody, responseWrapper.getOutputStream());
responseWrapper.copyBodyToResponse();
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_CREATED);
assertHeader(response, TRANSFER_ENCODING, "chunked");
assertHeader(response, CONTENT_LENGTH, null);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getHeader(HttpHeaders.TRANSFER_ENCODING)).isEqualTo("chunked");
assertThat(response.getHeader(HttpHeaders.CONTENT_LENGTH)).isNull();
assertThat(response.getContentAsByteArray()).isEqualTo(responseBody);
}
private void assertHeader(HttpServletResponse response, String header, int value) {
assertHeader(response, header, Integer.toString(value));
}
private void assertHeader(HttpServletResponse response, String header, String value) {
if (value == null) {
assertThat(response.containsHeader(header)).as(header).isFalse();
assertThat(response.getHeader(header)).as(header).isNull();
assertThat(response.getHeaders(header)).as(header).isEmpty();
}
else {
assertThat(response.containsHeader(header)).as(header).isTrue();
assertThat(response.getHeader(header)).as(header).isEqualTo(value);
assertThat(response.getHeaders(header)).as(header).containsExactly(value);
}
}
private void assertContentTypeHeader(HttpServletResponse response, String contentType) {
assertHeader(response, CONTENT_TYPE, contentType);
assertThat(response.getContentType()).as(CONTENT_TYPE).isEqualTo(contentType);
}
@FunctionalInterface
private interface SetContentLength {
void invoke(HttpServletResponse response, int contentLength);
}
@FunctionalInterface
private interface SetContentType {
void invoke(HttpServletResponse response, String contentType);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,36 +16,33 @@
package org.springframework.web.filter;
import java.nio.charset.StandardCharsets;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
/**
* Tests for {@link ShallowEtagHeaderFilter}.
*
* @author Arjen Poutsma
* @author Brian Clozel
* @author Juergen Hoeller
* @author Sam Brannen
*/
class ShallowEtagHeaderFilterTests {
public class ShallowEtagHeaderFilterTests {
private final ShallowEtagHeaderFilter filter = new ShallowEtagHeaderFilter();
@Test
void isEligibleForEtag() {
public void isEligibleForEtag() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -64,60 +61,60 @@ class ShallowEtagHeaderFilterTests {
}
@Test
void filterNoMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterNoMatch() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
byte[] responseBody = "Hello World".getBytes(UTF_8);
final byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
filterResponse.setContentType(TEXT_PLAIN_VALUE);
filterResponse.setContentType(MediaType.TEXT_PLAIN_VALUE);
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
};
filter.doFilter(request, response, filterChain);
assertThat(response.getStatus()).as("Invalid status").isEqualTo(200);
assertThat(response.getHeader("ETag")).as("Invalid ETag").isEqualTo("\"0b10a8db164e0754105b7a99be72e3fe5\"");
assertThat(response.getContentLength()).as("Invalid Content-Length header").isGreaterThan(0);
assertThat(response.getContentType()).as("Invalid Content-Type header").isEqualTo(TEXT_PLAIN_VALUE);
assertThat(response.getContentLength() > 0).as("Invalid Content-Length header").isTrue();
assertThat(response.getContentType()).as("Invalid Content-Type header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
assertThat(response.getContentAsByteArray()).as("Invalid content").isEqualTo(responseBody);
}
@Test
void filterNoMatchWeakETag() throws Exception {
public void filterNoMatchWeakETag() throws Exception {
this.filter.setWriteWeakETag(true);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
byte[] responseBody = "Hello World".getBytes(UTF_8);
final byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
filterResponse.setContentType(TEXT_PLAIN_VALUE);
filterResponse.setContentType(MediaType.TEXT_PLAIN_VALUE);
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
};
filter.doFilter(request, response, filterChain);
assertThat(response.getStatus()).as("Invalid status").isEqualTo(200);
assertThat(response.getHeader("ETag")).as("Invalid ETag").isEqualTo("W/\"0b10a8db164e0754105b7a99be72e3fe5\"");
assertThat(response.getContentLength()).as("Invalid Content-Length header").isGreaterThan(0);
assertThat(response.getContentType()).as("Invalid Content-Type header").isEqualTo(TEXT_PLAIN_VALUE);
assertThat(response.getContentLength() > 0).as("Invalid Content-Length header").isTrue();
assertThat(response.getContentType()).as("Invalid Content-Type header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
assertThat(response.getContentAsByteArray()).as("Invalid content").isEqualTo(responseBody);
}
@Test
void filterMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterMatch() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
String etag = "\"0b10a8db164e0754105b7a99be72e3fe5\"";
request.addHeader("If-None-Match", etag);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
byte[] responseBody = "Hello World".getBytes(UTF_8);
byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
filterResponse.setContentLength(responseBody.length);
filterResponse.setContentType(TEXT_PLAIN_VALUE);
filterResponse.setContentType(MediaType.TEXT_PLAIN_VALUE);
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
};
filter.doFilter(request, response, filterChain);
@@ -125,20 +122,21 @@ class ShallowEtagHeaderFilterTests {
assertThat(response.getStatus()).as("Invalid status").isEqualTo(304);
assertThat(response.getHeader("ETag")).as("Invalid ETag").isEqualTo("\"0b10a8db164e0754105b7a99be72e3fe5\"");
assertThat(response.containsHeader("Content-Length")).as("Response has Content-Length header").isFalse();
assertThat(response.getContentType()).as("Invalid Content-Type header").isEqualTo(TEXT_PLAIN_VALUE);
assertThat(response.getContentAsByteArray()).as("Invalid content").isEmpty();
assertThat(response.containsHeader("Content-Type")).as("Response has Content-Type header").isFalse();
byte[] expecteds = new byte[0];
assertThat(response.getContentAsByteArray()).as("Invalid content").isEqualTo(expecteds);
}
@Test
void filterMatchWeakEtag() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterMatchWeakEtag() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
String etag = "\"0b10a8db164e0754105b7a99be72e3fe5\"";
request.addHeader("If-None-Match", "W/" + etag);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
byte[] responseBody = "Hello World".getBytes(UTF_8);
byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
filterResponse.setContentLength(responseBody.length);
};
@@ -147,12 +145,13 @@ class ShallowEtagHeaderFilterTests {
assertThat(response.getStatus()).as("Invalid status").isEqualTo(304);
assertThat(response.getHeader("ETag")).as("Invalid ETag").isEqualTo("\"0b10a8db164e0754105b7a99be72e3fe5\"");
assertThat(response.containsHeader("Content-Length")).as("Response has Content-Length header").isFalse();
assertThat(response.getContentAsByteArray()).as("Invalid content").isEmpty();
byte[] expecteds = new byte[0];
assertThat(response.getContentAsByteArray()).as("Invalid content").isEqualTo(expecteds);
}
@Test
void filterWriter() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterWriter() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
String etag = "\"0b10a8db164e0754105b7a99be72e3fe5\"";
request.addHeader("If-None-Match", etag);
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -168,20 +167,19 @@ class ShallowEtagHeaderFilterTests {
assertThat(response.getStatus()).as("Invalid status").isEqualTo(304);
assertThat(response.getHeader("ETag")).as("Invalid ETag").isEqualTo("\"0b10a8db164e0754105b7a99be72e3fe5\"");
assertThat(response.containsHeader("Content-Length")).as("Response has Content-Length header").isFalse();
assertThat(response.getContentAsByteArray()).as("Invalid content").isEmpty();
byte[] expecteds = new byte[0];
assertThat(response.getContentAsByteArray()).as("Invalid content").isEqualTo(expecteds);
}
@Test // SPR-12960
void filterWriterWithDisabledCaching() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterWriterWithDisabledCaching() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentType(TEXT_PLAIN_VALUE);
byte[] responseBody = "Hello World".getBytes(UTF_8);
final byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
filterResponse.setContentType(APPLICATION_JSON_VALUE);
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
};
@@ -190,16 +188,15 @@ class ShallowEtagHeaderFilterTests {
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getHeader("ETag")).isNull();
assertThat(response.getContentType()).as("Invalid Content-Type header").isEqualTo(APPLICATION_JSON_VALUE);
assertThat(response.getContentAsByteArray()).isEqualTo(responseBody);
}
@Test
void filterSendError() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterSendError() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
byte[] responseBody = "Hello World".getBytes(UTF_8);
final byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
response.setContentLength(100);
@@ -215,11 +212,11 @@ class ShallowEtagHeaderFilterTests {
}
@Test
void filterSendErrorMessage() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterSendErrorMessage() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
byte[] responseBody = "Hello World".getBytes(UTF_8);
final byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
response.setContentLength(100);
@@ -236,11 +233,11 @@ class ShallowEtagHeaderFilterTests {
}
@Test
void filterSendRedirect() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterSendRedirect() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
byte[] responseBody = "Hello World".getBytes(UTF_8);
final byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
response.setContentLength(100);
@@ -257,11 +254,11 @@ class ShallowEtagHeaderFilterTests {
}
@Test // SPR-13717
void filterFlushResponse() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
public void filterFlushResponse() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
byte[] responseBody = "Hello World".getBytes(UTF_8);
final byte[] responseBody = "Hello World".getBytes(StandardCharsets.UTF_8);
FilterChain filterChain = (filterRequest, filterResponse) -> {
assertThat(filterRequest).as("Invalid request passed").isEqualTo(request);
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,104 +27,79 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Rossen Stoyanchev
*/
class UriTemplateTests {
public class UriTemplateTests {
@Test
void emptyPathDoesNotThrowException() {
assertThatNoException().isThrownBy(() -> new UriTemplate(""));
}
@Test
void nullPathThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new UriTemplate(null));
}
@Test
void getVariableNames() {
public void getVariableNames() throws Exception {
UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
List<String> variableNames = template.getVariableNames();
assertThat(variableNames).as("Invalid variable names").isEqualTo(Arrays.asList("hotel", "booking"));
}
@Test
void getVariableNamesFromEmpty() {
UriTemplate template = new UriTemplate("");
List<String> variableNames = template.getVariableNames();
assertThat(variableNames).isEmpty();
}
@Test
void expandVarArgs() {
public void expandVarArgs() throws Exception {
UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
URI result = template.expand("1", "42");
assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotels/1/bookings/42"));
}
@Test
void expandVarArgsFromEmpty() {
UriTemplate template = new UriTemplate("");
URI result = template.expand();
assertThat(result).as("Invalid expanded template").isEqualTo(URI.create(""));
assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotels/1/bookings/42"));
}
@Test // SPR-9712
void expandVarArgsWithArrayValue() {
public void expandVarArgsWithArrayValue() throws Exception {
UriTemplate template = new UriTemplate("/sum?numbers={numbers}");
URI result = template.expand(new int[] {1, 2, 3});
assertThat(result).isEqualTo(URI.create("/sum?numbers=1,2,3"));
assertThat(result).isEqualTo(new URI("/sum?numbers=1,2,3"));
}
@Test
void expandVarArgsNotEnoughVariables() {
public void expandVarArgsNotEnoughVariables() throws Exception {
UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
assertThatIllegalArgumentException().isThrownBy(() -> template.expand("1"));
}
@Test
void expandMap() {
public void expandMap() throws Exception {
Map<String, String> uriVariables = new HashMap<>(2);
uriVariables.put("booking", "42");
uriVariables.put("hotel", "1");
UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
URI result = template.expand(uriVariables);
assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotels/1/bookings/42"));
assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotels/1/bookings/42"));
}
@Test
void expandMapDuplicateVariables() {
public void expandMapDuplicateVariables() throws Exception {
UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}");
assertThat(template.getVariableNames()).isEqualTo(Arrays.asList("c", "c", "c"));
URI result = template.expand(Collections.singletonMap("c", "cheeseburger"));
assertThat(result).isEqualTo(URI.create("/order/cheeseburger/cheeseburger/cheeseburger"));
assertThat(result).isEqualTo(new URI("/order/cheeseburger/cheeseburger/cheeseburger"));
}
@Test
void expandMapNonString() {
public void expandMapNonString() throws Exception {
Map<String, Integer> uriVariables = new HashMap<>(2);
uriVariables.put("booking", 42);
uriVariables.put("hotel", 1);
UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
URI result = template.expand(uriVariables);
assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotels/1/bookings/42"));
assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotels/1/bookings/42"));
}
@Test
void expandMapEncoded() {
public void expandMapEncoded() throws Exception {
Map<String, String> uriVariables = Collections.singletonMap("hotel", "Z\u00fcrich");
UriTemplate template = new UriTemplate("/hotel list/{hotel}");
URI result = template.expand(uriVariables);
assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotel%20list/Z%C3%BCrich"));
assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotel%20list/Z%C3%BCrich"));
}
@Test
void expandMapUnboundVariables() {
public void expandMapUnboundVariables() throws Exception {
Map<String, String> uriVariables = new HashMap<>(2);
uriVariables.put("booking", "42");
uriVariables.put("bar", "1");
@@ -134,14 +109,14 @@ class UriTemplateTests {
}
@Test
void expandEncoded() {
public void expandEncoded() throws Exception {
UriTemplate template = new UriTemplate("/hotel list/{hotel}");
URI result = template.expand("Z\u00fcrich");
assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotel%20list/Z%C3%BCrich"));
assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotel%20list/Z%C3%BCrich"));
}
@Test
void matches() {
public void matches() throws Exception {
UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
assertThat(template.matches("/hotels/1/bookings/42")).as("UriTemplate does not match").isTrue();
assertThat(template.matches("/hotels/bookings")).as("UriTemplate matches").isFalse();
@@ -150,23 +125,14 @@ class UriTemplateTests {
}
@Test
void matchesAgainstEmpty() {
UriTemplate template = new UriTemplate("");
assertThat(template.matches("/hotels/1/bookings/42")).as("UriTemplate matches").isFalse();
assertThat(template.matches("/hotels/bookings")).as("UriTemplate matches").isFalse();
assertThat(template.matches("")).as("UriTemplate does not match").isTrue();
assertThat(template.matches(null)).as("UriTemplate matches").isFalse();
}
@Test
void matchesCustomRegex() {
public void matchesCustomRegex() throws Exception {
UriTemplate template = new UriTemplate("/hotels/{hotel:\\d+}");
assertThat(template.matches("/hotels/42")).as("UriTemplate does not match").isTrue();
assertThat(template.matches("/hotels/foo")).as("UriTemplate matches").isFalse();
}
@Test
void match() {
public void match() throws Exception {
Map<String, String> expected = new HashMap<>(2);
expected.put("booking", "42");
expected.put("hotel", "1");
@@ -177,14 +143,7 @@ class UriTemplateTests {
}
@Test
void matchAgainstEmpty() {
UriTemplate template = new UriTemplate("");
Map<String, String> result = template.match("/hotels/1/bookings/42");
assertThat(result).as("Invalid match").isEmpty();
}
@Test
void matchCustomRegex() {
public void matchCustomRegex() throws Exception {
Map<String, String> expected = new HashMap<>(2);
expected.put("booking", "42");
expected.put("hotel", "1");
@@ -195,14 +154,14 @@ class UriTemplateTests {
}
@Test // SPR-13627
void matchCustomRegexWithNestedCurlyBraces() {
public void matchCustomRegexWithNestedCurlyBraces() throws Exception {
UriTemplate template = new UriTemplate("/site.{domain:co.[a-z]{2}}");
Map<String, String> result = template.match("/site.co.eu");
assertThat(result).as("Invalid match").isEqualTo(Collections.singletonMap("domain", "co.eu"));
}
@Test
void matchDuplicate() {
public void matchDuplicate() throws Exception {
UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}");
Map<String, String> result = template.match("/order/cheeseburger/cheeseburger/cheeseburger");
Map<String, String> expected = Collections.singletonMap("c", "cheeseburger");
@@ -210,7 +169,7 @@ class UriTemplateTests {
}
@Test
void matchMultipleInOneSegment() {
public void matchMultipleInOneSegment() throws Exception {
UriTemplate template = new UriTemplate("/{foo}-{bar}");
Map<String, String> result = template.match("/12-34");
Map<String, String> expected = new HashMap<>(2);
@@ -220,19 +179,19 @@ class UriTemplateTests {
}
@Test // SPR-16169
void matchWithMultipleSegmentsAtTheEnd() {
public void matchWithMultipleSegmentsAtTheEnd() throws Exception {
UriTemplate template = new UriTemplate("/account/{accountId}");
assertThat(template.matches("/account/15/alias/5")).isFalse();
}
@Test
void queryVariables() {
public void queryVariables() throws Exception {
UriTemplate template = new UriTemplate("/search?q={query}");
assertThat(template.matches("/search?q=foo")).isTrue();
}
@Test
void fragments() {
public void fragments() throws Exception {
UriTemplate template = new UriTemplate("/search#{fragment}");
assertThat(template.matches("/search#foo")).isTrue();
@@ -241,19 +200,19 @@ class UriTemplateTests {
}
@Test // SPR-13705
void matchesWithSlashAtTheEnd() {
public void matchesWithSlashAtTheEnd() throws Exception {
assertThat(new UriTemplate("/test/").matches("/test/")).isTrue();
}
@Test
void expandWithDollar() {
public void expandWithDollar() throws Exception {
UriTemplate template = new UriTemplate("/{a}");
URI uri = template.expand("$replacement");
assertThat(uri.toString()).isEqualTo("/$replacement");
}
@Test
void expandWithAtSign() {
public void expandWithAtSign() throws Exception {
UriTemplate template = new UriTemplate("http://localhost/query={query}");
URI uri = template.expand("foo@bar");
assertThat(uri.toString()).isEqualTo("http://localhost/query=foo@bar");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,6 +61,7 @@ import javax.servlet.http.Part;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
@@ -992,7 +993,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
}
private static String encodeCookies(Cookie... cookies) {
private static String encodeCookies(@NonNull Cookie... cookies) {
return Arrays.stream(cookies)
.map(c -> c.getName() + '=' + (c.getValue() == null ? "" : c.getValue()))
.collect(Collectors.joining("; "));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -94,7 +94,6 @@ public class HandlerFunctionAdapter implements HandlerAdapter, Ordered {
Object handler) throws Exception {
WebAsyncManager asyncManager = getWebAsyncManager(servletRequest, servletResponse);
servletResponse = getWrappedResponse(asyncManager);
ServerRequest serverRequest = getServerRequest(servletRequest);
ServerResponse serverResponse;
@@ -124,22 +123,6 @@ public class HandlerFunctionAdapter implements HandlerAdapter, Ordered {
return asyncManager;
}
/**
* Obtain response wrapped by
* {@link org.springframework.web.context.request.async.StandardServletAsyncWebRequest}
* to enforce lifecycle rules from Servlet spec (section 2.3.3.4)
* in case of async handling.
*/
private static HttpServletResponse getWrappedResponse(WebAsyncManager asyncManager) {
AsyncWebRequest asyncRequest = asyncManager.getAsyncWebRequest();
Assert.notNull(asyncRequest, "No AsyncWebRequest");
HttpServletResponse servletResponse = asyncRequest.getNativeResponse(HttpServletResponse.class);
Assert.notNull(servletResponse, "No HttpServletResponse");
return servletResponse;
}
private ServerRequest getServerRequest(HttpServletRequest servletRequest) {
ServerRequest serverRequest =
(ServerRequest) servletRequest.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
@@ -853,21 +853,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
// Obtain wrapped response to enforce lifecycle rule from Servlet spec, section 2.3.3.4
response = asyncWebRequest.getNativeResponse(HttpServletResponse.class);
ServletWebRequest webRequest = (asyncWebRequest instanceof ServletWebRequest ?
(ServletWebRequest) asyncWebRequest : new ServletWebRequest(request, response));
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
@@ -887,6 +873,15 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
Object[] resultContext = asyncManager.getConcurrentResultContext();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -203,6 +203,7 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume
"Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder);
}
return pushBuilder;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,6 @@ import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
@@ -229,10 +228,6 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestNotUsableException) {
return handleAsyncRequestNotUsableException(
(AsyncRequestNotUsableException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
@@ -546,23 +541,6 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
return new ModelAndView();
}
/**
* Handle the case of an I/O failure from the ServletOutputStream.
* <p>By default, do nothing since the response is not usable.
* @param ex the {@link AsyncRequestTimeoutException} to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen
* at the time of the exception (for example, if multipart resolution failed)
* @return an empty ModelAndView indicating the exception was handled
* @since 5.3.33
*/
protected ModelAndView handleAsyncRequestNotUsableException(AsyncRequestNotUsableException ex,
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) {
return new ModelAndView();
}
/**
* Invoked to send a server error. Sets the status to 500 and also sets the
* request attribute "javax.servlet.error.exception" to the Exception.
@@ -1,108 +0,0 @@
/*
* Copyright 2002-2024 the original author or 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.
*/
package org.springframework.web.servlet.function.support;
import java.io.IOException;
import java.util.Collections;
import javax.servlet.AsyncEvent;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.servlet.function.HandlerFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.testfixture.servlet.MockAsyncContext;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.doThrow;
import static org.mockito.BDDMockito.mock;
/**
* Unit tests for {@link HandlerFunctionAdapter}.
*
* @author Rossen Stoyanchev
*/
public class HandlerFunctionAdapterTests {
private final MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
private final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
private final HandlerFunctionAdapter adapter = new HandlerFunctionAdapter();
@BeforeEach
void setUp() {
this.servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE,
ServerRequest.create(this.servletRequest, Collections.singletonList(new StringHttpMessageConverter())));
}
@Test
void asyncRequestNotUsable() throws Exception {
HandlerFunction<?> handler = request -> ServerResponse.sse(sseBuilder -> {
try {
sseBuilder.data("data 1");
sseBuilder.data("data 2");
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
});
this.servletRequest.setAsyncSupported(true);
HttpServletResponse mockServletResponse = mock(HttpServletResponse.class);
doThrow(new IOException("Broken pipe")).when(mockServletResponse).getOutputStream();
// Use of response should be rejected
assertThatThrownBy(() -> adapter.handle(servletRequest, mockServletResponse, handler))
.hasRootCauseInstanceOf(IOException.class)
.hasRootCauseMessage("Broken pipe");
}
@Test
void asyncRequestNotUsableOnAsyncDispatch() throws Exception {
HandlerFunction<?> handler = request -> ServerResponse.ok().body("body");
// Put AsyncWebRequest in ERROR state
StandardServletAsyncWebRequest asyncRequest = new StandardServletAsyncWebRequest(servletRequest, servletResponse);
asyncRequest.onError(new AsyncEvent(new MockAsyncContext(servletRequest, servletResponse), new Exception()));
// Set it as the current AsyncWebRequest, from the initial REQUEST dispatch
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
asyncManager.setAsyncWebRequest(asyncRequest);
// Use of response should be rejected
assertThatThrownBy(() -> adapter.handle(servletRequest, servletResponse, handler))
.isInstanceOf(AsyncRequestNotUsableException.class);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,8 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -28,8 +25,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.AsyncEvent;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -51,10 +46,6 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ModelMethodProcessor;
@@ -64,15 +55,13 @@ import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.testfixture.servlet.MockAsyncContext;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link RequestMappingHandlerAdapter}.
* Unit tests for {@link RequestMappingHandlerAdapter}.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
@@ -261,7 +250,9 @@ public class RequestMappingHandlerAdapterTests {
assertThat(mav.getModel().get("attr3")).isNull();
}
@Test // gh-15486
// SPR-10859
@Test
public void responseBodyAdvice() throws Exception {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
@@ -281,26 +272,6 @@ public class RequestMappingHandlerAdapterTests {
assertThat(this.response.getContentAsString()).isEqualTo("{\"status\":400,\"message\":\"body\"}");
}
@Test
void asyncRequestNotUsable() throws Exception {
// Put AsyncWebRequest in ERROR state
StandardServletAsyncWebRequest asyncRequest = new StandardServletAsyncWebRequest(this.request, this.response);
asyncRequest.onError(new AsyncEvent(new MockAsyncContext(this.request, this.response), new Exception()));
// Set it as the current AsyncWebRequest, from the initial REQUEST dispatch
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request);
asyncManager.setAsyncWebRequest(asyncRequest);
// AsyncWebRequest created for current dispatch should inherit state
HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handleOutputStream", OutputStream.class);
this.handlerAdapter.afterPropertiesSet();
// Use of response should be rejected
assertThatThrownBy(() -> this.handlerAdapter.handle(this.request, this.response, handlerMethod))
.isInstanceOf(AsyncRequestNotUsableException.class);
}
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
return new InvocableHandlerMethod(handler, method);
@@ -326,16 +297,14 @@ public class RequestMappingHandlerAdapterTests {
}
public ResponseEntity<Map<String, String>> handleWithResponseEntity() {
return new ResponseEntity<>(Collections.singletonMap("foo", "bar"), HttpStatus.OK);
return new ResponseEntity<>(Collections.singletonMap(
"foo", "bar"), HttpStatus.OK);
}
public ResponseEntity<String> handleBadRequest() {
return new ResponseEntity<>("body", HttpStatus.BAD_REQUEST);
}
public void handleOutputStream(OutputStream outputStream) throws IOException {
outputStream.write("body".getBytes(StandardCharsets.UTF_8));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,9 +92,6 @@ public class ResponseEntityExceptionHandlerTests {
Class<?>[] paramTypes = method.getParameterTypes();
if (method.getName().startsWith("handle") && (paramTypes.length == 4)) {
String name = paramTypes[0].getSimpleName();
if (name.equals("AsyncRequestNotUsableException")) {
continue;
}
assertThat(exceptionTypes.contains(paramTypes[0])).as("@ExceptionHandler is missing " + name).isTrue();
}
}