Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Buildmaster 7c2a72c9b4 Release v5.3.5 2021-03-16 08:03:00 +00:00
89 changed files with 355 additions and 1634 deletions
+16 -16
View File
@@ -1,7 +1,7 @@
plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
id 'io.spring.nohttp' version '0.0.5.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.4.32' apply false
id 'org.jetbrains.kotlin.jvm' version '1.4.31' apply false
id 'org.jetbrains.dokka' version '0.10.1' apply false
id 'org.asciidoctor.jvm.convert' version '3.1.0'
id 'org.asciidoctor.jvm.pdf' version '3.1.0'
@@ -10,7 +10,7 @@ plugins {
id "com.github.ben-manes.versions" version '0.28.0'
id "com.github.johnrengelman.shadow" version "6.1.0" apply false
id "me.champeau.gradle.jmh" version "0.5.2" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.32" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.31" apply false
}
ext {
@@ -27,17 +27,17 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.2"
mavenBom "io.netty:netty-bom:4.1.63.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.6"
mavenBom "io.netty:netty-bom:4.1.60.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.5"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR9"
mavenBom "io.rsocket:rsocket-bom:1.1.0"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.39.v20210325"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.32"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.38.v20210224"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.30"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.3"
mavenBom "org.junit:junit-bom:5.7.1"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.14.1') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.14.0') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
@@ -65,7 +65,7 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.0.12"
dependency "io.reactivex.rxjava3:rxjava:3.0.11"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "com.caucho:hessian:4.0.63"
@@ -117,30 +117,30 @@ configure(allprojects) { project ->
}
dependency "org.apache.poi:poi-ooxml:4.1.2"
dependency "org.apache-extras.beanshell:bsh:2.0b6"
dependency "org.freemarker:freemarker:2.3.31"
dependency "org.freemarker:freemarker:2.3.30"
dependency "org.hsqldb:hsqldb:2.5.1"
dependency "org.quartz-scheduler:quartz:2.3.2"
dependency "org.codehaus.fabric3.api:commonj:1.1.0"
dependency "net.sf.ehcache:ehcache:2.10.6"
dependency "org.ehcache:jcache:1.0.1"
dependency "org.ehcache:ehcache:3.4.0"
dependency "org.hibernate:hibernate-core:5.4.30.Final"
dependency "org.hibernate:hibernate-core:5.4.29.Final"
dependency "org.hibernate:hibernate-validator:6.2.0.Final"
dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.45') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.43') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
}
}
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.45') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.43') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.7.Final') {
dependencySet(group: 'io.undertow', version: '2.2.5.Final') {
entry 'undertow-core'
entry('undertow-websockets-jsr') {
exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec"
@@ -188,7 +188,7 @@ configure(allprojects) { project ->
dependency("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1") {
exclude group: "junit", name: "junit"
}
dependency "org.testng:testng:7.4.0"
dependency "org.testng:testng:7.3.0"
dependency "org.hamcrest:hamcrest:2.1"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.19.0"
@@ -206,10 +206,10 @@ configure(allprojects) { project ->
}
dependency "io.mockk:mockk:1.10.2"
dependency("net.sourceforge.htmlunit:htmlunit:2.48.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.47.1") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.48.0") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.47.1") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
+1 -1
View File
@@ -14,7 +14,7 @@ rm -rf /var/lib/apt/lists/*
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.2/concourse-release-scripts-0.3.2.jar
curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.0/concourse-release-scripts-0.3.0.jar
###########################################################
# JAVA
+3 -3
View File
@@ -9,8 +9,8 @@ anchors:
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
sonatype-task-params: &sonatype-task-params
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
SONATYPE_USER_TOKEN: ((sonatype-user-token))
SONATYPE_PASSWORD_TOKEN: ((sonatype-user-token-password))
SONATYPE_URL: ((sonatype-url))
SONATYPE_STAGING_PROFILE_ID: ((sonatype-staging-profile-id))
artifactory-task-params: &artifactory-task-params
@@ -376,7 +376,7 @@ jobs:
trigger: false
passed: [stage-release]
params:
download_artifacts: true
download_artifacts: false
save_build_info: true
- task: promote
image: ci-image
+1 -1
View File
@@ -1,7 +1,7 @@
---
platform: linux
image_resource:
type: registry-image
type: docker-image
source:
repository: springio/github-changelog-generator
tag: '0.0.6'
+2 -2
View File
@@ -10,8 +10,8 @@ params:
ARTIFACTORY_SERVER:
ARTIFACTORY_USERNAME:
ARTIFACTORY_PASSWORD:
SONATYPE_USER:
SONATYPE_PASSWORD:
SONATYPE_USER_TOKEN:
SONATYPE_PASSWORD_TOKEN:
SONATYPE_URL:
SONATYPE_STAGING_PROFILE_ID:
run:
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.6
version=5.3.5
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
+18 -15
View File
@@ -28,11 +28,13 @@
*
* @author Brian Clozel
*/
def mainToolchain = 'mainToolchain'
def testToolchain = 'testToolchain'
plugins.withType(JavaPlugin) {
// Configure the Java Toolchain if the 'mainToolchain' property is defined
if (project.hasProperty('mainToolchain') && project.mainToolchain) {
def mainLanguageVersion = JavaLanguageVersion.of(project.mainToolchain.toString())
if (project.hasProperty(mainToolchain)) {
def mainLanguageVersion = JavaLanguageVersion.of(project.property(mainToolchain).toString())
java {
toolchain {
languageVersion = mainLanguageVersion
@@ -46,8 +48,8 @@ plugins.withType(JavaPlugin) {
}
}
// Configure a specific Java Toolchain for compiling and running tests if the 'testToolchain' property is defined
if (project.hasProperty('testToolchain') && project.testToolchain) {
def testLanguageVersion = JavaLanguageVersion.of(project.testToolchain.toString());
if (project.hasProperty(testToolchain)) {
def testLanguageVersion = JavaLanguageVersion.of(project.property(testToolchain).toString());
tasks.withType(JavaCompile).matching { it.name.contains("Test") }.configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = testLanguageVersion
@@ -63,7 +65,7 @@ plugins.withType(JavaPlugin) {
plugins.withType(GroovyPlugin) {
// Fallback to JDK8
if (!project.hasProperty('mainToolchain')) {
if (!project.hasProperty(mainToolchain)) {
compileGroovy {
sourceCompatibility = JavaVersion.VERSION_1_8
}
@@ -72,8 +74,8 @@ plugins.withType(GroovyPlugin) {
// Configure the Kotlin compiler if the 'mainToolchain' property is defined
pluginManager.withPlugin("kotlin") {
if (project.hasProperty('mainToolchain') && project.mainToolchain) {
def mainLanguageVersion = JavaLanguageVersion.of(project.mainToolchain.toString());
if (project.hasProperty(mainToolchain)) {
def mainLanguageVersion = JavaLanguageVersion.of(project.property(mainToolchain).toString());
def compiler = javaToolchains.compilerFor {
languageVersion = mainLanguageVersion
}
@@ -94,21 +96,15 @@ pluginManager.withPlugin("kotlin") {
}
}
else {
// Fallback to JDK8
compileKotlin {
kotlinOptions {
jvmTarget = '1.8'
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = '1.8'
}
}
}
if (project.hasProperty('testToolchain') && project.testToolchain) {
def testLanguageVersion = JavaLanguageVersion.of(project.testToolchain.toString());
if (project.hasProperty(testToolchain)) {
def testLanguageVersion = JavaLanguageVersion.of(project.property(testToolchain).toString());
def compiler = javaToolchains.compilerFor {
languageVersion = testLanguageVersion
}
@@ -121,4 +117,11 @@ pluginManager.withPlugin("kotlin") {
}
}
}
else {
compileTestKotlin {
kotlinOptions {
jvmTarget = '1.8'
}
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.6.1"
id "com.gradle.enterprise" version "3.5.1"
id "io.spring.ge.conventions" version "0.0.7"
}
@@ -26,8 +26,8 @@ import java.nio.file.Paths;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
/**
* Editor for {@code java.nio.file.Path}, to directly populate a Path
@@ -74,7 +74,7 @@ public class PathEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
boolean nioPathCandidate = !text.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX);
boolean nioPathCandidate = !text.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX);
if (nioPathCandidate && !text.startsWith("/")) {
try {
URI uri = new URI(text);
@@ -85,13 +85,9 @@ public class PathEditor extends PropertyEditorSupport {
return;
}
}
catch (URISyntaxException ex) {
// Not a valid URI; potentially a Windows-style path after
// a file prefix (let's try as Spring resource location)
nioPathCandidate = !text.startsWith(ResourceUtils.FILE_URL_PREFIX);
}
catch (FileSystemNotFoundException ex) {
// URI scheme not registered for NIO (let's try URL
catch (URISyntaxException | FileSystemNotFoundException ex) {
// Not a valid URI (let's try as Spring resource location),
// or a URI scheme not registered for NIO (let's try URL
// protocol handlers via Spring's resource mechanism).
}
}
@@ -101,7 +97,8 @@ public class PathEditor extends PropertyEditorSupport {
if (resource == null) {
setValue(null);
}
else if (nioPathCandidate && !resource.exists()) {
else if (!resource.isFile() && !resource.exists() && nioPathCandidate) {
// Prefer getFile().toPath() below for non-existent file handles
setValue(Paths.get(text).normalize());
}
else {
@@ -39,7 +39,8 @@ public class PathEditorTests {
pathEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
ClassUtils.getShortName(getClass()) + ".class");
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
boolean condition = value instanceof Path;
assertThat(condition).isTrue();
Path path = (Path) value;
assertThat(path.toFile().exists()).isTrue();
}
@@ -56,9 +57,11 @@ public class PathEditorTests {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("file:/no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
boolean condition1 = value instanceof Path;
assertThat(condition1).isTrue();
Path path = (Path) value;
assertThat(!path.toFile().exists()).isTrue();
boolean condition = !path.toFile().exists();
assertThat(condition).isTrue();
}
@Test
@@ -66,9 +69,11 @@ public class PathEditorTests {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("/no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
boolean condition1 = value instanceof Path;
assertThat(condition1).isTrue();
Path path = (Path) value;
assertThat(!path.toFile().exists()).isTrue();
boolean condition = !path.toFile().exists();
assertThat(condition).isTrue();
}
@Test
@@ -76,26 +81,23 @@ public class PathEditorTests {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("C:\\no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
boolean condition1 = value instanceof Path;
assertThat(condition1).isTrue();
Path path = (Path) value;
assertThat(!path.toFile().exists()).isTrue();
boolean condition = !path.toFile().exists();
assertThat(condition).isTrue();
}
@Test
public void testWindowsAbsoluteFilePath() {
PropertyEditor pathEditor = new PathEditor();
try {
pathEditor.setAsText("file://C:\\no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
assertThat(!path.toFile().exists()).isTrue();
}
catch (IllegalArgumentException ex) {
if (File.separatorChar == '\\') { // on Windows, otherwise silently ignore
throw ex;
}
}
pathEditor.setAsText("file://C:\\no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
boolean condition1 = value instanceof Path;
assertThat(condition1).isTrue();
Path path = (Path) value;
boolean condition = !path.toFile().exists();
assertThat(condition).isTrue();
}
@Test
@@ -105,7 +107,8 @@ public class PathEditorTests {
ClassUtils.getShortName(getClass()) + ".class";
pathEditor.setAsText(fileName);
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
boolean condition = value instanceof Path;
assertThat(condition).isTrue();
Path path = (Path) value;
File file = path.toFile();
assertThat(file.exists()).isTrue();
@@ -123,7 +126,8 @@ public class PathEditorTests {
ClassUtils.getShortName(getClass()) + ".clazz";
pathEditor.setAsText(fileName);
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
boolean condition = value instanceof Path;
assertThat(condition).isTrue();
Path path = (Path) value;
File file = path.toFile();
assertThat(file.exists()).isFalse();
@@ -101,7 +101,7 @@ public final class TemporalAccessorParser implements Parser<TemporalAccessor> {
if (this.source != null) {
throw new DateTimeParseException(
String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
text, ex.getErrorIndex(), ex);
text, ex.getErrorIndex());
}
// else rethrow original exception
throw ex;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -237,12 +237,7 @@ abstract class CronField {
public <T extends Temporal & Comparable<? super T>> T elapseUntil(T temporal, int goal) {
int current = get(temporal);
if (current < goal) {
T result = this.field.getBaseUnit().addTo(temporal, goal - current);
current = get(result);
if (current > goal) { // can occur due to daylight saving, see gh-26744
result = this.field.getBaseUnit().addTo(result, goal - current);
}
return result;
return this.field.getBaseUnit().addTo(temporal, goal - current);
}
else {
ValueRange range = temporal.range(this.field);
@@ -16,7 +16,6 @@
package org.springframework.format.datetime.standard;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
@@ -321,35 +320,6 @@ class DateTimeFormattingTests {
assertThat(binder.getBindingResult().getFieldValue("isoLocalDate")).isEqualTo("2009-10-31");
}
@Test
void isoLocalDateWithInvalidFormat() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
String propertyName = "isoLocalDate";
propertyValues.add(propertyName, "2009-31-10");
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(1);
FieldError fieldError = bindingResult.getFieldError(propertyName);
assertThat(fieldError.unwrap(TypeMismatchException.class))
.hasMessageContaining("for property 'isoLocalDate'")
.hasCauseInstanceOf(ConversionFailedException.class).getCause()
.hasMessageContaining("for value '2009-31-10'")
.hasCauseInstanceOf(IllegalArgumentException.class).getCause()
.hasMessageContaining("Parse attempt failed for value [2009-31-10]")
.hasCauseInstanceOf(DateTimeParseException.class).getCause()
// Unable to parse date time value "2009-31-10" using configuration from
// @org.springframework.format.annotation.DateTimeFormat(pattern=, style=SS, iso=DATE, fallbackPatterns=[])
.hasMessageContainingAll(
"Unable to parse date time value \"2009-31-10\" using configuration from",
"@org.springframework.format.annotation.DateTimeFormat",
"iso=DATE", "fallbackPatterns=[]")
.hasCauseInstanceOf(DateTimeParseException.class).getCause()
.hasMessageStartingWith("Text '2009-31-10'")
.hasCauseInstanceOf(DateTimeException.class).getCause()
.hasMessageContaining("Invalid value for MonthOfYear (valid values 1 - 12): 31")
.hasNoCause();
}
@Test
void testBindISOTime() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
@@ -549,12 +519,9 @@ class DateTimeFormattingTests {
.hasMessageContainingAll(
"Unable to parse date time value \"210302\" using configuration from",
"@org.springframework.format.annotation.DateTimeFormat",
"yyyy-MM-dd", "M/d/yy", "yyyyMMdd", "yyyy.MM.dd")
.hasCauseInstanceOf(DateTimeParseException.class).getCause()
.hasMessageStartingWith("Text '210302'")
.hasNoCause();
"yyyy-MM-dd", "M/d/yy", "yyyyMMdd", "yyyy.MM.dd");
}
}
}
public static class DateTimeBean {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -1261,23 +1261,6 @@ class CronExpressionTests {
assertThat(actual.getDayOfWeek()).isEqualTo(SUNDAY);
}
@Test
public void daylightSaving() {
CronExpression cronExpression = CronExpression.parse("0 0 9 * * *");
ZonedDateTime last = ZonedDateTime.parse("2021-03-27T09:00:00+01:00[Europe/Amsterdam]");
ZonedDateTime expected = ZonedDateTime.parse("2021-03-28T09:00:00+02:00[Europe/Amsterdam]");
ZonedDateTime actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = ZonedDateTime.parse("2021-10-30T09:00:00+02:00[Europe/Amsterdam]");
expected = ZonedDateTime.parse("2021-10-31T09:00:00+01:00[Europe/Amsterdam]");
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
}
+3 -3
View File
@@ -8,7 +8,7 @@ apply plugin: "kotlin"
// cglib itself depends on asm and is therefore further transformed by the ShadowJar task to
// depend on org.springframework.asm; this avoids including two different copies of asm.
def cglibVersion = "3.3.0"
def objenesisVersion = "3.2"
def objenesisVersion = "3.1"
configurations {
cglib
@@ -70,8 +70,8 @@ dependencies {
jar {
reproducibleFileOrder = true
preserveFileTimestamps = false // maybe not necessary here, but good for reproducibility
manifest.attributes["Dependencies"] = "jdk.unsupported" // for WildFly (-> Objenesis 3.2)
preserveFileTimestamps = false // maybe not necessary here, but good for reproducibility
manifest.attributes["Dependencies"] = "jdk.unsupported" // JBoss modules
// Inline repackaged cglib classes directly into spring-core jar
dependsOn cglibRepackJar
@@ -163,7 +163,7 @@ public final class BridgeMethodResolver {
}
}
// A non-array type: compare the type itself.
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
if (!candidateParameter.equals(genericParameter.toClass())) {
return false;
}
}
@@ -1,6 +1,6 @@
/**
* Spring's repackaging of
* <a href="http://objenesis.org">Objenesis 3.2</a>
* <a href="http://objenesis.org">Objenesis 3.0</a>
* (with SpringObjenesis entry point; for internal use only).
*
* <p>This repackaging technique avoids any potential conflicts with
@@ -1,71 +0,0 @@
/*
* 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.
* 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 org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
class KotlinBridgeMethodResolverTests {
@Test
fun findBridgedMethod() {
val unbridged = GenericRepository::class.java.getDeclaredMethod("delete", Int::class.java)
val bridged = GenericRepository::class.java.getDeclaredMethod("delete", Any::class.java)
Assertions.assertThat(unbridged.isBridge).isFalse
Assertions.assertThat(bridged.isBridge).isTrue
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
}
@Test
fun findBridgedMethodWithArrays() {
val unbridged = GenericRepository::class.java.getDeclaredMethod("delete", Array<Int>::class.java)
val bridged = GenericRepository::class.java.getDeclaredMethod("delete", Array<Any>::class.java)
Assertions.assertThat(unbridged.isBridge).isFalse
Assertions.assertThat(bridged.isBridge).isTrue
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
}
}
interface GenericInterface<ID> {
fun delete(id: ID)
fun delete(ids: Array<ID>)
}
abstract class AbstractGenericClass<ID> : GenericInterface<ID> {
override fun delete(id: ID) {
}
override fun delete(ids: Array<ID>) {
}
}
class GenericRepository : AbstractGenericClass<Int>() {
override fun delete(id: Int) {
error("gotcha")
}
override fun delete(ids: Array<Int>) {
error("gotcha")
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -20,7 +20,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
@@ -90,12 +89,8 @@ public abstract class AbstractDataBufferAllocatingTests {
}
protected Consumer<DataBuffer> stringConsumer(String expected) {
return stringConsumer(expected, UTF_8);
}
protected Consumer<DataBuffer> stringConsumer(String expected, Charset charset) {
return dataBuffer -> {
String value = dataBuffer.toString(charset);
String value = dataBuffer.toString(UTF_8);
DataBufferUtils.release(dataBuffer);
assertThat(value).isEqualTo(expected);
};
@@ -136,7 +136,6 @@ public final class SpelCompiler implements Opcodes {
private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
String className = "spel/Ex" + getNextSuffix();
String evaluationContextClass = "org/springframework/expression/EvaluationContext";
ClassWriter cw = new ExpressionClassWriter();
cw.visit(V1_8, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
@@ -152,7 +151,7 @@ public final class SpelCompiler implements Opcodes {
// Create getValue() method
mv = cw.visitMethod(ACC_PUBLIC, "getValue",
"(Ljava/lang/Object;L" + evaluationContextClass + ";)Ljava/lang/Object;", null,
"(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
new String[] {"org/springframework/expression/EvaluationException"});
mv.visitCode();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -224,7 +224,8 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
q.setFlushMode(FlushModeType.AUTO);
List<Person> people = q.getResultList();
assertThat(people.size()).isEqualTo(0);
assertThatExceptionOfType(Exception.class).isThrownBy(q::getSingleResult)
assertThatExceptionOfType(Exception.class).isThrownBy(() ->
q.getSingleResult())
.withMessageContaining("closed");
// We would typically expect an IllegalStateException, but Hibernate throws a
// PersistenceException. So we assert the contents of the exception message instead.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2017 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.
@@ -34,9 +34,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -108,7 +105,6 @@ abstract class AbstractExpressionEvaluatingCondition implements ExecutionConditi
boolean loadContext = loadContextExtractor.apply(annotation.get());
boolean evaluatedToTrue = evaluateExpression(expression, loadContext, annotationType, context);
ConditionEvaluationResult result;
if (evaluatedToTrue) {
String adjective = (enabledOnTrue ? "enabled" : "disabled");
@@ -118,7 +114,7 @@ abstract class AbstractExpressionEvaluatingCondition implements ExecutionConditi
if (logger.isInfoEnabled()) {
logger.info(reason);
}
result = (enabledOnTrue ? ConditionEvaluationResult.enabled(reason)
return (enabledOnTrue ? ConditionEvaluationResult.enabled(reason)
: ConditionEvaluationResult.disabled(reason));
}
else {
@@ -128,26 +124,9 @@ abstract class AbstractExpressionEvaluatingCondition implements ExecutionConditi
if (logger.isDebugEnabled()) {
logger.debug(reason);
}
result = (enabledOnTrue ? ConditionEvaluationResult.disabled(reason) :
return (enabledOnTrue ? ConditionEvaluationResult.disabled(reason) :
ConditionEvaluationResult.enabled(reason));
}
// If we eagerly loaded the ApplicationContext to evaluate SpEL expressions
// and the test class ends up being disabled, we have to check if the
// user asked for the ApplicationContext to be closed via @DirtiesContext,
// since the DirtiesContextTestExecutionListener will never be invoked for
// a disabled test class.
// See https://github.com/spring-projects/spring-framework/issues/26694
if (loadContext && result.isDisabled() && element instanceof Class) {
Class<?> testClass = (Class<?>) element;
DirtiesContext dirtiesContext = TestContextAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class);
if (dirtiesContext != null) {
HierarchyMode hierarchyMode = dirtiesContext.hierarchyMode();
SpringExtension.getTestContextManager(context).getTestContext().markApplicationContextDirty(hierarchyMode);
}
}
return result;
}
private <A extends Annotation> boolean evaluateExpression(String expression, boolean loadContext,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -287,7 +287,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* Get the {@link TestContextManager} associated with the supplied {@code ExtensionContext}.
* @return the {@code TestContextManager} (never {@code null})
*/
static TestContextManager getTestContextManager(ExtensionContext context) {
private static TestContextManager getTestContextManager(ExtensionContext context) {
Assert.notNull(context, "ExtensionContext must not be null");
Class<?> testClass = context.getRequiredTestClass();
Store store = getStore(context);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -557,10 +557,10 @@ class TestContextAnnotationUtilsTests {
@Target(ElementType.TYPE)
@interface MetaConfig {
class DevConfig {
static class DevConfig {
}
class ProductionConfig {
static class ProductionConfig {
}
@@ -607,11 +607,11 @@ class TestContextAnnotationUtilsTests {
}
@MetaConfig
static class MetaConfigWithDefaultAttributesTestCase {
class MetaConfigWithDefaultAttributesTestCase {
}
@MetaConfig(classes = TestContextAnnotationUtilsTests.class)
static class MetaConfigWithOverriddenAttributesTestCase {
class MetaConfigWithOverriddenAttributesTestCase {
}
// -------------------------------------------------------------------------
@@ -1,107 +0,0 @@
/*
* 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.
* 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.test.context.junit.jupiter;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
/**
* Integration tests which verify support for {@link DisabledIf @DisabledIf} in
* conjunction with {@link DirtiesContext @DirtiesContext} and the
* {@link SpringExtension} in a JUnit Jupiter environment.
*
* @author Sam Brannen
* @since 5.2.14
* @see EnabledIfAndDirtiesContextTests
*/
class DisabledIfAndDirtiesContextTests {
private static AtomicBoolean contextClosed = new AtomicBoolean();
@BeforeEach
void reset() {
contextClosed.set(false);
}
@Test
void contextShouldBeClosedForEnabledTestClass() {
assertThat(contextClosed).as("context closed").isFalse();
EngineTestKit.engine("junit-jupiter").selectors(
selectClass(EnabledAndDirtiesContextTestCase.class))//
.execute()//
.testEvents()//
.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));
assertThat(contextClosed).as("context closed").isTrue();
}
@Test
void contextShouldBeClosedForDisabledTestClass() {
assertThat(contextClosed).as("context closed").isFalse();
EngineTestKit.engine("junit-jupiter").selectors(
selectClass(DisabledAndDirtiesContextTestCase.class))//
.execute()//
.testEvents()//
.assertStatistics(stats -> stats.started(0).succeeded(0).failed(0));
assertThat(contextClosed).as("context closed").isTrue();
}
@SpringJUnitConfig(Config.class)
@DisabledIf(expression = "false", loadContext = true)
@DirtiesContext
static class EnabledAndDirtiesContextTestCase {
@Test
void test() {
/* no-op */
}
}
@SpringJUnitConfig(Config.class)
@DisabledIf(expression = "true", loadContext = true)
@DirtiesContext
static class DisabledAndDirtiesContextTestCase {
@Test
void test() {
fail("This test must be disabled");
}
}
@Configuration
static class Config {
@Bean
DisposableBean disposableBean() {
return () -> contextClosed.set(true);
}
}
}
@@ -1,107 +0,0 @@
/*
* 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.
* 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.test.context.junit.jupiter;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
/**
* Integration tests which verify support for {@link EnabledIf @EnabledIf} in
* conjunction with {@link DirtiesContext @DirtiesContext} and the
* {@link SpringExtension} in a JUnit Jupiter environment.
*
* @author Sam Brannen
* @since 5.2.14
* @see DisabledIfAndDirtiesContextTests
*/
class EnabledIfAndDirtiesContextTests {
private static AtomicBoolean contextClosed = new AtomicBoolean();
@BeforeEach
void reset() {
contextClosed.set(false);
}
@Test
void contextShouldBeClosedForEnabledTestClass() {
assertThat(contextClosed).as("context closed").isFalse();
EngineTestKit.engine("junit-jupiter").selectors(
selectClass(EnabledAndDirtiesContextTestCase.class))//
.execute()//
.testEvents()//
.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));
assertThat(contextClosed).as("context closed").isTrue();
}
@Test
void contextShouldBeClosedForDisabledTestClass() {
assertThat(contextClosed).as("context closed").isFalse();
EngineTestKit.engine("junit-jupiter").selectors(
selectClass(DisabledAndDirtiesContextTestCase.class))//
.execute()//
.testEvents()//
.assertStatistics(stats -> stats.started(0).succeeded(0).failed(0));
assertThat(contextClosed).as("context closed").isTrue();
}
@SpringJUnitConfig(Config.class)
@EnabledIf(expression = "true", loadContext = true)
@DirtiesContext
static class EnabledAndDirtiesContextTestCase {
@Test
void test() {
/* no-op */
}
}
@SpringJUnitConfig(Config.class)
@EnabledIf(expression = "false", loadContext = true)
@DirtiesContext
static class DisabledAndDirtiesContextTestCase {
@Test
void test() {
fail("This test must be disabled");
}
}
@Configuration
static class Config {
@Bean
DisposableBean disposableBean() {
return () -> contextClosed.set(true);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -32,7 +32,6 @@ import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@@ -100,7 +99,7 @@ public class AsyncTests {
}
@Test
public void deferredResultWithImmediateValue() {
public void deferredResultWithImmediateValue() throws Exception {
this.testClient.get()
.uri("/1?deferredResultWithImmediateValue=true")
.exchange()
@@ -110,7 +109,7 @@ public class AsyncTests {
}
@Test
public void deferredResultWithDelayedError() {
public void deferredResultWithDelayedError() throws Exception {
this.testClient.get()
.uri("/1?deferredResultWithDelayedError=true")
.exchange()
@@ -119,7 +118,7 @@ public class AsyncTests {
}
@Test
public void listenableFuture() {
public void listenableFuture() throws Exception {
this.testClient.get()
.uri("/1?listenableFuture=true")
.exchange()
@@ -143,17 +142,17 @@ public class AsyncTests {
@RequestMapping(path = "/{id}", produces = "application/json")
private static class AsyncController {
@GetMapping(params = "callable")
@RequestMapping(params = "callable")
public Callable<Person> getCallable() {
return () -> new Person("Joe");
}
@GetMapping(params = "streaming")
@RequestMapping(params = "streaming")
public StreamingResponseBody getStreaming() {
return os -> os.write("name=Joe".getBytes(StandardCharsets.UTF_8));
}
@GetMapping(params = "streamingSlow")
@RequestMapping(params = "streamingSlow")
public StreamingResponseBody getStreamingSlow() {
return os -> {
os.write("name=Joe".getBytes());
@@ -167,41 +166,41 @@ public class AsyncTests {
};
}
@GetMapping(params = "streamingJson")
@RequestMapping(params = "streamingJson")
public ResponseEntity<StreamingResponseBody> getStreamingJson() {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
.body(os -> os.write("{\"name\":\"Joe\",\"someDouble\":0.5}".getBytes(StandardCharsets.UTF_8)));
}
@GetMapping(params = "deferredResult")
@RequestMapping(params = "deferredResult")
public DeferredResult<Person> getDeferredResult() {
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setResult(new Person("Joe")));
return result;
}
@GetMapping(params = "deferredResultWithImmediateValue")
@RequestMapping(params = "deferredResultWithImmediateValue")
public DeferredResult<Person> getDeferredResultWithImmediateValue() {
DeferredResult<Person> result = new DeferredResult<>();
result.setResult(new Person("Joe"));
return result;
}
@GetMapping(params = "deferredResultWithDelayedError")
@RequestMapping(params = "deferredResultWithDelayedError")
public DeferredResult<Person> getDeferredResultWithDelayedError() {
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setErrorResult(new RuntimeException("Delayed Error")));
return result;
}
@GetMapping(params = "listenableFuture")
@RequestMapping(params = "listenableFuture")
public ListenableFuture<Person> getListenableFuture() {
ListenableFutureTask<Person> futureTask = new ListenableFutureTask<>(() -> new Person("Joe"));
delay(100, futureTask);
return futureTask;
}
@GetMapping(params = "completableFutureWithImmediateValue")
@RequestMapping(params = "completableFutureWithImmediateValue")
public CompletableFuture<Person> getCompletableFutureWithImmediateValue() {
CompletableFuture<Person> future = new CompletableFuture<>();
future.complete(new Person("Joe"));
@@ -1,71 +0,0 @@
/*
* 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.
* 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.test.web.servlet.samples.client.standalone;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static java.time.Duration.ofMillis;
import static org.assertj.core.api.Assertions.assertThat;
/**
* SSE controller tests with MockMvc and WebTestClient.
*
* @author Rossen Stoyanchev
*/
public class SseTests {
private final WebTestClient testClient =
MockMvcWebTestClient.bindToController(new SseController()).build();
@Test
public void sse() {
FluxExchangeResult<Person> exchangeResult = this.testClient.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
}
@RestController
private static class SseController {
@GetMapping(path = "/persons", produces = "text/event-stream")
public Flux<Person> getPersonStream() {
return Flux.interval(ofMillis(100)).take(50).onBackpressureBuffer(50)
.map(index -> new Person("N" + index));
}
}
}
@@ -25,7 +25,6 @@
<Logger name="org.springframework.test.context.support.DelegatingSmartContextLoader" level="info" />
<Logger name="org.springframework.test.context.support.AbstractGenericContextLoader" level="info" />
<Logger name="org.springframework.test.context.support.AnnotationConfigContextLoader" level="info" />
<Logger name="org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener" level="warn" />
<Logger name="org.springframework.test.context.support.TestPropertySourceUtils" level="trace" />
<Logger name="org.springframework.beans" level="warn" />
<Logger name="org.springframework.test.web.servlet.result" level="debug" />
-2
View File
@@ -7,7 +7,6 @@ dependencies {
compile(project(":spring-beans"))
compile(project(":spring-core"))
compileOnly(project(":kotlin-coroutines"))
compileOnly("io.projectreactor.tools:blockhound")
optional(project(":spring-aop"))
optional(project(":spring-context"))
optional(project(":spring-oxm"))
@@ -76,7 +75,6 @@ dependencies {
testCompile("org.skyscreamer:jsonassert")
testCompile("org.xmlunit:xmlunit-assertj")
testCompile("org.xmlunit:xmlunit-matchers")
testCompile("io.projectreactor.tools:blockhound")
testRuntime("com.sun.mail:javax.mail")
testRuntime("com.sun.xml.bind:jaxb-core")
testRuntime("com.sun.xml.bind:jaxb-impl")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -99,12 +99,6 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
*/
public static final String ACCEPT_LANGUAGE = "Accept-Language";
/**
* The HTTP {@code Accept-Patch} header field name.
* @since 5.3.6
* @see <a href="https://tools.ietf.org/html/rfc5789#section-3.1">Section 3.1 of RFC 5789</a>
*/
public static final String ACCEPT_PATCH = "Accept-Patch";
/**
* The HTTP {@code Accept-Ranges} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
@@ -531,25 +525,6 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
.collect(Collectors.toList());
}
/**
* Set the list of acceptable {@linkplain MediaType media types} for
* {@code PATCH} methods, as specified by the {@code Accept-Patch} header.
* @since 5.3.6
*/
public void setAcceptPatch(List<MediaType> mediaTypes) {
set(ACCEPT_PATCH, MediaType.toString(mediaTypes));
}
/**
* Return the list of acceptable {@linkplain MediaType media types} for
* {@code PATCH} methods, as specified by the {@code Accept-Patch} header.
* <p>Returns an empty list when the acceptable media types are unspecified.
* @since 5.3.6
*/
public List<MediaType> getAcceptPatch() {
return MediaType.parseMediaTypes(get(ACCEPT_PATCH));
}
/**
* Set the (new) value of the {@code Access-Control-Allow-Credentials} response header.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -28,7 +28,6 @@ import java.util.Optional;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
@@ -66,7 +65,6 @@ public final class MediaTypeFactory {
*/
private static MultiValueMap<String, MediaType> parseMimeTypes() {
InputStream is = MediaTypeFactory.class.getResourceAsStream(MIME_TYPES_FILE_NAME);
Assert.state(is != null, MIME_TYPES_FILE_NAME + " not found in classpath");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII))) {
MultiValueMap<String, MediaType> result = new LinkedMultiValueMap<>();
String line;
@@ -84,7 +82,7 @@ public final class MediaTypeFactory {
return result;
}
catch (IOException ex) {
throw new IllegalStateException("Could not read " + MIME_TYPES_FILE_NAME, ex);
throw new IllegalStateException("Could not load '" + MIME_TYPES_FILE_NAME + "'", ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -18,6 +18,7 @@ package org.springframework.http.codec;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -170,14 +171,12 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
if (this.encoder == null) {
throw new CodecException("No SSE encoder configured and the data is not String.");
}
return Flux.defer(() -> {
DataBuffer startBuffer = encodeText(eventContent, mediaType, factory);
DataBuffer endBuffer = encodeText("\n\n", mediaType, factory);
DataBuffer dataBuffer = ((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints);
Hints.touchDataBuffer(dataBuffer, hints, logger);
return Flux.just(startBuffer, dataBuffer, endBuffer);
});
DataBuffer buffer = ((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints);
Hints.touchDataBuffer(buffer, hints, logger);
return Flux.just(factory.join(Arrays.asList(
encodeText(eventContent, mediaType, factory),
buffer,
encodeText("\n\n", mediaType, factory))));
}
private void writeField(String fieldName, Object fieldValue, StringBuilder sb) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -17,7 +17,6 @@
package org.springframework.http.codec.multipart;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -79,8 +78,6 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
private Mono<Path> fileStorageDirectory = Mono.defer(this::defaultFileStorageDirectory).cache();
private Charset headersCharset = StandardCharsets.UTF_8;
/**
* Configure the maximum amount of memory that is allowed per headers section of each part.
@@ -135,7 +132,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
}
/**
* Set the directory used to store parts larger than
* Sets the directory used to store parts larger than
* {@link #setMaxInMemorySize(int) maxInMemorySize}. By default, a directory
* named {@code spring-webflux-multipart} is created under the system
* temporary directory.
@@ -154,7 +151,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
}
/**
* Set the Reactor {@link Scheduler} to be used for creating files and
* Sets the Reactor {@link Scheduler} to be used for creating files and
* directories, and writing to files. By default,
* {@link Schedulers#boundedElastic()} is used, but this property allows for
* changing it to an externally managed scheduler.
@@ -174,11 +171,13 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
* in memory nor file.
* When {@code false}, parts are backed by
* in-memory and/or file storage. Defaults to {@code false}.
*
* <p><strong>NOTE</strong> that with streaming enabled, the
* {@code Flux<Part>} that is produced by this message reader must be
* consumed in the original order, i.e. the order of the HTTP message.
* Additionally, the {@linkplain Part#content() body contents} must either
* be completely consumed or canceled before moving to the next part.
*
* <p>Also note that enabling this property effectively ignores
* {@link #setMaxInMemorySize(int) maxInMemorySize},
* {@link #setMaxDiskUsagePerPart(long) maxDiskUsagePerPart},
@@ -189,18 +188,6 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
this.streaming = streaming;
}
/**
* Set the character set used to decode headers.
* Defaults to UTF-8 as per RFC 7578.
* @param headersCharset the charset to use for decoding headers
* @since 5.3.6
* @see <a href="https://tools.ietf.org/html/rfc7578#section-5.1">RFC-7578 Section 5.2</a>
*/
public void setHeadersCharset(Charset headersCharset) {
Assert.notNull(headersCharset, "HeadersCharset must not be null");
this.headersCharset = headersCharset;
}
@Override
public List<MediaType> getReadableMediaTypes() {
return Collections.singletonList(MediaType.MULTIPART_FORM_DATA);
@@ -227,7 +214,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
message.getHeaders().getContentType() + "\""));
}
Flux<MultipartParser.Token> tokens = MultipartParser.parse(message.getBody(), boundary,
this.maxHeadersSize, this.headersCharset);
this.maxHeadersSize);
return PartGenerator.createParts(tokens, this.maxParts, this.maxInMemorySize, this.maxDiskUsagePerPart,
this.streaming, this.fileStorageDirectory, this.blockingOperationScheduler);
@@ -235,7 +222,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
}
@Nullable
private byte[] boundary(HttpMessage message) {
private static byte[] boundary(HttpMessage message) {
MediaType contentType = message.getHeaders().getContentType();
if (contentType != null) {
String boundary = contentType.getParameter("boundary");
@@ -244,7 +231,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
if (len > 2 && boundary.charAt(0) == '"' && boundary.charAt(len - 1) == '"') {
boundary = boundary.substring(1, len - 1);
}
return boundary.getBytes(this.headersCharset);
return boundary.getBytes(StandardCharsets.ISO_8859_1);
}
}
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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,7 +16,7 @@
package org.springframework.http.codec.multipart;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -69,14 +69,11 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
private final AtomicBoolean requestOutstanding = new AtomicBoolean();
private final Charset headersCharset;
private MultipartParser(FluxSink<Token> sink, byte[] boundary, int maxHeadersSize, Charset headersCharset) {
private MultipartParser(FluxSink<Token> sink, byte[] boundary, int maxHeadersSize) {
this.sink = sink;
this.boundary = boundary;
this.maxHeadersSize = maxHeadersSize;
this.headersCharset = headersCharset;
this.state = new AtomicReference<>(new PreambleState());
}
@@ -85,13 +82,11 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
* @param buffers the input buffers
* @param boundary the multipart boundary, as found in the {@code Content-Type} header
* @param maxHeadersSize the maximum buffered header size
* @param headersCharset the charset to use for decoding headers
* @return a stream of parsed tokens
*/
public static Flux<Token> parse(Flux<DataBuffer> buffers, byte[] boundary, int maxHeadersSize,
Charset headersCharset) {
public static Flux<Token> parse(Flux<DataBuffer> buffers, byte[] boundary, int maxHeadersSize) {
return Flux.create(sink -> {
MultipartParser parser = new MultipartParser(sink, boundary, maxHeadersSize, headersCharset);
MultipartParser parser = new MultipartParser(sink, boundary, maxHeadersSize);
sink.onCancel(parser::onSinkCancel);
sink.onRequest(n -> parser.requestBuffer());
buffers.subscribe(parser);
@@ -185,7 +180,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
/**
* Represents the output of {@link #parse(Flux, byte[], int, Charset)}.
* Represents the output of {@link #parse(Flux, byte[], int)}.
*/
public abstract static class Token {
@@ -377,6 +372,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
DataBufferUtils.release(buf);
emitHeaders(parseHeaders());
// TODO: no need to check result of changeState, no further statements
changeState(this, new BodyState(), bodyBuf);
}
else {
@@ -412,7 +408,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
}
DataBuffer joined = this.buffers.get(0).factory().join(this.buffers);
this.buffers.clear();
String string = joined.toString(MultipartParser.this.headersCharset);
String string = joined.toString(StandardCharsets.ISO_8859_1);
DataBufferUtils.release(joined);
String[] lines = string.split(HEADER_ENTRY_SEPARATOR);
HttpHeaders result = new HttpHeaders();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -20,7 +20,6 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@@ -54,7 +53,7 @@ import org.springframework.util.FastByteArrayOutputStream;
/**
* Subscribes to a token stream (i.e. the result of
* {@link MultipartParser#parse(Flux, byte[], int, Charset)}, and produces a flux of {@link Part} objects.
* {@link MultipartParser#parse(Flux, byte[], int)}, and produces a flux of {@link Part} objects.
*
* @author Arjen Poutsma
* @since 5.3
@@ -578,9 +577,6 @@ final class PartGenerator extends BaseSubscriber<MultipartParser.Token> {
private WritingFileState createFileState(Path directory) {
try {
if (!Files.exists(directory)) {
Files.createDirectory(directory);
}
Path tempFile = Files.createTempFile(directory, null, ".multipart");
if (logger.isTraceEnabled()) {
logger.trace("Storing multipart data in file " + tempFile);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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,7 +28,7 @@ import org.springframework.core.MethodParameter;
* @see MissingPathVariableException
*/
@SuppressWarnings("serial")
public class MissingMatrixVariableException extends MissingRequestValueException {
public class MissingMatrixVariableException extends ServletRequestBindingException {
private final String variableName;
@@ -41,20 +41,7 @@ public class MissingMatrixVariableException extends MissingRequestValueException
* @param parameter the method parameter
*/
public MissingMatrixVariableException(String variableName, MethodParameter parameter) {
this(variableName, parameter, false);
}
/**
* Constructor for use when a value was present but converted to {@code null}.
* @param variableName the name of the missing matrix variable
* @param parameter the method parameter
* @param missingAfterConversion whether the value became null after conversion
* @since 5.3.6
*/
public MissingMatrixVariableException(
String variableName, MethodParameter parameter, boolean missingAfterConversion) {
super("", missingAfterConversion);
super("");
this.variableName = variableName;
this.parameter = parameter;
}
@@ -62,9 +49,8 @@ public class MissingMatrixVariableException extends MissingRequestValueException
@Override
public String getMessage() {
return "Required matrix variable '" + this.variableName + "' for method parameter type " +
this.parameter.getNestedParameterType().getSimpleName() + " is " +
(isMissingAfterConversion() ? "present but converted to null" : "not present");
return "Missing matrix variable '" + this.variableName +
"' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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,7 @@ import org.springframework.core.MethodParameter;
* @see MissingMatrixVariableException
*/
@SuppressWarnings("serial")
public class MissingPathVariableException extends MissingRequestValueException {
public class MissingPathVariableException extends ServletRequestBindingException {
private final String variableName;
@@ -43,20 +43,7 @@ public class MissingPathVariableException extends MissingRequestValueException {
* @param parameter the method parameter
*/
public MissingPathVariableException(String variableName, MethodParameter parameter) {
this(variableName, parameter, false);
}
/**
* Constructor for use when a value was present but converted to {@code null}.
* @param variableName the name of the missing path variable
* @param parameter the method parameter
* @param missingAfterConversion whether the value became null after conversion
* @since 5.3.6
*/
public MissingPathVariableException(
String variableName, MethodParameter parameter, boolean missingAfterConversion) {
super("", missingAfterConversion);
super("");
this.variableName = variableName;
this.parameter = parameter;
}
@@ -64,9 +51,8 @@ public class MissingPathVariableException extends MissingRequestValueException {
@Override
public String getMessage() {
return "Required URI template variable '" + this.variableName + "' for method parameter type " +
this.parameter.getNestedParameterType().getSimpleName() + " is " +
(isMissingAfterConversion() ? "present but converted to null" : "not present");
return "Missing URI template variable '" + this.variableName +
"' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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,7 +28,7 @@ import org.springframework.core.MethodParameter;
* @see MissingRequestHeaderException
*/
@SuppressWarnings("serial")
public class MissingRequestCookieException extends MissingRequestValueException {
public class MissingRequestCookieException extends ServletRequestBindingException {
private final String cookieName;
@@ -41,20 +41,7 @@ public class MissingRequestCookieException extends MissingRequestValueException
* @param parameter the method parameter
*/
public MissingRequestCookieException(String cookieName, MethodParameter parameter) {
this(cookieName, parameter, false);
}
/**
* Constructor for use when a value was present but converted to {@code null}.
* @param cookieName the name of the missing request cookie
* @param parameter the method parameter
* @param missingAfterConversion whether the value became null after conversion
* @since 5.3.6
*/
public MissingRequestCookieException(
String cookieName, MethodParameter parameter, boolean missingAfterConversion) {
super("", missingAfterConversion);
super("");
this.cookieName = cookieName;
this.parameter = parameter;
}
@@ -62,9 +49,8 @@ public class MissingRequestCookieException extends MissingRequestValueException
@Override
public String getMessage() {
return "Required cookie '" + this.cookieName + "' for method parameter type " +
this.parameter.getNestedParameterType().getSimpleName() + " is " +
(isMissingAfterConversion() ? "present but converted to null" : "not present");
return "Missing cookie '" + this.cookieName +
"' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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,7 +28,7 @@ import org.springframework.core.MethodParameter;
* @see MissingRequestCookieException
*/
@SuppressWarnings("serial")
public class MissingRequestHeaderException extends MissingRequestValueException {
public class MissingRequestHeaderException extends ServletRequestBindingException {
private final String headerName;
@@ -41,20 +41,7 @@ public class MissingRequestHeaderException extends MissingRequestValueException
* @param parameter the method parameter
*/
public MissingRequestHeaderException(String headerName, MethodParameter parameter) {
this(headerName, parameter, false);
}
/**
* Constructor for use when a value was present but converted to {@code null}.
* @param headerName the name of the missing request header
* @param parameter the method parameter
* @param missingAfterConversion whether the value became null after conversion
* @since 5.3.6
*/
public MissingRequestHeaderException(
String headerName, MethodParameter parameter, boolean missingAfterConversion) {
super("", missingAfterConversion);
super("");
this.headerName = headerName;
this.parameter = parameter;
}
@@ -62,9 +49,8 @@ public class MissingRequestHeaderException extends MissingRequestValueException
@Override
public String getMessage() {
String typeName = this.parameter.getNestedParameterType().getSimpleName();
return "Required request header '" + this.headerName + "' for method parameter type " + typeName + " is " +
(isMissingAfterConversion() ? "present but converted to null" : "not present");
return "Missing request header '" + this.headerName +
"' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName();
}
/**
@@ -1,50 +0,0 @@
/*
* 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.
* 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.bind;
/**
* Base class for {@link ServletRequestBindingException} exceptions that could
* not bind because the request value is required but is either missing or
* otherwise resolves to {@code null} after conversion.
*
* @author Rossen Stoyanchev
* @since 5.3.6
*/
@SuppressWarnings("serial")
public class MissingRequestValueException extends ServletRequestBindingException {
private final boolean missingAfterConversion;
public MissingRequestValueException(String msg) {
this(msg, false);
}
public MissingRequestValueException(String msg, boolean missingAfterConversion) {
super(msg);
this.missingAfterConversion = missingAfterConversion;
}
/**
* Whether the request value was present but converted to {@code null}, e.g. via
* {@code org.springframework.core.convert.support.IdToEntityConverter}.
*/
public boolean isMissingAfterConversion() {
return this.missingAfterConversion;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -23,7 +23,7 @@ package org.springframework.web.bind;
* @since 2.0.2
*/
@SuppressWarnings("serial")
public class MissingServletRequestParameterException extends MissingRequestValueException {
public class MissingServletRequestParameterException extends ServletRequestBindingException {
private final String parameterName;
@@ -36,20 +36,7 @@ public class MissingServletRequestParameterException extends MissingRequestValue
* @param parameterType the expected type of the missing parameter
*/
public MissingServletRequestParameterException(String parameterName, String parameterType) {
this(parameterName, parameterType, false);
}
/**
* Constructor for use when a value was present but converted to {@code null}.
* @param parameterName the name of the missing parameter
* @param parameterType the expected type of the missing parameter
* @param missingAfterConversion whether the value became null after conversion
* @since 5.3.6
*/
public MissingServletRequestParameterException(
String parameterName, String parameterType, boolean missingAfterConversion) {
super("", missingAfterConversion);
super("");
this.parameterName = parameterName;
this.parameterType = parameterType;
}
@@ -57,9 +44,7 @@ public class MissingServletRequestParameterException extends MissingRequestValue
@Override
public String getMessage() {
return "Required request parameter '" + this.parameterName + "' for method parameter type " +
this.parameterType + " is " +
(isMissingAfterConversion() ? "present but converted to null" : "not present");
return "Required " + this.parameterType + " parameter '" + this.parameterName + "' is not present";
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -26,7 +26,6 @@ import java.util.Set;
import java.util.function.Predicate;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -70,7 +69,7 @@ public final class HandlerTypePredicate implements Predicate<Class<?>> {
@Override
public boolean test(@Nullable Class<?> controllerType) {
public boolean test(Class<?> controllerType) {
if (!hasSelectors()) {
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -24,7 +24,6 @@ import org.springframework.web.bind.MissingRequestCookieException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.context.request.NativeWebRequest;
/**
* A base abstract class to resolve method arguments annotated with
@@ -71,12 +70,6 @@ public abstract class AbstractCookieValueMethodArgumentResolver extends Abstract
throw new MissingRequestCookieException(name, parameter);
}
@Override
protected void handleMissingValueAfterConversion(
String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
throw new MissingRequestCookieException(name, parameter, true);
}
private static final class CookieValueNamedValueInfo extends NamedValueInfo {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -135,7 +135,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
// Check for null value after conversion of incoming argument value
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
}
@@ -237,19 +237,6 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
/**
* Invoked when a named value is present but becomes {@code null} after conversion.
* @param name the name for the value
* @param parameter the method parameter
* @param request the current request
* @since 5.3.6
*/
protected void handleMissingValueAfterConversion(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception {
handleMissingValue(name, parameter, request);
}
/**
* A {@code null} results in a {@code false} value for {@code boolean}s or an exception for other primitives.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -87,12 +87,6 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMetho
throw new MissingRequestHeaderException(name, parameter);
}
@Override
protected void handleMissingValueAfterConversion(
String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
throw new MissingRequestHeaderException(name, parameter, true);
}
private static final class RequestHeaderNamedValueInfo extends NamedValueInfo {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -190,20 +190,6 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception {
handleMissingValueInternal(name, parameter, request, false);
}
@Override
protected void handleMissingValueAfterConversion(
String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
handleMissingValueInternal(name, parameter, request, true);
}
protected void handleMissingValueInternal(
String name, MethodParameter parameter, NativeWebRequest request, boolean missingAfterConversion)
throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
if (servletRequest == null || !MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
@@ -215,7 +201,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
}
else {
throw new MissingServletRequestParameterException(name,
parameter.getNestedParameterType().getSimpleName(), missingAfterConversion);
parameter.getNestedParameterType().getSimpleName());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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,12 +20,9 @@ import java.util.Collections;
import java.util.List;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* Exception for errors that fit response status 415 (unsupported media type).
@@ -44,9 +41,6 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException
@Nullable
private final ResolvableType bodyType;
@Nullable
private final HttpMethod method;
/**
* Constructor for when the specified Content-Type is invalid.
@@ -56,14 +50,13 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException
this.contentType = null;
this.supportedMediaTypes = Collections.emptyList();
this.bodyType = null;
this.method = null;
}
/**
* Constructor for when the Content-Type can be parsed but is not supported.
*/
public UnsupportedMediaTypeStatusException(@Nullable MediaType contentType, List<MediaType> supportedTypes) {
this(contentType, supportedTypes, null, null);
this(contentType, supportedTypes, null);
}
/**
@@ -72,30 +65,11 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException
*/
public UnsupportedMediaTypeStatusException(@Nullable MediaType contentType, List<MediaType> supportedTypes,
@Nullable ResolvableType bodyType) {
this(contentType, supportedTypes, bodyType, null);
}
/**
* Constructor that provides the HTTP method.
* @since 5.3.6
*/
public UnsupportedMediaTypeStatusException(@Nullable MediaType contentType, List<MediaType> supportedTypes,
@Nullable HttpMethod method) {
this(contentType, supportedTypes, null, method);
}
/**
* Constructor for when trying to encode from or decode to a specific Java type.
* @since 5.3.6
*/
public UnsupportedMediaTypeStatusException(@Nullable MediaType contentType, List<MediaType> supportedTypes,
@Nullable ResolvableType bodyType, @Nullable HttpMethod method) {
super(HttpStatus.UNSUPPORTED_MEDIA_TYPE, initReason(contentType, bodyType));
this.contentType = contentType;
this.supportedMediaTypes = Collections.unmodifiableList(supportedTypes);
this.bodyType = bodyType;
this.method = method;
}
private static String initReason(@Nullable MediaType contentType, @Nullable ResolvableType bodyType) {
@@ -133,14 +107,4 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException
return this.bodyType;
}
@Override
public HttpHeaders getResponseHeaders() {
if (HttpMethod.PATCH != this.method || CollectionUtils.isEmpty(this.supportedMediaTypes) ) {
return HttpHeaders.EMPTY;
}
HttpHeaders headers = new HttpHeaders();
headers.setAcceptPatch(this.supportedMediaTypes);
return headers;
}
}
@@ -23,9 +23,6 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import reactor.blockhound.BlockHound;
import reactor.blockhound.integration.BlockHoundIntegration;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -421,20 +418,4 @@ public final class WebHttpHandlerBuilder {
return new WebHttpHandlerBuilder(this);
}
/**
* {@code BlockHoundIntegration} for spring-web classes.
* @since 5.3.6
*/
public static class SpringWebBlockHoundIntegration implements BlockHoundIntegration {
@Override
public void applyTo(BlockHound.Builder builder) {
// Avoid hard references potentially anywhere in spring-web (no need for structural dependency)
builder.allowBlockingCallsInside("org.springframework.web.util.HtmlUtils", "<clinit>");
}
}
}
@@ -357,28 +357,18 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
String value = matcher.group(1).trim();
String host = value;
int portSeparatorIdx = value.lastIndexOf(':');
int squareBracketIdx = value.lastIndexOf(']');
if (portSeparatorIdx > squareBracketIdx) {
if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) {
throw new IllegalArgumentException("Invalid IPv4 address: " + value);
}
if (portSeparatorIdx > value.lastIndexOf(']')) {
host = value.substring(0, portSeparatorIdx);
try {
port = Integer.parseInt(value.substring(portSeparatorIdx + 1));
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Failed to parse a port from \"forwarded\"-type header value: " + value);
}
port = Integer.parseInt(value.substring(portSeparatorIdx + 1));
}
return InetSocketAddress.createUnresolved(host, port);
return new InetSocketAddress(host, port);
}
}
String forHeader = request.getHeaders().getFirst("X-Forwarded-For");
if (StringUtils.hasText(forHeader)) {
String host = StringUtils.tokenizeToStringArray(forHeader, ",")[0];
return InetSocketAddress.createUnresolved(host, port);
return new InetSocketAddress(host, port);
}
return null;
@@ -896,11 +886,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
private void adaptForwardedHost(String rawValue) {
int portSeparatorIdx = rawValue.lastIndexOf(':');
int squareBracketIdx = rawValue.lastIndexOf(']');
if (portSeparatorIdx > squareBracketIdx) {
if (squareBracketIdx == -1 && rawValue.indexOf(':') != portSeparatorIdx) {
throw new IllegalArgumentException("Invalid IPv4 address: " + rawValue);
}
if (portSeparatorIdx > rawValue.lastIndexOf(']')) {
host(rawValue.substring(0, portSeparatorIdx));
port(Integer.parseInt(rawValue.substring(portSeparatorIdx + 1)));
}
@@ -1,15 +0,0 @@
# 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.
# 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.
org.springframework.web.server.adapter.WebHttpHandlerBuilder$SpringWebBlockHoundIntegration
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class HttpEntityTests {
@Test
void noHeaders() {
public void noHeaders() {
String body = "foo";
HttpEntity<String> entity = new HttpEntity<>(body);
assertThat(entity.getBody()).isSameAs(body);
@@ -39,7 +39,7 @@ public class HttpEntityTests {
}
@Test
void httpHeaders() {
public void httpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
String body = "foo";
@@ -50,7 +50,7 @@ public class HttpEntityTests {
}
@Test
void multiValueMap() {
public void multiValueMap() {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.set("Content-Type", "text/plain");
String body = "foo";
@@ -61,30 +61,30 @@ public class HttpEntityTests {
}
@Test
void testEquals() {
public void testEquals() {
MultiValueMap<String, String> map1 = new LinkedMultiValueMap<>();
map1.set("Content-Type", "text/plain");
MultiValueMap<String, String> map2 = new LinkedMultiValueMap<>();
map2.set("Content-Type", "application/json");
assertThat(new HttpEntity<>().equals(new HttpEntity<>())).isTrue();
assertThat(new HttpEntity<>(map1).equals(new HttpEntity<>())).isFalse();
assertThat(new HttpEntity<>().equals(new HttpEntity<>(map2))).isFalse();
assertThat(new HttpEntity<>().equals(new HttpEntity<Object>())).isTrue();
assertThat(new HttpEntity<>(map1).equals(new HttpEntity<Object>())).isFalse();
assertThat(new HttpEntity<>().equals(new HttpEntity<Object>(map2))).isFalse();
assertThat(new HttpEntity<>(map1).equals(new HttpEntity<>(map1))).isTrue();
assertThat(new HttpEntity<>(map1).equals(new HttpEntity<>(map2))).isFalse();
assertThat(new HttpEntity<>(map1).equals(new HttpEntity<Object>(map1))).isTrue();
assertThat(new HttpEntity<>(map1).equals(new HttpEntity<Object>(map2))).isFalse();
assertThat(new HttpEntity<String>(null, null).equals(new HttpEntity<String>(null, null))).isTrue();
assertThat(new HttpEntity<>("foo", null).equals(new HttpEntity<String>(null, null))).isFalse();
assertThat(new HttpEntity<String>(null, null).equals(new HttpEntity<>("bar", null))).isFalse();
assertThat(new HttpEntity<>("foo", map1).equals(new HttpEntity<>("foo", map1))).isTrue();
assertThat(new HttpEntity<>("foo", map1).equals(new HttpEntity<>("bar", map1))).isFalse();
assertThat(new HttpEntity<>("foo", map1).equals(new HttpEntity<String>("foo", map1))).isTrue();
assertThat(new HttpEntity<>("foo", map1).equals(new HttpEntity<String>("bar", map1))).isFalse();
}
@Test
void responseEntity() {
public void responseEntity() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
String body = "foo";
@@ -104,7 +104,7 @@ public class HttpEntityTests {
}
@Test
void requestEntity() throws Exception {
public void requestEntity() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
String body = "foo";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -150,12 +150,8 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
testWrite(source, outputMessage, Pojo.class);
StepVerifier.create(outputMessage.getBody())
.consumeNextWith(stringConsumer("data:"))
.consumeNextWith(stringConsumer("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}"))
.consumeNextWith(stringConsumer("\n\n"))
.consumeNextWith(stringConsumer("data:"))
.consumeNextWith(stringConsumer("{\"foo\":\"foofoofoo\",\"bar\":\"barbarbar\"}"))
.consumeNextWith(stringConsumer("\n\n"))
.consumeNextWith(stringConsumer("data:{\"foo\":\"foofoo\",\"bar\":\"barbar\"}\n\n"))
.consumeNextWith(stringConsumer("data:{\"foo\":\"foofoofoo\",\"bar\":\"barbarbar\"}\n\n"))
.expectComplete()
.verify();
}
@@ -172,16 +168,12 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
testWrite(source, outputMessage, Pojo.class);
StepVerifier.create(outputMessage.getBody())
.consumeNextWith(stringConsumer("data:"))
.consumeNextWith(stringConsumer("{\n" +
.consumeNextWith(stringConsumer("data:{\n" +
"data: \"foo\" : \"foofoo\",\n" +
"data: \"bar\" : \"barbar\"\n" + "data:}"))
.consumeNextWith(stringConsumer("\n\n"))
.consumeNextWith(stringConsumer("data:"))
.consumeNextWith(stringConsumer("{\n" +
"data: \"bar\" : \"barbar\"\n" + "data:}\n\n"))
.consumeNextWith(stringConsumer("data:{\n" +
"data: \"foo\" : \"foofoofoo\",\n" +
"data: \"bar\" : \"barbarbar\"\n" + "data:}"))
.consumeNextWith(stringConsumer("\n\n"))
"data: \"bar\" : \"barbarbar\"\n" + "data:}\n\n"))
.expectComplete()
.verify();
}
@@ -198,9 +190,11 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
assertThat(outputMessage.getHeaders().getContentType()).isEqualTo(mediaType);
StepVerifier.create(outputMessage.getBody())
.consumeNextWith(stringConsumer("data:", charset))
.consumeNextWith(stringConsumer("{\"foo\":\"foo\uD834\uDD1E\",\"bar\":\"bar\uD834\uDD1E\"}", charset))
.consumeNextWith(stringConsumer("\n\n", charset))
.consumeNextWith(dataBuffer -> {
String value = dataBuffer.toString(charset);
DataBufferUtils.release(dataBuffer);
assertThat(value).isEqualTo("data:{\"foo\":\"foo\uD834\uDD1E\",\"bar\":\"bar\uD834\uDD1E\"}\n\n");
})
.expectComplete()
.verify();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -23,7 +23,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@@ -252,24 +251,6 @@ public class DefaultPartHttpMessageReaderTests {
latch.await();
}
@ParameterizedDefaultPartHttpMessageReaderTest
public void utf8Headers(String displayName, DefaultPartHttpMessageReader reader) throws InterruptedException {
MockServerHttpRequest request = createRequest(
new ClassPathResource("utf8.multipart", getClass()), "\"simple-boundary\"");
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
CountDownLatch latch = new CountDownLatch(1);
StepVerifier.create(result)
.consumeNextWith(part -> {
assertThat(part.headers()).containsEntry("Føø", Collections.singletonList("Bår"));
testPart(part, null, "This is plain ASCII text.", latch);
})
.verifyComplete();
latch.await();
}
private void testBrowser(DefaultPartHttpMessageReader reader, Resource resource, String boundary)
throws InterruptedException {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -45,20 +45,20 @@ public class ServletServerHttpRequestTests {
@BeforeEach
void create() {
public void create() {
mockRequest = new MockHttpServletRequest();
request = new ServletServerHttpRequest(mockRequest);
}
@Test
void getMethod() {
public void getMethod() {
mockRequest.setMethod("POST");
assertThat(request.getMethod()).as("Invalid method").isEqualTo(HttpMethod.POST);
}
@Test
void getUriForSimplePath() throws URISyntaxException {
public void getUriForSimplePath() throws URISyntaxException {
URI uri = new URI("https://example.com/path");
mockRequest.setScheme(uri.getScheme());
mockRequest.setServerName(uri.getHost());
@@ -69,7 +69,7 @@ public class ServletServerHttpRequestTests {
}
@Test
void getUriWithQueryString() throws URISyntaxException {
public void getUriWithQueryString() throws URISyntaxException {
URI uri = new URI("https://example.com/path?query");
mockRequest.setScheme(uri.getScheme());
mockRequest.setServerName(uri.getHost());
@@ -80,7 +80,7 @@ public class ServletServerHttpRequestTests {
}
@Test // SPR-16414
void getUriWithQueryParam() throws URISyntaxException {
public void getUriWithQueryParam() throws URISyntaxException {
mockRequest.setScheme("https");
mockRequest.setServerPort(443);
mockRequest.setServerName("example.com");
@@ -90,7 +90,7 @@ public class ServletServerHttpRequestTests {
}
@Test // SPR-16414
void getUriWithMalformedQueryParam() throws URISyntaxException {
public void getUriWithMalformedQueryParam() throws URISyntaxException {
mockRequest.setScheme("https");
mockRequest.setServerPort(443);
mockRequest.setServerName("example.com");
@@ -100,7 +100,7 @@ public class ServletServerHttpRequestTests {
}
@Test // SPR-13876
void getUriWithEncoding() throws URISyntaxException {
public void getUriWithEncoding() throws URISyntaxException {
URI uri = new URI("https://example.com/%E4%B8%AD%E6%96%87" +
"?redirect=https%3A%2F%2Fgithub.com%2Fspring-projects%2Fspring-framework");
mockRequest.setScheme(uri.getScheme());
@@ -112,7 +112,7 @@ public class ServletServerHttpRequestTests {
}
@Test
void getHeaders() {
public void getHeaders() {
String headerName = "MyHeader";
String headerValue1 = "value1";
String headerValue2 = "value2";
@@ -132,7 +132,7 @@ public class ServletServerHttpRequestTests {
}
@Test
void getHeadersWithEmptyContentTypeAndEncoding() {
public void getHeadersWithEmptyContentTypeAndEncoding() {
String headerName = "MyHeader";
String headerValue1 = "value1";
String headerValue2 = "value2";
@@ -152,8 +152,8 @@ public class ServletServerHttpRequestTests {
}
@Test
void getBody() throws IOException {
byte[] content = "Hello World".getBytes(StandardCharsets.UTF_8);
public void getBody() throws IOException {
byte[] content = "Hello World".getBytes("UTF-8");
mockRequest.setContent(content);
byte[] result = FileCopyUtils.copyToByteArray(request.getBody());
@@ -161,17 +161,16 @@ public class ServletServerHttpRequestTests {
}
@Test
void getFormBody() throws IOException {
public void getFormBody() throws IOException {
// Charset (SPR-8676)
mockRequest.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
mockRequest.setMethod("POST");
mockRequest.addParameter("name 1", "value 1");
mockRequest.addParameter("name 2", "value 2+1", "value 2+2");
mockRequest.addParameter("name 2", new String[] {"value 2+1", "value 2+2"});
mockRequest.addParameter("name 3", (String) null);
byte[] result = FileCopyUtils.copyToByteArray(request.getBody());
byte[] content = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3".getBytes(
StandardCharsets.UTF_8);
byte[] content = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3".getBytes("UTF-8");
assertThat(result).as("Invalid content returned").isEqualTo(content);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -36,7 +36,6 @@ 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.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
@@ -441,7 +440,7 @@ public class ForwardedHeaderFilterTests {
request.addHeader(FORWARDED, "for=\"[2001:db8:cafe::17]\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("2001:db8:cafe:0:0:0:0:17");
assertThat(actual.getRemotePort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
}
@@ -459,7 +458,7 @@ public class ForwardedHeaderFilterTests {
request.addHeader(FORWARDED, "For=\"[2001:db8:cafe::17]:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("2001:db8:cafe:0:0:0:0:17");
assertThat(actual.getRemotePort()).isEqualTo(47011);
}
@@ -471,13 +470,6 @@ public class ForwardedHeaderFilterTests {
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("203.0.113.195");
assertThat(actual.getRemotePort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
}
@Test // gh-26748
public void forwardedForInvalidIpV6Address() {
request.addHeader(FORWARDED, "for=\"2a02:918:175:ab60:45ee:c12c:dac1:808b\"");
assertThatIllegalArgumentException().isThrownBy(
ForwardedHeaderFilterTests.this::filterAndGetWrappedRequest);
}
}
@Nested
@@ -453,21 +453,6 @@ class UriComponentsBuilderTests {
assertThat(result.toString()).isEqualTo("http://[1abc:2abc:3abc::5ABC:6abc]:8080/mvc-showcase");
}
@Test // gh-26748
void fromHttpRequestWithForwardedInvalidIPv6Address() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setScheme("http");
request.setServerName("localhost");
request.setServerPort(-1);
request.setRequestURI("/mvc-showcase");
request.addHeader("X-Forwarded-Host", "2a02:918:175:ab60:45ee:c12c:dac1:808b");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
assertThatIllegalArgumentException().isThrownBy(() ->
UriComponentsBuilder.fromHttpRequest(httpRequest).build());
}
@Test
void fromHttpRequestWithForwardedHost() {
MockHttpServletRequest request = new MockHttpServletRequest();
@@ -1,5 +0,0 @@
--simple-boundary
Føø: Bår
This is plain ASCII text.
--simple-boundary--
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -110,7 +110,7 @@ public class PathResourceResolver extends AbstractResourceResolver {
*/
protected Mono<Resource> getResource(String resourcePath, Resource location) {
try {
if (!(location instanceof UrlResource)) {
if (location instanceof ClassPathResource) {
resourcePath = UriUtils.decode(resourcePath, StandardCharsets.UTF_8);
}
Resource resource = location.createRelative(resourcePath);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -37,7 +37,6 @@ import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.result.condition.NameValueExpression;
@@ -174,8 +173,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
String httpMethod = request.getMethodValue();
Set<HttpMethod> methods = helper.getAllowedMethods();
if (HttpMethod.OPTIONS.matches(httpMethod)) {
Set<MediaType> mediaTypes = helper.getConsumablePatchMediaTypes();
HttpOptionsHandler handler = new HttpOptionsHandler(methods, mediaTypes);
HttpOptionsHandler handler = new HttpOptionsHandler(methods);
return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
}
throw new MethodNotAllowedException(httpMethod, methods);
@@ -190,7 +188,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
catch (InvalidMediaTypeException ex) {
throw new UnsupportedMediaTypeStatusException(ex.getMessage());
}
throw new UnsupportedMediaTypeStatusException(contentType, new ArrayList<>(mediaTypes), exchange.getRequest().getMethod());
throw new UnsupportedMediaTypeStatusException(contentType, new ArrayList<>(mediaTypes));
}
if (helper.hasProducesMismatch()) {
@@ -303,22 +301,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
collect(Collectors.toList());
}
/**
* Return declared "consumable" types but only among those that have
* PATCH specified, or that have no methods at all.
*/
public Set<MediaType> getConsumablePatchMediaTypes() {
Set<MediaType> result = new LinkedHashSet<>();
for (PartialMatch match : this.partialMatches) {
Set<RequestMethod> methods = match.getInfo().getMethodsCondition().getMethods();
if (methods.isEmpty() || methods.contains(RequestMethod.PATCH)) {
result.addAll(match.getInfo().getConsumesCondition().getConsumableMediaTypes());
}
}
return result;
}
/**
* Container for a RequestMappingInfo that matches the URL path at least.
@@ -385,9 +367,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
private final HttpHeaders headers = new HttpHeaders();
public HttpOptionsHandler(Set<HttpMethod> declaredMethods, Set<MediaType> acceptPatch) {
public HttpOptionsHandler(Set<HttpMethod> declaredMethods) {
this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
this.headers.setAcceptPatch(new ArrayList<>(acceptPatch));
}
private static Set<HttpMethod> initAllowedHttpMethods(Set<HttpMethod> declaredMethods) {
@@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.awaitSingle
import kotlinx.coroutines.reactive.awaitSingle
import kotlinx.coroutines.reactive.awaitSingleOrNull
import kotlinx.coroutines.reactor.asFlux
import kotlinx.coroutines.reactor.mono
import org.reactivestreams.Publisher
@@ -144,18 +143,6 @@ suspend inline fun <reified T : Any> WebClient.ResponseSpec.awaitBody() : T =
else -> bodyToMono<T>().awaitSingle()
}
/**
* Coroutines variant of [WebClient.ResponseSpec.bodyToMono].
*
* @author Valentin Shakhov
* @since 5.3.6
*/
suspend inline fun <reified T : Any> WebClient.ResponseSpec.awaitBodyOrNull() : T? =
when (T::class) {
Unit::class -> awaitBodilessEntity().let { Unit as T? }
else -> bodyToMono<T>().awaitSingleOrNull()
}
/**
* Coroutines variant of [WebClient.ResponseSpec.toBodilessEntity].
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -32,7 +32,6 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.buffer.DataBuffer;
@@ -52,7 +51,6 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse;
import org.springframework.web.testfixture.server.MockServerWebExchange;
import org.springframework.web.util.UriUtils;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
@@ -234,25 +232,6 @@ public class ResourceWebHandlerTests {
assertResponseBody(exchange, "foo bar foo bar foo bar");
}
@Test
public void getResourceFromFileSystem() throws Exception {
String path = new ClassPathResource("", getClass()).getFile().getCanonicalPath()
.replace('\\', '/').replace("classes/java", "resources") + "/";
ResourceWebHandler handler = new ResourceWebHandler();
handler.setLocations(Collections.singletonList(new FileSystemResource(path)));
handler.afterPropertiesSet();
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(""));
setPathWithinHandlerMapping(exchange, UriUtils.encodePath("test/foo with spaces.css", UTF_8));
handler.handle(exchange).block(TIMEOUT);
HttpHeaders headers = exchange.getResponse().getHeaders();
assertThat(headers.getContentType()).isEqualTo(MediaType.parseMediaType("text/css"));
assertThat(headers.getContentLength()).isEqualTo(17);
assertResponseBody(exchange, "h1 { color:red; }");
}
@Test // SPR-14577
public void getMediaTypeWithFavorPathExtensionOff() throws Exception {
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -37,7 +37,6 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
@@ -45,7 +44,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerMapping;
@@ -194,12 +192,10 @@ public class RequestMappingInfoHandlerMappingTests {
List<HttpMethod> allMethodExceptTrace = new ArrayList<>(Arrays.asList(HttpMethod.values()));
allMethodExceptTrace.remove(HttpMethod.TRACE);
testHttpOptions("/foo", EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS), null);
testHttpOptions("/person/1", EnumSet.of(HttpMethod.PUT, HttpMethod.OPTIONS), null);
testHttpOptions("/persons", EnumSet.copyOf(allMethodExceptTrace), null);
testHttpOptions("/something", EnumSet.of(HttpMethod.PUT, HttpMethod.POST), null);
testHttpOptions("/qux", EnumSet.of(HttpMethod.PATCH,HttpMethod.GET,HttpMethod.HEAD,HttpMethod.OPTIONS),
new MediaType("foo", "bar"));
testHttpOptions("/foo", EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS));
testHttpOptions("/person/1", EnumSet.of(HttpMethod.PUT, HttpMethod.OPTIONS));
testHttpOptions("/persons", EnumSet.copyOf(allMethodExceptTrace));
testHttpOptions("/something", EnumSet.of(HttpMethod.PUT, HttpMethod.POST));
}
@Test
@@ -317,26 +313,6 @@ public class RequestMappingInfoHandlerMappingTests {
assertThat(uriVariables.get("cars")).isEqualTo("cars");
}
@Test
public void handlePatchUnsupportedMediaType() {
MockServerHttpRequest request = MockServerHttpRequest.patch("/qux")
.header("content-type", "application/xml")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Object> mono = this.handlerMapping.getHandler(exchange);
StepVerifier.create(mono)
.expectErrorSatisfies(ex -> {
assertThat(ex).isInstanceOf(UnsupportedMediaTypeStatusException.class);
UnsupportedMediaTypeStatusException umtse = (UnsupportedMediaTypeStatusException) ex;
MediaType mediaType = new MediaType("foo", "bar");
assertThat(umtse.getSupportedMediaTypes()).containsExactly(mediaType);
assertThat(umtse.getResponseHeaders().getAcceptPatch()).containsExactly(mediaType);
})
.verify();
}
@SuppressWarnings("unchecked")
private <T> void assertError(Mono<Object> mono, final Class<T> exceptionClass, final Consumer<T> consumer) {
@@ -356,7 +332,7 @@ public class RequestMappingInfoHandlerMappingTests {
assertError(mono, UnsupportedMediaTypeStatusException.class, ex -> assertThat(ex.getSupportedMediaTypes()).as("Invalid supported consumable media types").isEqualTo(Collections.singletonList(new MediaType("application", "xml"))));
}
private void testHttpOptions(String requestURI, Set<HttpMethod> allowedMethods, @Nullable MediaType acceptPatch) {
private void testHttpOptions(String requestURI, Set<HttpMethod> allowedMethods) {
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.options(requestURI));
HandlerMethod handlerMethod = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
@@ -370,13 +346,7 @@ public class RequestMappingInfoHandlerMappingTests {
Object value = result.getReturnValue();
assertThat(value).isNotNull();
assertThat(value.getClass()).isEqualTo(HttpHeaders.class);
HttpHeaders headers = (HttpHeaders) value;
assertThat(headers.getAllow()).hasSameElementsAs(allowedMethods);
if (acceptPatch != null && headers.getAllow().contains(HttpMethod.PATCH) ) {
assertThat(headers.getAcceptPatch()).containsExactly(acceptPatch);
}
assertThat(((HttpHeaders) value).getAllow()).isEqualTo(allowedMethods);
}
private void testMediaTypeNotAcceptable(String url) {
@@ -460,16 +430,6 @@ public class RequestMappingInfoHandlerMappingTests {
return headers;
}
@RequestMapping(value = "/qux", method = RequestMethod.GET, produces = "application/xml")
public String getBaz() {
return "";
}
@RequestMapping(value = "/qux", method = RequestMethod.PATCH, consumes = "foo/bar")
public void patchBaz(String value) {
}
public void dummy() { }
}
@@ -136,25 +136,6 @@ class WebClientExtensionsTests {
}
}
@Test
fun awaitBodyOrNull() {
val spec = mockk<WebClient.ResponseSpec>()
every { spec.bodyToMono<String>() } returns Mono.just("foo")
runBlocking {
assertThat(spec.awaitBodyOrNull<String>()).isEqualTo("foo")
}
}
@Test
fun `awaitBodyOrNull of type Unit`() {
val spec = mockk<WebClient.ResponseSpec>()
val entity = mockk<ResponseEntity<Void>>()
every { spec.toBodilessEntity() } returns Mono.just(entity)
runBlocking {
assertThat(spec.awaitBodyOrNull<Unit>()).isEqualTo(Unit)
}
}
@Test
fun awaitBodilessEntity() {
val spec = mockk<WebClient.ResponseSpec>()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -60,7 +60,7 @@ public class CorsRegistration {
/**
* Alternative to {@link #allowCredentials} that supports origins declared
* via wildcard patterns. Please, see
* {@link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
* @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
* <p>By default this is not set.
* @since 5.3
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -34,7 +34,6 @@ import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import org.springframework.web.util.pattern.PatternParseException;
/**
* Wraps a {@link HandlerInterceptor} and uses URL patterns to determine whether
@@ -65,10 +64,10 @@ public final class MappedInterceptor implements HandlerInterceptor {
@Nullable
private final PatternAdapter[] includePatterns;
private final PathPattern[] includePatterns;
@Nullable
private final PatternAdapter[] excludePatterns;
private final PathPattern[] excludePatterns;
private PathMatcher pathMatcher = defaultPathMatcher;
@@ -89,11 +88,21 @@ public final class MappedInterceptor implements HandlerInterceptor {
public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns,
HandlerInterceptor interceptor, @Nullable PathPatternParser parser) {
this.includePatterns = PatternAdapter.initPatterns(includePatterns, parser);
this.excludePatterns = PatternAdapter.initPatterns(excludePatterns, parser);
this.includePatterns = initPatterns(includePatterns, parser);
this.excludePatterns = initPatterns(excludePatterns, parser);
this.interceptor = interceptor;
}
@Nullable
private static PathPattern[] initPatterns(
@Nullable String[] patterns, @Nullable PathPatternParser parser) {
if (ObjectUtils.isEmpty(patterns)) {
return null;
}
parser = (parser != null ? parser : PathPatternParser.defaultInstance);
return Arrays.stream(patterns).map(parser::parse).toArray(PathPattern[]::new);
}
/**
* Variant of
@@ -142,7 +151,7 @@ public final class MappedInterceptor implements HandlerInterceptor {
@Nullable
public String[] getPathPatterns() {
return (!ObjectUtils.isEmpty(this.includePatterns) ?
Arrays.stream(this.includePatterns).map(PatternAdapter::getPatternString).toArray(String[]::new) :
Arrays.stream(this.includePatterns).map(PathPattern::getPatternString).toArray(String[]::new) :
null);
}
@@ -190,8 +199,8 @@ public final class MappedInterceptor implements HandlerInterceptor {
}
boolean isPathContainer = (path instanceof PathContainer);
if (!ObjectUtils.isEmpty(this.excludePatterns)) {
for (PatternAdapter adapter : this.excludePatterns) {
if (adapter.match(path, isPathContainer, this.pathMatcher)) {
for (PathPattern pattern : this.excludePatterns) {
if (matchPattern(path, isPathContainer, pattern)) {
return false;
}
}
@@ -199,14 +208,20 @@ public final class MappedInterceptor implements HandlerInterceptor {
if (ObjectUtils.isEmpty(this.includePatterns)) {
return true;
}
for (PatternAdapter adapter : this.includePatterns) {
if (adapter.match(path, isPathContainer, this.pathMatcher)) {
for (PathPattern pattern : this.includePatterns) {
if (matchPattern(path, isPathContainer, pattern)) {
return true;
}
}
return false;
}
private boolean matchPattern(Object path, boolean isPathContainer, PathPattern pattern) {
return (isPathContainer ?
pattern.matches((PathContainer) path) :
this.pathMatcher.match(pattern.getPatternString(), (String) path));
}
/**
* Determine a match for the given lookup path.
* @param lookupPath the current request path
@@ -218,8 +233,8 @@ public final class MappedInterceptor implements HandlerInterceptor {
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
pathMatcher = (this.pathMatcher != defaultPathMatcher ? this.pathMatcher : pathMatcher);
if (!ObjectUtils.isEmpty(this.excludePatterns)) {
for (PatternAdapter adapter : this.excludePatterns) {
if (pathMatcher.match(adapter.getPatternString(), lookupPath)) {
for (PathPattern pattern : this.excludePatterns) {
if (pathMatcher.match(pattern.getPatternString(), lookupPath)) {
return false;
}
}
@@ -227,8 +242,8 @@ public final class MappedInterceptor implements HandlerInterceptor {
if (ObjectUtils.isEmpty(this.includePatterns)) {
return true;
}
for (PatternAdapter adapter : this.includePatterns) {
if (pathMatcher.match(adapter.getPatternString(), lookupPath)) {
for (PathPattern pattern : this.includePatterns) {
if (pathMatcher.match(pattern.getPatternString(), lookupPath)) {
return true;
}
}
@@ -259,64 +274,4 @@ public final class MappedInterceptor implements HandlerInterceptor {
this.interceptor.afterCompletion(request, response, handler, ex);
}
/**
* Contains both the parsed {@link PathPattern} and the raw String pattern,
* and uses the former when the cached path is {@link PathContainer} or the
* latter otherwise. If the pattern cannot be parsed due to unsupported
* syntax, then {@link PathMatcher} is used for all requests.
* @since 5.3.6
*/
private static class PatternAdapter {
private final String patternString;
@Nullable
private final PathPattern pathPattern;
public PatternAdapter(String pattern, @Nullable PathPatternParser parser) {
this.patternString = pattern;
this.pathPattern = initPathPattern(pattern, parser);
}
@Nullable
private static PathPattern initPathPattern(String pattern, @Nullable PathPatternParser parser) {
try {
return (parser != null ? parser : PathPatternParser.defaultInstance).parse(pattern);
}
catch (PatternParseException ex) {
return null;
}
}
public String getPatternString() {
return this.patternString;
}
public boolean match(Object path, boolean isPathContainer, PathMatcher pathMatcher) {
if (isPathContainer) {
PathContainer pathContainer = (PathContainer) path;
if (this.pathPattern != null) {
return this.pathPattern.matches(pathContainer);
}
String lookupPath = pathContainer.value();
path = UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
}
return pathMatcher.match(this.patternString, (String) path);
}
@Nullable
public static PatternAdapter[] initPatterns(
@Nullable String[] patterns, @Nullable PathPatternParser parser) {
if (ObjectUtils.isEmpty(patterns)) {
return null;
}
return Arrays.stream(patterns)
.map(pattern -> new PatternAdapter(pattern, parser))
.toArray(PatternAdapter[]::new);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -72,7 +72,7 @@ public class AcceptHeaderLocaleResolver implements LocaleResolver {
/**
* Configure a fixed default locale to fall back on if the request does not
* have an "Accept-Language" header.
* <p>By default this is not set in which case when there is no "Accept-Language"
* <p>By default this is not set in which case when there is "Accept-Language"
* header, the default locale for the server is used as defined in
* {@link HttpServletRequest#getLocale()}.
* @param defaultLocale the default locale to use
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -244,8 +244,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
if (helper.hasMethodsMismatch()) {
Set<String> methods = helper.getAllowedMethods();
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
Set<MediaType> mediaTypes = helper.getConsumablePatchMediaTypes();
HttpOptionsHandler handler = new HttpOptionsHandler(methods, mediaTypes);
HttpOptionsHandler handler = new HttpOptionsHandler(methods);
return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
}
throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
@@ -412,21 +411,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
return result;
}
/**
* Return declared "consumable" types but only among those that have
* PATCH specified, or that have no methods at all.
*/
public Set<MediaType> getConsumablePatchMediaTypes() {
Set<MediaType> result = new LinkedHashSet<>();
for (PartialMatch match : this.partialMatches) {
Set<RequestMethod> methods = match.getInfo().getMethodsCondition().getMethods();
if (methods.isEmpty() || methods.contains(RequestMethod.PATCH)) {
result.addAll(match.getInfo().getConsumesCondition().getConsumableMediaTypes());
}
}
return result;
}
/**
* Container for a RequestMappingInfo that matches the URL path at least.
@@ -491,9 +475,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
private final HttpHeaders headers = new HttpHeaders();
public HttpOptionsHandler(Set<String> declaredMethods, Set<MediaType> acceptPatch) {
public HttpOptionsHandler(Set<String> declaredMethods) {
this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
this.headers.setAcceptPatch(new ArrayList<>(acceptPatch));
}
private static Set<HttpMethod> initAllowedHttpMethods(Set<String> declaredMethods) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -126,12 +126,6 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
throw new MissingMatrixVariableException(name, parameter);
}
@Override
protected void handleMissingValueAfterConversion(
String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
throw new MissingMatrixVariableException(name, parameter, true);
}
private static final class MatrixVariableNamedValueInfo extends NamedValueInfo {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -97,9 +97,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public class MvcUriComponentsBuilder {
/**
* Well-known name for the {@link CompositeUriComponentsContributor} object in the bean factory.
*/
/** Well-known name for the {@link CompositeUriComponentsContributor} object in the bean factory. */
public static final String MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME = "mvcUriComponentsContributor";
@@ -718,7 +716,7 @@ public class MvcUriComponentsBuilder {
@Override
@Nullable
public Object intercept(@Nullable Object obj, Method method, Object[] args, @Nullable MethodProxy proxy) {
public Object intercept(Object obj, Method method, Object[] args, @Nullable MethodProxy proxy) {
switch (method.getName()) {
case "getControllerType": return this.controllerType;
case "getControllerMethod": return this.controllerMethod;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -101,13 +101,6 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
throw new MissingPathVariableException(name, parameter);
}
@Override
protected void handleMissingValueAfterConversion(
String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
throw new MissingPathVariableException(name, parameter, true);
}
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2017 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.
@@ -57,7 +57,7 @@ public interface RequestBodyAdvice {
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @param converterType the converter used to deserialize the body
* @return the input request or a new instance (never {@code null})
* @return the input request or a new instance, never {@code null}
*/
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
@@ -83,8 +83,8 @@ public interface RequestBodyAdvice {
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @param converterType the selected converter type
* @return the value to use, or {@code null} which may then raise an
* {@code HttpMessageNotReadableException} if the argument is required
* @return the value to use or {@code null} which may then raise an
* {@code HttpMessageNotReadableException} if the argument is required.
*/
@Nullable
Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2015 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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
@@ -26,10 +25,10 @@ import org.springframework.lang.Nullable;
/**
* A convenient starting point for implementing
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice
* RequestBodyAdvice} with default method implementations.
* {@link org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
* ResponseBodyAdvice} with default method implementations.
*
* <p>Subclasses are required to implement {@link #supports} to return true
* <p>Sub-classes are required to implement {@link #supports} to return true
* depending on when the advice applies.
*
* @author Rossen Stoyanchev
@@ -42,7 +41,8 @@ public abstract class RequestBodyAdviceAdapter implements RequestBodyAdvice {
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
Type targetType, Class<? extends HttpMessageConverter<?>> converterType)
throws IOException {
return inputMessage;
}
@@ -62,8 +62,9 @@ public abstract class RequestBodyAdviceAdapter implements RequestBodyAdvice {
*/
@Override
@Nullable
public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -231,12 +231,6 @@ public abstract class ResponseEntityExceptionHandler {
List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) {
headers.setAccept(mediaTypes);
if (request instanceof ServletWebRequest) {
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
if (HttpMethod.PATCH.equals(servletWebRequest.getHttpMethod())) {
headers.setAcceptPatch(mediaTypes);
}
}
}
return handleExceptionInternal(ex, null, headers, status, request);
@@ -281,9 +281,6 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) {
response.setHeader("Accept", MediaType.toString(mediaTypes));
if (request.getMethod().equals("PATCH")) {
response.setHeader("Accept-Patch", MediaType.toString(mediaTypes));
}
}
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
return new ModelAndView();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -33,11 +33,9 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.UrlPathHelper;
@@ -153,7 +151,7 @@ public class PathResourceResolver extends AbstractResourceResolver {
for (Resource location : locations) {
try {
String pathToUse = encodeOrDecodeIfNecessary(resourcePath, request, location);
String pathToUse = encodeIfNecessary(resourcePath, request, location);
Resource resource = getResource(pathToUse, location);
if (resource != null) {
return resource;
@@ -257,11 +255,8 @@ public class PathResourceResolver extends AbstractResourceResolver {
return (resourcePath.startsWith(locationPath) && !isInvalidEncodedPath(resourcePath));
}
private String encodeOrDecodeIfNecessary(String path, @Nullable HttpServletRequest request, Resource location) {
if (shouldDecodeRelativePath(location, request)) {
return UriUtils.decode(path, StandardCharsets.UTF_8);
}
else if (shouldEncodeRelativePath(location) && request != null) {
private String encodeIfNecessary(String path, @Nullable HttpServletRequest request, Resource location) {
if (shouldEncodeRelativePath(location) && request != null) {
Charset charset = this.locationCharsets.getOrDefault(location, StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder();
StringTokenizer tokenizer = new StringTokenizer(path, "/");
@@ -280,15 +275,8 @@ public class PathResourceResolver extends AbstractResourceResolver {
}
}
private boolean shouldDecodeRelativePath(Resource location, @Nullable HttpServletRequest request) {
return (!(location instanceof UrlResource) && request != null &&
ServletRequestPathUtils.hasCachedPath(request) &&
ServletRequestPathUtils.getCachedPath(request) instanceof PathContainer);
}
private boolean shouldEncodeRelativePath(Resource location) {
return (location instanceof UrlResource &&
this.urlPathHelper != null && this.urlPathHelper.isUrlDecode());
return (location instanceof UrlResource && this.urlPathHelper != null && this.urlPathHelper.isUrlDecode());
}
private boolean isInvalidEncodedPath(String resourcePath) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -88,15 +88,6 @@ class MappedInterceptorTests {
assertThat(interceptor.matches(requestFactory.apply("/admin/foo"))).isFalse();
}
@PathPatternsParameterizedTest // gh-26690
void includePatternWithFallbackOnPathMatcher(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor = new MappedInterceptor(new String[] { "/path1/**/path2" }, null, delegate);
assertThat(interceptor.matches(requestFactory.apply("/path1/foo/bar/path2"))).isTrue();
assertThat(interceptor.matches(requestFactory.apply("/path1/foo/bar/path3"))).isFalse();
assertThat(interceptor.matches(requestFactory.apply("/path3/foo/bar/path2"))).isFalse();
}
@PathPatternsParameterizedTest
void customPathMatcher(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor = new MappedInterceptor(new String[] { "/foo/[0-9]*" }, null, delegate);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -22,7 +22,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
@@ -32,10 +31,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
@@ -180,11 +177,10 @@ class RequestMappingInfoHandlerMappingTests {
@PathPatternsParameterizedTest
void getHandlerHttpOptions(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
testHttpOptions(mapping, "/foo", "GET,HEAD,OPTIONS", null);
testHttpOptions(mapping, "/person/1", "PUT,OPTIONS", null);
testHttpOptions(mapping, "/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", null);
testHttpOptions(mapping, "/something", "PUT,POST", null);
testHttpOptions(mapping, "/qux", "PATCH,GET,HEAD,OPTIONS", new MediaType("foo", "bar"));
testHttpOptions(mapping, "/foo", "GET,HEAD,OPTIONS");
testHttpOptions(mapping, "/person/1", "PUT,OPTIONS");
testHttpOptions(mapping, "/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
testHttpOptions(mapping, "/something", "PUT,POST");
}
@PathPatternsParameterizedTest
@@ -405,8 +401,8 @@ class RequestMappingInfoHandlerMappingTests {
.satisfies(ex -> assertThat(ex.getSupportedMediaTypes()).containsExactly(MediaType.APPLICATION_XML));
}
private void testHttpOptions(TestRequestMappingInfoHandlerMapping mapping, String requestURI,
String allowHeader, @Nullable MediaType acceptPatch) throws Exception {
private void testHttpOptions(
TestRequestMappingInfoHandlerMapping mapping, String requestURI, String allowHeader) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", requestURI);
HandlerMethod handlerMethod = getHandler(mapping, request);
@@ -417,15 +413,7 @@ class RequestMappingInfoHandlerMappingTests {
assertThat(result).isNotNull();
assertThat(result.getClass()).isEqualTo(HttpHeaders.class);
HttpHeaders headers = (HttpHeaders) result;
Set<HttpMethod> allowedMethods = Arrays.stream(allowHeader.split(","))
.map(HttpMethod::valueOf)
.collect(Collectors.toSet());
assertThat(headers.getAllow()).hasSameElementsAs(allowedMethods);
if (acceptPatch != null && headers.getAllow().contains(HttpMethod.PATCH) ) {
assertThat(headers.getAcceptPatch()).containsExactly(acceptPatch);
}
assertThat(((HttpHeaders) result).getFirst("Allow")).isEqualTo(allowHeader);
}
private void testHttpMediaTypeNotAcceptableException(TestRequestMappingInfoHandlerMapping mapping, String url) {
@@ -514,15 +502,6 @@ class RequestMappingInfoHandlerMappingTests {
headers.add("Allow", "PUT,POST");
return headers;
}
@RequestMapping(value = "/qux", method = RequestMethod.GET, produces = "application/xml")
public String getBaz() {
return "";
}
@RequestMapping(value = "/qux", method = RequestMethod.PATCH, consumes = "foo/bar")
public void patchBaz(String value) {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -113,20 +113,6 @@ public class ResponseEntityExceptionHandlerTests {
ResponseEntity<Object> responseEntity = testException(ex);
assertThat(responseEntity.getHeaders().getAccept()).isEqualTo(acceptable);
assertThat(responseEntity.getHeaders().getAcceptPatch()).isEmpty();
}
@Test
public void patchHttpMediaTypeNotSupported() {
this.servletRequest = new MockHttpServletRequest("PATCH", "/");
this.request = new ServletWebRequest(this.servletRequest, this.servletResponse);
List<MediaType> acceptable = Arrays.asList(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML);
Exception ex = new HttpMediaTypeNotSupportedException(MediaType.APPLICATION_JSON, acceptable);
ResponseEntity<Object> responseEntity = testException(ex);
assertThat(responseEntity.getHeaders().getAccept()).isEqualTo(acceptable);
assertThat(responseEntity.getHeaders().getAcceptPatch()).isEqualTo(acceptable);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -976,26 +976,6 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertThat(response.getHeader("Accept")).isEqualTo("text/plain");
}
@PathPatternsParameterizedTest
void unsupportedPatchBody(boolean usePathPatterns) throws Exception {
initDispatcherServlet(RequestResponseBodyController.class, usePathPatterns, wac -> {
RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_PLAIN));
adapterDef.getPropertyValues().add("messageConverters", converter);
wac.registerBeanDefinition("handlerAdapter", adapterDef);
});
MockHttpServletRequest request = new MockHttpServletRequest("PATCH", "/something");
String requestBody = "Hello World";
request.setContent(requestBody.getBytes(StandardCharsets.UTF_8));
request.addHeader("Content-Type", "application/pdf");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertThat(response.getStatus()).isEqualTo(415);
assertThat(response.getHeader("Accept-Patch")).isEqualTo("text/plain");
}
@PathPatternsParameterizedTest
void responseBodyNoAcceptHeader(boolean usePathPatterns) throws Exception {
initDispatcherServlet(RequestResponseBodyController.class, usePathPatterns);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -87,18 +87,6 @@ public class DefaultHandlerExceptionResolverTests {
assertThat(response.getHeader("Accept")).as("Invalid Accept header").isEqualTo("application/pdf");
}
@Test
public void patchHttpMediaTypeNotSupported() {
HttpMediaTypeNotSupportedException ex = new HttpMediaTypeNotSupportedException(new MediaType("text", "plain"),
Collections.singletonList(new MediaType("application", "pdf")));
MockHttpServletRequest request = new MockHttpServletRequest("PATCH", "/");
ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
assertThat(mav).as("No ModelAndView returned").isNotNull();
assertThat(mav.isEmpty()).as("No Empty ModelAndView returned").isTrue();
assertThat(response.getStatus()).as("Invalid status code").isEqualTo(415);
assertThat(response.getHeader("Accept-Patch")).as("Invalid Accept header").isEqualTo("application/pdf");
}
@Test
public void handleMissingPathVariable() throws NoSuchMethodException {
Method method = getClass().getMethod("handle", String.class);
@@ -108,8 +96,7 @@ public class DefaultHandlerExceptionResolverTests {
assertThat(mav).as("No ModelAndView returned").isNotNull();
assertThat(mav.isEmpty()).as("No Empty ModelAndView returned").isTrue();
assertThat(response.getStatus()).as("Invalid status code").isEqualTo(500);
assertThat(response.getErrorMessage())
.isEqualTo("Required URI template variable 'foo' for method parameter type String is not present");
assertThat(response.getErrorMessage()).isEqualTo("Missing URI template variable 'foo' for method parameter of type String");
}
@Test
@@ -119,8 +106,7 @@ public class DefaultHandlerExceptionResolverTests {
assertThat(mav).as("No ModelAndView returned").isNotNull();
assertThat(mav.isEmpty()).as("No Empty ModelAndView returned").isTrue();
assertThat(response.getStatus()).as("Invalid status code").isEqualTo(400);
assertThat(response.getErrorMessage()).isEqualTo(
"Required request parameter 'foo' for method parameter type bar is not present");
assertThat(response.getErrorMessage()).isEqualTo("Required bar parameter 'foo' is not present");
}
@Test
@@ -1,179 +0,0 @@
/*
* 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.
* 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.resource;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.UrlResource;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import org.springframework.web.testfixture.servlet.MockServletConfig;
import org.springframework.web.testfixture.servlet.MockServletContext;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
* Integration tests for static resource handling.
*
* @author Rossen Stoyanchev
*/
public class ResourceHttpRequestHandlerIntegrationTests {
private final MockServletContext servletContext = new MockServletContext();
private final MockServletConfig servletConfig = new MockServletConfig(this.servletContext);
public static Stream<Arguments> argumentSource() {
return Stream.of(
arguments(true, "/cp"),
arguments(true, "/fs"),
arguments(true, "/url"),
arguments(false, "/cp"),
arguments(false, "/fs"),
arguments(false, "/url")
);
}
@ParameterizedTest
@MethodSource("argumentSource")
void cssFile(boolean usePathPatterns, String pathPrefix) throws Exception {
MockHttpServletRequest request = initRequest(pathPrefix + "/test/foo.css");
MockHttpServletResponse response = new MockHttpServletResponse();
DispatcherServlet servlet = initDispatcherServlet(usePathPatterns, WebConfig.class);
servlet.service(request, response);
String description = "usePathPattern=" + usePathPatterns + ", prefix=" + pathPrefix;
assertThat(response.getStatus()).as(description).isEqualTo(200);
assertThat(response.getContentType()).as(description).isEqualTo("text/css");
assertThat(response.getContentAsString()).as(description).isEqualTo("h1 { color:red; }");
}
@ParameterizedTest
@MethodSource("argumentSource")
void classpathLocationWithEncodedPath(boolean usePathPatterns, String pathPrefix) throws Exception {
MockHttpServletRequest request = initRequest(pathPrefix + "/test/foo with spaces.css");
MockHttpServletResponse response = new MockHttpServletResponse();
DispatcherServlet servlet = initDispatcherServlet(usePathPatterns, WebConfig.class);
servlet.service(request, response);
String description = "usePathPattern=" + usePathPatterns + ", prefix=" + pathPrefix;
assertThat(response.getStatus()).as(description).isEqualTo(200);
assertThat(response.getContentType()).as(description).isEqualTo("text/css");
assertThat(response.getContentAsString()).as(description).isEqualTo("h1 { color:red; }");
}
private DispatcherServlet initDispatcherServlet(boolean usePathPatterns, Class<?>... configClasses)
throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
if (usePathPatterns) {
context.register(PathPatternParserConfig.class);
}
context.setServletConfig(this.servletConfig);
context.refresh();
DispatcherServlet servlet = new DispatcherServlet();
servlet.setApplicationContext(context);
servlet.init(this.servletConfig);
return servlet;
}
private MockHttpServletRequest initRequest(String path) {
path = UriUtils.encodePath(path, StandardCharsets.UTF_8);
MockHttpServletRequest request = new MockHttpServletRequest("GET", path);
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
return request;
}
@EnableWebMvc
static class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
ClassPathResource classPathLocation = new ClassPathResource("", getClass());
String path = getPath(classPathLocation);
registerClasspathLocation("/cp/**", classPathLocation, registry);
registerFileSystemLocation("/fs/**", path, registry);
registerUrlLocation("/url/**", "file:" + path, registry);
}
protected void registerClasspathLocation(String pattern, ClassPathResource resource, ResourceHandlerRegistry registry) {
registry.addResourceHandler(pattern).addResourceLocations(resource);
}
protected void registerFileSystemLocation(String pattern, String path, ResourceHandlerRegistry registry) {
FileSystemResource fileSystemLocation = new FileSystemResource(path);
registry.addResourceHandler(pattern).addResourceLocations(fileSystemLocation);
}
protected void registerUrlLocation(String pattern, String path, ResourceHandlerRegistry registry) {
try {
UrlResource urlLocation = new UrlResource(path);
registry.addResourceHandler(pattern).addResourceLocations(urlLocation);
}
catch (MalformedURLException ex) {
throw new IllegalStateException(ex);
}
}
private String getPath(ClassPathResource resource) {
try {
return resource.getFile().getCanonicalPath().replace('\\', '/').replace("classes/java", "resources") + "/";
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
static class PathPatternParserConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setPatternParser(new PathPatternParser());
}
}
}
+2 -3
View File
@@ -568,9 +568,8 @@ is a convenience mechanism that sets up a <<core.adoc#beans-factory-placeholderc
This element activates the Spring infrastructure to detect annotations in bean classes:
* Spring's <<core.adoc#beans-factory-metadata, `@Configuration`>> model
* <<core.adoc#beans-annotation-config, `@Autowired`/`@Inject`>>, `@Value`, and `@Lookup`
* JSR-250's `@Resource`, `@PostConstruct`, and `@PreDestroy` (if available)
* JAX-WS's `@WebServiceRef` and EJB 3's `@EJB` (if available)
* <<core.adoc#beans-annotation-config, `@Autowired`/`@Inject`>> and `@Value`
* JSR-250's `@Resource`, `@PostConstruct` and `@PreDestroy` (if available)
* JPA's `@PersistenceContext` and `@PersistenceUnit` (if available)
* Spring's <<core.adoc#context-functionality-events-annotation, `@EventListener`>>
+28 -29
View File
@@ -923,7 +923,7 @@ injection:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final MovieFinder movieFinder;
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
@@ -943,7 +943,7 @@ injection:
----
Notice that there is nothing special about this class. It is a POJO that
has no dependencies on container specific interfaces, base classes, or annotations.
has no dependencies on container specific interfaces, base classes or annotations.
[[beans-factory-ctor-arguments-resolution]]
===== Constructor Argument Resolution
@@ -974,10 +974,10 @@ being instantiated. Consider the following class:
class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
----
Assuming that the `ThingTwo` and `ThingThree` classes are not related by inheritance, no
potential ambiguity exists. Thus, the following configuration works fine, and you do not
need to specify the constructor argument indexes or types explicitly in the
`<constructor-arg/>` element.
Assuming that `ThingTwo` and `ThingThree` classes are not related by inheritance, no potential
ambiguity exists. Thus, the following configuration works fine, and you do not need to specify
the constructor argument indexes or types explicitly in the `<constructor-arg/>`
element.
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -1006,10 +1006,10 @@ by type without help. Consider the following class:
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
private int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
@@ -1031,7 +1031,7 @@ by type without help. Consider the following class:
.[[beans-factory-ctor-arguments-type]]Constructor argument type matching
--
In the preceding scenario, the container can use type matching with simple types if
you explicitly specify the type of the constructor argument by using the `type` attribute,
you explicitly specify the type of the constructor argument by using the `type` attribute.
as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -1251,8 +1251,7 @@ visibility of some configuration issues is why `ApplicationContext` implementati
default pre-instantiate singleton beans. At the cost of some upfront time and memory to
create these beans before they are actually needed, you discover configuration issues
when the `ApplicationContext` is created, not later. You can still override this default
behavior so that singleton beans initialize lazily, rather than being eagerly
pre-instantiated.
behavior so that singleton beans initialize lazily, rather than being pre-instantiated.
If no circular dependencies exist, when one or more collaborating beans are being
injected into a dependent bean, each collaborating bean is totally configured prior
@@ -4607,8 +4606,8 @@ Annotation injection is performed before XML injection. Thus, the XML configurat
overrides the annotations for properties wired through both approaches.
====
As always, you can register the post-processors as individual bean definitions, but they
can also be implicitly registered by including the following tag in an XML-based Spring
As always, you can register them as individual bean definitions, but they can also be
implicitly registered by including the following tag in an XML-based Spring
configuration (notice the inclusion of the `context` namespace):
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -4627,13 +4626,11 @@ configuration (notice the inclusion of the `context` namespace):
</beans>
----
The `<context:annotation-config/>` element implicitly registers the following post-processors:
* {api-spring-framework}/context/annotation/ConfigurationClassPostProcessor.html[`ConfigurationClassPostProcessor`]
* {api-spring-framework}/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html[`AutowiredAnnotationBeanPostProcessor`]
* {api-spring-framework}/context/annotation/CommonAnnotationBeanPostProcessor.html[`CommonAnnotationBeanPostProcessor`]
* {api-spring-framework}/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.html[`PersistenceAnnotationBeanPostProcessor`]
* {api-spring-framework}/context/event/EventListenerMethodProcessor.html[`EventListenerMethodProcessor`]
(The implicitly registered post-processors include
{api-spring-framework}/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html[`AutowiredAnnotationBeanPostProcessor`],
{api-spring-framework}/context/annotation/CommonAnnotationBeanPostProcessor.html[`CommonAnnotationBeanPostProcessor`],
{api-spring-framework}/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.html[`PersistenceAnnotationBeanPostProcessor`], and
{api-spring-framework}/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.html[`RequiredAnnotationBeanPostProcessor`].)
[NOTE]
====
@@ -4690,7 +4687,10 @@ references and values even when you use the class outside of a container.
[TIP]
====
The {api-spring-framework}/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.html[`RequiredAnnotationBeanPostProcessor`]
must be registered as a bean to enable support for the `@Required` annotation.
must be registered as a bean to enable support for the `@Required` annotation. Note,
however, that a `RequiredAnnotationBeanPostProcessor` bean is registered automatically
when using the `<context:annotation-config/>` or `<context:component-scan/>` XML
namespace elements.
====
[NOTE]
@@ -10652,8 +10652,9 @@ architectures that build upon the well-known Spring programming model.
[[context-functionality-events-annotation]]
==== Annotation-based Event Listeners
You can register an event listener on any method of a managed bean by using the
`@EventListener` annotation. The `BlockedListNotifier` can be rewritten as follows:
As of Spring 4.2, you can register an event listener on any public method of a managed
bean by using the `@EventListener` annotation. The `BlockedListNotifier` can be rewritten as
follows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
@@ -10791,9 +10792,9 @@ method signature to return the event that should be published, as the following
NOTE: This feature is not supported for
<<context-functionality-events-async, asynchronous listeners>>.
The `handleBlockedListEvent()` method publishes a new `ListUpdateEvent` for every
`BlockedListEvent` that it handles. If you need to publish several events, you can return
a `Collection` or an array of events instead.
This new method publishes a new `ListUpdateEvent` for every `BlockedListEvent` handled by the
method above. If you need to publish several events, you can return a `Collection` of events
instead.
[[context-functionality-events-async]]
@@ -10953,10 +10954,8 @@ location path as a classpath location. You can also use location paths (resource
with special prefixes to force loading of definitions from the classpath or a URL,
regardless of the actual context type.
[[context-functionality-startup]]
=== Application Startup Tracking
=== Application Startup tracking
The `ApplicationContext` manages the lifecycle of Spring applications and provides a rich
programming model around components. As a result, complex applications can have equally
+6 -29
View File
@@ -7471,35 +7471,12 @@ or reactive type such as Reactor `Mono`:
[[spring-mvc-test-vs-streaming-response]]
===== Streaming Responses
The best way to test streaming responses such as Server-Sent Events is through the
<<WebTestClient>> which can be used as a test client to connect to a `MockMvc` instance
to perform tests on Spring MVC controllers without a running server. For example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
// Use StepVerifier from Project Reactor to test the streaming response
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
----
`WebTestClient` can also connect to a live server and perform full end-to-end integration
tests. This is also supported in Spring Boot where you can
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server].
There are no options built into Spring MVC Test for container-less testing of streaming
responses. However you can test streaming requests through the <<WebTestClient>>.
This is also supported in Spring Boot where you can
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server]
with `WebTestClient`. One extra advantage is the ability to use the `StepVerifier` from
project Reactor that allows declaring expectations on a stream of data.
[[spring-mvc-test-server-filters]]
+1 -1
View File
@@ -1521,7 +1521,7 @@ segments. For example `/resources/{*path}` matches all files under `/resources/`
`"path"` variable captures the complete relative path.
The syntax `{varName:regex}` declares a URI variable with a regular expression that has the
syntax: `{varName:regex}`. For example, given a URL of `/spring-web-3.0.5.jar`, the following method
syntax: `{varName:regex}`. For example, given a URL of `/spring-web-3.0.5 .jar`, the following method
extracts the name, version, and file extension:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+1 -1
View File
@@ -1618,7 +1618,7 @@ leave that detail out if the names are the same and your code is compiled with d
information or with the `-parameters` compiler flag on Java 8.
The syntax `{varName:regex}` declares a URI variable with a regular expression that has
syntax of `{varName:regex}`. For example, given URL `"/spring-web-3.0.5.jar"`, the following method
syntax of `{varName:regex}`. For example, given URL `"/spring-web-3.0.5 .jar"`, the following method
extracts the name, version, and file extension:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]