mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cedb5874b7 | |||
| a7cf19cec5 | |||
| 0cf7f7bd89 | |||
| 949c3d450c | |||
| 3b4ae7b028 | |||
| 8b39698553 | |||
| 6fad00ed22 | |||
| 5f6d8df34b | |||
| 35de7e19ee | |||
| 10e979e58b | |||
| 4143b445d6 | |||
| eefdd2c768 | |||
| 9f91168396 | |||
| 7aed6279a2 | |||
| c3fe112fd7 | |||
| 90103b0ae9 | |||
| 0cf15c0fdd | |||
| 4e9af3e277 | |||
| 01fd489b47 | |||
| 270b167e08 | |||
| b158110801 | |||
| 17f7a24118 | |||
| d518a7d8c8 | |||
| 24cd3c1f4c | |||
| 2a8c369cff |
+12
-12
@@ -29,10 +29,10 @@ configure(allprojects) { project ->
|
||||
imports {
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6"
|
||||
mavenBom "io.netty:netty-bom:4.1.75.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.17"
|
||||
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR12"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.1.1"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.45.v20220203"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.18"
|
||||
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.1.2"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.46.v20220331"
|
||||
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
|
||||
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
|
||||
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
|
||||
@@ -96,7 +96,7 @@ configure(allprojects) { project ->
|
||||
|
||||
dependency "com.h2database:h2:2.1.210"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
|
||||
dependency "com.github.librepdf:openpdf:1.3.26"
|
||||
dependency "com.github.librepdf:openpdf:1.3.27"
|
||||
dependency "com.rometools:rome:1.18.0"
|
||||
dependency "commons-io:commons-io:2.5"
|
||||
dependency "io.vavr:vavr:0.10.4"
|
||||
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
|
||||
dependency "org.webjars:webjars-locator-core:0.48"
|
||||
dependency "org.webjars:underscorejs:1.8.3"
|
||||
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.60') {
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.62') {
|
||||
entry 'tomcat-util'
|
||||
entry('tomcat-websocket') {
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
|
||||
}
|
||||
}
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.60') {
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.62') {
|
||||
entry 'tomcat-embed-core'
|
||||
entry 'tomcat-embed-websocket'
|
||||
}
|
||||
dependencySet(group: 'io.undertow', version: '2.2.16.Final') {
|
||||
dependencySet(group: 'io.undertow', version: '2.2.17.Final') {
|
||||
entry 'undertow-core'
|
||||
entry('undertow-servlet') {
|
||||
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
|
||||
@@ -150,7 +150,7 @@ configure(allprojects) { project ->
|
||||
}
|
||||
}
|
||||
|
||||
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.10"
|
||||
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.11"
|
||||
dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.3'
|
||||
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3'
|
||||
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
|
||||
@@ -206,10 +206,10 @@ configure(allprojects) { project ->
|
||||
}
|
||||
dependency "io.mockk:mockk:1.12.1"
|
||||
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.59.0") {
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.60.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.59.0") {
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.60.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
|
||||
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "9.3"
|
||||
toolVersion = "10.1"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal-20220302
|
||||
FROM ubuntu:focal-20220404
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -12,7 +12,7 @@ case "$1" in
|
||||
echo "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/OpenJDK17U-jdk_x64_linux_hotspot_17.0.2_8.tar.gz"
|
||||
;;
|
||||
java18)
|
||||
echo "https://github.com/adoptium/temurin18-binaries/releases/download/jdk18-2022-02-12-08-06-beta/OpenJDK18-jdk_x64_linux_hotspot_2022-02-12-08-06.tar.gz"
|
||||
echo "https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18%2B36/OpenJDK18U-jdk_x64_linux_hotspot_18_36.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
@@ -41,6 +41,11 @@ anchors:
|
||||
GITHUB_TOKEN: ((github-ci-release-token))
|
||||
|
||||
resource_types:
|
||||
- name: registry-image
|
||||
type: registry-image
|
||||
source:
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.5.0
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.3.18-SNAPSHOT
|
||||
version=5.3.19
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -134,7 +134,7 @@ public abstract class AopProxyUtils {
|
||||
if (targetClass.isInterface()) {
|
||||
advised.setInterfaces(targetClass);
|
||||
}
|
||||
else if (Proxy.isProxyClass(targetClass) || isLambda(targetClass)) {
|
||||
else if (Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
|
||||
advised.setInterfaces(targetClass.getInterfaces());
|
||||
}
|
||||
specifiedInterfaces = advised.getProxiedInterfaces();
|
||||
@@ -245,18 +245,4 @@ public abstract class AopProxyUtils {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link Class} is a JVM-generated implementation
|
||||
* class for a lambda expression or method reference.
|
||||
* <p>This method makes a best-effort attempt at determining this, based on
|
||||
* checks that work on modern, main stream JVMs.
|
||||
* @param clazz the class to check
|
||||
* @return {@code true} if the class is a lambda implementation class
|
||||
* @since 5.3.16
|
||||
*/
|
||||
static boolean isLambda(Class<?> clazz) {
|
||||
return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
|
||||
(clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -375,6 +375,22 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given method with a CGLIB MethodProxy if possible, falling back
|
||||
* to a plain reflection invocation in case of a fast-class generation failure.
|
||||
*/
|
||||
@Nullable
|
||||
private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy)
|
||||
throws Throwable {
|
||||
try {
|
||||
return methodProxy.invoke(target, args);
|
||||
}
|
||||
catch (CodeGenerationException ex) {
|
||||
CglibMethodInvocation.logFastClassGenerationFailure(method);
|
||||
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a return value. Wraps a return of {@code this} if necessary to be the
|
||||
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
|
||||
@@ -425,7 +441,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
@Override
|
||||
@Nullable
|
||||
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
|
||||
Object retVal = methodProxy.invoke(this.target, args);
|
||||
Object retVal = invokeMethod(this.target, method, args, methodProxy);
|
||||
return processReturnType(proxy, this.target, method, retVal);
|
||||
}
|
||||
}
|
||||
@@ -450,7 +466,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
Object oldProxy = null;
|
||||
try {
|
||||
oldProxy = AopContext.setCurrentProxy(proxy);
|
||||
Object retVal = methodProxy.invoke(this.target, args);
|
||||
Object retVal = invokeMethod(this.target, method, args, methodProxy);
|
||||
return processReturnType(proxy, this.target, method, retVal);
|
||||
}
|
||||
finally {
|
||||
@@ -478,7 +494,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
|
||||
Object target = this.targetSource.getTarget();
|
||||
try {
|
||||
Object retVal = methodProxy.invoke(target, args);
|
||||
Object retVal = invokeMethod(target, method, args, methodProxy);
|
||||
return processReturnType(proxy, target, method, retVal);
|
||||
}
|
||||
finally {
|
||||
@@ -508,7 +524,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
Object target = this.targetSource.getTarget();
|
||||
try {
|
||||
oldProxy = AopContext.setCurrentProxy(proxy);
|
||||
Object retVal = methodProxy.invoke(target, args);
|
||||
Object retVal = invokeMethod(target, method, args, methodProxy);
|
||||
return processReturnType(proxy, target, method, retVal);
|
||||
}
|
||||
finally {
|
||||
@@ -685,13 +701,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
// it does nothing but a reflective operation on the target, and no hot
|
||||
// swapping or fancy proxying.
|
||||
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
|
||||
try {
|
||||
retVal = methodProxy.invoke(target, argsToUse);
|
||||
}
|
||||
catch (CodeGenerationException ex) {
|
||||
CglibMethodInvocation.logFastClassGenerationFailure(method);
|
||||
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
|
||||
}
|
||||
retVal = invokeMethod(target, method, argsToUse, methodProxy);
|
||||
}
|
||||
else {
|
||||
// We need to create a method invocation...
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@ import java.lang.reflect.Proxy;
|
||||
|
||||
import org.springframework.aop.SpringProxy;
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
|
||||
@@ -60,7 +61,7 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
|
||||
throw new AopConfigException("TargetSource cannot determine target class: " +
|
||||
"Either an interface or a target is required for proxy creation.");
|
||||
}
|
||||
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || AopProxyUtils.isLambda(targetClass)) {
|
||||
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
|
||||
return new JdkDynamicAopProxy(config);
|
||||
}
|
||||
return new ObjenesisCglibAopProxy(config);
|
||||
|
||||
+5
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,6 +50,7 @@ import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostP
|
||||
import org.springframework.core.SmartClassLoader;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -85,6 +86,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Rod Johnson
|
||||
* @author Rob Harrop
|
||||
* @author Sam Brannen
|
||||
* @since 13.10.2003
|
||||
* @see #setInterceptorNames
|
||||
* @see #getAdvicesAndAdvisorsForBean
|
||||
@@ -442,8 +444,8 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
|
||||
proxyFactory.copyFrom(this);
|
||||
|
||||
if (proxyFactory.isProxyTargetClass()) {
|
||||
// Explicit handling of JDK proxy targets (for introduction advice scenarios)
|
||||
if (Proxy.isProxyClass(beanClass)) {
|
||||
// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
|
||||
if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
|
||||
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
|
||||
for (Class<?> ifc : beanClass.getInterfaces()) {
|
||||
proxyFactory.addInterface(ifc);
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.springframework.aop.framework;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -134,61 +133,4 @@ public class AopProxyUtilsTests {
|
||||
AopProxyUtils.proxiedUserInterfaces(proxy));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLambda() {
|
||||
assertIsLambda(AopProxyUtilsTests.staticLambdaExpression);
|
||||
assertIsLambda(AopProxyUtilsTests::staticStringFactory);
|
||||
|
||||
assertIsLambda(this.instanceLambdaExpression);
|
||||
assertIsLambda(this::instanceStringFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isNotLambda() {
|
||||
assertIsNotLambda(new EnigmaSupplier());
|
||||
|
||||
assertIsNotLambda(new Supplier<String>() {
|
||||
@Override
|
||||
public String get() {
|
||||
return "anonymous inner class";
|
||||
}
|
||||
});
|
||||
|
||||
assertIsNotLambda(new Fake$$LambdaSupplier());
|
||||
}
|
||||
|
||||
private static void assertIsLambda(Supplier<String> supplier) {
|
||||
assertThat(AopProxyUtils.isLambda(supplier.getClass())).isTrue();
|
||||
}
|
||||
|
||||
private static void assertIsNotLambda(Supplier<String> supplier) {
|
||||
assertThat(AopProxyUtils.isLambda(supplier.getClass())).isFalse();
|
||||
}
|
||||
|
||||
private static final Supplier<String> staticLambdaExpression = () -> "static lambda expression";
|
||||
|
||||
private final Supplier<String> instanceLambdaExpression = () -> "instance lambda expressions";
|
||||
|
||||
private static String staticStringFactory() {
|
||||
return "static string factory";
|
||||
}
|
||||
|
||||
private String instanceStringFactory() {
|
||||
return "instance string factory";
|
||||
}
|
||||
|
||||
private static class EnigmaSupplier implements Supplier<String> {
|
||||
@Override
|
||||
public String get() {
|
||||
return "enigma";
|
||||
}
|
||||
}
|
||||
|
||||
private static class Fake$$LambdaSupplier implements Supplier<String> {
|
||||
@Override
|
||||
public String get() {
|
||||
return "fake lambda";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-10
@@ -287,13 +287,15 @@ public final class CachedIntrospectionResults {
|
||||
// This call is slow so we do it once.
|
||||
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
|
||||
for (PropertyDescriptor pd : pds) {
|
||||
if (Class.class == beanClass && (!"name".equals(pd.getName()) && !pd.getName().endsWith("Name"))) {
|
||||
if (Class.class == beanClass && !("name".equals(pd.getName()) ||
|
||||
(pd.getName().endsWith("Name") && String.class == pd.getPropertyType()))) {
|
||||
// Only allow all name variants of Class properties
|
||||
continue;
|
||||
}
|
||||
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|
||||
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
|
||||
// Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
|
||||
if (pd.getWriteMethod() == null && pd.getPropertyType() != null &&
|
||||
(ClassLoader.class.isAssignableFrom(pd.getPropertyType()) ||
|
||||
ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
|
||||
// Ignore ClassLoader and ProtectionDomain read-only properties - no need to bind to those
|
||||
continue;
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
@@ -342,9 +344,10 @@ public final class CachedIntrospectionResults {
|
||||
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
|
||||
// against a declared read method, so we prefer read method descriptors here.
|
||||
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
|
||||
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|
||||
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
|
||||
// Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
|
||||
if (pd.getWriteMethod() == null && pd.getPropertyType() != null &&
|
||||
(ClassLoader.class.isAssignableFrom(pd.getPropertyType()) ||
|
||||
ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
|
||||
// Ignore ClassLoader and ProtectionDomain read-only properties - no need to bind to those
|
||||
continue;
|
||||
}
|
||||
this.propertyDescriptors.put(pd.getName(), pd);
|
||||
@@ -364,7 +367,7 @@ public final class CachedIntrospectionResults {
|
||||
|
||||
for (Method method : beanClass.getMethods()) {
|
||||
if (!this.propertyDescriptors.containsKey(method.getName()) &&
|
||||
!readMethodNames.contains((method.getName())) && isPlainAccessor(method)) {
|
||||
!readMethodNames.contains(method.getName()) && isPlainAccessor(method)) {
|
||||
this.propertyDescriptors.put(method.getName(),
|
||||
new GenericTypeAwarePropertyDescriptor(beanClass, method.getName(), method, null, null));
|
||||
readMethodNames.add(method.getName());
|
||||
@@ -373,8 +376,11 @@ public final class CachedIntrospectionResults {
|
||||
}
|
||||
|
||||
private boolean isPlainAccessor(Method method) {
|
||||
if (method.getParameterCount() > 0 || method.getReturnType() == void.class ||
|
||||
method.getDeclaringClass() == Object.class || Modifier.isStatic(method.getModifiers())) {
|
||||
if (Modifier.isStatic(method.getModifiers()) ||
|
||||
method.getDeclaringClass() == Object.class || method.getDeclaringClass() == Class.class ||
|
||||
method.getParameterCount() > 0 || method.getReturnType() == void.class ||
|
||||
ClassLoader.class.isAssignableFrom(method.getReturnType()) ||
|
||||
ProtectionDomain.class.isAssignableFrom(method.getReturnType())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,8 +23,9 @@ import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Common interface for classes that can access named properties
|
||||
* (such as bean properties of an object or fields in an object)
|
||||
* Serves as base interface for {@link BeanWrapper}.
|
||||
* (such as bean properties of an object or fields in an object).
|
||||
*
|
||||
* <p>Serves as base interface for {@link BeanWrapper}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,8 @@ import java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.core.OverridingClassLoader;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -105,7 +107,7 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
|
||||
.satisfies(ex -> assertThat(ex.getPossibleMatches()).containsExactly("age"));
|
||||
}
|
||||
|
||||
@Test // Can't be shared; there is no such thing as a read-only field
|
||||
@Test // Can't be shared; there is no such thing as a read-only field
|
||||
void setReadOnlyMapProperty() {
|
||||
TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean()));
|
||||
TypedReadOnlyMapClient target = new TypedReadOnlyMapClient();
|
||||
@@ -157,12 +159,34 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
|
||||
BeanWrapper accessor = createAccessor(target);
|
||||
accessor.setPropertyValue("name", "a");
|
||||
accessor.setPropertyValue("spouse.name", "b");
|
||||
|
||||
assertThat(target.getName()).isEqualTo("a");
|
||||
assertThat(target.getSpouse().getName()).isEqualTo("b");
|
||||
assertThat(accessor.getPropertyValue("name")).isEqualTo("a");
|
||||
assertThat(accessor.getPropertyValue("spouse.name")).isEqualTo("b");
|
||||
assertThat(accessor.getPropertyDescriptor("name").getPropertyType()).isEqualTo(String.class);
|
||||
assertThat(accessor.getPropertyDescriptor("spouse.name").getPropertyType()).isEqualTo(String.class);
|
||||
|
||||
assertThat(accessor.isReadableProperty("class.package")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("class.module")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("class.name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
|
||||
assertThat(accessor.getPropertyValue("class.name")).isEqualTo(TestBean.class.getName());
|
||||
assertThat(accessor.getPropertyValue("class.simpleName")).isEqualTo(TestBean.class.getSimpleName());
|
||||
assertThat(accessor.getPropertyDescriptor("class.name").getPropertyType()).isEqualTo(String.class);
|
||||
assertThat(accessor.getPropertyDescriptor("class.simpleName").getPropertyType()).isEqualTo(String.class);
|
||||
|
||||
accessor = createAccessor(new DefaultResourceLoader());
|
||||
|
||||
assertThat(accessor.isReadableProperty("class.package")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("class.module")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("classLoader")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("classLoader")).isTrue();
|
||||
OverridingClassLoader ocl = new OverridingClassLoader(getClass().getClassLoader());
|
||||
accessor.setPropertyValue("classLoader", ocl);
|
||||
assertThat(accessor.getPropertyValue("classLoader")).isSameAs(ocl);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+90
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import java.util.Set;
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Payload;
|
||||
import javax.validation.Valid;
|
||||
@@ -43,6 +44,7 @@ import org.hibernate.validator.HibernateValidatorFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
@@ -52,18 +54,18 @@ import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public class ValidatorFactoryTests {
|
||||
class ValidatorFactoryTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("cast")
|
||||
public void testSimpleValidation() {
|
||||
void simpleValidation() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -78,15 +80,15 @@ public class ValidatorFactoryTests {
|
||||
|
||||
Validator nativeValidator = validator.unwrap(Validator.class);
|
||||
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
|
||||
assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
|
||||
assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
|
||||
assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
|
||||
assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("cast")
|
||||
public void testSimpleValidationWithCustomProvider() {
|
||||
void simpleValidationWithCustomProvider() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.setProviderClass(HibernateValidator.class);
|
||||
validator.afterPropertiesSet();
|
||||
@@ -102,14 +104,15 @@ public class ValidatorFactoryTests {
|
||||
|
||||
Validator nativeValidator = validator.unwrap(Validator.class);
|
||||
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
|
||||
assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
|
||||
assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
|
||||
assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
|
||||
assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleValidationWithClassLevel() {
|
||||
void simpleValidationWithClassLevel() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -122,10 +125,13 @@ public class ValidatorFactoryTests {
|
||||
ConstraintViolation<?> cv = iterator.next();
|
||||
assertThat(cv.getPropertyPath().toString()).isEqualTo("");
|
||||
assertThat(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid).isTrue();
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidationFieldType() {
|
||||
void springValidationFieldType() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -135,11 +141,16 @@ public class ValidatorFactoryTests {
|
||||
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(person, "person");
|
||||
validator.validate(person, errors);
|
||||
assertThat(errors.getErrorCount()).isEqualTo(1);
|
||||
assertThat(errors.getFieldError("address").getRejectedValue()).isInstanceOf(ValidAddress.class);
|
||||
assertThat(errors.getFieldError("address").getRejectedValue())
|
||||
.as("Field/Value type mismatch")
|
||||
.isInstanceOf(ValidAddress.class);
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidation() {
|
||||
void springValidation() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -164,10 +175,13 @@ public class ValidatorFactoryTests {
|
||||
assertThat(errorCodes.contains("NotNull.street")).isTrue();
|
||||
assertThat(errorCodes.contains("NotNull.java.lang.String")).isTrue();
|
||||
assertThat(errorCodes.contains("NotNull")).isTrue();
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidationWithClassLevel() {
|
||||
void springValidationWithClassLevel() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -182,10 +196,12 @@ public class ValidatorFactoryTests {
|
||||
assertThat(errorCodes.size()).isEqualTo(2);
|
||||
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
|
||||
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidationWithAutowiredValidator() {
|
||||
void springValidationWithAutowiredValidator() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(
|
||||
LocalValidatorFactoryBean.class);
|
||||
LocalValidatorFactoryBean validator = ctx.getBean(LocalValidatorFactoryBean.class);
|
||||
@@ -202,11 +218,14 @@ public class ValidatorFactoryTests {
|
||||
assertThat(errorCodes.size()).isEqualTo(2);
|
||||
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
|
||||
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
|
||||
|
||||
validator.destroy();
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidationWithErrorInListElement() {
|
||||
void springValidationWithErrorInListElement() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -221,10 +240,13 @@ public class ValidatorFactoryTests {
|
||||
assertThat(fieldError.getField()).isEqualTo("address.street");
|
||||
fieldError = result.getFieldError("addressList[0].street");
|
||||
assertThat(fieldError.getField()).isEqualTo("addressList[0].street");
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidationWithErrorInSetElement() {
|
||||
void springValidationWithErrorInSetElement() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -239,10 +261,13 @@ public class ValidatorFactoryTests {
|
||||
assertThat(fieldError.getField()).isEqualTo("address.street");
|
||||
fieldError = result.getFieldError("addressSet[].street");
|
||||
assertThat(fieldError.getField()).isEqualTo("addressSet[].street");
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInnerBeanValidation() {
|
||||
void innerBeanValidation() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -251,10 +276,13 @@ public class ValidatorFactoryTests {
|
||||
validator.validate(mainBean, errors);
|
||||
Object rejected = errors.getFieldValue("inner.value");
|
||||
assertThat(rejected).isNull();
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidationWithOptionalField() {
|
||||
void validationWithOptionalField() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -263,10 +291,13 @@ public class ValidatorFactoryTests {
|
||||
validator.validate(mainBean, errors);
|
||||
Object rejected = errors.getFieldValue("inner.value");
|
||||
assertThat(rejected).isNull();
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListValidation() {
|
||||
void listValidation() {
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
@@ -282,6 +313,34 @@ public class ValidatorFactoryTests {
|
||||
assertThat(fieldError).isNotNull();
|
||||
assertThat(fieldError.getRejectedValue()).isEqualTo("X");
|
||||
assertThat(errors.getFieldValue("list[1]")).isEqualTo("X");
|
||||
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withConstraintValidatorFactory() {
|
||||
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.setConstraintValidatorFactory(cvf);
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withCustomInitializer() {
|
||||
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.setConfigurationInitializer(configuration -> configuration.constraintValidatorFactory(cvf));
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -380,8 +439,8 @@ public class ValidatorFactoryTests {
|
||||
}
|
||||
boolean valid = (value.name == null || !value.address.street.contains(value.name));
|
||||
if (!valid && "Phil".equals(value.name)) {
|
||||
context.buildConstraintViolationWithTemplate(
|
||||
context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
|
||||
.addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
@@ -417,6 +476,7 @@ public class ValidatorFactoryTests {
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
@@ -425,8 +485,8 @@ public class ValidatorFactoryTests {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Constraint(validatedBy=InnerValidator.class)
|
||||
public static @interface InnerValid {
|
||||
@Constraint(validatedBy = InnerValidator.class)
|
||||
public @interface InnerValid {
|
||||
|
||||
String message() default "NOT VALID";
|
||||
|
||||
@@ -446,7 +506,8 @@ public class ValidatorFactoryTests {
|
||||
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
if (bean.getValue() == null) {
|
||||
context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
|
||||
context.buildConstraintViolationWithTemplate("NULL")
|
||||
.addPropertyNode("value").addConstraintViolation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -494,7 +555,8 @@ public class ValidatorFactoryTests {
|
||||
boolean valid = true;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if ("X".equals(list.get(i))) {
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
|
||||
.addBeanNode().inIterable().atIndex(i).addConstraintViolation();
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,18 +51,20 @@ import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Binder that allows for setting property values onto a target object,
|
||||
* including support for validation and binding result analysis.
|
||||
* The binding process can be customized through specifying allowed fields,
|
||||
* Binder that allows for setting property values on a target object, including
|
||||
* support for validation and binding result analysis.
|
||||
*
|
||||
* <p>The binding process can be customized by specifying allowed field patterns,
|
||||
* required fields, custom editors, etc.
|
||||
*
|
||||
* <p>Note that there are potential security implications in failing to set an array
|
||||
* of allowed fields. In the case of HTTP form POST data for example, malicious clients
|
||||
* can attempt to subvert an application by supplying values for fields or properties
|
||||
* that do not exist on the form. In some cases this could lead to illegal data being
|
||||
* set on command objects <i>or their nested objects</i>. For this reason, it is
|
||||
* <b>highly recommended to specify the {@link #setAllowedFields allowedFields} property</b>
|
||||
* on the DataBinder.
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>The binding results can be examined via the {@link BindingResult} interface,
|
||||
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
|
||||
@@ -96,6 +98,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Rob Harrop
|
||||
* @author Stephane Nicoll
|
||||
* @author Kazuki Shimizu
|
||||
* @author Sam Brannen
|
||||
* @see #setAllowedFields
|
||||
* @see #setRequiredFields
|
||||
* @see #registerCustomEditor
|
||||
@@ -418,15 +421,21 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register fields that should be allowed for binding. Default is all fields.
|
||||
* Restrict this for example to avoid unwanted modifications by malicious
|
||||
* Register field patterns that should be allowed for binding.
|
||||
* <p>Default is all fields.
|
||||
* <p>Restrict this for example to avoid unwanted modifications by malicious
|
||||
* users when binding HTTP request parameters.
|
||||
* <p>Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
|
||||
* arbitrary number of pattern parts), as well as direct equality. More
|
||||
* sophisticated matching can be implemented by overriding the
|
||||
* {@code isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>disallowed</i> fields.
|
||||
* @param allowedFields array of field names
|
||||
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality.
|
||||
* <p>The default implementation of this method stores allowed field patterns
|
||||
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
|
||||
* form. Subclasses which override this method must therefore take this into
|
||||
* account.
|
||||
* <p>More sophisticated matching can be implemented by overriding the
|
||||
* {@link #isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>disallowed</i> field patterns.
|
||||
* @param allowedFields array of allowed field patterns
|
||||
* @see #setDisallowedFields
|
||||
* @see #isAllowed(String)
|
||||
*/
|
||||
@@ -435,8 +444,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fields that should be allowed for binding.
|
||||
* @return array of field names
|
||||
* Return the field patterns that should be allowed for binding.
|
||||
* @return array of allowed field patterns
|
||||
* @see #setAllowedFields(String...)
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getAllowedFields() {
|
||||
@@ -444,25 +454,44 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register fields that should <i>not</i> be allowed for binding. Default
|
||||
* is none. Mark fields as disallowed for example to avoid unwanted
|
||||
* Register field patterns that should <i>not</i> be allowed for binding.
|
||||
* <p>Default is none.
|
||||
* <p>Mark fields as disallowed, for example to avoid unwanted
|
||||
* modifications by malicious users when binding HTTP request parameters.
|
||||
* <p>Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
|
||||
* arbitrary number of pattern parts), as well as direct equality.
|
||||
* More sophisticated matching can be implemented by overriding the
|
||||
* {@code isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>allowed</i> fields.
|
||||
* @param disallowedFields array of field names
|
||||
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality.
|
||||
* <p>The default implementation of this method stores disallowed field patterns
|
||||
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
|
||||
* form. As of Spring Framework 5.2.21, the default implementation also transforms
|
||||
* disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
|
||||
* support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
|
||||
* which override this method must therefore take both of these transformations
|
||||
* into account.
|
||||
* <p>More sophisticated matching can be implemented by overriding the
|
||||
* {@link #isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
|
||||
* @param disallowedFields array of disallowed field patterns
|
||||
* @see #setAllowedFields
|
||||
* @see #isAllowed(String)
|
||||
*/
|
||||
public void setDisallowedFields(@Nullable String... disallowedFields) {
|
||||
this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
|
||||
if (disallowedFields == null) {
|
||||
this.disallowedFields = null;
|
||||
}
|
||||
else {
|
||||
String[] fieldPatterns = new String[disallowedFields.length];
|
||||
for (int i = 0; i < fieldPatterns.length; i++) {
|
||||
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]).toLowerCase();
|
||||
}
|
||||
this.disallowedFields = fieldPatterns;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fields that should <i>not</i> be allowed for binding.
|
||||
* @return array of field names
|
||||
* Return the field patterns that should <i>not</i> be allowed for binding.
|
||||
* @return array of disallowed field patterns
|
||||
* @see #setDisallowedFields(String...)
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getDisallowedFields() {
|
||||
@@ -774,16 +803,20 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the given field is allowed for binding.
|
||||
* Invoked for each passed-in property value.
|
||||
* <p>The default implementation checks for "xxx*", "*xxx", "*xxx*" and "xxx*yyy"
|
||||
* matches (with an arbitrary number of pattern parts), as well as direct equality,
|
||||
* in the specified lists of allowed fields and disallowed fields. A field matching
|
||||
* a disallowed pattern will not be accepted even if it also happens to match a
|
||||
* pattern in the allowed list.
|
||||
* <p>Can be overridden in subclasses.
|
||||
* Determine if the given field is allowed for binding.
|
||||
* <p>Invoked for each passed-in property value.
|
||||
* <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality, in the configured lists of allowed field patterns
|
||||
* and disallowed field patterns.
|
||||
* <p>Matching against allowed field patterns is case-sensitive; whereas,
|
||||
* matching against disallowed field patterns is case-insensitive.
|
||||
* <p>A field matching a disallowed pattern will not be accepted even if it
|
||||
* also happens to match a pattern in the allowed list.
|
||||
* <p>Can be overridden in subclasses, but care must be taken to honor the
|
||||
* aforementioned contract.
|
||||
* @param field the field to check
|
||||
* @return if the field is allowed
|
||||
* @return {@code true} if the field is allowed
|
||||
* @see #setAllowedFields
|
||||
* @see #setDisallowedFields
|
||||
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
|
||||
@@ -792,7 +825,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
String[] allowed = getAllowedFields();
|
||||
String[] disallowed = getDisallowedFields();
|
||||
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
|
||||
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
|
||||
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+20
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.validation.Configuration;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
@@ -113,6 +114,9 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
|
||||
private final Map<String, String> validationPropertyMap = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
private Consumer<Configuration<?>> configurationInitializer;
|
||||
|
||||
@Nullable
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@@ -234,6 +238,18 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
return this.validationPropertyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a callback for customizing the Bean Validation {@code Configuration} instance,
|
||||
* as an alternative to overriding the {@link #postProcessConfiguration(Configuration)}
|
||||
* method in custom {@code LocalValidatorFactoryBean} subclasses.
|
||||
* <p>This enables convenient customizations for application purposes. Infrastructure
|
||||
* extensions may keep overriding the {@link #postProcessConfiguration} template method.
|
||||
* @since 5.3.19
|
||||
*/
|
||||
public void setConfigurationInitializer(Consumer<Configuration<?>> configurationInitializer) {
|
||||
this.configurationInitializer = configurationInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
@@ -312,6 +328,9 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
this.validationPropertyMap.forEach(configuration::addProperty);
|
||||
|
||||
// Allow for custom post-processing before we actually build the ValidatorFactory.
|
||||
if (this.configurationInitializer != null) {
|
||||
this.configurationInitializer.accept(configuration);
|
||||
}
|
||||
postProcessConfiguration(configuration);
|
||||
|
||||
try {
|
||||
|
||||
+103
-2
@@ -21,6 +21,8 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
@@ -31,11 +33,17 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.aop.ClassFilter;
|
||||
import org.springframework.aop.IntroductionAdvisor;
|
||||
import org.springframework.aop.IntroductionInterceptor;
|
||||
import org.springframework.aop.MethodBeforeAdvice;
|
||||
import org.springframework.aop.SpringProxy;
|
||||
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
|
||||
import org.springframework.aop.aspectj.annotation.AspectMetadata;
|
||||
import org.springframework.aop.config.AopConfigUtils;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyConfig;
|
||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
|
||||
import org.springframework.beans.PropertyValue;
|
||||
@@ -52,6 +60,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.DecoratingProxy;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -304,10 +313,26 @@ public class AspectJAutoProxyCreatorTests {
|
||||
@ValueSource(classes = {ProxyTargetClassFalseConfig.class, ProxyTargetClassTrueConfig.class})
|
||||
void lambdaIsAlwaysProxiedWithJdkProxy(Class<?> configClass) {
|
||||
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
|
||||
Supplier<?> supplier = context.getBean(Supplier.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Supplier<String> supplier = context.getBean(Supplier.class);
|
||||
assertThat(AopUtils.isAopProxy(supplier)).as("AOP proxy").isTrue();
|
||||
assertThat(AopUtils.isJdkDynamicProxy(supplier)).as("JDK Dynamic proxy").isTrue();
|
||||
assertThat(supplier.get()).asString().isEqualTo("advised: lambda");
|
||||
assertThat(supplier.getClass().getInterfaces())
|
||||
.containsExactlyInAnyOrder(Supplier.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
|
||||
assertThat(supplier.get()).isEqualTo("advised: lambda");
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@ValueSource(classes = {MixinProxyTargetClassFalseConfig.class, MixinProxyTargetClassTrueConfig.class})
|
||||
void lambdaIsAlwaysProxiedWithJdkProxyWithIntroductions(Class<?> configClass) {
|
||||
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
|
||||
MessageGenerator messageGenerator = context.getBean(MessageGenerator.class);
|
||||
assertThat(AopUtils.isAopProxy(messageGenerator)).as("AOP proxy").isTrue();
|
||||
assertThat(AopUtils.isJdkDynamicProxy(messageGenerator)).as("JDK Dynamic proxy").isTrue();
|
||||
assertThat(messageGenerator.getClass().getInterfaces())
|
||||
.containsExactlyInAnyOrder(MessageGenerator.class, Mixin.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
|
||||
assertThat(messageGenerator.generateMessage()).isEqualTo("mixin: lambda");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,3 +641,79 @@ class ProxyTargetClassFalseConfig extends AbstractProxyTargetClassConfig {
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||
class ProxyTargetClassTrueConfig extends AbstractProxyTargetClassConfig {
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface MessageGenerator {
|
||||
String generateMessage();
|
||||
}
|
||||
|
||||
interface Mixin {
|
||||
}
|
||||
|
||||
class MixinIntroductionInterceptor implements IntroductionInterceptor {
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
return "mixin: " + invocation.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean implementsInterface(Class<?> intf) {
|
||||
return Mixin.class.isAssignableFrom(intf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
class MixinAdvisor extends AbstractPointcutAdvisor implements IntroductionAdvisor {
|
||||
|
||||
@Override
|
||||
public org.springframework.aop.Pointcut getPointcut() {
|
||||
return org.springframework.aop.Pointcut.TRUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return new MixinIntroductionInterceptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?>[] getInterfaces() {
|
||||
return new Class[] { Mixin.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassFilter getClassFilter() {
|
||||
return MessageGenerator.class::isAssignableFrom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateInterfaces() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class AbstractMixinConfig {
|
||||
|
||||
@Bean
|
||||
MessageGenerator messageGenerator() {
|
||||
return () -> "lambda";
|
||||
}
|
||||
|
||||
@Bean
|
||||
MixinAdvisor mixinAdvisor() {
|
||||
return new MixinAdvisor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = false)
|
||||
class MixinProxyTargetClassFalseConfig extends AbstractMixinConfig {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||
class MixinProxyTargetClassTrueConfig extends AbstractMixinConfig {
|
||||
}
|
||||
|
||||
+127
-135
@@ -64,23 +64,26 @@ import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.tests.sample.beans.BeanWithObjectProperty;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DataBinder}.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @author Kazuki Shimizu
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class DataBinderTests {
|
||||
|
||||
@Test
|
||||
void testBindingNoErrors() throws BindException {
|
||||
void bindingNoErrors() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
assertThat(binder.isIgnoreUnknownFields()).isTrue();
|
||||
@@ -110,12 +113,11 @@ class DataBinderTests {
|
||||
assertThat(ex).isEqualTo(binder.getBindingResult());
|
||||
|
||||
other.reject("xxx");
|
||||
boolean condition = !other.equals(binder.getBindingResult());
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(other).isNotEqualTo(binder.getBindingResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithDefaultConversionNoErrors() throws BindException {
|
||||
void bindingWithDefaultConversionNoErrors() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
assertThat(binder.isIgnoreUnknownFields()).isTrue();
|
||||
@@ -131,7 +133,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedBindingWithDefaultConversionNoErrors() throws BindException {
|
||||
void nestedBindingWithDefaultConversionNoErrors() throws BindException {
|
||||
TestBean rod = new TestBean(new TestBean());
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
assertThat(binder.isIgnoreUnknownFields()).isTrue();
|
||||
@@ -147,7 +149,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingNoErrorsNotIgnoreUnknown() {
|
||||
void bindingNoErrorsNotIgnoreUnknown() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.setIgnoreUnknownFields(false);
|
||||
@@ -160,7 +162,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingNoErrorsWithInvalidField() {
|
||||
void bindingNoErrorsWithInvalidField() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -171,7 +173,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingNoErrorsWithIgnoreInvalid() {
|
||||
void bindingNoErrorsWithIgnoreInvalid() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.setIgnoreInvalidFields(true);
|
||||
@@ -180,10 +182,14 @@ class DataBinderTests {
|
||||
pvs.add("spouse.age", 32);
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
|
||||
assertThat(rod.getName()).isEqualTo("Rod");
|
||||
assertThat(rod.getSpouse()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithErrors() {
|
||||
void bindingWithErrors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -245,7 +251,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithSystemFieldError() {
|
||||
void bindingWithSystemFieldError() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -257,7 +263,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithErrorsAndCustomEditors() {
|
||||
void bindingWithErrorsAndCustomEditors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.registerCustomEditor(String.class, "touchy", new PropertyEditorSupport() {
|
||||
@@ -325,7 +331,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithCustomEditorOnObjectField() {
|
||||
void bindingWithCustomEditorOnObjectField() {
|
||||
BeanWithObjectProperty tb = new BeanWithObjectProperty();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.registerCustomEditor(Integer.class, "object", new CustomNumberEditor(Integer.class, true));
|
||||
@@ -336,7 +342,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithFormatter() {
|
||||
void bindingWithFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
@@ -368,7 +374,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithFormatter() {
|
||||
void bindingErrorWithFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
@@ -391,7 +397,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithParseExceptionFromFormatter() {
|
||||
void bindingErrorWithParseExceptionFromFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
@@ -419,7 +425,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithRuntimeExceptionFromFormatter() {
|
||||
void bindingErrorWithRuntimeExceptionFromFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
@@ -447,7 +453,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithFormatterAgainstList() {
|
||||
void bindingWithFormatterAgainstList() {
|
||||
BeanWithIntegerList tb = new BeanWithIntegerList();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
@@ -469,7 +475,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithFormatterAgainstList() {
|
||||
void bindingErrorWithFormatterAgainstList() {
|
||||
BeanWithIntegerList tb = new BeanWithIntegerList();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
@@ -492,7 +498,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithFormatterAgainstFields() {
|
||||
void bindingWithFormatterAgainstFields() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
@@ -525,7 +531,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithFormatterAgainstFields() {
|
||||
void bindingErrorWithFormatterAgainstFields() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.initDirectFieldAccess();
|
||||
@@ -549,7 +555,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithCustomFormatter() {
|
||||
void bindingWithCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.addCustomFormatter(new NumberStyleFormatter(), Float.class);
|
||||
@@ -578,7 +584,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithCustomFormatter() {
|
||||
void bindingErrorWithCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.addCustomFormatter(new NumberStyleFormatter());
|
||||
@@ -599,7 +605,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithParseExceptionFromCustomFormatter() {
|
||||
void bindingErrorWithParseExceptionFromCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
|
||||
@@ -624,7 +630,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithRuntimeExceptionFromCustomFormatter() {
|
||||
void bindingErrorWithRuntimeExceptionFromCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
|
||||
@@ -649,7 +655,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConversionWithInappropriateStringEditor() {
|
||||
void conversionWithInappropriateStringEditor() {
|
||||
DataBinder dataBinder = new DataBinder(null);
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
dataBinder.setConversionService(conversionService);
|
||||
@@ -662,7 +668,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedFields() throws BindException {
|
||||
void bindingWithAllowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("name", "myparam");
|
||||
@@ -672,30 +678,32 @@ class DataBinderTests {
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithDisallowedFields() throws BindException {
|
||||
void bindingWithDisallowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setDisallowedFields("age");
|
||||
binder.setDisallowedFields(" ", "\t", "favouriteColour", null, "age");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", "Rod");
|
||||
pvs.add("age", "32x");
|
||||
pvs.add("favouriteColour", "BLUE");
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields.length).isEqualTo(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("age");
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
assertThat(rod.getFavouriteColour()).as("did not change favourite colour").isNull();
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactlyInAnyOrder("age", "favouriteColour");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedAndDisallowedFields() throws BindException {
|
||||
void bindingWithAllowedAndDisallowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("name", "myparam");
|
||||
@@ -706,34 +714,32 @@ class DataBinderTests {
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("age");
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
|
||||
void bindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("name", "age");
|
||||
binder.setDisallowedFields("age");
|
||||
binder.setDisallowedFields("AGE");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", "Rod");
|
||||
pvs.add("age", "32x");
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("age");
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedFieldsUsingAsterisks() throws BindException {
|
||||
void bindingWithAllowedFieldsUsingAsterisks() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.setAllowedFields("nam*", "*ouchy");
|
||||
@@ -760,11 +766,11 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedAndDisallowedMapFields() throws BindException {
|
||||
void bindingWithAllowedAndDisallowedMapFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("someMap[key1]", "someMap[key2]");
|
||||
binder.setDisallowedFields("someMap['key3']", "someMap[key4]");
|
||||
binder.setDisallowedFields("someMap['KEY3']", "SomeMap[key4]");
|
||||
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("someMap[key1]", "value1");
|
||||
@@ -774,21 +780,18 @@ class DataBinderTests {
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getSomeMap().get("key1")).isEqualTo("value1");
|
||||
assertThat(rod.getSomeMap().get("key2")).isEqualTo("value2");
|
||||
assertThat(rod.getSomeMap().get("key3")).isNull();
|
||||
assertThat(rod.getSomeMap().get("key4")).isNull();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(2);
|
||||
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key3]")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key4]")).isTrue();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> someMap = (Map<String, String>) rod.getSomeMap();
|
||||
assertThat(someMap).containsOnly(entry("key1", "value1"), entry("key2", "value2"));
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("someMap[key3]", "someMap[key4]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for required field, both null, non-existing and empty strings.
|
||||
*/
|
||||
@Test
|
||||
void testBindingWithRequiredFields() {
|
||||
void bindingWithRequiredFields() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
@@ -819,7 +822,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithRequiredMapFields() {
|
||||
void bindingWithRequiredMapFields() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
@@ -839,7 +842,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithNestedObjectCreation() {
|
||||
void bindingWithNestedObjectCreation() {
|
||||
TestBean tb = new TestBean();
|
||||
|
||||
DataBinder binder = new DataBinder(tb, "person");
|
||||
@@ -860,7 +863,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorWithOldValueAccess() {
|
||||
void customEditorWithOldValueAccess() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
@@ -885,7 +888,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorForSingleProperty() {
|
||||
void customEditorForSingleProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
@@ -925,7 +928,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorForPrimitiveProperty() {
|
||||
void customEditorForPrimitiveProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
@@ -949,7 +952,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorForAllStringProperties() {
|
||||
void customEditorForAllStringProperties() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
@@ -981,7 +984,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomFormatterForSingleProperty() {
|
||||
void customFormatterForSingleProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
@@ -1021,7 +1024,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomFormatterForPrimitiveProperty() {
|
||||
void customFormatterForPrimitiveProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
@@ -1045,7 +1048,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomFormatterForAllStringProperties() {
|
||||
void customFormatterForAllStringProperties() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
@@ -1077,7 +1080,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJavaBeanPropertyConventions() {
|
||||
void javaBeanPropertyConventions() {
|
||||
Book book = new Book();
|
||||
DataBinder binder = new DataBinder(book);
|
||||
|
||||
@@ -1101,7 +1104,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOptionalProperty() {
|
||||
void optionalProperty() {
|
||||
OptionalHolder bean = new OptionalHolder();
|
||||
DataBinder binder = new DataBinder(bean);
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
@@ -1122,7 +1125,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidatorNoErrors() throws Exception {
|
||||
void validatorNoErrors() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setAge(33);
|
||||
tb.setName("Rod");
|
||||
@@ -1175,15 +1178,13 @@ class DataBinderTests {
|
||||
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
||||
|
||||
assertThat(errors.getErrorCount()).isEqualTo(1);
|
||||
boolean condition1 = !errors.hasGlobalErrors();
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(errors.hasGlobalErrors()).isFalse();
|
||||
assertThat(errors.getFieldErrorCount("age")).isEqualTo(1);
|
||||
boolean condition = !errors.hasFieldErrors("name");
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(errors.hasFieldErrors("name")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidatorWithErrors() {
|
||||
void validatorWithErrors() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
@@ -1252,7 +1253,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidatorWithErrorsAndCodesPrefix() {
|
||||
void validatorWithErrorsAndCodesPrefix() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
@@ -1324,7 +1325,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidatorWithNestedObjectNull() {
|
||||
void validatorWithNestedObjectNull() {
|
||||
TestBean tb = new TestBean();
|
||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||
Validator testValidator = new TestBeanValidator();
|
||||
@@ -1343,7 +1344,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedValidatorWithoutNestedPath() {
|
||||
void nestedValidatorWithoutNestedPath() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("XXX");
|
||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||
@@ -1357,7 +1358,8 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingStringArrayToIntegerSet() {
|
||||
@SuppressWarnings("unchecked")
|
||||
void bindingStringArrayToIntegerSet() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class) {
|
||||
@@ -1371,12 +1373,8 @@ class DataBinderTests {
|
||||
binder.bind(pvs);
|
||||
|
||||
assertThat(binder.getBindingResult().getFieldValue("set")).isEqualTo(tb.getSet());
|
||||
boolean condition = tb.getSet() instanceof TreeSet;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(tb.getSet().size()).isEqualTo(3);
|
||||
assertThat(tb.getSet().contains(10)).isTrue();
|
||||
assertThat(tb.getSet().contains(20)).isTrue();
|
||||
assertThat(tb.getSet().contains(30)).isTrue();
|
||||
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
|
||||
assertThat((Set<Integer>) tb.getSet()).containsExactly(10, 20, 30);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add("set", null);
|
||||
@@ -1386,7 +1384,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingNullToEmptyCollection() {
|
||||
void bindingNullToEmptyCollection() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class, true));
|
||||
@@ -1394,13 +1392,12 @@ class DataBinderTests {
|
||||
pvs.add("set", null);
|
||||
binder.bind(pvs);
|
||||
|
||||
boolean condition = tb.getSet() instanceof TreeSet;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(tb.getSet().isEmpty()).isTrue();
|
||||
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
|
||||
assertThat(tb.getSet()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingToIndexedField() {
|
||||
void bindingToIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(String.class, "array.name", new PropertyEditorSupport() {
|
||||
@@ -1439,7 +1436,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingToNestedIndexedField() {
|
||||
void bindingToNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
@@ -1470,7 +1467,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEditorForNestedIndexedField() {
|
||||
void editorForNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
@@ -1496,7 +1493,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSpecificEditorForNestedIndexedField() {
|
||||
void specificEditorForNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
@@ -1522,7 +1519,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInnerSpecificEditorForNestedIndexedField() {
|
||||
void innerSpecificEditorForNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
@@ -1548,7 +1545,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDirectBindingToIndexedField() {
|
||||
void directBindingToIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, "array", new PropertyEditorSupport() {
|
||||
@@ -1601,7 +1598,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDirectBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
|
||||
void directBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, "map[key0]", new PropertyEditorSupport() {
|
||||
@@ -1632,7 +1629,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDirectBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
|
||||
void directBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() {
|
||||
@@ -1663,7 +1660,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorWithSubclass() {
|
||||
void customEditorWithSubclass() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, new PropertyEditorSupport() {
|
||||
@@ -1697,7 +1694,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindToStringArrayWithArrayEditor() {
|
||||
void bindToStringArrayWithArrayEditor() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(String[].class, "stringArray", new PropertyEditorSupport() {
|
||||
@@ -1709,15 +1706,12 @@ class DataBinderTests {
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("stringArray", "a1-b2");
|
||||
binder.bind(pvs);
|
||||
boolean condition = !binder.getBindingResult().hasErrors();
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(tb.getStringArray().length).isEqualTo(2);
|
||||
assertThat(tb.getStringArray()[0]).isEqualTo("a1");
|
||||
assertThat(tb.getStringArray()[1]).isEqualTo("b2");
|
||||
assertThat(binder.getBindingResult().hasErrors()).isFalse();
|
||||
assertThat(tb.getStringArray()).containsExactly("a1", "b2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindToStringArrayWithComponentEditor() {
|
||||
void bindToStringArrayWithComponentEditor() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() {
|
||||
@@ -1729,15 +1723,14 @@ class DataBinderTests {
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("stringArray", new String[] {"a1", "b2"});
|
||||
binder.bind(pvs);
|
||||
boolean condition = !binder.getBindingResult().hasErrors();
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(binder.getBindingResult().hasErrors()).isFalse();
|
||||
assertThat(tb.getStringArray().length).isEqualTo(2);
|
||||
assertThat(tb.getStringArray()[0]).isEqualTo("Xa1");
|
||||
assertThat(tb.getStringArray()[1]).isEqualTo("Xb2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrors() {
|
||||
void bindingErrors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -1764,7 +1757,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddAllErrors() {
|
||||
void addAllErrors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -1784,7 +1777,7 @@ class DataBinderTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testBindingWithResortedList() {
|
||||
void bindingWithResortedList() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -1802,7 +1795,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRejectWithoutDefaultMessage() {
|
||||
void rejectWithoutDefaultMessage() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("myName");
|
||||
tb.setAge(99);
|
||||
@@ -1820,7 +1813,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindExceptionSerializable() throws Exception {
|
||||
void bindExceptionSerializable() throws Exception {
|
||||
SerializablePerson tb = new SerializablePerson();
|
||||
tb.setName("myName");
|
||||
tb.setAge(99);
|
||||
@@ -1849,27 +1842,27 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTrackDisallowedFields() {
|
||||
void trackDisallowedFields() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setAllowedFields("name", "age");
|
||||
|
||||
String name = "Rob Harrop";
|
||||
String beanName = "foobar";
|
||||
int age = 42;
|
||||
|
||||
MutablePropertyValues mpvs = new MutablePropertyValues();
|
||||
mpvs.add("name", name);
|
||||
mpvs.add("beanName", beanName);
|
||||
mpvs.add("age", age);
|
||||
mpvs.add("beanName", "foobar");
|
||||
binder.bind(mpvs);
|
||||
|
||||
assertThat(testBean.getName()).isEqualTo(name);
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("beanName");
|
||||
assertThat(testBean.getAge()).isEqualTo(age);
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("beanName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowWithinDefaultLimit() {
|
||||
void autoGrowWithinDefaultLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
|
||||
@@ -1881,7 +1874,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowBeyondDefaultLimit() {
|
||||
void autoGrowBeyondDefaultLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
|
||||
@@ -1894,7 +1887,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowWithinCustomLimit() {
|
||||
void autoGrowWithinCustomLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setAutoGrowCollectionLimit(10);
|
||||
@@ -1907,7 +1900,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowBeyondCustomLimit() {
|
||||
void autoGrowBeyondCustomLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setAutoGrowCollectionLimit(10);
|
||||
@@ -1921,7 +1914,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedGrowingList() {
|
||||
void nestedGrowingList() {
|
||||
Form form = new Form();
|
||||
DataBinder binder = new DataBinder(form, "form");
|
||||
MutablePropertyValues mpv = new MutablePropertyValues();
|
||||
@@ -1937,7 +1930,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFieldErrorAccessVariations() {
|
||||
void fieldErrorAccessVariations() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
assertThat(binder.getBindingResult().getGlobalError()).isNull();
|
||||
@@ -1958,7 +1951,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test // SPR-14888
|
||||
void testSetAutoGrowCollectionLimit() {
|
||||
void setAutoGrowCollectionLimit() {
|
||||
BeanWithIntegerList tb = new BeanWithIntegerList();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.setAutoGrowCollectionLimit(257);
|
||||
@@ -1972,7 +1965,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test // SPR-14888
|
||||
void testSetAutoGrowCollectionLimitAfterInitialization() {
|
||||
void setAutoGrowCollectionLimitAfterInitialization() {
|
||||
DataBinder binder = new DataBinder(new BeanWithIntegerList());
|
||||
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
@@ -1981,7 +1974,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
|
||||
void setCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
|
||||
@@ -1998,7 +1991,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
|
||||
void setCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
|
||||
@@ -2013,7 +2006,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetCustomMessageCodesResolverAfterInitializeBindingResult() {
|
||||
void setCustomMessageCodesResolverAfterInitializeBindingResult() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.initBeanPropertyAccess();
|
||||
@@ -2028,7 +2021,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetMessageCodesResolverIsNullAfterInitializeBindingResult() {
|
||||
void setMessageCodesResolverIsNullAfterInitializeBindingResult() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.initBeanPropertyAccess();
|
||||
@@ -2042,8 +2035,7 @@ class DataBinderTests {
|
||||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testCallSetMessageCodesResolverTwice() {
|
||||
|
||||
void callSetMessageCodesResolverTwice() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setMessageCodesResolver(new DefaultMessageCodesResolver());
|
||||
|
||||
+36
-5
@@ -31,6 +31,7 @@ import java.util.Set;
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Payload;
|
||||
import javax.validation.Valid;
|
||||
@@ -43,6 +44,7 @@ import org.hibernate.validator.HibernateValidatorFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
@@ -313,6 +315,32 @@ class ValidatorFactoryTests {
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withConstraintValidatorFactory() {
|
||||
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.setConstraintValidatorFactory(cvf);
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withCustomInitializer() {
|
||||
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.setConfigurationInitializer(configuration -> configuration.constraintValidatorFactory(cvf));
|
||||
validator.afterPropertiesSet();
|
||||
|
||||
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
|
||||
validator.destroy();
|
||||
}
|
||||
|
||||
|
||||
@NameAddressValid
|
||||
public static class ValidPerson {
|
||||
@@ -409,8 +437,8 @@ class ValidatorFactoryTests {
|
||||
}
|
||||
boolean valid = (value.name == null || !value.address.street.contains(value.name));
|
||||
if (!valid && "Phil".equals(value.name)) {
|
||||
context.buildConstraintViolationWithTemplate(
|
||||
context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
|
||||
.addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
@@ -446,6 +474,7 @@ class ValidatorFactoryTests {
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
@@ -454,7 +483,7 @@ class ValidatorFactoryTests {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Constraint(validatedBy=InnerValidator.class)
|
||||
@Constraint(validatedBy = InnerValidator.class)
|
||||
public @interface InnerValid {
|
||||
|
||||
String message() default "NOT VALID";
|
||||
@@ -475,7 +504,8 @@ class ValidatorFactoryTests {
|
||||
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
if (bean.getValue() == null) {
|
||||
context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
|
||||
context.buildConstraintViolationWithTemplate("NULL")
|
||||
.addPropertyNode("value").addConstraintViolation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -523,7 +553,8 @@ class ValidatorFactoryTests {
|
||||
boolean valid = true;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if ("X".equals(list.get(i))) {
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
|
||||
.addBeanNode().inIterable().atIndex(i).addConstraintViolation();
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -5,19 +5,19 @@
|
||||
|
||||
<bean id="publisher" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisher"/>
|
||||
|
||||
<bean id="publisherMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherMBean"/>
|
||||
<bean id="publisherMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherMBean"/>
|
||||
|
||||
<bean id="publisherStandardMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherStandardMBean"/>
|
||||
<bean id="publisherStandardMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherStandardMBean"/>
|
||||
|
||||
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
||||
<property name="beans">
|
||||
<map>
|
||||
<entry key="spring:type=Publisher" value-ref="publisher"/>
|
||||
<entry key="spring:type=PublisherMBean" value-ref="publisherMBean"/>
|
||||
<entry key="spring:type=PublisherStandardMBean" value-ref="publisherStandardMBean"/>
|
||||
<entry key="spring:type=PublisherMBean" value-ref="publisherMBean"/>
|
||||
<entry key="spring:type=PublisherStandardMBean" value-ref="publisherStandardMBean"/>
|
||||
</map>
|
||||
</property>
|
||||
<property name="server" ref="server"/>
|
||||
<property name="server" ref="server"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,7 +47,7 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public final class Property {
|
||||
|
||||
private static Map<Property, Annotation[]> annotationCache = new ConcurrentReferenceHashMap<>();
|
||||
private static final Map<Property, Annotation[]> annotationCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
private final Class<?> objectType;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -1061,7 +1061,7 @@ public abstract class DataBufferUtils {
|
||||
|
||||
@Override
|
||||
public Context currentContext() {
|
||||
return this.sink.currentContext();
|
||||
return Context.of(this.sink.contextView());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1158,7 +1158,7 @@ public abstract class DataBufferUtils {
|
||||
|
||||
@Override
|
||||
public Context currentContext() {
|
||||
return this.sink.currentContext();
|
||||
return Context.of(this.sink.contextView());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -842,6 +842,20 @@ public abstract class ClassUtils {
|
||||
return (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link Class} is a JVM-generated implementation
|
||||
* class for a lambda expression or method reference.
|
||||
* <p>This method makes a best-effort attempt at determining this, based on
|
||||
* checks that work on modern, mainstream JVMs.
|
||||
* @param clazz the class to check
|
||||
* @return {@code true} if the class is a lambda implementation class
|
||||
* @since 5.3.19
|
||||
*/
|
||||
public static boolean isLambdaClass(Class<?> clazz) {
|
||||
return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
|
||||
(clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given object is a CGLIB proxy.
|
||||
* @param object the object to check
|
||||
|
||||
@@ -65,6 +65,17 @@ public abstract class MimeTypeUtils {
|
||||
*/
|
||||
public static final String ALL_VALUE = "*/*";
|
||||
|
||||
/**
|
||||
* Public constant mime type for {@code application/graphql+json}.
|
||||
* @see <a href="https://github.com/graphql/graphql-over-http">GraphQL over HTTP spec</a>
|
||||
* */
|
||||
public static final MimeType APPLICATION_GRAPHQL;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MimeTypeUtils#APPLICATION_GRAPHQL}.
|
||||
*/
|
||||
public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json";
|
||||
|
||||
/**
|
||||
* Public constant mime type for {@code application/json}.
|
||||
* */
|
||||
@@ -165,6 +176,7 @@ public abstract class MimeTypeUtils {
|
||||
static {
|
||||
// Not using "parseMimeType" to avoid static init cost
|
||||
ALL = new MimeType("*", "*");
|
||||
APPLICATION_GRAPHQL = new MimeType("application", "graphql+json");
|
||||
APPLICATION_JSON = new MimeType("application", "json");
|
||||
APPLICATION_OCTET_STREAM = new MimeType("application", "octet-stream");
|
||||
APPLICATION_XML = new MimeType("application", "xml");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
@@ -408,6 +409,29 @@ class ClassUtilsTests {
|
||||
assertThat(ClassUtils.isPrimitiveOrWrapper(type)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLambda() {
|
||||
assertIsLambda(ClassUtilsTests.staticLambdaExpression);
|
||||
assertIsLambda(ClassUtilsTests::staticStringFactory);
|
||||
|
||||
assertIsLambda(this.instanceLambdaExpression);
|
||||
assertIsLambda(this::instanceStringFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isNotLambda() {
|
||||
assertIsNotLambda(new EnigmaSupplier());
|
||||
|
||||
assertIsNotLambda(new Supplier<String>() {
|
||||
@Override
|
||||
public String get() {
|
||||
return "anonymous inner class";
|
||||
}
|
||||
});
|
||||
|
||||
assertIsNotLambda(new Fake$$LambdaSupplier());
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class GetStaticMethodTests {
|
||||
@@ -500,4 +524,38 @@ class ClassUtilsTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertIsLambda(Supplier<String> supplier) {
|
||||
assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isTrue();
|
||||
}
|
||||
|
||||
private static void assertIsNotLambda(Supplier<String> supplier) {
|
||||
assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isFalse();
|
||||
}
|
||||
|
||||
private static final Supplier<String> staticLambdaExpression = () -> "static lambda expression";
|
||||
|
||||
private final Supplier<String> instanceLambdaExpression = () -> "instance lambda expressions";
|
||||
|
||||
private static String staticStringFactory() {
|
||||
return "static string factory";
|
||||
}
|
||||
|
||||
private String instanceStringFactory() {
|
||||
return "instance string factory";
|
||||
}
|
||||
|
||||
private static class EnigmaSupplier implements Supplier<String> {
|
||||
@Override
|
||||
public String get() {
|
||||
return "enigma";
|
||||
}
|
||||
}
|
||||
|
||||
private static class Fake$$LambdaSupplier implements Supplier<String> {
|
||||
@Override
|
||||
public String get() {
|
||||
return "fake lambda";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -288,8 +288,8 @@ public class ConstructorReference extends SpelNodeImpl {
|
||||
else {
|
||||
// There is an initializer
|
||||
if (this.dimensions == null || this.dimensions.length > 1) {
|
||||
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
|
||||
// is not currently supported
|
||||
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}})
|
||||
// - this is not currently supported
|
||||
throw new SpelEvaluationException(getStartPosition(),
|
||||
SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -98,9 +98,18 @@ public class DataClassRowMapper<T> extends BeanPropertyRowMapper<T> {
|
||||
if (this.constructorParameterNames != null && this.constructorParameterTypes != null) {
|
||||
args = new Object[this.constructorParameterNames.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String name = underscoreName(this.constructorParameterNames[i]);
|
||||
String name = this.constructorParameterNames[i];
|
||||
int index;
|
||||
try {
|
||||
// Try direct name match first
|
||||
index = rs.findColumn(lowerCaseName(name));
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
// Try underscored name match instead
|
||||
index = rs.findColumn(underscoreName(name));
|
||||
}
|
||||
TypeDescriptor td = this.constructorParameterTypes[i];
|
||||
Object value = getColumnValue(rs, rs.findColumn(name), td.getType());
|
||||
Object value = getColumnValue(rs, index, td.getType());
|
||||
args[i] = tc.convertIfNecessary(value, td.getType(), td);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-5
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLFeatureNotSupportedException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
@@ -63,7 +64,7 @@ public abstract class AbstractRowMapperTests {
|
||||
protected void verifyPerson(ConcretePerson person) {
|
||||
assertThat(person.getName()).isEqualTo("Bubba");
|
||||
assertThat(person.getAge()).isEqualTo(22L);
|
||||
assertThat(person.getBirth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.getBirthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.getBalance()).isEqualTo(new BigDecimal("1234.56"));
|
||||
verifyPersonViaBeanWrapper(person);
|
||||
}
|
||||
@@ -94,7 +95,14 @@ public abstract class AbstractRowMapperTests {
|
||||
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(person);
|
||||
assertThat(bw.getPropertyValue("name")).isEqualTo("Bubba");
|
||||
assertThat(bw.getPropertyValue("age")).isEqualTo(22L);
|
||||
assertThat((Date) bw.getPropertyValue("birth_date")).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
Date birthDate;
|
||||
if (bw.isReadableProperty("birth_date")) {
|
||||
birthDate = (Date) bw.getPropertyValue("birth_date");
|
||||
}
|
||||
else {
|
||||
birthDate = (Date) bw.getPropertyValue("birthDate");
|
||||
}
|
||||
assertThat(birthDate).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(bw.getPropertyValue("balance")).isEqualTo(new BigDecimal("1234.56"));
|
||||
}
|
||||
|
||||
@@ -107,7 +115,7 @@ public abstract class AbstractRowMapperTests {
|
||||
}
|
||||
|
||||
|
||||
protected enum MockType {ONE, TWO, THREE}
|
||||
protected enum MockType {ONE, TWO, THREE, FOUR}
|
||||
|
||||
|
||||
protected static class Mock {
|
||||
@@ -152,13 +160,19 @@ public abstract class AbstractRowMapperTests {
|
||||
given(resultSetMetaData.getColumnLabel(1)).willReturn(
|
||||
type == MockType.THREE ? "Last Name" : "name");
|
||||
given(resultSetMetaData.getColumnLabel(2)).willReturn("age");
|
||||
given(resultSetMetaData.getColumnLabel(3)).willReturn("birth_date");
|
||||
given(resultSetMetaData.getColumnLabel(3)).willReturn(type == MockType.FOUR ? "birthdate" :"birth_date");
|
||||
given(resultSetMetaData.getColumnLabel(4)).willReturn("balance");
|
||||
given(resultSetMetaData.getColumnLabel(5)).willReturn("e_mail");
|
||||
|
||||
given(resultSet.findColumn("name")).willReturn(1);
|
||||
given(resultSet.findColumn("age")).willReturn(2);
|
||||
given(resultSet.findColumn("birth_date")).willReturn(3);
|
||||
if (type == MockType.FOUR) {
|
||||
given(resultSet.findColumn("birthdate")).willReturn(3);
|
||||
}
|
||||
else {
|
||||
given(resultSet.findColumn("birthdate")).willThrow(new SQLException());
|
||||
given(resultSet.findColumn("birth_date")).willReturn(3);
|
||||
}
|
||||
given(resultSet.findColumn("balance")).willReturn(4);
|
||||
given(resultSet.findColumn("e_mail")).willReturn(5);
|
||||
|
||||
|
||||
+11
@@ -140,6 +140,17 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryWithDirectNameMatchOnBirthDate() throws Exception {
|
||||
Mock mock = new Mock(MockType.FOUR);
|
||||
List<ConcretePerson> result = mock.getJdbcTemplate().query(
|
||||
"select name, age, birthdate, balance from people",
|
||||
new BeanPropertyRowMapper<>(ConcretePerson.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -57,7 +57,7 @@ public class DataClassRowMapperTests extends AbstractRowMapperTests {
|
||||
ConstructorPersonWithGenerics person = result.get(0);
|
||||
assertThat(person.name()).isEqualTo("Bubba");
|
||||
assertThat(person.age()).isEqualTo(22L);
|
||||
assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.balance()).isEqualTo(Collections.singletonList(new BigDecimal("1234.56")));
|
||||
|
||||
mock.verifyClosed();
|
||||
@@ -65,15 +65,15 @@ public class DataClassRowMapperTests extends AbstractRowMapperTests {
|
||||
|
||||
@Test
|
||||
public void testStaticQueryWithDataClassAndSetters() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
Mock mock = new Mock(MockType.FOUR);
|
||||
List<ConstructorPersonWithSetters> result = mock.getJdbcTemplate().query(
|
||||
"select name, age, birth_date, balance from people",
|
||||
"select name, age, birthdate, balance from people",
|
||||
new DataClassRowMapper<>(ConstructorPersonWithSetters.class));
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
ConstructorPersonWithSetters person = result.get(0);
|
||||
assertThat(person.name()).isEqualTo("BUBBA");
|
||||
assertThat(person.age()).isEqualTo(22L);
|
||||
assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
|
||||
|
||||
mock.verifyClosed();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,7 +27,7 @@ public abstract class AbstractPerson {
|
||||
|
||||
private long age;
|
||||
|
||||
private Date birth_date;
|
||||
private Date birthDate;
|
||||
|
||||
|
||||
public String getName() {
|
||||
@@ -46,12 +46,12 @@ public abstract class AbstractPerson {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public Date getBirth_date() {
|
||||
return birth_date;
|
||||
public Date getBirthDate() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
public void setBirth_date(Date birth_date) {
|
||||
this.birth_date = birth_date;
|
||||
public void setBirthDate(Date birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,7 @@ public class ConstructorPersonWithGenerics {
|
||||
|
||||
private final long age;
|
||||
|
||||
private final Date birth_date;
|
||||
private final Date birthDate;
|
||||
|
||||
private final List<BigDecimal> balance;
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ConstructorPersonWithGenerics {
|
||||
public ConstructorPersonWithGenerics(String name, long age, Date birth_date, List<BigDecimal> balance) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.birth_date = birth_date;
|
||||
this.birthDate = birth_date;
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ public class ConstructorPersonWithGenerics {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
public Date birth_date() {
|
||||
return this.birth_date;
|
||||
public Date birthDate() {
|
||||
return this.birthDate;
|
||||
}
|
||||
|
||||
public List<BigDecimal> balance() {
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,15 +28,15 @@ public class ConstructorPersonWithSetters {
|
||||
|
||||
private long age;
|
||||
|
||||
private Date birth_date;
|
||||
private Date birthDate;
|
||||
|
||||
private BigDecimal balance;
|
||||
|
||||
|
||||
public ConstructorPersonWithSetters(String name, long age, Date birth_date, BigDecimal balance) {
|
||||
public ConstructorPersonWithSetters(String name, long age, Date birthDate, BigDecimal balance) {
|
||||
this.name = name.toUpperCase();
|
||||
this.age = age;
|
||||
this.birth_date = birth_date;
|
||||
this.birthDate = birthDate;
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ public class ConstructorPersonWithSetters {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public void setBirth_date(Date birth_date) {
|
||||
this.birth_date = birth_date;
|
||||
public void setBirthDate(Date birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
public void setBalance(BigDecimal balance) {
|
||||
@@ -65,8 +65,8 @@ public class ConstructorPersonWithSetters {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
public Date birth_date() {
|
||||
return this.birth_date;
|
||||
public Date birthDate() {
|
||||
return this.birthDate;
|
||||
}
|
||||
|
||||
public BigDecimal balance() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -60,8 +60,8 @@ public class SpacePerson {
|
||||
return balance;
|
||||
}
|
||||
|
||||
public void setBalance(BigDecimal balanace) {
|
||||
this.balance = balanace;
|
||||
public void setBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -420,21 +420,21 @@ public class HandlerMethod {
|
||||
private class ReturnValueMethodParameter extends HandlerMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private final Object returnValue;
|
||||
private final Class<?> returnValueType;
|
||||
|
||||
public ReturnValueMethodParameter(@Nullable Object returnValue) {
|
||||
super(-1);
|
||||
this.returnValue = returnValue;
|
||||
this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
|
||||
}
|
||||
|
||||
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
|
||||
super(original);
|
||||
this.returnValue = original.returnValue;
|
||||
this.returnValueType = original.returnValueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
|
||||
return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -95,6 +95,17 @@ public class MediaType extends MimeType implements Serializable {
|
||||
*/
|
||||
public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/graphql+json}.
|
||||
* @see <a href="https://github.com/graphql/graphql-over-http">GraphQL over HTTP spec</a>
|
||||
*/
|
||||
public static final MediaType APPLICATION_GRAPHQL;
|
||||
|
||||
/**
|
||||
* A String equivalent of {@link MediaType#APPLICATION_GRAPHQL}.
|
||||
*/
|
||||
public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json";
|
||||
|
||||
/**
|
||||
* Public constant media type for {@code application/json}.
|
||||
*/
|
||||
@@ -396,6 +407,7 @@ public class MediaType extends MimeType implements Serializable {
|
||||
APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
|
||||
APPLICATION_CBOR = new MediaType("application", "cbor");
|
||||
APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
|
||||
APPLICATION_GRAPHQL = new MediaType("application", "graphql+json");
|
||||
APPLICATION_JSON = new MediaType("application", "json");
|
||||
APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
|
||||
APPLICATION_NDJSON = new MediaType("application", "x-ndjson");
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -105,7 +105,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
|
||||
|
||||
@Override
|
||||
public Context currentContext() {
|
||||
return this.sink.currentContext();
|
||||
return Context.of(this.sink.contextView());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -116,7 +116,7 @@ final class PartGenerator extends BaseSubscriber<MultipartParser.Token> {
|
||||
|
||||
@Override
|
||||
public Context currentContext() {
|
||||
return this.sink.currentContext();
|
||||
return Context.of(this.sink.contextView());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getLocalAddress() {
|
||||
return new InetSocketAddress(this.servletRequest.getLocalName(), this.servletRequest.getLocalPort());
|
||||
return new InetSocketAddress(this.servletRequest.getLocalAddr(), this.servletRequest.getLocalPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+12
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,8 @@ import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import reactor.core.publisher.Operators;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.core.log.LogDelegateFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -56,6 +58,8 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
|
||||
*/
|
||||
protected static Log rsReadLogger = LogDelegateFactory.getHiddenLog(AbstractListenerReadPublisher.class);
|
||||
|
||||
final static DataBuffer EMPTY_BUFFER = DefaultDataBufferFactory.sharedInstance.allocateBuffer(0);
|
||||
|
||||
|
||||
private final AtomicReference<State> state = new AtomicReference<>(State.UNSUBSCRIBED);
|
||||
|
||||
@@ -180,7 +184,7 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
|
||||
|
||||
/**
|
||||
* Read and publish data one at a time until there is no more data, no more
|
||||
* demand, or perhaps we completed in the mean time.
|
||||
* demand, or perhaps we completed meanwhile.
|
||||
* @return {@code true} if there is more demand; {@code false} if there is
|
||||
* no more demand or we have completed.
|
||||
*/
|
||||
@@ -188,7 +192,12 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
|
||||
long r;
|
||||
while ((r = this.demand) > 0 && (this.state.get() != State.COMPLETED)) {
|
||||
T data = read();
|
||||
if (data != null) {
|
||||
if (data == EMPTY_BUFFER) {
|
||||
if (rsReadLogger.isTraceEnabled()) {
|
||||
rsReadLogger.trace(getLogPrefix() + "0 bytes read, trying again");
|
||||
}
|
||||
}
|
||||
else if (data != null) {
|
||||
if (r != Long.MAX_VALUE) {
|
||||
DEMAND_FIELD_UPDATER.addAndGet(this, -1L);
|
||||
}
|
||||
|
||||
+7
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -198,7 +198,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
@Nullable
|
||||
protected SslInfo initSslInfo() {
|
||||
X509Certificate[] certificates = getX509Certificates();
|
||||
return certificates != null ? new DefaultSslInfo(getSslSessionId(), certificates) : null;
|
||||
return (certificates != null ? new DefaultSslInfo(getSslSessionId(), certificates) : null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -208,8 +208,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
|
||||
@Nullable
|
||||
private X509Certificate[] getX509Certificates() {
|
||||
String name = "javax.servlet.request.X509Certificate";
|
||||
return (X509Certificate[]) this.request.getAttribute(name);
|
||||
return (X509Certificate[]) this.request.getAttribute("javax.servlet.request.X509Certificate");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -236,10 +235,10 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
/**
|
||||
* Read from the request body InputStream and return a DataBuffer.
|
||||
* Invoked only when {@link ServletInputStream#isReady()} returns "true".
|
||||
* @return a DataBuffer with data read, or {@link #EOF_BUFFER} if the input
|
||||
* stream returned -1, or null if 0 bytes were read.
|
||||
* @return a DataBuffer with data read, or
|
||||
* {@link AbstractListenerReadPublisher#EMPTY_BUFFER} if 0 bytes were read,
|
||||
* or {@link #EOF_BUFFER} if the input stream returned -1.
|
||||
*/
|
||||
@Nullable
|
||||
DataBuffer readFromInputStream() throws IOException {
|
||||
int read = this.request.getInputStream().read(this.buffer);
|
||||
logBytesRead(read);
|
||||
@@ -254,7 +253,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
return EOF_BUFFER;
|
||||
}
|
||||
|
||||
return null;
|
||||
return AbstractListenerReadPublisher.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
protected final void logBytesRead(int read) {
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
||||
return EOF_BUFFER;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
return AbstractListenerReadPublisher.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +33,15 @@ import org.springframework.web.util.WebUtils;
|
||||
* Special {@link org.springframework.validation.DataBinder} to perform data binding
|
||||
* from servlet request parameters to JavaBeans, including support for multipart files.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
|
||||
* which include specifying allowed/required fields, and registering custom
|
||||
* property editors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,6 +34,15 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
* the Servlet API; serves as base class for more specific DataBinder variants,
|
||||
* such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>Includes support for field markers which address a common problem with
|
||||
* HTML checkboxes and select options: detecting that a field was part of
|
||||
* the form, but did not generate a request parameter because it was empty.
|
||||
|
||||
@@ -100,6 +100,7 @@ import java.lang.annotation.Target;
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ControllerAdvice
|
||||
* @see org.springframework.web.context.request.WebRequest
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,15 +23,24 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation that identifies methods which initialize the
|
||||
* Annotation that identifies methods that initialize the
|
||||
* {@link org.springframework.web.bind.WebDataBinder} which
|
||||
* will be used for populating command and form object arguments
|
||||
* of annotated handler methods.
|
||||
*
|
||||
* <p>Such init-binder methods support all arguments that {@link RequestMapping}
|
||||
* supports, except for command/form objects and corresponding validation result
|
||||
* objects. Init-binder methods must not have a return value; they are usually
|
||||
* declared as {@code void}.
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>{@code @InitBinder} methods support all arguments that
|
||||
* {@link RequestMapping @RequestMapping} methods support, except for command/form
|
||||
* objects and corresponding validation result objects. {@code @InitBinder} methods
|
||||
* must not have a return value; they are usually declared as {@code void}.
|
||||
*
|
||||
* <p>Typical arguments are {@link org.springframework.web.bind.WebDataBinder}
|
||||
* in combination with {@link org.springframework.web.context.request.WebRequest}
|
||||
@@ -39,6 +48,7 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see ControllerAdvice
|
||||
* @see org.springframework.web.bind.WebDataBinder
|
||||
* @see org.springframework.web.context.request.WebRequest
|
||||
*/
|
||||
|
||||
+18
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,18 +31,27 @@ import org.springframework.ui.Model;
|
||||
* for controller classes with {@link RequestMapping @RequestMapping}
|
||||
* methods.
|
||||
*
|
||||
* <p>Can be used to expose command objects to a web view, using
|
||||
* specific attribute names, through annotating corresponding
|
||||
* parameters of an {@link RequestMapping @RequestMapping} method.
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>Can also be used to expose reference data to a web view
|
||||
* through annotating accessor methods in a controller class with
|
||||
* <p>{@code @ModelAttribute} can be used to expose command objects to a web view,
|
||||
* using specific attribute names, by annotating corresponding parameters of an
|
||||
* {@link RequestMapping @RequestMapping} method.
|
||||
*
|
||||
* <p>{@code @ModelAttribute} can also be used to expose reference data to a web
|
||||
* view by annotating accessor methods in a controller class with
|
||||
* {@link RequestMapping @RequestMapping} methods. Such accessor
|
||||
* methods are allowed to have any arguments that
|
||||
* {@link RequestMapping @RequestMapping} methods support, returning
|
||||
* the model attribute value to expose.
|
||||
*
|
||||
* <p>Note however that reference data and all other model content is
|
||||
* <p>Note however that reference data and all other model content are
|
||||
* not available to web views when request processing results in an
|
||||
* {@code Exception} since the exception could be raised at any time
|
||||
* making the content of the model unreliable. For this reason
|
||||
@@ -52,6 +61,7 @@ import org.springframework.ui.Model;
|
||||
* @author Juergen Hoeller
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 2.5
|
||||
* @see ControllerAdvice
|
||||
*/
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -77,7 +87,7 @@ public @interface ModelAttribute {
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* Allows declaring data binding disabled directly on an {@code @ModelAttribute}
|
||||
* Allows data binding to be disabled directly on an {@code @ModelAttribute}
|
||||
* method parameter or on the attribute returned from an {@code @ModelAttribute}
|
||||
* method, both of which would prevent data binding for that attribute.
|
||||
* <p>By default this is set to {@code true} in which case data binding applies.
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,6 +36,15 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
* Specialized {@link org.springframework.validation.DataBinder} to perform data
|
||||
* binding from URL query parameters or form data in the request data to Java objects.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,6 +35,15 @@ import org.springframework.web.multipart.support.StandardServletPartUtils;
|
||||
* Special {@link org.springframework.validation.DataBinder} to perform data binding
|
||||
* from web request parameters to JavaBeans, including support for multipart files.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
|
||||
* which include specifying allowed/required fields, and registering custom
|
||||
* property editors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -582,21 +582,21 @@ public class HandlerMethod {
|
||||
private class ReturnValueMethodParameter extends HandlerMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private final Object returnValue;
|
||||
private final Class<?> returnValueType;
|
||||
|
||||
public ReturnValueMethodParameter(@Nullable Object returnValue) {
|
||||
super(-1);
|
||||
this.returnValue = returnValue;
|
||||
this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
|
||||
}
|
||||
|
||||
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
|
||||
super(original);
|
||||
this.returnValue = original.returnValue;
|
||||
this.returnValueType = original.returnValueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
|
||||
return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
-5
@@ -103,7 +103,7 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTests<Jackson2JsonE
|
||||
);
|
||||
}
|
||||
|
||||
@Test // SPR-15866
|
||||
@Test // SPR-15866
|
||||
public void canEncodeWithCustomMimeType() {
|
||||
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
|
||||
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(new ObjectMapper(), textJavascript);
|
||||
@@ -231,9 +231,8 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTests<Jackson2JsonE
|
||||
);
|
||||
}
|
||||
|
||||
@Test // gh-28045
|
||||
@Test // gh-28045
|
||||
public void jacksonValueUnwrappedBeforeObjectMapperSelection() {
|
||||
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
@@ -248,13 +247,15 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTests<Jackson2JsonE
|
||||
ObjectMapper mapper = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
this.encoder.registerObjectMappersForType(JacksonViewBean.class, map -> map.put(halMediaType, mapper));
|
||||
|
||||
String ls = System.lineSeparator(); // output below is different between Unix and Windows
|
||||
testEncode(Mono.just(jacksonValue), type, halMediaType, Collections.emptyMap(), step -> step
|
||||
.consumeNextWith(expectString("{\n \"withView1\" : \"with\"\n}").andThen(DataBufferUtils::release))
|
||||
.consumeNextWith(expectString("{" + ls + " \"withView1\" : \"with\"" + ls + "}")
|
||||
.andThen(DataBufferUtils::release))
|
||||
.verifyComplete()
|
||||
);
|
||||
}
|
||||
|
||||
@Test // gh-22771
|
||||
@Test // gh-22771
|
||||
public void encodeWithFlushAfterWriteOff() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, false);
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -116,7 +116,7 @@ public class InitBinderDataBinderFactoryTests {
|
||||
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, "foo");
|
||||
|
||||
assertThat(dataBinder.getDisallowedFields()).isNotNull();
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualTo("requestParam-22");
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualToIgnoringCase("requestParam-22");
|
||||
}
|
||||
|
||||
private WebDataBinderFactory createFactory(String methodName, Class<?>... parameterTypes)
|
||||
|
||||
+7
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -269,7 +269,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
assertThat(this.container.getModel().get("testBean")).isSameAs(testBean);
|
||||
}
|
||||
|
||||
@Test // gh-25182
|
||||
@Test // gh-25182
|
||||
public void resolveConstructorListArgumentFromCommaSeparatedRequestParameter() throws Exception {
|
||||
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
|
||||
mockRequest.addParameter("listOfStrings", "1,2");
|
||||
@@ -279,7 +279,6 @@ public class ModelAttributeMethodProcessorTests {
|
||||
given(factory.createBinder(any(), any(), eq("testBeanWithConstructorArgs")))
|
||||
.willAnswer(invocation -> {
|
||||
WebRequestDataBinder binder = new WebRequestDataBinder(invocation.getArgument(1));
|
||||
|
||||
// Add conversion service which will convert "1,2" to a list
|
||||
binder.setConversionService(new DefaultFormattingConversionService());
|
||||
return binder;
|
||||
@@ -309,7 +308,6 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
private boolean validateInvoked;
|
||||
|
||||
|
||||
public StubRequestDataBinder(Object target, String objectName) {
|
||||
super(target, objectName);
|
||||
}
|
||||
@@ -345,7 +343,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
}
|
||||
|
||||
|
||||
@SessionAttributes(types=TestBean.class)
|
||||
@SessionAttributes(types = TestBean.class)
|
||||
private static class ModelAttributeHandler {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -360,6 +358,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class TestBeanWithConstructorArgs {
|
||||
|
||||
final List<String> listOfStrings;
|
||||
@@ -367,15 +366,15 @@ public class ModelAttributeMethodProcessorTests {
|
||||
public TestBeanWithConstructorArgs(List<String> listOfStrings) {
|
||||
this.listOfStrings = listOfStrings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ModelAttribute("modelAttrName") @SuppressWarnings("unused")
|
||||
|
||||
@ModelAttribute("modelAttrName")
|
||||
@SuppressWarnings("unused")
|
||||
private String annotatedReturnValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private TestBean notAnnotatedReturnValue() {
|
||||
return null;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -121,7 +121,7 @@ public class InitBinderBindingContextTests {
|
||||
WebDataBinder dataBinder = context.createDataBinder(exchange, null, "foo");
|
||||
|
||||
assertThat(dataBinder.getDisallowedFields()).isNotNull();
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualTo("requestParam-22");
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualToIgnoringCase("requestParam-22");
|
||||
}
|
||||
|
||||
|
||||
|
||||
-2
@@ -25,7 +25,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
@@ -63,7 +62,6 @@ import org.springframework.web.testfixture.http.server.reactive.bootstrap.Undert
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeFalse;
|
||||
|
||||
@Disabled
|
||||
class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
+1
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
@@ -40,7 +39,6 @@ import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpSe
|
||||
import reactor.core.publisher.Flux
|
||||
import java.time.Duration
|
||||
|
||||
@Disabled
|
||||
class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() {
|
||||
|
||||
override fun initApplicationContext(): ApplicationContext {
|
||||
|
||||
+3
-3
@@ -242,12 +242,12 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
}
|
||||
}
|
||||
if (mediaTypesToUse.isEmpty()) {
|
||||
if (body != null) {
|
||||
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
|
||||
}
|
||||
if (body != null) {
|
||||
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,15 @@ import org.springframework.web.servlet.HandlerMapping;
|
||||
* Subclass of {@link ServletRequestDataBinder} that adds URI template variables
|
||||
* to the values used for data binding.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
* @see ServletRequestDataBinder
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -2236,6 +2236,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
assertThat(response.getContentAsString()).isEqualTo("foo-body");
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
static class ControllerWithEmptyValueMapping {
|
||||
|
||||
@@ -3573,7 +3574,6 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
assertThat(headers.getContentType()).as("Invalid Content-Type").isEqualTo(new MediaType("text", "html"));
|
||||
multiValueMap(headers, writer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
In the context of web applications, _data binding_ involves the binding of HTTP request
|
||||
parameters (that is, form data or query parameters) to properties in a model object and
|
||||
its nested objects.
|
||||
|
||||
Only `public` properties following the
|
||||
https://www.oracle.com/java/technologies/javase/javabeans-spec.html[JavaBeans naming conventions]
|
||||
are exposed for data binding — for example, `public String getFirstName()` and
|
||||
`public void setFirstName(String)` methods for a `firstName` property.
|
||||
|
||||
TIP: The model object, and its nested object graph, is also sometimes referred to as a
|
||||
_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
|
||||
|
||||
By default, Spring permits binding to all public properties in the model object graph.
|
||||
This means you need to carefully consider what public properties the model has, since a
|
||||
client could target any public property path, even some that are not expected to be
|
||||
targeted for a given use case.
|
||||
|
||||
For example, given an HTTP form data endpoint, a malicious client could supply values for
|
||||
properties that exist in the model object graph but are not part of the HTML form
|
||||
presented in the browser. This could lead to data being set on the model object and any
|
||||
of its nested objects, that is not expected to be updated.
|
||||
|
||||
The recommended approach is to use a _dedicated model object_ that exposes only
|
||||
properties that are relevant for the form submission. For example, on a form for changing
|
||||
a user's email address, the model object should declare a minimum set of properties such
|
||||
as in the following `ChangeEmailForm`.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class ChangeEmailForm {
|
||||
|
||||
private String oldEmailAddress;
|
||||
private String newEmailAddress;
|
||||
|
||||
public void setOldEmailAddress(String oldEmailAddress) {
|
||||
this.oldEmailAddress = oldEmailAddress;
|
||||
}
|
||||
|
||||
public String getOldEmailAddress() {
|
||||
return this.oldEmailAddress;
|
||||
}
|
||||
|
||||
public void setNewEmailAddress(String newEmailAddress) {
|
||||
this.newEmailAddress = newEmailAddress;
|
||||
}
|
||||
|
||||
public String getNewEmailAddress() {
|
||||
return this.newEmailAddress;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
If you cannot or do not want to use a _dedicated model object_ for each data
|
||||
binding use case, you **must** limit the properties that are allowed for data binding.
|
||||
Ideally, you can achieve this by registering _allowed field patterns_ via the
|
||||
`setAllowedFields()` method on `WebDataBinder`.
|
||||
|
||||
For example, to register allowed field patterns in your application, you can implement an
|
||||
`@InitBinder` method in a `@Controller` or `@ControllerAdvice` component as shown below:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
public class ChangeEmailController {
|
||||
|
||||
@InitBinder
|
||||
void initBinder(WebDataBinder binder) {
|
||||
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
|
||||
}
|
||||
|
||||
// @RequestMapping methods, etc.
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
In addition to registering allowed patterns, it is also possible to register _disallowed
|
||||
field patterns_ via the `setDisallowedFields()` method in `DataBinder` and its subclasses.
|
||||
Please note, however, that an "allow list" is safer than a "deny list". Consequently,
|
||||
`setAllowedFields()` should be favored over `setDisallowedFields()`.
|
||||
|
||||
Note that matching against allowed field patterns is case-sensitive; whereas, matching
|
||||
against disallowed field patterns is case-insensitive. In addition, a field matching a
|
||||
disallowed pattern will not be accepted even if it also happens to match a pattern in the
|
||||
allowed list.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is extremely important to properly configure allowed and disallowed field patterns
|
||||
when exposing your domain model directly for data binding purposes. Otherwise, it is a
|
||||
big security risk.
|
||||
|
||||
Furthermore, it is strongly recommended that you do **not** use types from your domain
|
||||
model such as JPA or Hibernate entities as the model object in data binding scenarios.
|
||||
====
|
||||
@@ -3319,6 +3319,11 @@ controller-specific `Formatter` instances, as the following example shows:
|
||||
----
|
||||
<1> Adding a custom formatter (a `DateFormatter`, in this case).
|
||||
|
||||
[[webflux-ann-initbinder-model-design]]
|
||||
==== Model Design
|
||||
[.small]#<<web.adoc#mvc-ann-initbinder-model-design, Web MVC>>#
|
||||
|
||||
include::web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
[[webflux-ann-controller-exceptions]]
|
||||
|
||||
@@ -3751,6 +3751,13 @@ controller-specific `Formatter` implementations, as the following example shows:
|
||||
----
|
||||
<1> Defining an `@InitBinder` method on a custom formatter.
|
||||
|
||||
[[mvc-ann-initbinder-model-design]]
|
||||
==== Model Design
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-initbinder-model-design, WebFlux>>#
|
||||
|
||||
include::web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
[[mvc-ann-exceptionhandler]]
|
||||
=== Exceptions
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller-exceptions, WebFlux>>#
|
||||
|
||||
Reference in New Issue
Block a user