diff --git a/build.gradle b/build.gradle index 6012af4ccc51..58352ec87409 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'io.spring.dependency-management' version '1.0.11.RELEASE' apply false id 'io.spring.nohttp' version '0.0.10' id "io.freefair.aspectj" version '6.2.0' apply false - id 'org.jetbrains.dokka' version '1.5.0' apply false + id 'org.jetbrains.dokka' version '1.6.10' apply false id 'org.jetbrains.kotlin.jvm' version '1.5.32' apply false id "org.jetbrains.kotlin.plugin.serialization" version "1.5.32" apply false id 'org.asciidoctor.jvm.convert' version '3.3.2' @@ -28,11 +28,11 @@ configure(allprojects) { project -> dependencyManagement { imports { mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6" - mavenBom "io.netty:netty-bom:4.1.72.Final" - mavenBom "io.projectreactor:reactor-bom:2020.0.15" - mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR11" + mavenBom "io.netty:netty-bom:4.1.74.Final" + mavenBom "io.projectreactor:reactor-bom:2020.0.16" + mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR12" mavenBom "io.rsocket:rsocket-bom:1.1.1" - mavenBom "org.eclipse.jetty:jetty-bom:9.4.44.v20210927" + mavenBom "org.eclipse.jetty:jetty-bom:9.4.45.v20220203" 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" @@ -45,7 +45,7 @@ configure(allprojects) { project -> entry 'log4j-jul' entry 'log4j-slf4j-impl' } - dependency "org.slf4j:slf4j-api:1.7.32" + dependency "org.slf4j:slf4j-api:1.7.35" dependency("com.google.code.findbugs:findbugs:3.0.1") { exclude group: "dom4j", name: "dom4j" } @@ -68,22 +68,22 @@ configure(allprojects) { project -> dependency "io.reactivex:rxjava-reactive-streams:1.2.1" dependency "io.reactivex.rxjava2:rxjava:2.2.21" dependency "io.reactivex.rxjava3:rxjava:3.1.3" - dependency "io.smallrye.reactive:mutiny:1.2.0" + dependency "io.smallrye.reactive:mutiny:1.3.1" dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE" dependency "com.caucho:hessian:4.0.63" - dependency "com.fasterxml:aalto-xml:1.3.0" - dependency("com.fasterxml.woodstox:woodstox-core:6.2.7") { + dependency "com.fasterxml:aalto-xml:1.3.1" + dependency("com.fasterxml.woodstox:woodstox-core:6.2.8") { exclude group: "stax", name: "stax-api" } dependency "com.google.code.gson:gson:2.8.9" - dependency "com.google.protobuf:protobuf-java-util:3.19.1" + dependency "com.google.protobuf:protobuf-java-util:3.19.3" dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4" dependency("com.thoughtworks.xstream:xstream:1.4.18") { exclude group: "xpp3", name: "xpp3_min" exclude group: "xmlpull", name: "xmlpull" } - dependency "org.apache.johnzon:johnzon-jsonb:1.2.15" + dependency "org.apache.johnzon:johnzon-jsonb:1.2.16" dependency("org.codehaus.jettison:jettison:1.3.8") { exclude group: "stax", name: "stax-api" } @@ -94,10 +94,10 @@ configure(allprojects) { project -> dependency "org.ogce:xpp3:1.1.6" dependency "org.yaml:snakeyaml:1.30" - dependency "com.h2database:h2:2.0.206" + 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.rometools:rome:1.16.0" + dependency "com.rometools:rome:1.18.0" dependency "commons-io:commons-io:2.5" dependency "io.vavr:vavr:0.10.4" dependency "net.sf.jopt-simple:jopt-simple:5.0.4" @@ -124,22 +124,22 @@ configure(allprojects) { project -> dependency "org.ehcache:jcache:1.0.1" dependency "org.ehcache:ehcache:3.4.0" dependency "org.hibernate:hibernate-core:5.4.33.Final" - dependency "org.hibernate:hibernate-validator:6.2.1.Final" + dependency "org.hibernate:hibernate-validator:6.2.2.Final" dependency "org.webjars:webjars-locator-core:0.48" dependency "org.webjars:underscorejs:1.8.3" - dependencySet(group: 'org.apache.tomcat', version: '9.0.56') { + dependencySet(group: 'org.apache.tomcat', version: '9.0.58') { 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.56') { + dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.58') { entry 'tomcat-embed-core' entry 'tomcat-embed-websocket' } - dependencySet(group: 'io.undertow', version: '2.2.14.Final') { + dependencySet(group: 'io.undertow', version: '2.2.16.Final') { entry 'undertow-core' entry('undertow-servlet') { exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec" @@ -151,8 +151,8 @@ configure(allprojects) { project -> } dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.10" - dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.2' - dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.2' + 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") { exclude group: "commons-logging", name: "commons-logging" } @@ -192,13 +192,13 @@ configure(allprojects) { project -> dependency "org.hamcrest:hamcrest:2.1" dependency "org.awaitility:awaitility:3.1.6" dependency "org.assertj:assertj-core:3.22.0" - dependencySet(group: 'org.xmlunit', version: '2.8.4') { + dependencySet(group: 'org.xmlunit', version: '2.9.0') { entry 'xmlunit-assertj' entry('xmlunit-matchers') { exclude group: "org.hamcrest", name: "hamcrest-core" } } - dependencySet(group: 'org.mockito', version: '4.2.0') { + dependencySet(group: 'org.mockito', version: '4.3.1') { entry('mockito-core') { exclude group: "org.hamcrest", name: "hamcrest-core" } @@ -206,10 +206,10 @@ configure(allprojects) { project -> } dependency "io.mockk:mockk:1.12.1" - dependency("net.sourceforge.htmlunit:htmlunit:2.56.0") { + dependency("net.sourceforge.htmlunit:htmlunit:2.58.0") { exclude group: "commons-logging", name: "commons-logging" } - dependency("org.seleniumhq.selenium:htmlunit-driver:2.56.0") { + dependency("org.seleniumhq.selenium:htmlunit-driver:2.58.0") { exclude group: "commons-logging", name: "commons-logging" } dependency("org.seleniumhq.selenium:selenium-java:3.141.59") { @@ -238,7 +238,7 @@ configure(allprojects) { project -> dependency "com.ibm.websphere:uow:6.0.2.17" dependency "com.jamonapi:jamon:2.82" dependency "joda-time:joda-time:2.10.13" - dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.9" + dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10" dependency "org.javamoney:moneta:1.3" dependency "com.sun.activation:javax.activation:1.2.0" @@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project -> } checkstyle { - toolVersion = "9.2" + toolVersion = "9.3" configDirectory.set(rootProject.file("src/checkstyle")) } @@ -362,7 +362,7 @@ configure([rootProject] + javaProjects) { project -> // JSR-305 only used for non-required meta-annotations compileOnly("com.google.code.findbugs:jsr305") testCompileOnly("com.google.code.findbugs:jsr305") - checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.29") + checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.31") } ext.javadocLinks = [ @@ -388,7 +388,13 @@ configure([rootProject] + javaProjects) { project -> // "https://junit.org/junit5/docs/5.8.2/api/", "https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/", "https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/", - "https://r2dbc.io/spec/0.8.5.RELEASE/api/" + "https://r2dbc.io/spec/0.8.5.RELEASE/api/", + // The external Javadoc link for JSR 305 must come last to ensure that types from + // JSR 250 (such as @PostConstruct) are still supported. This is due to the fact + // that JSR 250 and JSR 305 both define types in javax.annotation, which results + // in a split package, and the javadoc tool does not support split packages + // across multiple external Javadoc sites. + "https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/" ] as String[] } @@ -399,7 +405,6 @@ configure(moduleProjects) { project -> configure(rootProject) { description = "Spring Framework" - apply plugin: "groovy" apply plugin: "kotlin" apply plugin: "io.spring.nohttp" apply plugin: 'org.springframework.build.api-diff' diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile index df1be9f33457..edc9eac649b0 100644 --- a/ci/images/ci-image/Dockerfile +++ b/ci/images/ci-image/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:focal-20210827 +FROM ubuntu:focal-20220113 ADD setup.sh /setup.sh ADD get-jdk-url.sh /get-jdk-url.sh @@ -6,6 +6,6 @@ RUN ./setup.sh java8 ENV JAVA_HOME /opt/openjdk/java8 ENV JDK11 /opt/openjdk/java11 -ENV JDK16 /opt/openjdk/java16 +ENV JDK17 /opt/openjdk/java17 ENV PATH $JAVA_HOME/bin:$PATH diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index e170d55171c3..d090494f6627 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,13 +3,16 @@ set -e case "$1" in java8) - echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u302-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u302b08.tar.gz" + echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz" ;; java11) - echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.12%2B7/OpenJDK11U-jdk_x64_linux_hotspot_11.0.12_7.tar.gz" + echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz" ;; - java16) - echo "https://github.com/adoptium/temurin16-binaries/releases/download/jdk-16.0.2%2B7/OpenJDK16U-jdk_x64_linux_hotspot_16.0.2_7.tar.gz" + java17) + 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 $"Unknown java version" diff --git a/ci/images/setup.sh b/ci/images/setup.sh index ff18f849f40c..3e1e0bc05c2d 100755 --- a/ci/images/setup.sh +++ b/ci/images/setup.sh @@ -14,7 +14,7 @@ rm -rf /var/lib/apt/lists/* curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh -curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.2/concourse-release-scripts-0.3.2.jar +curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.3/concourse-release-scripts-0.3.3.jar ########################################################### # JAVA @@ -22,7 +22,7 @@ curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/ mkdir -p /opt/openjdk pushd /opt/openjdk > /dev/null -for jdk in java8 java11 java16 +for jdk in java8 java11 java17 do JDK_URL=$( /get-jdk-url.sh $jdk ) mkdir $jdk diff --git a/ci/parameters.yml b/ci/parameters.yml index 28486ecec729..7f26578d27a2 100644 --- a/ci/parameters.yml +++ b/ci/parameters.yml @@ -1,6 +1,3 @@ -email-server: "smtp.svc.pivotal.io" -email-from: "ci@spring.io" -email-to: ["spring-framework-dev@pivotal.io"] github-repo: "https://github.com/spring-projects/spring-framework.git" github-repo-name: "spring-projects/spring-framework" docker-hub-organization: "springci" diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 64f6ffe3ad1a..eb8de81ea1e1 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -124,14 +124,14 @@ resources: access_token: ((github-ci-status-token)) branch: ((branch)) context: jdk11-build -- name: repo-status-jdk16-build +- name: repo-status-jdk17-build type: github-status-resource icon: eye-check-outline source: repository: ((github-repo-name)) access_token: ((github-ci-status-token)) branch: ((branch)) - context: jdk16-build + context: jdk17-build - name: slack-alert type: slack-notification icon: slack @@ -249,7 +249,7 @@ jobs: <<: *slack-fail-params - put: repo-status-jdk11-build params: { state: "success", commit: "git-repo" } -- name: jdk16-build +- name: jdk17-build serial: true public: true plan: @@ -257,7 +257,7 @@ jobs: - get: git-repo - get: every-morning trigger: true - - put: repo-status-jdk16-build + - put: repo-status-jdk17-build params: { state: "pending", commit: "git-repo" } - do: - task: check-project @@ -270,12 +270,12 @@ jobs: <<: *build-project-task-params on_failure: do: - - put: repo-status-jdk16-build + - put: repo-status-jdk17-build params: { state: "failure", commit: "git-repo" } - put: slack-alert params: <<: *slack-fail-params - - put: repo-status-jdk16-build + - put: repo-status-jdk17-build params: { state: "success", commit: "git-repo" } - name: build-pull-requests serial: true @@ -458,7 +458,7 @@ jobs: groups: - name: "builds" - jobs: ["build", "jdk11-build", "jdk16-build"] + jobs: ["build", "jdk11-build", "jdk17-build"] - name: "releases" jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"] - name: "ci-images" diff --git a/gradle.properties b/gradle.properties index 74ccc5d2080b..04842d94e7b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=5.3.15-SNAPSHOT +version=5.3.16 org.gradle.jvmargs=-Xmx1536M org.gradle.caching=true org.gradle.parallel=true diff --git a/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java b/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java index bd86cc9041d1..2f13da238f4e 100644 --- a/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java +++ b/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java @@ -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. @@ -34,7 +34,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.ScopeMetadata; -import org.springframework.context.annotation.ScopeMetadataResolver; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; @@ -307,29 +306,26 @@ private ApplicationContext createContext(final ScopedProxyMode scopedProxyMode) GenericWebApplicationContext context = new GenericWebApplicationContext(); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); scanner.setIncludeAnnotationConfig(false); - scanner.setScopeMetadataResolver(new ScopeMetadataResolver() { - @Override - public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { - ScopeMetadata metadata = new ScopeMetadata(); - if (definition instanceof AnnotatedBeanDefinition) { - AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; - for (String type : annDef.getMetadata().getAnnotationTypes()) { - if (type.equals(javax.inject.Singleton.class.getName())) { - metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON); - break; - } - else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) { - metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase()); - metadata.setScopedProxyMode(scopedProxyMode); - break; - } - else if (type.startsWith("javax.inject")) { - metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); - } + scanner.setScopeMetadataResolver(definition -> { + ScopeMetadata metadata = new ScopeMetadata(); + if (definition instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; + for (String type : annDef.getMetadata().getAnnotationTypes()) { + if (type.equals(javax.inject.Singleton.class.getName())) { + metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON); + break; + } + else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) { + metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase()); + metadata.setScopedProxyMode(scopedProxyMode); + break; + } + else if (type.startsWith("javax.inject")) { + metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); } } - return metadata; } + return metadata; }); // Scan twice in order to find errors in the bean definition compatibility check. diff --git a/settings.gradle b/settings.gradle index 0f97d1fcaa87..e1638d26c9ad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,13 +1,14 @@ pluginManagement { repositories { + mavenCentral() gradlePluginPortal() - maven { url "https://repo.spring.io/plugins-release" } + maven { url "https://repo.spring.io/release" } } } plugins { - id "com.gradle.enterprise" version "3.7.2" - id "io.spring.ge.conventions" version "0.0.7" + id "com.gradle.enterprise" version "3.8.1" + id "io.spring.ge.conventions" version "0.0.9" } include "spring-aop" diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java index 12ce1ce86cdc..f6f7bd7acfd6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java @@ -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. @@ -44,6 +44,7 @@ * * @author Rod Johnson * @author Juergen Hoeller + * @author Sam Brannen * @see org.springframework.aop.support.AopUtils */ public abstract class AopProxyUtils { @@ -133,7 +134,7 @@ static Class[] completeProxiedInterfaces(AdvisedSupport advised, boolean deco if (targetClass.isInterface()) { advised.setInterfaces(targetClass); } - else if (Proxy.isProxyClass(targetClass)) { + else if (Proxy.isProxyClass(targetClass) || isLambda(targetClass)) { advised.setInterfaces(targetClass.getInterfaces()); } specifiedInterfaces = advised.getProxiedInterfaces(); @@ -244,4 +245,18 @@ static Object[] adaptArgumentsIfNecessary(Method method, @Nullable Object[] argu return arguments; } + /** + * Determine if the supplied {@link Class} is a JVM-generated implementation + * class for a lambda expression or method reference. + *

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")); + } + } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java index 8692371d3a8c..5f1acad9a9a2 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -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. @@ -40,6 +40,7 @@ * @author Rod Johnson * @author Juergen Hoeller * @author Sebastien Deleuze + * @author Sam Brannen * @since 12.03.2004 * @see AdvisedSupport#setOptimize * @see AdvisedSupport#setProxyTargetClass @@ -59,7 +60,7 @@ public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException 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)) { + if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || AopProxyUtils.isLambda(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 8908cab491d2..a559a9f765fa 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -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. @@ -233,7 +233,8 @@ protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) { return beanFactory.getBean(TaskExecutor.class); } catch (NoUniqueBeanDefinitionException ex) { - logger.debug("Could not find unique TaskExecutor bean", ex); + logger.debug("Could not find unique TaskExecutor bean. " + + "Continuing search for an Executor bean named 'taskExecutor'", ex); try { return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); } @@ -246,7 +247,8 @@ protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) { } } catch (NoSuchBeanDefinitionException ex) { - logger.debug("Could not find default TaskExecutor bean", ex); + logger.debug("Could not find default TaskExecutor bean. " + + "Continuing search for an Executor bean named 'taskExecutor'", ex); try { return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); } diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java index 9aeeef0f1564..3f5a7b562c5b 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java @@ -98,7 +98,7 @@ void rejectsPerCflowBelowAspect() { } @Test - void perTargetAspect() throws SecurityException, NoSuchMethodException { + void perTargetAspect() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -130,7 +130,7 @@ void perTargetAspect() throws SecurityException, NoSuchMethodException { } @Test - void multiplePerTargetAspects() throws SecurityException, NoSuchMethodException { + void multiplePerTargetAspects() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -158,7 +158,7 @@ void multiplePerTargetAspects() throws SecurityException, NoSuchMethodException } @Test - void multiplePerTargetAspectsWithOrderAnnotation() throws SecurityException, NoSuchMethodException { + void multiplePerTargetAspectsWithOrderAnnotation() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -184,7 +184,7 @@ void multiplePerTargetAspectsWithOrderAnnotation() throws SecurityException, NoS } @Test - void perThisAspect() throws SecurityException, NoSuchMethodException { + void perThisAspect() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -220,7 +220,7 @@ void perThisAspect() throws SecurityException, NoSuchMethodException { } @Test - void perTypeWithinAspect() throws SecurityException, NoSuchMethodException { + void perTypeWithinAspect() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java index 637baa2450a8..2554895430fb 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java @@ -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. @@ -28,7 +28,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** - * @since 2.0 * @author Rod Johnson * @author Chris Beams * @author Sam Brannen @@ -56,7 +55,7 @@ void perTargetAspect() { assertThat(am.getAjType().getPerClause().getKind()).isEqualTo(PerClauseKind.PERTARGET); assertThat(am.getPerClausePointcut()).isInstanceOf(AspectJExpressionPointcut.class); assertThat(((AspectJExpressionPointcut) am.getPerClausePointcut()).getExpression()) - .isEqualTo("execution(* *.getSpouse())"); + .isEqualTo("execution(* *.getSpouse())"); } @Test @@ -67,7 +66,7 @@ void perThisAspect() { assertThat(am.getAjType().getPerClause().getKind()).isEqualTo(PerClauseKind.PERTHIS); assertThat(am.getPerClausePointcut()).isInstanceOf(AspectJExpressionPointcut.class); assertThat(((AspectJExpressionPointcut) am.getPerClausePointcut()).getExpression()) - .isEqualTo("execution(* *.getSpouse())"); + .isEqualTo("execution(* *.getSpouse())"); } } diff --git a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java index d4c774424d04..ffb270cdd9b4 100644 --- a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java @@ -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. @@ -16,6 +16,7 @@ package org.springframework.aop.config; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -40,7 +41,7 @@ * @author Juergen Hoeller * @author Chris Beams */ -public class AopNamespaceHandlerEventTests { +class AopNamespaceHandlerEventTests { private static final Class CLASS = AopNamespaceHandlerEventTests.class; @@ -57,25 +58,24 @@ public class AopNamespaceHandlerEventTests { @BeforeEach - public void setup() { + void setup() { this.reader = new XmlBeanDefinitionReader(this.beanFactory); this.reader.setEventListener(this.eventListener); } @Test - public void testPointcutEvents() { + void pointcutEvents() { this.reader.loadBeanDefinitions(POINTCUT_EVENTS_CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(1); - boolean condition = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(1); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(2); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(2); PointcutComponentDefinition pcd = null; for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof PointcutComponentDefinition) { @@ -84,84 +84,77 @@ public void testPointcutEvents() { } } assertThat(pcd).as("PointcutComponentDefinition not found").isNotNull(); - assertThat(pcd.getBeanDefinitions().length).as("Incorrect number of BeanDefinitions").isEqualTo(1); + assertThat(pcd.getBeanDefinitions()).as("Incorrect number of BeanDefinitions").hasSize(1); } @Test - public void testAdvisorEventsWithPointcutRef() { + void advisorEventsWithPointcutRef() { this.reader.loadBeanDefinitions(POINTCUT_REF_CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(2); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(2); - boolean condition1 = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition1).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(3); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(3); AdvisorComponentDefinition acd = null; - for (int i = 0; i < nestedComponentDefs.length; i++) { - ComponentDefinition componentDefinition = nestedComponentDefs[i]; + for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof AdvisorComponentDefinition) { acd = (AdvisorComponentDefinition) componentDefinition; break; } } assertThat(acd).as("AdvisorComponentDefinition not found").isNotNull(); - assertThat(acd.getBeanDefinitions().length).isEqualTo(1); - assertThat(acd.getBeanReferences().length).isEqualTo(2); + assertThat(acd.getBeanDefinitions()).hasSize(1); + assertThat(acd.getBeanReferences()).hasSize(2); - boolean condition = componentDefinitions[1] instanceof BeanComponentDefinition; - assertThat(condition).as("No advice bean found").isTrue(); + assertThat(componentDefinitions[1]).as("No advice bean found").isInstanceOf(BeanComponentDefinition.class); BeanComponentDefinition adviceDef = (BeanComponentDefinition) componentDefinitions[1]; assertThat(adviceDef.getBeanName()).isEqualTo("countingAdvice"); } @Test - public void testAdvisorEventsWithDirectPointcut() { + void advisorEventsWithDirectPointcut() { this.reader.loadBeanDefinitions(DIRECT_POINTCUT_EVENTS_CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(2); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(2); - boolean condition1 = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition1).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(2); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(2); AdvisorComponentDefinition acd = null; - for (int i = 0; i < nestedComponentDefs.length; i++) { - ComponentDefinition componentDefinition = nestedComponentDefs[i]; + for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof AdvisorComponentDefinition) { acd = (AdvisorComponentDefinition) componentDefinition; break; } } assertThat(acd).as("AdvisorComponentDefinition not found").isNotNull(); - assertThat(acd.getBeanDefinitions().length).isEqualTo(2); - assertThat(acd.getBeanReferences().length).isEqualTo(1); + assertThat(acd.getBeanDefinitions()).hasSize(2); + assertThat(acd.getBeanReferences()).hasSize(1); - boolean condition = componentDefinitions[1] instanceof BeanComponentDefinition; - assertThat(condition).as("No advice bean found").isTrue(); + assertThat(componentDefinitions[1]).as("No advice bean found").isInstanceOf(BeanComponentDefinition.class); BeanComponentDefinition adviceDef = (BeanComponentDefinition) componentDefinitions[1]; assertThat(adviceDef.getBeanName()).isEqualTo("countingAdvice"); } @Test - public void testAspectEvent() { + void aspectEvent() { this.reader.loadBeanDefinitions(CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(5); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(2); - boolean condition = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(2); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(2); AspectComponentDefinition acd = null; for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof AspectComponentDefinition) { @@ -172,9 +165,9 @@ public void testAspectEvent() { assertThat(acd).as("AspectComponentDefinition not found").isNotNull(); BeanDefinition[] beanDefinitions = acd.getBeanDefinitions(); - assertThat(beanDefinitions.length).isEqualTo(5); + assertThat(beanDefinitions).hasSize(5); BeanReference[] beanReferences = acd.getBeanReferences(); - assertThat(beanReferences.length).isEqualTo(6); + assertThat(beanReferences).hasSize(6); Set expectedReferences = new HashSet<>(); expectedReferences.add("pc"); @@ -182,19 +175,16 @@ public void testAspectEvent() { for (BeanReference beanReference : beanReferences) { expectedReferences.remove(beanReference.getBeanName()); } - assertThat(expectedReferences.size()).as("Incorrect references found").isEqualTo(0); + assertThat(expectedReferences).as("Incorrect references found").isEmpty(); - for (int i = 1; i < componentDefinitions.length; i++) { - boolean condition1 = componentDefinitions[i] instanceof BeanComponentDefinition; - assertThat(condition1).isTrue(); - } + Arrays.stream(componentDefinitions).skip(1).forEach(definition -> + assertThat(definition).isInstanceOf(BeanComponentDefinition.class)); ComponentDefinition[] nestedComponentDefs2 = acd.getNestedComponents(); - assertThat(nestedComponentDefs2.length).as("Inner PointcutComponentDefinition not found").isEqualTo(1); - boolean condition1 = nestedComponentDefs2[0] instanceof PointcutComponentDefinition; - assertThat(condition1).isTrue(); + assertThat(nestedComponentDefs2).as("Inner PointcutComponentDefinition not found").hasSize(1); + assertThat(nestedComponentDefs2[0]).isInstanceOf(PointcutComponentDefinition.class); PointcutComponentDefinition pcd = (PointcutComponentDefinition) nestedComponentDefs2[0]; - assertThat(pcd.getBeanDefinitions().length).as("Incorrect number of BeanDefinitions").isEqualTo(1); + assertThat(pcd.getBeanDefinitions()).as("Incorrect number of BeanDefinitions").hasSize(1); } } diff --git a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java index 6a01b03ad7b2..db5fdd5fdc51 100644 --- a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java @@ -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. @@ -30,10 +30,10 @@ * @author Mark Fisher * @author Chris Beams */ -public class AopNamespaceHandlerPointcutErrorTests { +class AopNamespaceHandlerPointcutErrorTests { @Test - public void testDuplicatePointcutConfig() { + void duplicatePointcutConfig() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() -> new XmlBeanDefinitionReader(bf).loadBeanDefinitions( @@ -42,7 +42,7 @@ public void testDuplicatePointcutConfig() { } @Test - public void testMissingPointcutConfig() { + void missingPointcutConfig() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() -> new XmlBeanDefinitionReader(bf).loadBeanDefinitions( diff --git a/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java b/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java index f1ab7f273db9..7e34bc23ef32 100644 --- a/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java @@ -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. @@ -30,10 +30,10 @@ * @author Rob Harrop * @author Chris Beams */ -public class TopLevelAopTagTests { +class TopLevelAopTagTests { @Test - public void testParse() { + void parse() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions( qualifiedResource(TopLevelAopTagTests.class, "context.xml")); diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java index c8475794f337..3dbf550a1211 100644 --- a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java @@ -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. @@ -19,6 +19,7 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.List; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -32,6 +33,7 @@ /** * @author Rod Johnson * @author Chris Beams + * @author Sam Brannen */ public class AopProxyUtilsTests { @@ -132,4 +134,61 @@ public void testProxiedUserInterfacesWithNoInterface() { 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() { + @Override + public String get() { + return "anonymous inner class"; + } + }); + + assertIsNotLambda(new Fake$$LambdaSupplier()); + } + + private static void assertIsLambda(Supplier supplier) { + assertThat(AopProxyUtils.isLambda(supplier.getClass())).isTrue(); + } + + private static void assertIsNotLambda(Supplier supplier) { + assertThat(AopProxyUtils.isLambda(supplier.getClass())).isFalse(); + } + + private static final Supplier staticLambdaExpression = () -> "static lambda expression"; + + private final Supplier 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 { + @Override + public String get() { + return "enigma"; + } + } + + private static class Fake$$LambdaSupplier implements Supplier { + @Override + public String get() { + return "fake lambda"; + } + } + } diff --git a/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java b/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java index 9936573300d4..d135689decca 100644 --- a/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java @@ -125,16 +125,7 @@ public void run() { try { this.proxy.exceptional(this.ex); } - catch (RuntimeException ex) { - if (ex == this.ex) { - logger.debug("Expected exception thrown", ex); - } - else { - // should never happen - ex.printStackTrace(); - } - } - catch (Error err) { + catch (RuntimeException | Error err) { if (err == this.ex) { logger.debug("Expected exception thrown", err); } diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml index 6acd17a85671..1c2ba7c237b5 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml @@ -16,12 +16,6 @@ - - - - - - diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml index 7c8f47124e6b..75ca4e9d364b 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml @@ -12,10 +12,6 @@ - - - - diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml index 847f864eb114..0524b1071c61 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml @@ -12,10 +12,6 @@ - - - - diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 37f5884e671e..f31d48191a4d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -537,10 +537,10 @@ private MergedAnnotation findAutowiredAnnotation(AccessibleObject ao) { * @param ann the Autowired annotation * @return whether the annotation indicates that a dependency is required */ - @SuppressWarnings({"deprecation", "cast"}) + @SuppressWarnings("deprecation") protected boolean determineRequiredStatus(MergedAnnotation ann) { - return determineRequiredStatus( - ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()))); + return determineRequiredStatus(ann. asMap( + mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()))); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index e3908a2a7c30..7a94a91c86a8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -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. @@ -1908,7 +1908,7 @@ protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefi if (logger.isTraceEnabled()) { logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); } - Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod); + Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass()); if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction) () -> { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index e6f90239343d..b5fdef4dd813 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -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. @@ -138,7 +138,7 @@ else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) { beanName + "' has a non-boolean parameter - not supported as destroy method"); } } - destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod); + destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass()); } this.destroyMethod = destroyMethod; } @@ -252,9 +252,9 @@ else if (this.destroyMethod != null) { invokeCustomDestroyMethod(this.destroyMethod); } else if (this.destroyMethodName != null) { - Method methodToInvoke = determineDestroyMethod(this.destroyMethodName); - if (methodToInvoke != null) { - invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke)); + Method destroyMethod = determineDestroyMethod(this.destroyMethodName); + if (destroyMethod != null) { + invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(destroyMethod, this.bean.getClass())); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java index 7a282ed7fce6..28146701944c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java @@ -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. @@ -17,6 +17,7 @@ package org.springframework.beans.factory.support; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.beans.BeanMetadataElement; @@ -53,6 +54,20 @@ public ManagedList(int initialCapacity) { } + /** + * Return a new instance containing an arbitrary number of elements. + * @param elements the elements to be contained in the list + * @param the {@code List}'s element type + * @return a {@code List} containing the specified elements + * @since 5.3.16 + */ + @SuppressWarnings("unchecked") + public static ManagedList of(E... elements) { + ManagedList list = new ManagedList<>(); + list.addAll(Arrays.asList(elements)); + return list; + } + /** * Set the configuration source {@code Object} for this metadata element. *

The exact type of the object will depend on the configuration mechanism used. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java index 55b76f0317f0..acd80074bf7b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java @@ -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. @@ -18,6 +18,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Map.Entry; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.Mergeable; @@ -56,6 +57,25 @@ public ManagedMap(int initialCapacity) { } + /** + * Return a new instance containing keys and values extracted from the + * given entries. The entries themselves are not stored in the map. + * @param entries {@code Map.Entry}s containing the keys and values + * from which the map is populated + * @param the {@code Map}'s key type + * @param the {@code Map}'s value type + * @return a {@code Map} containing the specified mappings + * @since 5.3.16 + */ + @SuppressWarnings("unchecked") + public static ManagedMap ofEntries(Entry... entries) { + ManagedMap map = new ManagedMap<>(); + for (Entry entry : entries) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } + /** * Set the configuration source {@code Object} for this metadata element. *

The exact type of the object will depend on the configuration mechanism used. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java index 7f84ec7f4456..f5c72462379d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java @@ -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. @@ -16,6 +16,7 @@ package org.springframework.beans.factory.support; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; @@ -52,6 +53,20 @@ public ManagedSet(int initialCapacity) { } + /** + * Return a new instance containing an arbitrary number of elements. + * @param elements the elements to be contained in the set + * @param the {@code Set}'s element type + * @return a {@code Set} containing the specified elements + * @since 5.3.16 + */ + @SuppressWarnings("unchecked") + public static ManagedSet of(E... elements) { + ManagedSet set = new ManagedSet<>(); + set.addAll(Arrays.asList(elements)); + return set; + } + /** * Set the configuration source {@code Object} for this metadata element. *

The exact type of the object will depend on the configuration mechanism used. diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java index 9f3bd08ec9f3..7569be3d881b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java @@ -43,13 +43,13 @@ protected void doTestTony(PropertyValues pvs) { m.put("forname", "Tony"); m.put("surname", "Blair"); m.put("age", "50"); - for (int i = 0; i < ps.length; i++) { - Object val = m.get(ps[i].getName()); + for (PropertyValue element : ps) { + Object val = m.get(element.getName()); assertThat(val != null).as("Can't have unexpected value").isTrue(); boolean condition = val instanceof String; assertThat(condition).as("Val i string").isTrue(); - assertThat(val.equals(ps[i].getValue())).as("val matches expected").isTrue(); - m.remove(ps[i].getName()); + assertThat(val.equals(element.getValue())).as("val matches expected").isTrue(); + m.remove(element.getName()); } assertThat(m.size() == 0).as("Map size is 0").isTrue(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java index e869c9c6c906..b3230e53e0ee 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java @@ -483,6 +483,7 @@ public Class getObjectType() { return TestBean.class; } + @Override public TestBean getObject() { // We don't really care if the actual instance is a singleton or prototype // for the tests that use this factory. diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 1617ccdf993f..cbf9983dc0b6 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -48,8 +48,6 @@ import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.NotWritablePropertyException; -import org.springframework.beans.PropertyEditorRegistrar; -import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.PropertyValue; import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeMismatchException; @@ -84,7 +82,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.Resource; @@ -990,16 +987,13 @@ void customEditor() { @Test void customConverter() { GenericConversionService conversionService = new DefaultConversionService(); - conversionService.addConverter(new Converter() { - @Override - public Float convert(String source) { - try { - NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); - return nf.parse(source).floatValue(); - } - catch (ParseException ex) { - throw new IllegalArgumentException(ex); - } + conversionService.addConverter(String.class, Float.class, source -> { + try { + NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); + return nf.parse(source).floatValue(); + } + catch (ParseException ex) { + throw new IllegalArgumentException(ex); } }); lbf.setConversionService(conversionService); @@ -1014,12 +1008,9 @@ public Float convert(String source) { @Test void customEditorWithBeanReference() { - lbf.addPropertyEditorRegistrar(new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); - registry.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, nf, true)); - } + lbf.addPropertyEditorRegistrar(registry -> { + NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); + registry.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, nf, true)); }); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("myFloat", new RuntimeBeanReference("myFloat")); @@ -2307,9 +2298,7 @@ void prototypeStringCreatedRepeatedly() { @Test void prototypeWithArrayConversionForConstructor() { - List list = new ManagedList<>(); - list.add("myName"); - list.add("myBeanName"); + List list = ManagedList.of("myName", "myBeanName"); RootBeanDefinition bd = new RootBeanDefinition(DerivedTestBean.class); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); bd.getConstructorArgumentValues().addGenericArgumentValue(list); @@ -2325,9 +2314,7 @@ void prototypeWithArrayConversionForConstructor() { @Test void prototypeWithArrayConversionForFactoryMethod() { - List list = new ManagedList<>(); - list.add("myName"); - list.add("myBeanName"); + List list = ManagedList.of("myName", "myBeanName"); RootBeanDefinition bd = new RootBeanDefinition(DerivedTestBean.class); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); bd.setFactoryMethodName("create"); @@ -2718,8 +2705,12 @@ public void setBeanName(String name) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } ConstructorDependency that = (ConstructorDependency) o; return spouseAge == that.spouseAge && Objects.equals(spouse, that.spouse) && diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index c746f21949e8..b2ad93854e2a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -22,7 +22,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; @@ -3660,11 +3659,8 @@ public static class MocksControl { @SuppressWarnings("unchecked") public T createMock(Class toMock) { return (T) Proxy.newProxyInstance(AutowiredAnnotationBeanPostProcessorTests.class.getClassLoader(), new Class[] {toMock}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - throw new UnsupportedOperationException("mocked!"); - } + (InvocationHandler) (proxy, method, args) -> { + throw new UnsupportedOperationException("mocked!"); }); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java index a94425ccf1aa..48a87a9a7739 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java @@ -29,7 +29,6 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyEditorRegistrar; -import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.propertyeditors.CustomDateEditor; @@ -50,12 +49,7 @@ public void testCustomEditorConfigurerWithPropertyEditorRegistrar() throws Parse CustomEditorConfigurer cec = new CustomEditorConfigurer(); final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.GERMAN); cec.setPropertyEditorRegistrars(new PropertyEditorRegistrar[] { - new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - registry.registerCustomEditor(Date.class, new CustomDateEditor(df, true)); - } - }}); + registry -> registry.registerCustomEditor(Date.class, new CustomDateEditor(df, true))}); cec.postProcessBeanFactory(bf); MutablePropertyValues pvs = new MutablePropertyValues(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java index fae712817bd5..810b24778330 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java @@ -113,7 +113,7 @@ public void testGetObjectType() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello"); mcfb.afterPropertiesSet(); mcfb.getObjectType(); @@ -184,7 +184,7 @@ public void testGetObject() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello"); // should pass mcfb.afterPropertiesSet(); } @@ -194,7 +194,7 @@ public void testArgumentConversion() throws Exception { MethodInvokingFactoryBean mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello", "bogus"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello", "bogus"); assertThatExceptionOfType(NoSuchMethodException.class).as( "Matched method with wrong number of args").isThrownBy( mcfb::afterPropertiesSet); @@ -210,14 +210,14 @@ public void testArgumentConversion() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes2"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello", "bogus"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello", "bogus"); mcfb.afterPropertiesSet(); assertThat(mcfb.getObject()).isEqualTo("hello"); mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes2"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), new Object()); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), new Object()); assertThatExceptionOfType(NoSuchMethodException.class).as( "Matched method when shouldn't have matched").isThrownBy( mcfb::afterPropertiesSet); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java index 29ecb68699be..e51017df5d5b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java @@ -16,6 +16,7 @@ package org.springframework.beans.factory.config; +import java.util.AbstractMap.SimpleEntry; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -357,22 +358,18 @@ private void doTestPropertyPlaceholderConfigurer(boolean parentChildSeparation) MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("stringArray", new String[] {"${os.name}", "${age}"}); - List friends = new ManagedList<>(); - friends.add("na${age}me"); - friends.add(new RuntimeBeanReference("${ref}")); + List friends = ManagedList.of("na${age}me", new RuntimeBeanReference("${ref}")); pvs.add("friends", friends); - Set someSet = new ManagedSet<>(); - someSet.add("na${age}me"); - someSet.add(new RuntimeBeanReference("${ref}")); - someSet.add(new TypedStringValue("${age}", Integer.class)); + Set someSet = ManagedSet.of("na${age}me", + new RuntimeBeanReference("${ref}"), new TypedStringValue("${age}", Integer.class)); pvs.add("someSet", someSet); - Map someMap = new ManagedMap<>(); - someMap.put(new TypedStringValue("key${age}"), new TypedStringValue("${age}")); - someMap.put(new TypedStringValue("key${age}ref"), new RuntimeBeanReference("${ref}")); - someMap.put("key1", new RuntimeBeanReference("${ref}")); - someMap.put("key2", "${age}name"); + Map someMap = ManagedMap.ofEntries( + new SimpleEntry<>(new TypedStringValue("key${age}"), new TypedStringValue("${age}")), + new SimpleEntry<>(new TypedStringValue("key${age}ref"), new RuntimeBeanReference("${ref}")), + new SimpleEntry<>("key1", new RuntimeBeanReference("${ref}")), + new SimpleEntry<>("key2", "${age}name")); MutablePropertyValues innerPvs = new MutablePropertyValues(); innerPvs.add("country", "${os.name}"); RootBeanDefinition innerBd = new RootBeanDefinition(TestBean.class); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index 5a981f91e54c..ef53180c6a60 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -17,7 +17,6 @@ package org.springframework.beans.factory.support; import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URI; @@ -34,8 +33,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.springframework.beans.PropertyEditorRegistrar; -import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; @@ -409,12 +406,7 @@ void testGenericMapWithKeyTypeConstructor() { @Test void testGenericMapWithCollectionValueConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.addPropertyEditorRegistrar(new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)); - } - }); + bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); Map> input = new HashMap<>(); @@ -568,12 +560,7 @@ void testGenericMapWithKeyTypeFactoryMethod() { @Test void testGenericMapWithCollectionValueFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.addPropertyEditorRegistrar(new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)); - } - }); + bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -1010,11 +997,8 @@ public static class MocksControl { @SuppressWarnings("unchecked") public T createMock(Class toMock) { return (T) Proxy.newProxyInstance(BeanFactoryGenericsTests.class.getClassLoader(), new Class[] {toMock}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - throw new UnsupportedOperationException("mocked!"); - } + (InvocationHandler) (proxy, method, args) -> { + throw new UnsupportedOperationException("mocked!"); }); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java index 6e3f8465e9e8..b7bc696c68d5 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java @@ -18,8 +18,6 @@ import org.junit.jupiter.api.Test; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.testfixture.beans.DerivedTestBean; import org.springframework.beans.testfixture.beans.TestBean; @@ -40,12 +38,7 @@ public void testSingletons() { beanRegistry.registerSingleton("tb", tb); assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb); - TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", new ObjectFactory() { - @Override - public Object getObject() throws BeansException { - return new TestBean(); - } - }); + TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", () -> new TestBean()); assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2); assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java index 9bf6576d518c..420226d173ad 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java @@ -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. @@ -34,11 +34,8 @@ public class ManagedListTests { @Test public void mergeSunnyDay() { - ManagedList parent = new ManagedList(); - parent.add("one"); - parent.add("two"); - ManagedList child = new ManagedList(); - child.add("three"); + ManagedList parent = ManagedList.of("one", "two"); + ManagedList child = ManagedList.of("three"); child.setMergeEnabled(true); List mergedList = child.merge(parent); assertThat(mergedList.size()).as("merge() obviously did not work.").isEqualTo(3); @@ -46,8 +43,7 @@ public void mergeSunnyDay() { @Test public void mergeWithNullParent() { - ManagedList child = new ManagedList(); - child.add("one"); + ManagedList child = ManagedList.of("one"); child.setMergeEnabled(true); assertThat(child.merge(null)).isSameAs(child); } @@ -61,8 +57,7 @@ public void mergeNotAllowedWhenMergeNotEnabled() { @Test public void mergeWithNonCompatibleParentType() { - ManagedList child = new ManagedList(); - child.add("one"); + ManagedList child = ManagedList.of("one"); child.setMergeEnabled(true); assertThatIllegalArgumentException().isThrownBy(() -> child.merge("hello")); @@ -70,9 +65,7 @@ public void mergeWithNonCompatibleParentType() { @Test public void mergeEmptyChild() { - ManagedList parent = new ManagedList(); - parent.add("one"); - parent.add("two"); + ManagedList parent = ManagedList.of("one", "two"); ManagedList child = new ManagedList(); child.setMergeEnabled(true); List mergedList = child.merge(parent); @@ -82,11 +75,8 @@ public void mergeEmptyChild() { @Test public void mergeChildValuesOverrideTheParents() { // doesn't make much sense in the context of a list... - ManagedList parent = new ManagedList(); - parent.add("one"); - parent.add("two"); - ManagedList child = new ManagedList(); - child.add("one"); + ManagedList parent = ManagedList.of("one", "two"); + ManagedList child = ManagedList.of("one"); child.setMergeEnabled(true); List mergedList = child.merge(parent); assertThat(mergedList.size()).as("merge() obviously did not work.").isEqualTo(3); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java index 1f6dc5e416a4..b83fa176d311 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java @@ -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. @@ -16,6 +16,7 @@ package org.springframework.beans.factory.support; +import java.util.AbstractMap.SimpleEntry; import java.util.Map; import org.junit.jupiter.api.Test; @@ -34,11 +35,9 @@ public class ManagedMapTests { @Test public void mergeSunnyDay() { - ManagedMap parent = new ManagedMap(); - parent.put("one", "one"); - parent.put("two", "two"); - ManagedMap child = new ManagedMap(); - child.put("three", "three"); + ManagedMap parent = ManagedMap.ofEntries(new SimpleEntry<>("one", "one"), + new SimpleEntry<>("two", "two")); + ManagedMap child = ManagedMap.ofEntries(new SimpleEntry<>("tree", "three")); child.setMergeEnabled(true); Map mergedMap = (Map) child.merge(parent); assertThat(mergedMap.size()).as("merge() obviously did not work.").isEqualTo(3); @@ -67,9 +66,8 @@ public void mergeNotAllowedWhenMergeNotEnabled() { @Test public void mergeEmptyChild() { - ManagedMap parent = new ManagedMap(); - parent.put("one", "one"); - parent.put("two", "two"); + ManagedMap parent = ManagedMap.ofEntries(new SimpleEntry<>("one", "one"), + new SimpleEntry<>("two", "two")); ManagedMap child = new ManagedMap(); child.setMergeEnabled(true); Map mergedMap = (Map) child.merge(parent); @@ -78,11 +76,9 @@ public void mergeEmptyChild() { @Test public void mergeChildValuesOverrideTheParents() { - ManagedMap parent = new ManagedMap(); - parent.put("one", "one"); - parent.put("two", "two"); - ManagedMap child = new ManagedMap(); - child.put("one", "fork"); + ManagedMap parent = ManagedMap.ofEntries(new SimpleEntry<>("one", "one"), + new SimpleEntry<>("two", "two")); + ManagedMap child = ManagedMap.ofEntries(new SimpleEntry<>("one", "fork")); child.setMergeEnabled(true); Map mergedMap = (Map) child.merge(parent); // child value for 'one' must override parent value... diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java index 39c08997814c..c11a63a6da83 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java @@ -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. @@ -34,10 +34,8 @@ public class ManagedSetTests { @Test public void mergeSunnyDay() { - ManagedSet parent = new ManagedSet(); - parent.add("one"); - parent.add("two"); - ManagedSet child = new ManagedSet(); + ManagedSet parent = ManagedSet.of("one", "two"); + ManagedSet child = ManagedSet.of("three"); child.add("three"); child.setMergeEnabled(true); Set mergedSet = child.merge(parent); @@ -46,8 +44,7 @@ public void mergeSunnyDay() { @Test public void mergeWithNullParent() { - ManagedSet child = new ManagedSet(); - child.add("one"); + ManagedSet child = ManagedSet.of("one"); child.setMergeEnabled(true); assertThat(child.merge(null)).isSameAs(child); } @@ -60,8 +57,7 @@ public void mergeNotAllowedWhenMergeNotEnabled() { @Test public void mergeWithNonCompatibleParentType() { - ManagedSet child = new ManagedSet(); - child.add("one"); + ManagedSet child = ManagedSet.of("one"); child.setMergeEnabled(true); assertThatIllegalArgumentException().isThrownBy(() -> child.merge("hello")); @@ -69,9 +65,7 @@ public void mergeWithNonCompatibleParentType() { @Test public void mergeEmptyChild() { - ManagedSet parent = new ManagedSet(); - parent.add("one"); - parent.add("two"); + ManagedSet parent = ManagedSet.of("one", "two"); ManagedSet child = new ManagedSet(); child.setMergeEnabled(true); Set mergedSet = child.merge(parent); @@ -81,11 +75,8 @@ public void mergeEmptyChild() { @Test public void mergeChildValuesOverrideTheParents() { // asserts that the set contract is not violated during a merge() operation... - ManagedSet parent = new ManagedSet(); - parent.add("one"); - parent.add("two"); - ManagedSet child = new ManagedSet(); - child.add("one"); + ManagedSet parent = ManagedSet.of("one", "two"); + ManagedSet child = ManagedSet.of("one"); child.setMergeEnabled(true); Set mergedSet = child.merge(parent); assertThat(mergedSet.size()).as("merge() obviously did not work.").isEqualTo(2); diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java index 2e53ce2f885a..3ca9a82d72f3 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java @@ -40,8 +40,9 @@ public void afterPropertiesSet() throws Exception { * managed the bean's lifecycle correctly */ public void businessMethod() { - if (!this.inited) + if (!this.inited) { throw new RuntimeException("Factory didn't call afterPropertiesSet() on MustBeInitialized object"); + } } } diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java index 661eff92feb7..7b725d39c9b5 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java @@ -39,12 +39,18 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } final Pet pet = (Pet) o; - if (name != null ? !name.equals(pet.name) : pet.name != null) return false; + if (name != null ? !name.equals(pet.name) : pet.name != null) { + return false; + } return true; } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java index a9adcc823d5a..8d2956844077 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java @@ -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. @@ -171,6 +171,7 @@ public void execute(Runnable task) { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java index 35534962d9f9..6cfa717398ef 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java @@ -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. @@ -76,6 +76,7 @@ public void execute(Runnable task) { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java index e90a3ece9363..f8c0de21f2d7 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java @@ -178,14 +178,11 @@ public void setCacheNameNullRestoreDynamicMode() { @Test public void cacheLoaderUseLoadingCache() { CaffeineCacheManager cm = new CaffeineCacheManager("c1"); - cm.setCacheLoader(new CacheLoader() { - @Override - public Object load(Object key) throws Exception { - if ("ping".equals(key)) { - return "pong"; - } - throw new IllegalArgumentException("I only know ping"); + cm.setCacheLoader(key -> { + if ("ping".equals(key)) { + return "pong"; } + throw new IllegalArgumentException("I only know ping"); }); Cache cache1 = cm.getCache("c1"); Cache.ValueWrapper value = cache1.get("ping"); diff --git a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java index 3bd467b9106e..47045905723c 100644 --- a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java +++ b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java @@ -182,12 +182,9 @@ public void javaMailSenderWithMimeMessagePreparator() { final List messages = new ArrayList<>(); - MimeMessagePreparator preparator = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("you@mail.org")); - messages.add(mimeMessage); - } + MimeMessagePreparator preparator = mimeMessage -> { + mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("you@mail.org")); + messages.add(mimeMessage); }; sender.send(preparator); @@ -208,19 +205,13 @@ public void javaMailSenderWithMimeMessagePreparators() { final List messages = new ArrayList<>(); - MimeMessagePreparator preparator1 = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("he@mail.org")); - messages.add(mimeMessage); - } + MimeMessagePreparator preparator1 = mimeMessage -> { + mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("he@mail.org")); + messages.add(mimeMessage); }; - MimeMessagePreparator preparator2 = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("she@mail.org")); - messages.add(mimeMessage); - } + MimeMessagePreparator preparator2 = mimeMessage -> { + mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("she@mail.org")); + messages.add(mimeMessage); }; sender.send(preparator1, preparator2); @@ -323,12 +314,7 @@ public void javaMailSenderWithParseExceptionOnSimpleMessage() { @Test public void javaMailSenderWithParseExceptionOnMimeMessagePreparator() { MockJavaMailSender sender = new MockJavaMailSender(); - MimeMessagePreparator preparator = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setFrom(new InternetAddress("")); - } - }; + MimeMessagePreparator preparator = mimeMessage -> mimeMessage.setFrom(new InternetAddress("")); try { sender.send(preparator); } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java b/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java new file mode 100644 index 000000000000..e2f6c33127d4 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java @@ -0,0 +1,105 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.interceptor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cache.Cache; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A {@link CacheErrorHandler} implementation that logs error message. Can be + * used when underlying cache errors should be ignored. + * + * @author Adam Ostrožlík + * @author Stephane Nicoll + * @since 5.3.16 + */ +public class LoggingCacheErrorHandler implements CacheErrorHandler { + + private final Log logger; + + private final boolean logStacktrace; + + + /** + * Create an instance with the {@link Log logger} to use. + * @param logger the logger to use + * @param logStacktrace whether to log stack trace + */ + public LoggingCacheErrorHandler(Log logger, boolean logStacktrace) { + Assert.notNull(logger, "Logger must not be null"); + this.logger = logger; + this.logStacktrace = logStacktrace; + } + + /** + * Create an instance that does not log stack traces. + */ + public LoggingCacheErrorHandler() { + this(LogFactory.getLog(LoggingCacheErrorHandler.class), false); + } + + + @Override + public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { + logCacheError(logger, + createMessage(cache, "failed to get entry with key '" + key + "'"), + exception); + } + + @Override + public void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value) { + logCacheError(logger, + createMessage(cache, "failed to put entry with key '" + key + "'"), + exception); + } + + @Override + public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { + logCacheError(logger, + createMessage(cache, "failed to evict entry with key '" + key + "'"), + exception); + } + + @Override + public void handleCacheClearError(RuntimeException exception, Cache cache) { + logCacheError(logger, createMessage(cache, "failed to clear entries"), exception); + } + + /** + * Log the specified message. + * @param logger the logger + * @param message the message + * @param ex the exception + */ + protected void logCacheError(Log logger, String message, RuntimeException ex) { + if (this.logStacktrace) { + logger.warn(message, ex); + } + else { + logger.warn(message); + } + } + + private String createMessage(Cache cache, String reason) { + return String.format("Cache '%s' %s", cache.getName(), reason); + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java index a0907a1f3a15..6b5824cea5e1 100644 --- a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java +++ b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java @@ -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. @@ -24,10 +24,12 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PlaceholderConfigurerSupport; import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; @@ -57,6 +59,7 @@ * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 * @see org.springframework.core.env.ConfigurableEnvironment * @see org.springframework.beans.factory.config.PlaceholderConfigurerSupport @@ -129,12 +132,25 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) if (this.propertySources == null) { this.propertySources = new MutablePropertySources(); if (this.environment != null) { + PropertyResolver propertyResolver = this.environment; + // If the ignoreUnresolvablePlaceholders flag is set to true, we have to create a + // local PropertyResolver to enforce that setting, since the Environment is most + // likely not configured with ignoreUnresolvablePlaceholders set to true. + // See https://github.com/spring-projects/spring-framework/issues/27947 + if (this.ignoreUnresolvablePlaceholders && (this.environment instanceof ConfigurableEnvironment)) { + ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) this.environment; + PropertySourcesPropertyResolver resolver = + new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources()); + resolver.setIgnoreUnresolvableNestedPlaceholders(true); + propertyResolver = resolver; + } + PropertyResolver propertyResolverToUse = propertyResolver; this.propertySources.addLast( new PropertySource(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { @Override @Nullable public String getProperty(String key) { - return this.source.getProperty(key); + return propertyResolverToUse.getProperty(key); } } ); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java index 0b976d3f00a3..9c19aad56603 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java @@ -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. @@ -147,6 +147,7 @@ public void execute(Runnable task) { this.adaptedExecutor.execute(task); } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { this.adaptedExecutor.execute(task, startTimeout); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java index 928814524c67..6a8334feebfa 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java @@ -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. @@ -340,6 +340,7 @@ public void execute(Runnable task) { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java index 718c5833929e..946f5bcc6f22 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java @@ -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. @@ -282,6 +282,7 @@ public void execute(Runnable task) { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java index bed950663452..adc2c2ffe5d6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java @@ -269,7 +269,14 @@ public > T rollForward(T temporal) { int current = get(temporal); ValueRange range = temporal.range(this.field); long amount = range.getMaximum() - current + 1; - return this.field.getBaseUnit().addTo(temporal, amount); + T result = this.field.getBaseUnit().addTo(temporal, amount); + current = get(result); + range = result.range(this.field); + // adjust for daylight savings + if (current != range.getMinimum()) { + result = this.field.adjustInto(result, range.getMinimum()); + } + return result; } /** diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java b/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java index 0b72a273ab2c..36c70c088312 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java @@ -251,43 +251,46 @@ private static TemporalAdjuster lastDayWithOffset(int offset) { private static TemporalAdjuster weekdayNearestTo(int dayOfMonth) { return temporal -> { int current = Type.DAY_OF_MONTH.get(temporal); - int dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK); + DayOfWeek dayOfWeek = DayOfWeek.from(temporal); - if ((current == dayOfMonth && dayOfWeek < 6) || // dayOfMonth is a weekday - (dayOfWeek == 5 && current == dayOfMonth - 1) || // dayOfMonth is a Saturday, so Friday before - (dayOfWeek == 1 && current == dayOfMonth + 1) || // dayOfMonth is a Sunday, so Monday after - (dayOfWeek == 1 && dayOfMonth == 1 && current == 3)) { // dayOfMonth is the 1st, so Monday 3rd + if ((current == dayOfMonth && isWeekday(dayOfWeek)) || // dayOfMonth is a weekday + (dayOfWeek == DayOfWeek.FRIDAY && current == dayOfMonth - 1) || // dayOfMonth is a Saturday, so Friday before + (dayOfWeek == DayOfWeek.MONDAY && current == dayOfMonth + 1) || // dayOfMonth is a Sunday, so Monday after + (dayOfWeek == DayOfWeek.MONDAY && dayOfMonth == 1 && current == 3)) { // dayOfMonth is Saturday 1st, so Monday 3rd return temporal; } int count = 0; while (count++ < CronExpression.MAX_ATTEMPTS) { - temporal = Type.DAY_OF_MONTH.elapseUntil(cast(temporal), dayOfMonth); - temporal = atMidnight().adjustInto(temporal); - current = Type.DAY_OF_MONTH.get(temporal); if (current == dayOfMonth) { - dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK); + dayOfWeek = DayOfWeek.from(temporal); - if (dayOfWeek == 6) { // Saturday + if (dayOfWeek == DayOfWeek.SATURDAY) { if (dayOfMonth != 1) { - return temporal.minus(1, ChronoUnit.DAYS); + temporal = temporal.minus(1, ChronoUnit.DAYS); } else { - // exception for "1W" fields: execute on nearest Monday - return temporal.plus(2, ChronoUnit.DAYS); + // exception for "1W" fields: execute on next Monday + temporal = temporal.plus(2, ChronoUnit.DAYS); } } - else if (dayOfWeek == 7) { // Sunday - return temporal.plus(1, ChronoUnit.DAYS); - } - else { - return temporal; + else if (dayOfWeek == DayOfWeek.SUNDAY) { + temporal = temporal.plus(1, ChronoUnit.DAYS); } + return atMidnight().adjustInto(temporal); + } + else { + temporal = Type.DAY_OF_MONTH.elapseUntil(cast(temporal), dayOfMonth); + current = Type.DAY_OF_MONTH.get(temporal); } } return null; }; } + private static boolean isWeekday(DayOfWeek dayOfWeek) { + return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY; + } + /** * Return a temporal adjuster that finds the last of the given doy-of-week * in a month. diff --git a/spring-context/src/main/java/org/springframework/stereotype/Controller.java b/spring-context/src/main/java/org/springframework/stereotype/Controller.java index ef4167c13714..6629feea4b3e 100644 --- a/spring-context/src/main/java/org/springframework/stereotype/Controller.java +++ b/spring-context/src/main/java/org/springframework/stereotype/Controller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -39,7 +39,7 @@ * @see org.springframework.web.bind.annotation.RequestMapping * @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component diff --git a/spring-context/src/main/java/org/springframework/stereotype/Repository.java b/spring-context/src/main/java/org/springframework/stereotype/Repository.java index 97cb1358080b..d44864877011 100644 --- a/spring-context/src/main/java/org/springframework/stereotype/Repository.java +++ b/spring-context/src/main/java/org/springframework/stereotype/Repository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -55,7 +55,7 @@ * @see org.springframework.dao.DataAccessException * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component diff --git a/spring-context/src/main/java/org/springframework/stereotype/Service.java b/spring-context/src/main/java/org/springframework/stereotype/Service.java index 18e61c53d283..5c714540bbca 100644 --- a/spring-context/src/main/java/org/springframework/stereotype/Service.java +++ b/spring-context/src/main/java/org/springframework/stereotype/Service.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -41,7 +41,7 @@ * @see Component * @see Repository */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java index d841044f2650..7c017cfa1aad 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java @@ -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. @@ -19,6 +19,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; +import java.util.function.Supplier; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; @@ -27,6 +28,8 @@ import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator; @@ -42,6 +45,11 @@ import org.springframework.beans.testfixture.beans.ITestBean; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +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.NestedRuntimeException; @@ -292,6 +300,16 @@ public void testWithBeanNameAutoProxyCreator() { assertThat(tb.getAge()).isEqualTo(68); } + @ParameterizedTest(name = "[{index}] {0}") + @ValueSource(classes = {ProxyTargetClassFalseConfig.class, ProxyTargetClassTrueConfig.class}) + void lambdaIsAlwaysProxiedWithJdkProxy(Class configClass) { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) { + Supplier 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"); + } + } /** * Returns a new {@link ClassPathXmlApplicationContext} for the file ending in fileSuffix. @@ -557,12 +575,7 @@ class TestBeanAdvisor extends StaticMethodMatcherPointcutAdvisor { public int count; public TestBeanAdvisor() { - setAdvice(new MethodBeforeAdvice() { - @Override - public void before(Method method, Object[] args, @Nullable Object target) throws Throwable { - ++count; - } - }); + setAdvice((MethodBeforeAdvice) (method, args, target) -> ++count); } @Override @@ -571,3 +584,35 @@ public boolean matches(Method method, @Nullable Class targetClass) { } } + +abstract class AbstractProxyTargetClassConfig { + + @Bean + Supplier stringSupplier() { + return () -> "lambda"; + } + + @Bean + SupplierAdvice supplierAdvice() { + return new SupplierAdvice(); + } + + @Aspect + static class SupplierAdvice { + + @Around("execution(public * org.springframework.aop.aspectj.autoproxy..*.*(..))") + Object aroundSupplier(ProceedingJoinPoint joinPoint) throws Throwable { + return "advised: " + joinPoint.proceed(); + } + } +} + +@Configuration(proxyBeanMethods = false) +@EnableAspectJAutoProxy(proxyTargetClass = false) +class ProxyTargetClassFalseConfig extends AbstractProxyTargetClassConfig { +} + +@Configuration(proxyBeanMethods = false) +@EnableAspectJAutoProxy(proxyTargetClass = true) +class ProxyTargetClassTrueConfig extends AbstractProxyTargetClassConfig { +} diff --git a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java index db27a571fc40..76b7d336af56 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java @@ -372,17 +372,14 @@ public void testNoContext() throws Throwable { private void testContext(final boolean context) throws Throwable { final String s = "foo"; // Test return value - MethodInterceptor mi = new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - if (!context) { - assertNoInvocationContext(); - } - else { - assertThat(ExposeInvocationInterceptor.currentInvocation()).as("have context").isNotNull(); - } - return s; + MethodInterceptor mi = invocation -> { + if (!context) { + assertNoInvocationContext(); } + else { + assertThat(ExposeInvocationInterceptor.currentInvocation()).as("have context").isNotNull(); + } + return s; }; AdvisedSupport pc = new AdvisedSupport(ITestBean.class); if (context) { @@ -422,11 +419,8 @@ public void testTargetReturnsThis() throws Throwable { public void testDeclaredException() throws Throwable { final Exception expectedException = new Exception(); // Test return value - MethodInterceptor mi = new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - throw expectedException; - } + MethodInterceptor mi = invocation -> { + throw expectedException; }; AdvisedSupport pc = new AdvisedSupport(ITestBean.class); pc.addAdvice(ExposeInvocationInterceptor.INSTANCE); @@ -453,11 +447,8 @@ public Object invoke(MethodInvocation invocation) throws Throwable { public void testUndeclaredCheckedException() throws Throwable { final Exception unexpectedException = new Exception(); // Test return value - MethodInterceptor mi = new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - throw unexpectedException; - } + MethodInterceptor mi = invocation -> { + throw unexpectedException; }; AdvisedSupport pc = new AdvisedSupport(ITestBean.class); pc.addAdvice(ExposeInvocationInterceptor.INSTANCE); @@ -477,11 +468,8 @@ public Object invoke(MethodInvocation invocation) throws Throwable { public void testUndeclaredUncheckedException() throws Throwable { final RuntimeException unexpectedException = new RuntimeException(); // Test return value - MethodInterceptor mi = new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - throw unexpectedException; - } + MethodInterceptor mi = invocation -> { + throw unexpectedException; }; AdvisedSupport pc = new AdvisedSupport(ITestBean.class); pc.addAdvice(ExposeInvocationInterceptor.INSTANCE); @@ -660,12 +648,7 @@ public void testAdviceImplementsIntroductionInfo() throws Throwable { NopInterceptor di = new NopInterceptor(); pc.addAdvice(di); final long ts = 37; - pc.addAdvice(new DelegatingIntroductionInterceptor(new TimeStamped() { - @Override - public long getTimeStamp() { - return ts; - } - })); + pc.addAdvice(new DelegatingIntroductionInterceptor((TimeStamped) () -> ts)); ITestBean proxied = (ITestBean) createProxy(pc); assertThat(proxied.getName()).isEqualTo(name); @@ -1039,17 +1022,14 @@ public void testCloneInvocationToProceedThreeTimes() throws Throwable { ProxyFactory pc = new ProxyFactory(tb); pc.addInterface(ITestBean.class); - MethodInterceptor twoBirthdayInterceptor = new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - // Clone the invocation to proceed three times - // "The Moor's Last Sigh": this technology can cause premature aging - MethodInvocation clone1 = ((ReflectiveMethodInvocation) mi).invocableClone(); - MethodInvocation clone2 = ((ReflectiveMethodInvocation) mi).invocableClone(); - clone1.proceed(); - clone2.proceed(); - return mi.proceed(); - } + MethodInterceptor twoBirthdayInterceptor = mi -> { + // Clone the invocation to proceed three times + // "The Moor's Last Sigh": this technology can cause premature aging + MethodInvocation clone1 = ((ReflectiveMethodInvocation) mi).invocableClone(); + MethodInvocation clone2 = ((ReflectiveMethodInvocation) mi).invocableClone(); + clone1.proceed(); + clone2.proceed(); + return mi.proceed(); }; @SuppressWarnings("serial") StaticMethodMatcherPointcutAdvisor advisor = new StaticMethodMatcherPointcutAdvisor(twoBirthdayInterceptor) { @@ -1082,16 +1062,13 @@ public void testCanChangeArgumentsIndependentlyOnClonedInvocation() throws Throw /** * Changes the name, then changes it back. */ - MethodInterceptor nameReverter = new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - MethodInvocation clone = ((ReflectiveMethodInvocation) mi).invocableClone(); - String oldName = ((ITestBean) mi.getThis()).getName(); - clone.getArguments()[0] = oldName; - // Original method invocation should be unaffected by changes to argument list of clone - mi.proceed(); - return clone.proceed(); - } + MethodInterceptor nameReverter = mi -> { + MethodInvocation clone = ((ReflectiveMethodInvocation) mi).invocableClone(); + String oldName = ((ITestBean) mi.getThis()).getName(); + clone.getArguments()[0] = oldName; + // Original method invocation should be unaffected by changes to argument list of clone + mi.proceed(); + return clone.proceed(); }; class NameSaver implements MethodInterceptor { @@ -1347,8 +1324,9 @@ public void testBeforeAdviceThrowsException() { @Override public void before(Method m, Object[] args, Object target) throws Throwable { super.before(m, args, target); - if (m.getName().startsWith("set")) + if (m.getName().startsWith("set")) { throw rex; + } } }; @@ -1563,13 +1541,10 @@ public Object invoke(MethodInvocation mi) throws Throwable { @SuppressWarnings("serial") protected static class StringSetterNullReplacementAdvice extends DefaultPointcutAdvisor { - private static MethodInterceptor cleaner = new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - // We know it can only be invoked if there's a single parameter of type string - mi.getArguments()[0] = ""; - return mi.proceed(); - } + private static MethodInterceptor cleaner = mi -> { + // We know it can only be invoked if there's a single parameter of type string + mi.getArguments()[0] = ""; + return mi.proceed(); }; public StringSetterNullReplacementAdvice() { @@ -1601,7 +1576,9 @@ public TestDynamicPointcutAdvice(MethodInterceptor mi, final String pattern) { @Override public boolean matches(Method m, @Nullable Class targetClass, Object... args) { boolean run = m.getName().contains(pattern); - if (run) ++count; + if (run) { + ++count; + } return run; } }); @@ -1620,7 +1597,9 @@ public TestDynamicPointcutForSettersOnly(MethodInterceptor mi, final String patt @Override public boolean matches(Method m, @Nullable Class targetClass, Object... args) { boolean run = m.getName().contains(pattern); - if (run) ++count; + if (run) { + ++count; + } return run; } @Override @@ -1924,8 +1903,9 @@ public Object getTarget() throws Exception { */ @Override public void releaseTarget(Object pTarget) throws Exception { - if (pTarget != this.target) + if (pTarget != this.target) { throw new RuntimeException("Released wrong target"); + } ++releases; } @@ -1934,8 +1914,9 @@ public void releaseTarget(Object pTarget) throws Exception { * */ public void verify() { - if (gets != releases) + if (gets != releases) { throw new RuntimeException("Expectation failed: " + gets + " gets and " + releases + " releases"); + } } /** diff --git a/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java index 306c3d49909d..50b26ed19f9e 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java @@ -198,10 +198,16 @@ public String getName() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Person person = (Person) o; - if (!name.equals(person.name)) return false; + if (!name.equals(person.name)) { + return false; + } return true; } diff --git a/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java index 91d85d35dadd..3495ae05f92c 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java @@ -304,11 +304,8 @@ public void testCanGetFactoryReferenceAndManipulate() { final Exception ex = new UnsupportedOperationException("invoke"); // Add evil interceptor to head of list - config.addAdvice(0, new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - throw ex; - } + config.addAdvice(0, (MethodInterceptor) invocation -> { + throw ex; }); assertThat(config.getAdvisors().length).as("Have correct advisor count").isEqualTo(2); @@ -691,12 +688,9 @@ public static void reset() { } public PointcutForVoid() { - setAdvice(new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - methodNames.add(invocation.getMethod().getName()); - return invocation.proceed(); - } + setAdvice((MethodInterceptor) invocation -> { + methodNames.add(invocation.getMethod().getName()); + return invocation.proceed(); }); setPointcut(new DynamicMethodMatcherPointcut() { @Override diff --git a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java index eb39f93218cf..901c88665d5f 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java @@ -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. @@ -17,8 +17,6 @@ package org.springframework.aop.framework.autoproxy; import java.io.Serializable; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.aopalliance.intercept.MethodInterceptor; @@ -434,6 +432,7 @@ public FallbackTestAutoProxyCreator() { @SuppressWarnings("serial") public static class IntroductionTestAutoProxyCreator extends TestAutoProxyCreator { + @Override protected Object[] getAdvicesAndAdvisors() { DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(this.testInterceptor); advisor.addInterface(Serializable.class); @@ -491,12 +490,8 @@ public static class CustomProxyFactoryBean implements FactoryBean { @Override public ITestBean getObject() { - return (ITestBean) Proxy.newProxyInstance(CustomProxyFactoryBean.class.getClassLoader(), new Class[]{ITestBean.class}, new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return ReflectionUtils.invokeMethod(method, tb, args); - } - }); + return (ITestBean) Proxy.newProxyInstance(CustomProxyFactoryBean.class.getClassLoader(), new Class[]{ITestBean.class}, + (proxy, method, args) -> ReflectionUtils.invokeMethod(method, tb, args)); } @Override diff --git a/spring-context/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceTests.java b/spring-context/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceTests.java index 6708435685c1..d366fb6c6052 100644 --- a/spring-context/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceTests.java +++ b/spring-context/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceTests.java @@ -158,8 +158,8 @@ void testHitMaxSize() throws Exception { pooledInstances[9] = targetSource.getTarget(); // release all objects - for (int i = 0; i < pooledInstances.length; i++) { - targetSource.releaseTarget(pooledInstances[i]); + for (Object element : pooledInstances) { + targetSource.releaseTarget(element); } } diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java new file mode 100644 index 000000000000..39525d715ae6 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java @@ -0,0 +1,76 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.interceptor; + +import org.apache.commons.logging.Log; +import org.junit.jupiter.api.Test; + +import org.springframework.cache.support.NoOpCache; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link LoggingCacheErrorHandler}. + * + * @author Adam Ostrožlík + * @author Stephane Nicoll + */ +public class LoggingCacheErrorHandlerTests { + + @Test + void handleGetCacheErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCacheGetError(new RuntimeException(), new NoOpCache("NOOP"), "key"); + verify(logger).warn("Cache 'NOOP' failed to get entry with key 'key'"); + } + + @Test + void handlePutCacheErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCachePutError(new RuntimeException(), new NoOpCache("NOOP"), "key", new Object()); + verify(logger).warn("Cache 'NOOP' failed to put entry with key 'key'"); + } + + @Test + void handleEvictCacheErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCacheEvictError(new RuntimeException(), new NoOpCache("NOOP"), "key"); + verify(logger).warn("Cache 'NOOP' failed to evict entry with key 'key'"); + } + + @Test + void handleClearErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCacheClearError(new RuntimeException(), new NoOpCache("NOOP")); + verify(logger).warn("Cache 'NOOP' failed to clear entries"); + } + + @Test + void handleCacheErrorWithStacktrace() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, true); + RuntimeException exception = new RuntimeException(); + handler.handleCacheGetError(exception, new NoOpCache("NOOP"), "key"); + verify(logger).warn("Cache 'NOOP' failed to get entry with key 'key'", exception); + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/LifecycleContextBean.java b/spring-context/src/test/java/org/springframework/context/LifecycleContextBean.java index 1f04eb182ccc..b05fcb658b76 100644 --- a/spring-context/src/test/java/org/springframework/context/LifecycleContextBean.java +++ b/spring-context/src/test/java/org/springframework/context/LifecycleContextBean.java @@ -33,21 +33,24 @@ public class LifecycleContextBean extends LifecycleBean implements ApplicationCo @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); - if (this.owningContext != null) + if (this.owningContext != null) { throw new RuntimeException("Factory called setBeanFactory after setApplicationContext"); + } } @Override public void afterPropertiesSet() { super.afterPropertiesSet(); - if (this.owningContext == null) + if (this.owningContext == null) { throw new RuntimeException("Factory didn't call setApplicationContext before afterPropertiesSet on lifecycle bean"); + } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - if (this.owningFactory == null) + if (this.owningFactory == null) { throw new RuntimeException("Factory called setApplicationContext before setBeanFactory"); + } this.owningContext = applicationContext; } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index 336432ff0c77..566a445288c4 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -544,19 +544,24 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } TestBean other = (TestBean) obj; if (name == null) { - if (other.name != null) + if (other.name != null) { return false; + } } - else if (!name.equals(other.name)) + else if (!name.equals(other.name)) { return false; + } return true; } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java index 3d300b71dbe0..e943d4d8f2fd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java @@ -215,12 +215,7 @@ public void testResourceInjectionWithResolvableDependencyType() { bf.registerBeanDefinition("testBean4", tbd); bf.registerResolvableDependency(BeanFactory.class, bf); - bf.registerResolvableDependency(INestedTestBean.class, new ObjectFactory() { - @Override - public Object getObject() throws BeansException { - return new NestedTestBean(); - } - }); + bf.registerResolvableDependency(INestedTestBean.class, (ObjectFactory) () -> new NestedTestBean()); @SuppressWarnings("deprecation") org.springframework.beans.factory.config.PropertyPlaceholderConfigurer ppc = new org.springframework.beans.factory.config.PropertyPlaceholderConfigurer(); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBFPPTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBFPPTests.java index 21324a769edc..641b4d7ceaae 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBFPPTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBFPPTests.java @@ -18,10 +18,8 @@ import org.junit.jupiter.api.Test; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.testfixture.beans.TestBean; import static org.assertj.core.api.Assertions.assertThat; @@ -72,11 +70,8 @@ static class AutowiredConfigWithBFPPAsInstanceMethod { @Bean public BeanFactoryPostProcessor bfpp() { - return new BeanFactoryPostProcessor() { - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - // no-op - } + return beanFactory -> { + // no-op }; } } @@ -88,11 +83,8 @@ static class AutowiredConfigWithBFPPAsStaticMethod { @Bean public static final BeanFactoryPostProcessor bfpp() { - return new BeanFactoryPostProcessor() { - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - // no-op - } + return beanFactory -> { + // no-op }; } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java index 5a434faa2074..e5593c5d8731 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java @@ -291,7 +291,9 @@ public ExampleBean bean1() { static class ImportsNotCreated { static { - if (true) throw new RuntimeException(); + if (true) { + throw new RuntimeException(); + } } } @@ -299,14 +301,18 @@ static class ImportsNotCreated { static class ConfigurationNotCreated { static { - if (true) throw new RuntimeException(); + if (true) { + throw new RuntimeException(); + } } } static class RegistrarNotCreated implements ImportBeanDefinitionRegistrar { static { - if (true) throw new RuntimeException(); + if (true) { + throw new RuntimeException(); + } } @Override @@ -318,7 +324,9 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, static class ImportSelectorNotCreated implements ImportSelector { static { - if (true) throw new RuntimeException(); + if (true) { + throw new RuntimeException(); + } } @Override diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java index d46c3a44f548..28f8ce8e23d5 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java @@ -136,10 +136,7 @@ public static class Application { @Bean Runnable fromInnerClass() { - return new Runnable() { - @Override - public void run() { - } + return () -> { }; } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index ce9033334c04..a9660103ed83 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -39,7 +39,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.ListFactoryBean; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; @@ -559,12 +558,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { // @Bean public BeanFactoryPostProcessor beanFactoryPostProcessor() { - return new BeanFactoryPostProcessor() { - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - BeanDefinition bd = beanFactory.getBeanDefinition("beanPostProcessor"); - bd.getPropertyValues().addPropertyValue("nameSuffix", "-processed-" + myProp); - } + return beanFactory -> { + BeanDefinition bd = beanFactory.getBeanDefinition("beanPostProcessor"); + bd.getPropertyValues().addPropertyValue("nameSuffix", "-processed-" + myProp); }; } diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index 331336a848ad..ff72d9780e7f 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -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. @@ -20,7 +20,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.Executor; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; @@ -141,12 +140,9 @@ public void simpleApplicationEventMulticasterWithTaskExecutor() { ApplicationEvent evt = new ContextClosedEvent(new StaticApplicationContext()); SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster(); - smc.setTaskExecutor(new Executor() { - @Override - public void execute(Runnable command) { - command.run(); - command.run(); - } + smc.setTaskExecutor(command -> { + command.run(); + command.run(); }); smc.addApplicationListener(listener); @@ -429,12 +425,7 @@ public void innerBeanAsListener() { public void anonymousClassAsListener() { final Set seenEvents = new HashSet<>(); StaticApplicationContext context = new StaticApplicationContext(); - context.addApplicationListener(new ApplicationListener() { - @Override - public void onApplicationEvent(MyEvent event) { - seenEvents.add(event); - } - }); + context.addApplicationListener((MyEvent event) -> seenEvents.add(event)); context.refresh(); MyEvent event1 = new MyEvent(context); diff --git a/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java b/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java index c752623bb55f..2bcef2d552a1 100644 --- a/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java @@ -65,7 +65,7 @@ public void testProgrammaticEventListener() { public void testProgrammaticPayloadListener() { List events = new ArrayList<>(); ApplicationListener> listener = ApplicationListener.forPayload(events::add); - ApplicationListener> mismatch = ApplicationListener.forPayload(payload -> payload.intValue()); + ApplicationListener> mismatch = ApplicationListener.forPayload(Integer::intValue); ConfigurableApplicationContext ac = new GenericApplicationContext(); ac.addApplicationListener(listener); diff --git a/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java b/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java index 7dd8f260ce5f..3a19e4a69223 100644 --- a/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java +++ b/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java @@ -36,8 +36,12 @@ public String getId() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } AbstractIdentifiable that = (AbstractIdentifiable) o; diff --git a/spring-context/src/test/java/org/springframework/context/event/test/GenericEventPojo.java b/spring-context/src/test/java/org/springframework/context/event/test/GenericEventPojo.java index b2d1bc6b6f9f..79554e0f6460 100644 --- a/spring-context/src/test/java/org/springframework/context/event/test/GenericEventPojo.java +++ b/spring-context/src/test/java/org/springframework/context/event/test/GenericEventPojo.java @@ -38,8 +38,12 @@ public ResolvableType getResolvableType() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } GenericEventPojo that = (GenericEventPojo) o; diff --git a/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java b/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java index b22517905572..20b84cf49988 100644 --- a/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java +++ b/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java @@ -50,8 +50,12 @@ public String getId() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } IdentifiableApplicationEvent that = (IdentifiableApplicationEvent) o; diff --git a/spring-context/src/test/java/org/springframework/context/support/ConversionServiceFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/context/support/ConversionServiceFactoryBeanTests.java index ec96b605143c..d46d9fb61fa9 100644 --- a/spring-context/src/test/java/org/springframework/context/support/ConversionServiceFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/ConversionServiceFactoryBeanTests.java @@ -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. @@ -99,8 +99,7 @@ public void createDefaultConversionServiceWithInvalidSupplements() { Set converters = new HashSet<>(); converters.add("bogus"); factory.setConverters(converters); - assertThatIllegalArgumentException().isThrownBy( - factory::afterPropertiesSet); + assertThatIllegalArgumentException().isThrownBy(factory::afterPropertiesSet); } @Test diff --git a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java index bdb41244bdd5..42fe1a715e28 100644 --- a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java @@ -21,9 +21,14 @@ import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; @@ -40,8 +45,11 @@ import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; /** + * Tests for {@link PropertySourcesPlaceholderConfigurer}. + * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 */ public class PropertySourcesPlaceholderConfigurerTests { @@ -159,8 +167,11 @@ public void ignoreUnresolvablePlaceholders_falseIsDefault() { PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); //pc.setIgnoreUnresolvablePlaceholders(false); // the default - assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() -> - ppc.postProcessBeanFactory(bf)); + assertThatExceptionOfType(BeanDefinitionStoreException.class) + .isThrownBy(() -> ppc.postProcessBeanFactory(bf)) + .havingCause() + .isExactlyInstanceOf(IllegalArgumentException.class) + .withMessage("Could not resolve placeholder 'my.name' in value \"${my.name}\""); } @Test @@ -177,6 +188,38 @@ public void ignoreUnresolvablePlaceholders_true() { assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("${my.name}"); } + @Test + // https://github.com/spring-projects/spring-framework/issues/27947 + public void ignoreUnresolvablePlaceholdersInAtValueAnnotation__falseIsDefault() { + MockPropertySource mockPropertySource = new MockPropertySource("test"); + mockPropertySource.setProperty("my.key", "${enigma}"); + @SuppressWarnings("resource") + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.getEnvironment().getPropertySources().addLast(mockPropertySource); + context.register(IgnoreUnresolvablePlaceholdersFalseConfig.class); + + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(context::refresh) + .havingCause() + .isExactlyInstanceOf(IllegalArgumentException.class) + .withMessage("Could not resolve placeholder 'enigma' in value \"${enigma}\""); + } + + @Test + // https://github.com/spring-projects/spring-framework/issues/27947 + public void ignoreUnresolvablePlaceholdersInAtValueAnnotation_true() { + MockPropertySource mockPropertySource = new MockPropertySource("test"); + mockPropertySource.setProperty("my.key", "${enigma}"); + @SuppressWarnings("resource") + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.getEnvironment().getPropertySources().addLast(mockPropertySource); + context.register(IgnoreUnresolvablePlaceholdersTrueConfig.class); + context.refresh(); + + IgnoreUnresolvablePlaceholdersTrueConfig config = context.getBean(IgnoreUnresolvablePlaceholdersTrueConfig.class); + assertThat(config.value).isEqualTo("${enigma}"); + } + @Test @SuppressWarnings("serial") public void nestedUnresolvablePlaceholder() { @@ -402,4 +445,30 @@ public void setName(Optional name) { } } + @Configuration + static class IgnoreUnresolvablePlaceholdersFalseConfig { + + @Value("${my.key}") + String value; + + @Bean + static PropertySourcesPlaceholderConfigurer pspc() { + return new PropertySourcesPlaceholderConfigurer(); + } + } + + @Configuration + static class IgnoreUnresolvablePlaceholdersTrueConfig { + + @Value("${my.key}") + String value; + + @Bean + static PropertySourcesPlaceholderConfigurer pspc() { + PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer(); + pspc.setIgnoreUnresolvablePlaceholders(true); + return pspc; + } + } + } diff --git a/spring-context/src/test/java/org/springframework/format/number/NumberFormattingTests.java b/spring-context/src/test/java/org/springframework/format/number/NumberFormattingTests.java index 406d6549f9e3..609927bdf05e 100644 --- a/spring-context/src/test/java/org/springframework/format/number/NumberFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/number/NumberFormattingTests.java @@ -30,7 +30,6 @@ import org.springframework.format.annotation.NumberFormat; import org.springframework.format.annotation.NumberFormat.Style; import org.springframework.format.support.FormattingConversionService; -import org.springframework.util.StringValueResolver; import org.springframework.validation.DataBinder; import static org.assertj.core.api.Assertions.assertThat; @@ -49,15 +48,12 @@ public class NumberFormattingTests { @BeforeEach public void setUp() { DefaultConversionService.addDefaultConverters(conversionService); - conversionService.setEmbeddedValueResolver(new StringValueResolver() { - @Override - public String resolveStringValue(String strVal) { - if ("${pattern}".equals(strVal)) { - return "#,##.00"; - } - else { - return strVal; - } + conversionService.setEmbeddedValueResolver(strVal -> { + if ("${pattern}".equals(strVal)) { + return "#,##.00"; + } + else { + return strVal; } }); conversionService.addFormatterForFieldType(Number.class, new NumberStyleFormatter()); diff --git a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceFactoryBeanTests.java index 8b8aacf6dd56..ddfdeb002d91 100644 --- a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceFactoryBeanTests.java @@ -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. @@ -189,24 +189,14 @@ public Set> getFieldTypes() { public Printer getPrinter(SpecialInt annotation, Class fieldType) { assertThat(annotation.value()).isEqualTo("aliased"); assertThat(annotation.alias()).isEqualTo("aliased"); - return new Printer() { - @Override - public String print(Integer object, Locale locale) { - return ":" + object.toString(); - } - }; + return (object, locale) -> ":" + object.toString(); } @Override public Parser getParser(SpecialInt annotation, Class fieldType) { assertThat(annotation.value()).isEqualTo("aliased"); assertThat(annotation.alias()).isEqualTo("aliased"); - return new Parser() { - @Override - public Integer parse(String text, Locale locale) throws ParseException { - return Integer.parseInt(text.substring(1)); - } - }; + return (text, locale) -> Integer.parseInt(text.substring(1)); } } diff --git a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java index f98384c231ac..f9863819b447 100644 --- a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java @@ -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. @@ -39,7 +39,6 @@ import org.springframework.jmx.JmxTestBean; import org.springframework.jmx.export.MBeanExporter; import org.springframework.jmx.export.assembler.AbstractReflectiveMBeanInfoAssembler; -import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -177,7 +176,8 @@ void invokeUnexposedMethodWithException() throws Exception { void lazyConnectionToRemote() throws Exception { assumeTrue(runTests); - final int port = SocketUtils.findAvailableTcpPort(); + @SuppressWarnings("deprecation") + final int port = org.springframework.util.SocketUtils.findAvailableTcpPort(); JMXServiceURL url = new JMXServiceURL("service:jmx:jmxmp://localhost:" + port); JMXConnectorServer connector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, getServer()); diff --git a/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java b/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java index 5c4c4a3c0820..a617803188d7 100644 --- a/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java @@ -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. @@ -28,8 +28,6 @@ import org.junit.jupiter.api.AfterEach; -import org.springframework.util.SocketUtils; - /** * @author Rob Harrop * @author Chris Beams @@ -37,7 +35,8 @@ */ class RemoteMBeanClientInterceptorTests extends MBeanClientInterceptorTests { - private final int servicePort = SocketUtils.findAvailableTcpPort(); + @SuppressWarnings("deprecation") + private final int servicePort = org.springframework.util.SocketUtils.findAvailableTcpPort(); private final String serviceUrl = "service:jmx:jmxmp://localhost:" + servicePort; diff --git a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java index b850799ebdbe..c6f1a444dac9 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java @@ -27,7 +27,6 @@ import org.springframework.jmx.AbstractMBeanServerTests; import org.springframework.jmx.JmxTestBean; -import org.springframework.jmx.export.naming.ObjectNamingStrategy; import org.springframework.jmx.support.ObjectNameManager; import static org.assertj.core.api.Assertions.assertThat; @@ -74,12 +73,7 @@ void testRegisterManagedResourceWithGeneratedObjectName() throws Exception { MBeanExporter exporter = new MBeanExporter(); exporter.setServer(getServer()); - exporter.setNamingStrategy(new ObjectNamingStrategy() { - @Override - public ObjectName getObjectName(Object managedBean, String beanKey) { - return objectNameTemplate; - } - }); + exporter.setNamingStrategy((managedBean, beanKey) -> objectNameTemplate); JmxTestBean bean1 = new JmxTestBean(); JmxTestBean bean2 = new JmxTestBean(); @@ -101,12 +95,7 @@ void testRegisterManagedResourceWithGeneratedObjectNameWithoutUniqueness() throw MBeanExporter exporter = new MBeanExporter(); exporter.setServer(getServer()); exporter.setEnsureUniqueRuntimeObjectNames(false); - exporter.setNamingStrategy(new ObjectNamingStrategy() { - @Override - public ObjectName getObjectName(Object managedBean, String beanKey) { - return objectNameTemplate; - } - }); + exporter.setNamingStrategy((managedBean, beanKey) -> objectNameTemplate); JmxTestBean bean1 = new JmxTestBean(); JmxTestBean bean2 = new JmxTestBean(); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java index 988e068a0b1b..20affb977458 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java @@ -90,11 +90,8 @@ void testRegisterNullNotificationListenerType() throws Exception { @Test void testRegisterNotificationListenerForNonExistentMBean() throws Exception { Map listeners = new HashMap<>(); - NotificationListener dummyListener = new NotificationListener() { - @Override - public void handleNotification(Notification notification, Object handback) { - throw new UnsupportedOperationException(); - } + NotificationListener dummyListener = (notification, handback) -> { + throw new UnsupportedOperationException(); }; // the MBean with the supplied object name does not exist... listeners.put("spring:type=Test", dummyListener); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java index d38eb831901a..c842a656fd37 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java @@ -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. @@ -23,7 +23,6 @@ import javax.management.AttributeChangeNotification; import javax.management.MalformedObjectNameException; import javax.management.Notification; -import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; @@ -117,7 +116,7 @@ public void testRegisterNotificationListenerWithHandback() throws Exception { MBeanExporter exporter = new MBeanExporter(); exporter.setServer(server); exporter.setBeans(beans); - exporter.setNotificationListeners(new NotificationListenerBean[] { listenerBean }); + exporter.setNotificationListeners(listenerBean); start(exporter); // update the attribute @@ -145,7 +144,7 @@ public void testRegisterNotificationListenerForAllMBeans() throws Exception { MBeanExporter exporter = new MBeanExporter(); exporter.setServer(server); exporter.setBeans(beans); - exporter.setNotificationListeners(new NotificationListenerBean[] { listenerBean }); + exporter.setNotificationListeners(listenerBean); start(exporter); // update the attribute @@ -168,23 +167,20 @@ public void testRegisterNotificationListenerWithFilter() throws Exception { NotificationListenerBean listenerBean = new NotificationListenerBean(); listenerBean.setNotificationListener(listener); - listenerBean.setNotificationFilter(new NotificationFilter() { - @Override - public boolean isNotificationEnabled(Notification notification) { - if (notification instanceof AttributeChangeNotification) { - AttributeChangeNotification changeNotification = (AttributeChangeNotification) notification; - return "Name".equals(changeNotification.getAttributeName()); - } - else { - return false; - } + listenerBean.setNotificationFilter(notification -> { + if (notification instanceof AttributeChangeNotification) { + AttributeChangeNotification changeNotification = (AttributeChangeNotification) notification; + return "Name".equals(changeNotification.getAttributeName()); + } + else { + return false; } }); MBeanExporter exporter = new MBeanExporter(); exporter.setServer(server); exporter.setBeans(beans); - exporter.setNotificationListeners(new NotificationListenerBean[] { listenerBean }); + exporter.setNotificationListeners(listenerBean); start(exporter); // update the attributes diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java index c754af69ca15..b685aa9caf78 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java @@ -83,9 +83,9 @@ public void testGetMBeanAttributeInfo() throws Exception { MBeanAttributeInfo[] inf = info.getAttributes(); assertThat(inf).as("Invalid number of Attributes returned").hasSize(getExpectedAttributeCount()); - for (int x = 0; x < inf.length; x++) { - assertThat(inf[x]).as("MBeanAttributeInfo should not be null").isNotNull(); - assertThat(inf[x].getDescription()).as("Description for MBeanAttributeInfo should not be null").isNotNull(); + for (MBeanAttributeInfo element : inf) { + assertThat(element).as("MBeanAttributeInfo should not be null").isNotNull(); + assertThat(element.getDescription()).as("Description for MBeanAttributeInfo should not be null").isNotNull(); } } @@ -95,9 +95,9 @@ public void testGetMBeanOperationInfo() throws Exception { MBeanOperationInfo[] inf = info.getOperations(); assertThat(inf).as("Invalid number of Operations returned").hasSize(getExpectedOperationCount()); - for (int x = 0; x < inf.length; x++) { - assertThat(inf[x]).as("MBeanOperationInfo should not be null").isNotNull(); - assertThat(inf[x].getDescription()).as("Description for MBeanOperationInfo should not be null").isNotNull(); + for (MBeanOperationInfo element : inf) { + assertThat(element).as("MBeanOperationInfo should not be null").isNotNull(); + assertThat(element.getDescription()).as("Description for MBeanOperationInfo should not be null").isNotNull(); } } diff --git a/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java index 111721d749b1..fad3ed40ec00 100644 --- a/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java @@ -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. @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; import org.springframework.jmx.AbstractMBeanServerTests; -import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -47,7 +46,8 @@ class ConnectorServerFactoryBeanTests extends AbstractMBeanServerTests { private static final String OBJECT_NAME = "spring:type=connector,name=test"; - private final String serviceUrl = "service:jmx:jmxmp://localhost:" + SocketUtils.findAvailableTcpPort(); + @SuppressWarnings("deprecation") + private final String serviceUrl = "service:jmx:jmxmp://localhost:" + org.springframework.util.SocketUtils.findAvailableTcpPort(); @Test diff --git a/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java index faee0ae016c2..64159c6e5146 100644 --- a/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java @@ -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. @@ -25,7 +25,6 @@ import org.springframework.aop.support.AopUtils; import org.springframework.jmx.AbstractMBeanServerTests; -import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -39,7 +38,8 @@ */ class MBeanServerConnectionFactoryBeanTests extends AbstractMBeanServerTests { - private final String serviceUrl = "service:jmx:jmxmp://localhost:" + SocketUtils.findAvailableTcpPort(); + @SuppressWarnings("deprecation") + private final String serviceUrl = "service:jmx:jmxmp://localhost:" + org.springframework.util.SocketUtils.findAvailableTcpPort(); @Test diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java index 9fb8494cd436..70c97851a4e0 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java @@ -27,7 +27,6 @@ import java.util.concurrent.TimeUnit; import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; @@ -613,16 +612,13 @@ public static class DynamicAsyncInterfaceBean implements FactoryBean()); - DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - boolean condition = !Thread.currentThread().getName().equals(originalThreadName); - assertThat(condition).isTrue(); - if (Future.class.equals(invocation.getMethod().getReturnType())) { - return new AsyncResult<>(invocation.getArguments()[0].toString()); - } - return null; + DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor((MethodInterceptor) invocation -> { + boolean condition = !Thread.currentThread().getName().equals(originalThreadName); + assertThat(condition).isTrue(); + if (Future.class.equals(invocation.getMethod().getReturnType())) { + return new AsyncResult<>(invocation.getArguments()[0].toString()); } + return null; }); advisor.addInterface(AsyncInterface.class); pf.addAdvisor(advisor); @@ -686,16 +682,13 @@ public static class DynamicAsyncMethodsInterfaceBean implements FactoryBean()); - DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - boolean condition = !Thread.currentThread().getName().equals(originalThreadName); - assertThat(condition).isTrue(); - if (Future.class.equals(invocation.getMethod().getReturnType())) { - return new AsyncResult<>(invocation.getArguments()[0].toString()); - } - return null; + DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor((MethodInterceptor) invocation -> { + boolean condition = !Thread.currentThread().getName().equals(originalThreadName); + assertThat(condition).isTrue(); + if (Future.class.equals(invocation.getMethod().getReturnType())) { + return new AsyncResult<>(invocation.getArguments()[0].toString()); } + return null; }); advisor.addInterface(AsyncMethodsInterface.class); pf.addAdvisor(advisor); diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java index 8dbae46f7776..ef8f32846ef3 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java @@ -16,7 +16,6 @@ package org.springframework.scheduling.config; -import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; @@ -59,12 +58,7 @@ public void defaultExecutor() throws Exception { assertThat(getKeepAliveSeconds(executor)).isEqualTo(60); assertThat(getAllowCoreThreadTimeOut(executor)).isFalse(); - FutureTask task = new FutureTask<>(new Callable() { - @Override - public String call() throws Exception { - return "foo"; - } - }); + FutureTask task = new FutureTask<>(() -> "foo"); executor.execute(task); assertThat(task.get()).isEqualTo("foo"); } diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java index 3a21f984aa2d..5de5362fc1dd 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java @@ -16,13 +16,13 @@ package org.springframework.scheduling.support; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Year; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.temporal.ChronoField; import java.time.temporal.Temporal; import org.assertj.core.api.Condition; @@ -30,6 +30,7 @@ import static java.time.DayOfWeek.FRIDAY; import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; import static java.time.DayOfWeek.SUNDAY; import static java.time.DayOfWeek.THURSDAY; import static java.time.DayOfWeek.TUESDAY; @@ -46,8 +47,8 @@ class CronExpressionTests { @Override public boolean matches(Temporal value) { - int dayOfWeek = value.get(ChronoField.DAY_OF_WEEK); - return dayOfWeek != 6 && dayOfWeek != 7; + DayOfWeek dayOfWeek = DayOfWeek.from(value); + return dayOfWeek != SATURDAY && dayOfWeek != SUNDAY; } }; @@ -958,6 +959,24 @@ void quartzWeekdayNearestTo1() { assertThat(actual).isNotNull(); assertThat(actual).isEqualTo(expected); assertThat(actual).is(weekday); + + last = LocalDateTime.of(2022, 1, 1, 0, 0); + assertThat(last.getDayOfWeek()).isEqualTo(SATURDAY); + expected = LocalDateTime.of(2022, 1, 3, 0, 0); + assertThat(expected.getDayOfWeek()).isEqualTo(MONDAY); + actual = expression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); + assertThat(actual).is(weekday); + + last = LocalDateTime.of(2021, 8, 1, 0,0); + assertThat(last.getDayOfWeek()).isEqualTo(SUNDAY); + expected = LocalDateTime.of(2021, 8, 2, 0, 0); + assertThat(expected.getDayOfWeek()).isEqualTo(MONDAY); + actual = expression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); + assertThat(actual).is(weekday); } @Test @@ -1317,6 +1336,14 @@ public void daylightSaving() { actual = cronExpression.next(last); assertThat(actual).isNotNull(); assertThat(actual).isEqualTo(expected); + + cronExpression = CronExpression.parse("0 5 0 * * *"); + + last = ZonedDateTime.parse("2021-03-28T01:00:00+01:00[Europe/Amsterdam]"); + expected = ZonedDateTime.parse("2021-03-29T00:05+02:00[Europe/Amsterdam]"); + actual = cronExpression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); } @Test diff --git a/spring-context/src/test/java/org/springframework/scripting/groovy/MyBytecodeProcessor.java b/spring-context/src/test/java/org/springframework/scripting/groovy/MyBytecodeProcessor.java index fc73d71c77fb..48aac053ca21 100644 --- a/spring-context/src/test/java/org/springframework/scripting/groovy/MyBytecodeProcessor.java +++ b/spring-context/src/test/java/org/springframework/scripting/groovy/MyBytecodeProcessor.java @@ -26,7 +26,7 @@ */ public class MyBytecodeProcessor implements BytecodeProcessor { - public final Set processed = new HashSet(); + public final Set processed = new HashSet<>(); @Override public byte[] processBytecode(String name, byte[] original) { diff --git a/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java b/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java index 376d8a131072..93669ec3b215 100644 --- a/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java +++ b/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java @@ -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. @@ -17,8 +17,6 @@ package org.springframework.ui; import java.io.Serializable; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; @@ -281,12 +279,7 @@ public void testRawJdkProxy() throws Exception { Object proxy = Proxy.newProxyInstance( getClass().getClassLoader(), new Class[] {Map.class}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) { - return "proxy"; - } - }); + (proxy1, method, args) -> "proxy"); map.addAttribute(proxy); assertThat(map.get("map")).isSameAs(proxy); } diff --git a/spring-context/src/test/java/test/mixin/LockMixin.java b/spring-context/src/test/java/test/mixin/LockMixin.java index 96a78ad11314..12915b678b34 100644 --- a/spring-context/src/test/java/test/mixin/LockMixin.java +++ b/spring-context/src/test/java/test/mixin/LockMixin.java @@ -61,8 +61,9 @@ public boolean locked() { */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { - if (locked() && invocation.getMethod().getName().indexOf("set") == 0) + if (locked() && invocation.getMethod().getName().indexOf("set") == 0) { throw new LockedException(); + } return super.invoke(invocation); } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/SimpleMapScope.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/SimpleMapScope.java index 3b29b783bfd1..6a41c4fec173 100644 --- a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/SimpleMapScope.java +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/SimpleMapScope.java @@ -19,7 +19,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -75,8 +74,7 @@ public Object resolveContextualObject(String key) { } public void close() { - for (Iterator it = this.callbacks.iterator(); it.hasNext();) { - Runnable runnable = it.next(); + for (Runnable runnable : this.callbacks) { runnable.run(); } } diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index e4c568f84b13..69f78fc73060 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -76,6 +76,7 @@ jar { dependsOn cglibRepackJar from(zipTree(cglibRepackJar.archivePath)) { include "org/springframework/cglib/**" + exclude "org/springframework/cglib/beans/BeanMap*.class" exclude "org/springframework/cglib/core/AbstractClassGenerator*.class" exclude "org/springframework/cglib/core/AsmApi*.class" exclude "org/springframework/cglib/core/KeyFactory.class" diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java new file mode 100644 index 000000000000..3a6dd8e02057 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java @@ -0,0 +1,331 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cglib.beans; + +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.ClassVisitor; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.KeyFactory; +import org.springframework.cglib.core.ReflectUtils; + +/** + * A Map-based view of a JavaBean. The default set of keys is the + * union of all property names (getters or setters). An attempt to set + * a read-only property will be ignored, and write-only properties will + * be returned as null. Removal of objects is not a + * supported (the key set is fixed). + * @author Chris Nokleberg + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class BeanMap implements Map { + /** + * Limit the properties reflected in the key set of the map + * to readable properties. + * @see BeanMap.Generator#setRequire + */ + public static final int REQUIRE_GETTER = 1; + + /** + * Limit the properties reflected in the key set of the map + * to writable properties. + * @see BeanMap.Generator#setRequire + */ + public static final int REQUIRE_SETTER = 2; + + /** + * Helper method to create a new BeanMap. For finer + * control over the generated instance, use a new instance of + * BeanMap.Generator instead of this static method. + * @param bean the JavaBean underlying the map + * @return a new BeanMap instance + */ + public static BeanMap create(Object bean) { + Generator gen = new Generator(); + gen.setBean(bean); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(BeanMap.class.getName()); + + private static final BeanMapKey KEY_FACTORY = + (BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME); + + interface BeanMapKey { + public Object newInstance(Class type, int require); + } + + private Object bean; + private Class beanClass; + private int require; + + public Generator() { + super(SOURCE); + } + + /** + * Set the bean that the generated map should reflect. The bean may be swapped + * out for another bean of the same type using {@link #setBean}. + * Calling this method overrides any value previously set using {@link #setBeanClass}. + * You must call either this method or {@link #setBeanClass} before {@link #create}. + * @param bean the initial bean + */ + public void setBean(Object bean) { + this.bean = bean; + if (bean != null) { + beanClass = bean.getClass(); + setContextClass(beanClass); + } + } + + /** + * Set the class of the bean that the generated map should support. + * You must call either this method or {@link #setBeanClass} before {@link #create}. + * @param beanClass the class of the bean + */ + public void setBeanClass(Class beanClass) { + this.beanClass = beanClass; + } + + /** + * Limit the properties reflected by the generated map. + * @param require any combination of {@link #REQUIRE_GETTER} and + * {@link #REQUIRE_SETTER}; default is zero (any property allowed) + */ + public void setRequire(int require) { + this.require = require; + } + + protected ClassLoader getDefaultClassLoader() { + return beanClass.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(beanClass); + } + + /** + * Create a new instance of the BeanMap. An existing + * generated class will be reused if possible. + */ + public BeanMap create() { + if (beanClass == null) + throw new IllegalArgumentException("Class of bean unknown"); + setNamePrefix(beanClass.getName()); + return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require)); + } + + public void generateClass(ClassVisitor v) throws Exception { + new BeanMapEmitter(v, getClassName(), beanClass, require); + } + + protected Object firstInstance(Class type) { + return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean); + } + + protected Object nextInstance(Object instance) { + return ((BeanMap)instance).newInstance(bean); + } + } + + /** + * Create a new BeanMap instance using the specified bean. + * This is faster than using the {@link #create} static method. + * @param bean the JavaBean underlying the map + * @return a new BeanMap instance + */ + abstract public BeanMap newInstance(Object bean); + + /** + * Get the type of a property. + * @param name the name of the JavaBean property + * @return the type of the property, or null if the property does not exist + */ + abstract public Class getPropertyType(String name); + + protected Object bean; + + protected BeanMap() { + } + + protected BeanMap(Object bean) { + setBean(bean); + } + + public Object get(Object key) { + return get(bean, key); + } + + public Object put(Object key, Object value) { + return put(bean, key, value); + } + + /** + * Get the property of a bean. This allows a BeanMap + * to be used statically for multiple beans--the bean instance tied to the + * map is ignored and the bean passed to this method is used instead. + * @param bean the bean to query; must be compatible with the type of + * this BeanMap + * @param key must be a String + * @return the current value, or null if there is no matching property + */ + abstract public Object get(Object bean, Object key); + + /** + * Set the property of a bean. This allows a BeanMap + * to be used statically for multiple beans--the bean instance tied to the + * map is ignored and the bean passed to this method is used instead. + * @param key must be a String + * @return the old value, if there was one, or null + */ + abstract public Object put(Object bean, Object key, Object value); + + /** + * Change the underlying bean this map should use. + * @param bean the new JavaBean + * @see #getBean + */ + public void setBean(Object bean) { + this.bean = bean; + } + + /** + * Return the bean currently in use by this map. + * @return the current JavaBean + * @see #setBean + */ + public Object getBean() { + return bean; + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(Object key) { + return keySet().contains(key); + } + + public boolean containsValue(Object value) { + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object v = get(it.next()); + if (((value == null) && (v == null)) || (value != null && value.equals(v))) + return true; + } + return false; + } + + public int size() { + return keySet().size(); + } + + public boolean isEmpty() { + return size() == 0; + } + + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + public void putAll(Map t) { + for (Iterator it = t.keySet().iterator(); it.hasNext();) { + Object key = it.next(); + put(key, t.get(key)); + } + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof Map)) { + return false; + } + Map other = (Map)o; + if (size() != other.size()) { + return false; + } + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + if (!other.containsKey(key)) { + return false; + } + Object v1 = get(key); + Object v2 = other.get(key); + if (!((v1 == null) ? v2 == null : v1.equals(v2))) { + return false; + } + } + return true; + } + + public int hashCode() { + int code = 0; + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + Object value = get(key); + code += ((key == null) ? 0 : key.hashCode()) ^ + ((value == null) ? 0 : value.hashCode()); + } + return code; + } + + // TODO: optimize + public Set entrySet() { + HashMap copy = new HashMap(); + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + copy.put(key, get(key)); + } + return Collections.unmodifiableMap(copy).entrySet(); + } + + public Collection values() { + Set keys = keySet(); + List values = new ArrayList(keys.size()); + for (Iterator it = keys.iterator(); it.hasNext();) { + values.add(get(it.next())); + } + return Collections.unmodifiableCollection(values); + } + + /* + * @see java.util.AbstractMap#toString + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append('{'); + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + sb.append(key); + sb.append('='); + sb.append(get(key)); + if (it.hasNext()) { + sb.append(", "); + } + } + sb.append('}'); + return sb.toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/package-info.java b/spring-core/src/main/java/org/springframework/cglib/beans/package-info.java new file mode 100644 index 000000000000..51cffc06e70a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/beans/package-info.java @@ -0,0 +1,10 @@ +/** + * Spring's repackaging of the + * CGLIB beans package + * (for internal use only). + * + *

As this repackaging happens at the class file level, sources + * and javadocs are not available here... except for a few files + * that have been patched for Spring's purposes on JDK 9-17. + */ +package org.springframework.cglib.beans; diff --git a/spring-core/src/main/java/org/springframework/cglib/core/package-info.java b/spring-core/src/main/java/org/springframework/cglib/core/package-info.java index a2ed94ff2ead..6d43d8c8bcc2 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/package-info.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/package-info.java @@ -5,6 +5,6 @@ * *

As this repackaging happens at the class file level, sources * and javadocs are not available here... except for a few files - * that have been patched for Spring's purposes on JDK 9/10/11. + * that have been patched for Spring's purposes on JDK 9-17. */ package org.springframework.cglib.core; diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java b/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java index 0d651c8f0464..9f8cfe268e57 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java @@ -5,6 +5,6 @@ * *

As this repackaging happens at the class file level, sources * and javadocs are not available here... except for a few files - * that have been patched for Spring's purposes on JDK 9/10/11. + * that have been patched for Spring's purposes on JDK 9-17. */ package org.springframework.cglib.proxy; diff --git a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java index 50aec2deb3a6..6770f01c0a04 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -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. @@ -25,6 +25,7 @@ import kotlin.reflect.KClassifier; import kotlin.reflect.KFunction; import kotlin.reflect.full.KCallables; +import kotlin.reflect.jvm.KCallablesJvm; import kotlin.reflect.jvm.ReflectJvmMapping; import kotlinx.coroutines.BuildersKt; import kotlinx.coroutines.CoroutineStart; @@ -70,6 +71,9 @@ public static Deferred monoToDeferred(Mono source) { */ public static Publisher invokeSuspendingFunction(Method method, Object target, Object... args) { KFunction function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method)); + if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) { + KCallablesJvm.setAccessible(function, true); + } KClassifier classifier = function.getReturnType().getClassifier(); Mono mono = MonoKt.mono(Dispatchers.getUnconfined(), (scope, continuation) -> KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation)) diff --git a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java index 1eee8f2c3aa2..15c93e742639 100644 --- a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java @@ -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. @@ -32,6 +32,7 @@ /** * Simple implementation of the {@link AliasRegistry} interface. + * *

Serves as base class for * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} * implementations. @@ -101,8 +102,8 @@ protected boolean allowAliasOverriding() { */ public boolean hasAlias(String name, String alias) { String registeredName = this.aliasMap.get(alias); - return ObjectUtils.nullSafeEquals(registeredName, name) || (registeredName != null - && hasAlias(name, registeredName)); + return ObjectUtils.nullSafeEquals(registeredName, name) || + (registeredName != null && hasAlias(name, registeredName)); } @Override diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java index ef87e2d066fd..77302db78ae9 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java @@ -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. @@ -82,8 +82,8 @@ final class AnnotationTypeMapping { private final Set claimedAliases = new HashSet<>(); - AnnotationTypeMapping(@Nullable AnnotationTypeMapping source, - Class annotationType, @Nullable Annotation annotation) { + AnnotationTypeMapping(@Nullable AnnotationTypeMapping source, Class annotationType, + @Nullable Annotation annotation, Set> visitedAnnotationTypes) { this.source = source; this.root = (source != null ? source.getRoot() : this); @@ -103,7 +103,7 @@ final class AnnotationTypeMapping { processAliases(); addConventionMappings(); addConventionAnnotationValues(); - this.synthesizable = computeSynthesizableFlag(); + this.synthesizable = computeSynthesizableFlag(visitedAnnotationTypes); } @@ -311,7 +311,10 @@ private boolean isBetterConventionAnnotationValue(int index, boolean isValueAttr } @SuppressWarnings("unchecked") - private boolean computeSynthesizableFlag() { + private boolean computeSynthesizableFlag(Set> visitedAnnotationTypes) { + // Track that we have visited the current annotation type. + visitedAnnotationTypes.add(this.annotationType); + // Uses @AliasFor for local aliases? for (int index : this.aliasMappings) { if (index != -1) { @@ -340,9 +343,15 @@ private boolean computeSynthesizableFlag() { if (type.isAnnotation() || (type.isArray() && type.getComponentType().isAnnotation())) { Class annotationType = (Class) (type.isAnnotation() ? type : type.getComponentType()); - AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0); - if (mapping.isSynthesizable()) { - return true; + // Ensure we have not yet visited the current nested annotation type, in order + // to avoid infinite recursion for JVM languages other than Java that support + // recursive annotation definitions. + if (visitedAnnotationTypes.add(annotationType)) { + AnnotationTypeMapping mapping = + AnnotationTypeMappings.forAnnotationType(annotationType, visitedAnnotationTypes).get(0); + if (mapping.isSynthesizable()) { + return true; + } } } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java index 7bd535086ee5..a676583d570f 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java @@ -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. @@ -20,8 +20,10 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.springframework.lang.Nullable; import org.springframework.util.ConcurrentReferenceHashMap; @@ -40,6 +42,7 @@ * be searched once, regardless of how many times they are actually used. * * @author Phillip Webb + * @author Sam Brannen * @since 5.2 * @see AnnotationTypeMapping */ @@ -60,19 +63,22 @@ final class AnnotationTypeMappings { private AnnotationTypeMappings(RepeatableContainers repeatableContainers, - AnnotationFilter filter, Class annotationType) { + AnnotationFilter filter, Class annotationType, + Set> visitedAnnotationTypes) { this.repeatableContainers = repeatableContainers; this.filter = filter; this.mappings = new ArrayList<>(); - addAllMappings(annotationType); + addAllMappings(annotationType, visitedAnnotationTypes); this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet); } - private void addAllMappings(Class annotationType) { + private void addAllMappings(Class annotationType, + Set> visitedAnnotationTypes) { + Deque queue = new ArrayDeque<>(); - addIfPossible(queue, null, annotationType, null); + addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes); while (!queue.isEmpty()) { AnnotationTypeMapping mapping = queue.removeFirst(); this.mappings.add(mapping); @@ -102,14 +108,15 @@ private void addMetaAnnotationsToQueue(Deque queue, Annot } private void addIfPossible(Deque queue, AnnotationTypeMapping source, Annotation ann) { - addIfPossible(queue, source, ann.annotationType(), ann); + addIfPossible(queue, source, ann.annotationType(), ann, new HashSet<>()); } private void addIfPossible(Deque queue, @Nullable AnnotationTypeMapping source, - Class annotationType, @Nullable Annotation ann) { + Class annotationType, @Nullable Annotation ann, + Set> visitedAnnotationTypes) { try { - queue.addLast(new AnnotationTypeMapping(source, annotationType, ann)); + queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes)); } catch (Exception ex) { AnnotationUtils.rethrowAnnotationConfigurationException(ex); @@ -166,20 +173,22 @@ AnnotationTypeMapping get(int index) { * @return type mappings for the annotation type */ static AnnotationTypeMappings forAnnotationType(Class annotationType) { - return forAnnotationType(annotationType, AnnotationFilter.PLAIN); + return forAnnotationType(annotationType, new HashSet<>()); } /** * Create {@link AnnotationTypeMappings} for the specified annotation type. * @param annotationType the source annotation type - * @param annotationFilter the annotation filter used to limit which - * annotations are considered + * @param visitedAnnotationTypes the set of annotations that we have already + * visited; used to avoid infinite recursion for recursive annotations which + * some JVM languages support (such as Kotlin) * @return type mappings for the annotation type */ - static AnnotationTypeMappings forAnnotationType( - Class annotationType, AnnotationFilter annotationFilter) { + static AnnotationTypeMappings forAnnotationType(Class annotationType, + Set> visitedAnnotationTypes) { - return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(), annotationFilter); + return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(), + AnnotationFilter.PLAIN, visitedAnnotationTypes); } /** @@ -194,15 +203,34 @@ static AnnotationTypeMappings forAnnotationType( static AnnotationTypeMappings forAnnotationType(Class annotationType, RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) { + return forAnnotationType(annotationType, repeatableContainers, annotationFilter, new HashSet<>()); + } + + /** + * Create {@link AnnotationTypeMappings} for the specified annotation type. + * @param annotationType the source annotation type + * @param repeatableContainers the repeatable containers that may be used by + * the meta-annotations + * @param annotationFilter the annotation filter used to limit which + * annotations are considered + * @param visitedAnnotationTypes the set of annotations that we have already + * visited; used to avoid infinite recursion for recursive annotations which + * some JVM languages support (such as Kotlin) + * @return type mappings for the annotation type + */ + private static AnnotationTypeMappings forAnnotationType(Class annotationType, + RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter, + Set> visitedAnnotationTypes) { + if (repeatableContainers == RepeatableContainers.standardRepeatables()) { return standardRepeatablesCache.computeIfAbsent(annotationFilter, - key -> new Cache(repeatableContainers, key)).get(annotationType); + key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes); } if (repeatableContainers == RepeatableContainers.none()) { return noRepeatablesCache.computeIfAbsent(annotationFilter, - key -> new Cache(repeatableContainers, key)).get(annotationType); + key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes); } - return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType); + return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType, visitedAnnotationTypes); } static void clearCache() { @@ -235,14 +263,21 @@ private static class Cache { /** * Get or create {@link AnnotationTypeMappings} for the specified annotation type. * @param annotationType the annotation type + * @param visitedAnnotationTypes the set of annotations that we have already + * visited; used to avoid infinite recursion for recursive annotations which + * some JVM languages support (such as Kotlin) * @return a new or existing {@link AnnotationTypeMappings} instance */ - AnnotationTypeMappings get(Class annotationType) { - return this.mappings.computeIfAbsent(annotationType, this::createMappings); + AnnotationTypeMappings get(Class annotationType, + Set> visitedAnnotationTypes) { + + return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes)); } - AnnotationTypeMappings createMappings(Class annotationType) { - return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType); + private AnnotationTypeMappings createMappings(Class annotationType, + Set> visitedAnnotationTypes) { + + return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType, visitedAnnotationTypes); } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java index f5c06ff051e2..caf177214884 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java @@ -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. @@ -32,6 +32,7 @@ * with consistent ordering as well as a few useful utility methods. * * @author Phillip Webb + * @author Sam Brannen * @since 5.2 */ final class AttributeMethods { @@ -71,10 +72,10 @@ private AttributeMethods(@Nullable Class annotationType, M for (int i = 0; i < attributeMethods.length; i++) { Method method = this.attributeMethods[i]; Class type = method.getReturnType(); - if (method.getDefaultValue() != null) { + if (!foundDefaultValueMethod && (method.getDefaultValue() != null)) { foundDefaultValueMethod = true; } - if (type.isAnnotation() || (type.isArray() && type.getComponentType().isAnnotation())) { + if (!foundNestedAnnotation && (type.isAnnotation() || (type.isArray() && type.getComponentType().isAnnotation()))) { foundNestedAnnotation = true; } ReflectionUtils.makeAccessible(method); diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java index 0201e15e2698..5d3f9a40949f 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java @@ -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. @@ -177,7 +177,7 @@ private int getValueHashCode(Object value) { private String annotationToString() { String string = this.string; if (string == null) { - StringBuilder builder = new StringBuilder("@").append(this.type.getName()).append('('); + StringBuilder builder = new StringBuilder("@").append(getName(this.type)).append('('); for (int i = 0; i < this.attributes.size(); i++) { Method attribute = this.attributes.get(i); if (i > 0) { @@ -194,19 +194,53 @@ private String annotationToString() { return string; } + /** + * This method currently does not address the following issues which we may + * choose to address at a later point in time. + * + *

    + *
  • non-ASCII, non-visible, and non-printable characters within a character + * or String literal are not escaped.
  • + *
  • formatting for float and double values does not take into account whether + * a value is not a number (NaN) or infinite.
  • + *
+ * @param value the attribute value to format + * @return the formatted string representation + */ private String toString(Object value) { + if (value instanceof String) { + return '"' + value.toString() + '"'; + } + if (value instanceof Character) { + return '\'' + value.toString() + '\''; + } + if (value instanceof Byte) { + return String.format("(byte) 0x%02X", value); + } + if (value instanceof Long) { + return Long.toString(((Long) value)) + 'L'; + } + if (value instanceof Float) { + return Float.toString(((Float) value)) + 'f'; + } + if (value instanceof Double) { + return Double.toString(((Double) value)) + 'd'; + } + if (value instanceof Enum) { + return ((Enum) value).name(); + } if (value instanceof Class) { - return ((Class) value).getName(); + return getName((Class) value) + ".class"; } if (value.getClass().isArray()) { - StringBuilder builder = new StringBuilder("["); + StringBuilder builder = new StringBuilder("{"); for (int i = 0; i < Array.getLength(value); i++) { if (i > 0) { builder.append(", "); } builder.append(toString(Array.get(value, i))); } - builder.append(']'); + builder.append('}'); return builder.toString(); } return String.valueOf(value); @@ -271,6 +305,11 @@ static A createProxy(MergedAnnotation annotation, Clas return (A) Proxy.newProxyInstance(classLoader, interfaces, handler); } + private static String getName(Class clazz) { + String canonicalName = clazz.getCanonicalName(); + return (canonicalName != null ? canonicalName : clazz.getName()); + } + private static boolean isVisible(ClassLoader classLoader, Class interfaceClass) { if (classLoader == interfaceClass.getClassLoader()) { diff --git a/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java index 12ddd769cd62..d4cf0324bcad 100644 --- a/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java @@ -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. @@ -21,8 +21,7 @@ /** * Extended interface for asynchronous {@link TaskExecutor} implementations, - * offering an overloaded {@link #execute(Runnable, long)} variant with a start - * timeout parameter as well support for {@link java.util.concurrent.Callable}. + * offering support for {@link java.util.concurrent.Callable}. * *

Note: The {@link java.util.concurrent.Executors} class includes a set of * methods that can convert some other common closure-like objects, for example, @@ -41,10 +40,18 @@ */ public interface AsyncTaskExecutor extends TaskExecutor { - /** Constant that indicates immediate execution. */ + /** + * Constant that indicates immediate execution. + * @deprecated as of 5.3.16 along with {@link #execute(Runnable, long)} + */ + @Deprecated long TIMEOUT_IMMEDIATE = 0; - /** Constant that indicates no time limit. */ + /** + * Constant that indicates no time limit. + * @deprecated as of 5.3.16 along with {@link #execute(Runnable, long)} + */ + @Deprecated long TIMEOUT_INDEFINITE = Long.MAX_VALUE; @@ -58,7 +65,10 @@ public interface AsyncTaskExecutor extends TaskExecutor { * @throws TaskTimeoutException in case of the task being rejected because * of the timeout (i.e. it cannot be started in time) * @throws TaskRejectedException if the given task was not accepted + * @see #execute(Runnable) + * @deprecated as of 5.3.16 since the common executors do not support start timeouts */ + @Deprecated void execute(Runnable task, long startTimeout); /** diff --git a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java index 7d96032ad80b..072502a868c9 100644 --- a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java @@ -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. @@ -174,6 +174,7 @@ public final boolean isThrottleActive() { * if configured (through the superclass's settings). * @see #doExecute(Runnable) */ + @SuppressWarnings("deprecation") @Override public void execute(Runnable task) { execute(task, TIMEOUT_INDEFINITE); @@ -188,6 +189,7 @@ public void execute(Runnable task) { * @see #TIMEOUT_IMMEDIATE * @see #doExecute(Runnable) */ + @Deprecated @Override public void execute(Runnable task, long startTimeout) { Assert.notNull(task, "Runnable must not be null"); @@ -201,6 +203,7 @@ public void execute(Runnable task, long startTimeout) { } } + @SuppressWarnings("deprecation") @Override public Future submit(Runnable task) { FutureTask future = new FutureTask<>(task, null); @@ -208,6 +211,7 @@ public Future submit(Runnable task) { return future; } + @SuppressWarnings("deprecation") @Override public Future submit(Callable task) { FutureTask future = new FutureTask<>(task); @@ -215,6 +219,7 @@ public Future submit(Callable task) { return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Runnable task) { ListenableFutureTask future = new ListenableFutureTask<>(task, null); @@ -222,6 +227,7 @@ public ListenableFuture submitListenable(Runnable task) { return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Callable task) { ListenableFutureTask future = new ListenableFutureTask<>(task); diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java b/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java index f6294c521360..e77d4df1bc8c 100644 --- a/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java +++ b/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java @@ -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. @@ -25,7 +25,6 @@ * @author Juergen Hoeller * @since 2.0.1 * @see TaskExecutor#execute(Runnable) - * @see TaskTimeoutException */ @SuppressWarnings("serial") public class TaskRejectedException extends RejectedExecutionException { diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java b/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java index 3352a622bce1..ddec24acb697 100644 --- a/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java +++ b/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java @@ -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,8 +23,9 @@ * @author Juergen Hoeller * @since 2.0.3 * @see AsyncTaskExecutor#execute(Runnable, long) - * @see TaskRejectedException + * @deprecated as of 5.3.16 since the common executors do not support start timeouts */ +@Deprecated @SuppressWarnings("serial") public class TaskTimeoutException extends TaskRejectedException { diff --git a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java index 81da48db8e02..72a485b4eda0 100644 --- a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java +++ b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java @@ -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. @@ -97,6 +97,7 @@ public void execute(Runnable task) { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index c9fa24824afb..0c1cccf1d638 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -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. @@ -1256,7 +1256,7 @@ public static boolean hasAtLeastOneMethodWithName(Class clazz, String methodN * (may be {@code null} or may not even implement the method) * @return the specific target method, or the original method if the * {@code targetClass} does not implement it - * @see #getInterfaceMethodIfPossible + * @see #getInterfaceMethodIfPossible(Method, Class) */ public static Method getMostSpecificMethod(Method method, @Nullable Class targetClass) { if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) { @@ -1289,28 +1289,54 @@ public static Method getMostSpecificMethod(Method method, @Nullable Class tar * @param method the method to be invoked, potentially from an implementation class * @return the corresponding interface method, or the original method if none found * @since 5.1 - * @see #getMostSpecificMethod + * @deprecated in favor of {@link #getInterfaceMethodIfPossible(Method, Class)} */ + @Deprecated public static Method getInterfaceMethodIfPossible(Method method) { + return getInterfaceMethodIfPossible(method, null); + } + + /** + * Determine a corresponding interface method for the given method handle, if possible. + *

This is particularly useful for arriving at a public exported type on Jigsaw + * which can be reflectively invoked without an illegal access warning. + * @param method the method to be invoked, potentially from an implementation class + * @param targetClass the target class to check for declared interfaces + * @return the corresponding interface method, or the original method if none found + * @since 5.3.16 + * @see #getMostSpecificMethod + */ + public static Method getInterfaceMethodIfPossible(Method method, @Nullable Class targetClass) { if (!Modifier.isPublic(method.getModifiers()) || method.getDeclaringClass().isInterface()) { return method; } - return interfaceMethodCache.computeIfAbsent(method, key -> { - Class current = key.getDeclaringClass(); - while (current != null && current != Object.class) { - Class[] ifcs = current.getInterfaces(); - for (Class ifc : ifcs) { - try { - return ifc.getMethod(key.getName(), key.getParameterTypes()); - } - catch (NoSuchMethodException ex) { - // ignore - } + // Try cached version of method in its declaring class + Method result = interfaceMethodCache.computeIfAbsent(method, + key -> findInterfaceMethodIfPossible(key, key.getDeclaringClass(), Object.class)); + if (result == method && targetClass != null) { + // No interface method found yet -> try given target class (possibly a subclass of the + // declaring class, late-binding a base class method to a subclass-declared interface: + // see e.g. HashMap.HashIterator.hasNext) + result = findInterfaceMethodIfPossible(method, targetClass, method.getDeclaringClass()); + } + return result; + } + + private static Method findInterfaceMethodIfPossible(Method method, Class startClass, Class endClass) { + Class current = startClass; + while (current != null && current != endClass) { + Class[] ifcs = current.getInterfaces(); + for (Class ifc : ifcs) { + try { + return ifc.getMethod(method.getName(), method.getParameterTypes()); + } + catch (NoSuchMethodException ex) { + // ignore } - current = current.getSuperclass(); } - return key; - }); + current = current.getSuperclass(); + } + return method; } /** diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index 2177481f0d30..274cb7188703 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -46,12 +46,12 @@ public abstract class ReflectionUtils { /** - * Pre-built MethodFilter that matches all non-bridge non-synthetic methods + * Pre-built {@link MethodFilter} that matches all non-bridge non-synthetic methods * which are not declared on {@code java.lang.Object}. * @since 3.0.5 */ public static final MethodFilter USER_DECLARED_METHODS = - (method -> !method.isBridge() && !method.isSynthetic()); + (method -> !method.isBridge() && !method.isSynthetic() && (method.getDeclaringClass() != Object.class)); /** * Pre-built FieldFilter that matches all non-static, non-final fields. @@ -354,7 +354,10 @@ public static void doWithMethods(Class clazz, MethodCallback mc) { * @throws IllegalStateException if introspection fails */ public static void doWithMethods(Class clazz, MethodCallback mc, @Nullable MethodFilter mf) { - // Keep backing up the inheritance hierarchy. + if (mf == USER_DECLARED_METHODS && clazz == Object.class) { + // nothing to introspect + return; + } Method[] methods = getDeclaredMethods(clazz, false); for (Method method : methods) { if (mf != null && !mf.matches(method)) { @@ -367,6 +370,7 @@ public static void doWithMethods(Class clazz, MethodCallback mc, @Nullable Me throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); } } + // Keep backing up the inheritance hierarchy. if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) { doWithMethods(clazz.getSuperclass(), mc, mf); } diff --git a/spring-core/src/main/java/org/springframework/util/SocketUtils.java b/spring-core/src/main/java/org/springframework/util/SocketUtils.java index 557173e7e59c..a100bc6d8685 100644 --- a/spring-core/src/main/java/org/springframework/util/SocketUtils.java +++ b/spring-core/src/main/java/org/springframework/util/SocketUtils.java @@ -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. @@ -32,13 +32,25 @@ *

Within this class, a TCP port refers to a port for a {@link ServerSocket}; * whereas, a UDP port refers to a port for a {@link DatagramSocket}. * + *

{@code SocketUtils} was introduced in Spring Framework 4.0, primarily to + * assist in writing integration tests which start an external server on an + * available random port. However, these utilities make no guarantee about the + * subsequent availability of a given port and are therefore unreliable. Instead + * of using {@code SocketUtils} to find an available local port for a server, it + * is recommended that you rely on a server's ability to start on a random port + * that it selects or is assigned by the operating system. To interact with that + * server, you should query the server for the port it is currently using. + * * @author Sam Brannen * @author Ben Hale * @author Arjen Poutsma * @author Gunnar Hillert * @author Gary Russell * @since 4.0 + * @deprecated as of Spring Framework 5.3.16, to be removed in 6.0; see + * {@link SocketUtils class-level Javadoc} for details. */ +@Deprecated public class SocketUtils { /** diff --git a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java index 19814c2b83f7..869f9ca051bb 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java +++ b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java @@ -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. @@ -58,7 +58,7 @@ public class XmlValidationModeDetector { /** - * The token in a XML document that declares the DTD to use for validation + * The token in an XML document that declares the DTD to use for validation * and thus that DTD validation is being used. */ private static final String DOCTYPE = "DOCTYPE"; @@ -82,20 +82,22 @@ public class XmlValidationModeDetector { /** * Detect the validation mode for the XML document in the supplied {@link InputStream}. - * Note that the supplied {@link InputStream} is closed by this method before returning. + *

Note that the supplied {@link InputStream} is closed by this method before returning. * @param inputStream the InputStream to parse * @throws IOException in case of I/O failure * @see #VALIDATION_DTD * @see #VALIDATION_XSD */ public int detectValidationMode(InputStream inputStream) throws IOException { + this.inComment = false; + // Peek into the file to look for DOCTYPE. try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { boolean isDtdValidated = false; String content; while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); - if (this.inComment || !StringUtils.hasText(content)) { + if (!StringUtils.hasText(content)) { continue; } if (hasDoctype(content)) { @@ -125,9 +127,11 @@ private boolean hasDoctype(String content) { } /** - * Does the supplied content contain an XML opening tag. If the parse state is currently - * in an XML comment then this method always returns false. It is expected that all comment - * tokens will have consumed for the supplied content before passing the remainder to this method. + * Determine if the supplied content contains an XML opening tag. + *

It is expected that all comment tokens will have been consumed for the + * supplied content before passing the remainder to this method. However, as + * a sanity check, if the parse state is currently in an XML comment this + * method always returns {@code false}. */ private boolean hasOpeningTag(String content) { if (this.inComment) { @@ -139,11 +143,10 @@ private boolean hasOpeningTag(String content) { } /** - * Consume all leading and trailing comments in the given String and return - * the remaining content, which may be empty since the supplied content might - * be all comment data. + * Consume all comments in the given String and return the remaining content, + * which may be empty since the supplied content might be all comment data. + *

This method takes the current "in comment" parsing state into account. */ - @Nullable private String consumeCommentTokens(String line) { int indexOfStartComment = line.indexOf(START_COMMENT); if (indexOfStartComment == -1 && !line.contains(END_COMMENT)) { @@ -152,21 +155,19 @@ private String consumeCommentTokens(String line) { String result = ""; String currLine = line; - if (indexOfStartComment >= 0) { + if (!this.inComment && (indexOfStartComment >= 0)) { result = line.substring(0, indexOfStartComment); currLine = line.substring(indexOfStartComment); } - while ((currLine = consume(currLine)) != null) { - if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) { - return result + currLine; - } + if ((currLine = consume(currLine)) != null) { + result += consumeCommentTokens(currLine); } - return null; + return result; } /** - * Consume the next comment token, update the "inComment" flag + * Consume the next comment token, update the "inComment" flag, * and return the remaining content. */ @Nullable @@ -183,14 +184,19 @@ private int startComment(String line) { return commentToken(line, START_COMMENT, true); } + /** + * Try to consume the {@link #END_COMMENT} token. + * @see #commentToken(String, String, boolean) + */ private int endComment(String line) { return commentToken(line, END_COMMENT, false); } /** * Try to consume the supplied token against the supplied content and update the - * in comment parse state to the supplied value. Returns the index into the content - * which is after the token or -1 if the token is not found. + * "in comment" parse state to the supplied value. + *

Returns the index into the content which is after the token or -1 if the + * token is not found. */ private int commentToken(String line, String token, boolean inCommentIfPresent) { int index = line.indexOf(token); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java index a6baf3a8e71d..742dc9244053 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java @@ -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. @@ -88,7 +88,7 @@ void forAnnotationTypeWhenHasRepeatingMetaAnnotationReturnsMapping() { @Test void forAnnotationTypeWhenRepeatableMetaAnnotationIsFiltered() { AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(WithRepeatedMetaAnnotations.class, - Repeating.class.getName()::equals); + RepeatableContainers.standardRepeatables(), Repeating.class.getName()::equals); assertThat(getAll(mappings)).flatExtracting(AnnotationTypeMapping::getAnnotationType) .containsExactly(WithRepeatedMetaAnnotations.class); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index e9e3586bb6a4..02ec8f0980e9 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java @@ -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. @@ -1700,7 +1700,7 @@ void synthesizeFromMapWithNestedMap() throws Exception { assertThat(componentScan).isNotNull(); assertThat(componentScan.value().pattern()).isEqualTo("*Foo"); Map map = MergedAnnotation.from(componentScan).asMap( - annotation -> new LinkedHashMap(), + annotation -> new LinkedHashMap<>(), Adapt.ANNOTATION_TO_MAP); Map filterMap = (Map) map.get("value"); assertThat(filterMap.get("pattern")).isEqualTo("*Foo"); @@ -1720,7 +1720,7 @@ void synthesizeFromMapWithNestedArrayOfMaps() throws Exception { ComponentScan.class); assertThat(componentScan).isNotNull(); Map map = MergedAnnotation.from(componentScan).asMap( - annotation -> new LinkedHashMap(), + annotation -> new LinkedHashMap<>(), Adapt.ANNOTATION_TO_MAP); Map[] filters = (Map[]) map.get("excludeFilters"); List patterns = Arrays.stream(filters).map( @@ -1861,20 +1861,52 @@ void toStringForSynthesizedAnnotations() throws Exception { Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute"); RequestMapping webMappingWithAliases = methodWithPath.getAnnotation(RequestMapping.class); assertThat(webMappingWithAliases).isNotNull(); + Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes"); RequestMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(RequestMapping.class); assertThat(methodWithPathAndValue).isNotNull(); + RequestMapping synthesizedWebMapping1 = MergedAnnotation.from(webMappingWithAliases).synthesize(); RequestMapping synthesizedWebMapping2 = MergedAnnotation.from(webMappingWithPathAndValue).synthesize(); + assertThat(webMappingWithAliases.toString()).isNotEqualTo(synthesizedWebMapping1.toString()); + + // The unsynthesized annotation for handleMappedWithSamePathAndValueAttributes() + // should produce almost the same toString() results as synthesized annotations for + // handleMappedWithPathAttribute() on Java 9 or higher; however, due to multiple changes + // in the JDK's toString() implementation for annotations in JDK 9, 14, and 19, + // we do not test the JDK implementation. + // assertToStringForWebMappingWithPathAndValue(webMappingWithPathAndValue); + assertToStringForWebMappingWithPathAndValue(synthesizedWebMapping1); assertToStringForWebMappingWithPathAndValue(synthesizedWebMapping2); } private void assertToStringForWebMappingWithPathAndValue(RequestMapping webMapping) { - String prefix = "@" + RequestMapping.class.getName() + "("; - assertThat(webMapping.toString()).startsWith(prefix).contains("value=[/test]", - "path=[/test]", "name=bar", "method=", "[GET, POST]").endsWith(")"); + assertThat(webMapping.toString()) + .startsWith("@org.springframework.core.annotation.MergedAnnotationsTests.RequestMapping(") + .contains( + // Strings + "value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"", + // Characters + "ch='X'", "chars={'X'}", + // Enums + "method={GET, POST}", + // Classes + "clazz=org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class", + "classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod[].class}", + // Bytes + "byteValue=(byte) 0xFF", "bytes={(byte) 0xFF}", + // Shorts + "shortValue=9876", "shorts={9876}", + // Longs + "longValue=42L", "longs={42L}", + // Floats + "floatValue=3.14f", "floats={3.14f}", + // Doubles + "doubleValue=99.999d", "doubles={99.999d}" + ) + .endsWith(")"); } @Test @@ -2941,7 +2973,17 @@ static class SubSubMyRepeatableWithAdditionalLocalDeclarationsClass } enum RequestMethod { - GET, POST + GET, + + POST; + + /** + * custom override to verify annotation toString() implementations. + */ + @Override + public String toString() { + return "method: " + name().toLowerCase(); + } } @Retention(RetentionPolicy.RUNTIME) @@ -2956,6 +2998,30 @@ enum RequestMethod { String[] path() default ""; RequestMethod[] method() default {}; + + // --------------------------------------------------------------------- + // All remaining attributes declare default values that are used solely + // for the purpose of testing the toString() implementations for annotations. + Class clazz() default RequestMethod.class; + Class[] classes() default {int[][].class, RequestMethod[].class}; + + char ch() default 'X'; + char[] chars() default {'X'}; + + byte byteValue() default (byte) 0xFF; + byte[] bytes() default {(byte) 0xFF}; + + short shortValue() default 9876; + short[] shorts() default {9876}; + + long longValue() default 42L; + long[] longs() default {42L}; + + float floatValue() default 3.14F; + float[] floats() default {3.14F}; + + double doubleValue() default 99.999D; + double[] doubles() default {99.999D}; } @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-core/src/test/java/org/springframework/core/convert/converter/ConvertingComparatorTests.java b/spring-core/src/test/java/org/springframework/core/convert/converter/ConvertingComparatorTests.java index 93eb3659a99c..b7903d069752 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/converter/ConvertingComparatorTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/converter/ConvertingComparatorTests.java @@ -140,7 +140,7 @@ public int compare(Integer o1, Integer o2) { assertThat(o2).isInstanceOf(Integer.class); this.called = true; return super.compare(o1, o2); - }; + } public void assertCalled() { assertThat(this.called).isTrue(); diff --git a/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java b/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java index b3d553ff5ce6..1be14c376474 100644 --- a/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java +++ b/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java @@ -48,7 +48,7 @@ void withElementFactory() throws Exception { @Test void withElementFactoryAndUserSuppliedBackingList() throws Exception { - doTestWithElementFactory(new AutoPopulatingList(new ArrayList<>(), new MockElementFactory())); + doTestWithElementFactory(new AutoPopulatingList<>(new ArrayList<>(), new MockElementFactory())); } private void doTestWithClass(AutoPopulatingList list) { diff --git a/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java b/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java index e50168ee4e9e..ca9c41147c41 100644 --- a/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java +++ b/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java @@ -51,7 +51,7 @@ */ class ConcurrentReferenceHashMapTests { - private static final Comparator NULL_SAFE_STRING_SORT = new NullSafeComparator( + private static final Comparator NULL_SAFE_STRING_SORT = new NullSafeComparator<>( new ComparableComparator(), true); private TestWeakConcurrentCache map = new TestWeakConcurrentCache<>(); diff --git a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java index 8ca69ddc9a05..bbb05b743a86 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -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. @@ -24,10 +24,10 @@ import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.tests.sample.objects.TestObject; +import org.springframework.util.ReflectionUtils.MethodFilter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -184,40 +184,58 @@ private void testValidCopy(TestObject src, TestObject dest) { } @Test - void doWithProtectedMethods() { + void doWithMethodsUsingProtectedFilter() { ListSavingMethodCallback mc = new ListSavingMethodCallback(); ReflectionUtils.doWithMethods(TestObject.class, mc, method -> Modifier.isProtected(method.getModifiers())); - assertThat(mc.getMethodNames().isEmpty()).isFalse(); - assertThat(mc.getMethodNames().contains("clone")).as("Must find protected method on Object").isTrue(); - assertThat(mc.getMethodNames().contains("finalize")).as("Must find protected method on Object").isTrue(); - assertThat(mc.getMethodNames().contains("hashCode")).as("Public, not protected").isFalse(); - assertThat(mc.getMethodNames().contains("absquatulate")).as("Public, not protected").isFalse(); + assertThat(mc.getMethodNames()) + .hasSizeGreaterThanOrEqualTo(2) + .as("Must find protected methods on Object").contains("clone", "finalize") + .as("Public, not protected").doesNotContain("hashCode", "absquatulate"); } @Test - void duplicatesFound() { + void doWithMethodsUsingUserDeclaredMethodsFilterStartingWithObject() { + ListSavingMethodCallback mc = new ListSavingMethodCallback(); + ReflectionUtils.doWithMethods(Object.class, mc, ReflectionUtils.USER_DECLARED_METHODS); + assertThat(mc.getMethodNames()).isEmpty(); + } + + @Test + void doWithMethodsUsingUserDeclaredMethodsFilterStartingWithTestObject() { + ListSavingMethodCallback mc = new ListSavingMethodCallback(); + ReflectionUtils.doWithMethods(TestObject.class, mc, ReflectionUtils.USER_DECLARED_METHODS); + assertThat(mc.getMethodNames()) + .as("user declared methods").contains("absquatulate", "compareTo", "getName", "setName", "getAge", "setAge", "getSpouse", "setSpouse") + .as("methods on Object").doesNotContain("equals", "hashCode", "toString", "clone", "finalize", "getClass", "notify", "notifyAll", "wait"); + } + + @Test + void doWithMethodsUsingUserDeclaredMethodsComposedFilter() { + ListSavingMethodCallback mc = new ListSavingMethodCallback(); + // "q" because both absquatulate() and equals() contain "q" + MethodFilter isSetterMethodOrNameContainsQ = m -> m.getName().startsWith("set") || m.getName().contains("q"); + MethodFilter methodFilter = ReflectionUtils.USER_DECLARED_METHODS.and(isSetterMethodOrNameContainsQ); + ReflectionUtils.doWithMethods(TestObject.class, mc, methodFilter); + assertThat(mc.getMethodNames()).containsExactlyInAnyOrder("setName", "setAge", "setSpouse", "absquatulate"); + } + + @Test + void doWithMethodsFindsDuplicatesInClassHierarchy() { ListSavingMethodCallback mc = new ListSavingMethodCallback(); ReflectionUtils.doWithMethods(TestObjectSubclass.class, mc); - int absquatulateCount = 0; - for (String name : mc.getMethodNames()) { - if (name.equals("absquatulate")) { - ++absquatulateCount; - } - } - assertThat(absquatulateCount).as("Found 2 absquatulates").isEqualTo(2); + assertThat(mc.getMethodNames().stream()).filteredOn("absquatulate"::equals).as("Found 2 absquatulates").hasSize(2); } @Test - void findMethod() throws Exception { + void findMethod() { assertThat(ReflectionUtils.findMethod(B.class, "bar", String.class)).isNotNull(); assertThat(ReflectionUtils.findMethod(B.class, "foo", Integer.class)).isNotNull(); assertThat(ReflectionUtils.findMethod(B.class, "getClass")).isNotNull(); } - @Disabled("[SPR-8644] findMethod() does not currently support var-args") @Test - void findMethodWithVarArgs() throws Exception { - assertThat(ReflectionUtils.findMethod(B.class, "add", int.class, int.class, int.class)).isNotNull(); + void findMethodWithVarArgs() { + assertThat(ReflectionUtils.findMethod(B.class, "add", int[].class)).isNotNull(); } @Test @@ -259,37 +277,27 @@ public void m1() { } @Test - void getAllDeclaredMethods() throws Exception { + void getAllDeclaredMethods() { class Foo { @Override public String toString() { return super.toString(); } } - int toStringMethodCount = 0; - for (Method method : ReflectionUtils.getAllDeclaredMethods(Foo.class)) { - if (method.getName().equals("toString")) { - toStringMethodCount++; - } - } - assertThat(toStringMethodCount).isEqualTo(2); + Method[] allDeclaredMethods = ReflectionUtils.getAllDeclaredMethods(Foo.class); + assertThat(allDeclaredMethods).extracting(Method::getName).filteredOn("toString"::equals).hasSize(2); } @Test - void getUniqueDeclaredMethods() throws Exception { + void getUniqueDeclaredMethods() { class Foo { @Override public String toString() { return super.toString(); } } - int toStringMethodCount = 0; - for (Method method : ReflectionUtils.getUniqueDeclaredMethods(Foo.class)) { - if (method.getName().equals("toString")) { - toStringMethodCount++; - } - } - assertThat(toStringMethodCount).isEqualTo(1); + Method[] uniqueDeclaredMethods = ReflectionUtils.getUniqueDeclaredMethods(Foo.class); + assertThat(uniqueDeclaredMethods).extracting(Method::getName).filteredOn("toString"::equals).hasSize(1); } @Test @@ -306,16 +314,10 @@ public Integer m1() { return Integer.valueOf(42); } } - int m1MethodCount = 0; Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(Leaf.class); - for (Method method : methods) { - if (method.getName().equals("m1")) { - m1MethodCount++; - } - } - assertThat(m1MethodCount).isEqualTo(1); - assertThat(ObjectUtils.containsElement(methods, Leaf.class.getMethod("m1"))).isTrue(); - assertThat(ObjectUtils.containsElement(methods, Parent.class.getMethod("m1"))).isFalse(); + assertThat(methods).extracting(Method::getName).filteredOn("m1"::equals).hasSize(1); + assertThat(methods).contains(Leaf.class.getMethod("m1")); + assertThat(methods).doesNotContain(Parent.class.getMethod("m1")); } @Test @@ -389,8 +391,8 @@ void bar(String s) throws IllegalArgumentException { int add(int... args) { int sum = 0; - for (int i = 0; i < args.length; i++) { - sum += args[i]; + for (int arg : args) { + sum += arg; } return sum; } diff --git a/spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java b/spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java index d13e78e6c61b..c0f8748195be 100644 --- a/spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java @@ -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. @@ -28,8 +28,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.springframework.util.SocketUtils.PORT_RANGE_MAX; -import static org.springframework.util.SocketUtils.PORT_RANGE_MIN; /** * Unit tests for {@link SocketUtils}. @@ -37,6 +35,7 @@ * @author Sam Brannen * @author Gary Russell */ +@SuppressWarnings("deprecation") class SocketUtilsTests { @Test @@ -44,7 +43,7 @@ void canBeInstantiated() { // Just making sure somebody doesn't try to make SocketUtils abstract, // since that would be a breaking change due to the intentional public // constructor. - new SocketUtils(); + new org.springframework.util.SocketUtils(); } // TCP @@ -52,36 +51,37 @@ void canBeInstantiated() { @Test void findAvailableTcpPortWithZeroMinPort() { assertThatIllegalArgumentException().isThrownBy(() -> - SocketUtils.findAvailableTcpPort(0)); + org.springframework.util.SocketUtils.findAvailableTcpPort(0)); } @Test void findAvailableTcpPortWithNegativeMinPort() { assertThatIllegalArgumentException().isThrownBy(() -> - SocketUtils.findAvailableTcpPort(-500)); + org.springframework.util.SocketUtils.findAvailableTcpPort(-500)); } @Test void findAvailableTcpPort() { - int port = SocketUtils.findAvailableTcpPort(); - assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + int port = org.springframework.util.SocketUtils.findAvailableTcpPort(); + assertPortInRange(port, org.springframework.util.SocketUtils.PORT_RANGE_MIN, + org.springframework.util.SocketUtils.PORT_RANGE_MAX); } @Test void findAvailableTcpPortWithMinPortEqualToMaxPort() { - int minMaxPort = SocketUtils.findAvailableTcpPort(); - int port = SocketUtils.findAvailableTcpPort(minMaxPort, minMaxPort); + int minMaxPort = org.springframework.util.SocketUtils.findAvailableTcpPort(); + int port = org.springframework.util.SocketUtils.findAvailableTcpPort(minMaxPort, minMaxPort); assertThat(port).isEqualTo(minMaxPort); } @Test void findAvailableTcpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { - int port = SocketUtils.findAvailableTcpPort(); + int port = org.springframework.util.SocketUtils.findAvailableTcpPort(); try (ServerSocket socket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"))) { assertThat(socket).isNotNull(); // will only look for the exact port assertThatIllegalStateException().isThrownBy(() -> - SocketUtils.findAvailableTcpPort(port, port)) + org.springframework.util.SocketUtils.findAvailableTcpPort(port, port)) .withMessageStartingWith("Could not find an available TCP port") .withMessageEndingWith("after 1 attempts"); } @@ -89,15 +89,15 @@ void findAvailableTcpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exce @Test void findAvailableTcpPortWithMin() { - int port = SocketUtils.findAvailableTcpPort(50000); - assertPortInRange(port, 50000, PORT_RANGE_MAX); + int port = org.springframework.util.SocketUtils.findAvailableTcpPort(50000); + assertPortInRange(port, 50000, org.springframework.util.SocketUtils.PORT_RANGE_MAX); } @Test void findAvailableTcpPortInRange() { int minPort = 20000; int maxPort = minPort + 1000; - int port = SocketUtils.findAvailableTcpPort(minPort, maxPort); + int port = org.springframework.util.SocketUtils.findAvailableTcpPort(minPort, maxPort); assertPortInRange(port, minPort, maxPort); } @@ -133,29 +133,30 @@ void findAvailableTcpPortsWithRequestedNumberGreaterThanSizeOfRange() { @Test void findAvailableUdpPortWithZeroMinPort() { assertThatIllegalArgumentException().isThrownBy(() -> - SocketUtils.findAvailableUdpPort(0)); + org.springframework.util.SocketUtils.findAvailableUdpPort(0)); } @Test void findAvailableUdpPortWithNegativeMinPort() { assertThatIllegalArgumentException().isThrownBy(() -> - SocketUtils.findAvailableUdpPort(-500)); + org.springframework.util.SocketUtils.findAvailableUdpPort(-500)); } @Test void findAvailableUdpPort() { - int port = SocketUtils.findAvailableUdpPort(); - assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + int port = org.springframework.util.SocketUtils.findAvailableUdpPort(); + assertPortInRange(port, org.springframework.util.SocketUtils.PORT_RANGE_MIN, + org.springframework.util.SocketUtils.PORT_RANGE_MAX); } @Test void findAvailableUdpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { - int port = SocketUtils.findAvailableUdpPort(); + int port = org.springframework.util.SocketUtils.findAvailableUdpPort(); try (DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost"))) { assertThat(socket).isNotNull(); // will only look for the exact port assertThatIllegalStateException().isThrownBy(() -> - SocketUtils.findAvailableUdpPort(port, port)) + org.springframework.util.SocketUtils.findAvailableUdpPort(port, port)) .withMessageStartingWith("Could not find an available UDP port") .withMessageEndingWith("after 1 attempts"); } @@ -163,15 +164,15 @@ void findAvailableUdpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exce @Test void findAvailableUdpPortWithMin() { - int port = SocketUtils.findAvailableUdpPort(50000); - assertPortInRange(port, 50000, PORT_RANGE_MAX); + int port = org.springframework.util.SocketUtils.findAvailableUdpPort(50000); + assertPortInRange(port, 50000, org.springframework.util.SocketUtils.PORT_RANGE_MAX); } @Test void findAvailableUdpPortInRange() { int minPort = 20000; int maxPort = minPort + 1000; - int port = SocketUtils.findAvailableUdpPort(minPort, maxPort); + int port = org.springframework.util.SocketUtils.findAvailableUdpPort(minPort, maxPort); assertPortInRange(port, minPort, maxPort); } @@ -205,22 +206,24 @@ void findAvailableUdpPortsWithRequestedNumberGreaterThanSizeOfRange() { // Helpers private void findAvailableTcpPorts(int numRequested) { - SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested); - assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + SortedSet ports = org.springframework.util.SocketUtils.findAvailableTcpPorts(numRequested); + assertAvailablePorts(ports, numRequested, org.springframework.util.SocketUtils.PORT_RANGE_MIN, + org.springframework.util.SocketUtils.PORT_RANGE_MAX); } private void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { - SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort); + SortedSet ports = org.springframework.util.SocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort); assertAvailablePorts(ports, numRequested, minPort, maxPort); } private void findAvailableUdpPorts(int numRequested) { - SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested); - assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + SortedSet ports = org.springframework.util.SocketUtils.findAvailableUdpPorts(numRequested); + assertAvailablePorts(ports, numRequested, org.springframework.util.SocketUtils.PORT_RANGE_MIN, + org.springframework.util.SocketUtils.PORT_RANGE_MAX); } private void findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { - SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort); + SortedSet ports = org.springframework.util.SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort); assertAvailablePorts(ports, numRequested, minPort, maxPort); } private void assertPortInRange(int port, int minPort, int maxPort) { diff --git a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java index eaa318b59e9a..4609e6832cb8 100644 --- a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java @@ -620,14 +620,8 @@ void commaDelimitedListToStringArrayEmptyStrings() { } private void doTestCommaDelimitedListToStringArrayLegalMatch(String[] components) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < components.length; i++) { - if (i != 0) { - sb.append(','); - } - sb.append(components[i]); - } - String[] sa = StringUtils.commaDelimitedListToStringArray(sb.toString()); + String sb = String.join(String.valueOf(','), components); + String[] sa = StringUtils.commaDelimitedListToStringArray(sb); assertThat(sa != null).as("String array isn't null with legal match").isTrue(); assertThat(sa.length).as("String array length is correct with legal match").isEqualTo(components.length); assertThat(Arrays.equals(sa, components)).as("Output equals input").isTrue(); diff --git a/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java b/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java index 631a61d4f742..35a5e13222ca 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java +++ b/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java @@ -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. @@ -16,6 +16,7 @@ package org.springframework.util.xml; +import java.io.IOException; import java.io.InputStream; import org.junit.jupiter.params.ParameterizedTest; @@ -23,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.util.xml.XmlValidationModeDetector.VALIDATION_DTD; +import static org.springframework.util.xml.XmlValidationModeDetector.VALIDATION_XSD; /** * Unit tests for {@link XmlValidationModeDetector}. @@ -36,11 +38,36 @@ class XmlValidationModeDetectorTests { @ParameterizedTest - @ValueSource(strings = { "dtdWithTrailingComment.xml", "dtdWithLeadingComment.xml", "dtdWithCommentOnNextLine.xml", - "dtdWithMultipleComments.xml" }) + @ValueSource(strings = { + "dtdWithNoComments.xml", + "dtdWithLeadingComment.xml", + "dtdWithTrailingComment.xml", + "dtdWithTrailingCommentAcrossMultipleLines.xml", + "dtdWithCommentOnNextLine.xml", + "dtdWithMultipleComments.xml" + }) void dtdDetection(String fileName) throws Exception { - InputStream inputStream = getClass().getResourceAsStream(fileName); - assertThat(xmlValidationModeDetector.detectValidationMode(inputStream)).isEqualTo(VALIDATION_DTD); + assertValidationMode(fileName, VALIDATION_DTD); + } + + @ParameterizedTest + @ValueSource(strings = { + "xsdWithNoComments.xml", + "xsdWithMultipleComments.xml", + "xsdWithDoctypeInComment.xml", + "xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml" + }) + void xsdDetection(String fileName) throws Exception { + assertValidationMode(fileName, VALIDATION_XSD); + } + + + private void assertValidationMode(String fileName, int expectedValidationMode) throws IOException { + try (InputStream inputStream = getClass().getResourceAsStream(fileName)) { + assertThat(xmlValidationModeDetector.detectValidationMode(inputStream)) + .as("Validation Mode") + .isEqualTo(expectedValidationMode); + } } } diff --git a/spring-core/src/test/kotlin/org/springframework/core/annotation/Filter.kt b/spring-core/src/test/kotlin/org/springframework/core/annotation/Filter.kt new file mode 100644 index 000000000000..9a05b5ba1adc --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/Filter.kt @@ -0,0 +1,31 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation + +/** + * @author Sam Brannen + * @since 5.3.16 + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +public annotation class Filter( + + val value: String = "", + + val and: Filters = Filters() + +) diff --git a/spring-core/src/test/kotlin/org/springframework/core/annotation/Filters.kt b/spring-core/src/test/kotlin/org/springframework/core/annotation/Filters.kt new file mode 100644 index 000000000000..7a044fa2bb5c --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/Filters.kt @@ -0,0 +1,29 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation + +/** + * @author Sam Brannen + * @since 5.3.16 + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +public annotation class Filters( + + vararg val value: Filter + +) diff --git a/spring-core/src/test/kotlin/org/springframework/core/annotation/KotlinMergedAnnotationsTests.kt b/spring-core/src/test/kotlin/org/springframework/core/annotation/KotlinMergedAnnotationsTests.kt new file mode 100644 index 000000000000..fa32cfe3650d --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/KotlinMergedAnnotationsTests.kt @@ -0,0 +1,171 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.core.annotation.MergedAnnotations +import org.springframework.core.annotation.MergedAnnotation + +/** + * Tests for {@link MergedAnnotations} and {@link MergedAnnotation} in Kotlin. + * + * @author Sam Brannen + * @since 5.3.16 + */ +class KotlinMergedAnnotationsTests { + + @Test // gh-28012 + fun recursiveAnnotation() { + val method = javaClass.getMethod("personMethod") + + // MergedAnnotations + val mergedAnnotations = MergedAnnotations.from(method) + assertThat(mergedAnnotations.isPresent(Person::class.java)).isTrue(); + + // MergedAnnotation + val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(Person::class.java)) + assertThat(mergedAnnotation).isNotNull(); + + // NON-Synthesized Annotations + val jane = mergedAnnotation.synthesize() + assertThat(jane).isNotInstanceOf(SynthesizedAnnotation::class.java) + assertThat(jane.name).isEqualTo("jane") + val synthesizedFriends = jane.friends + assertThat(synthesizedFriends).hasSize(2) + + val john = synthesizedFriends[0] + assertThat(john).isNotInstanceOf(SynthesizedAnnotation::class.java) + assertThat(john.name).isEqualTo("john") + + val sally = synthesizedFriends[1] + assertThat(sally).isNotInstanceOf(SynthesizedAnnotation::class.java) + assertThat(sally.name).isEqualTo("sally") + } + + @Test // gh-28012 + fun recursiveAnnotationWithAttributeAliases() { + val method = javaClass.getMethod("synthesizablePersonMethod") + + // MergedAnnotations + val mergedAnnotations = MergedAnnotations.from(method) + assertThat(mergedAnnotations.isPresent(SynthesizablePerson::class.java)).isTrue(); + + // MergedAnnotation + val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(SynthesizablePerson::class.java)) + assertThat(mergedAnnotation).isNotNull(); + + // Synthesized Annotations + val jane = mergedAnnotation.synthesize() + assertThat(jane).isInstanceOf(SynthesizedAnnotation::class.java) + assertThat(jane.value).isEqualTo("jane") + assertThat(jane.name).isEqualTo("jane") + val synthesizedFriends = jane.friends + assertThat(synthesizedFriends).hasSize(2) + + val john = synthesizedFriends[0] + assertThat(john).isInstanceOf(SynthesizedAnnotation::class.java) + assertThat(john.value).isEqualTo("john") + assertThat(john.name).isEqualTo("john") + + val sally = synthesizedFriends[1] + assertThat(sally).isInstanceOf(SynthesizedAnnotation::class.java) + assertThat(sally.value).isEqualTo("sally") + assertThat(sally.name).isEqualTo("sally") + } + + @Test // gh-28012 + fun recursiveNestedAnnotation() { + val method = javaClass.getMethod("filterMethod") + + // MergedAnnotations + val mergedAnnotations = MergedAnnotations.from(method) + assertThat(mergedAnnotations.isPresent(Filter::class.java)).isTrue(); + + // MergedAnnotation + val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(Filter::class.java)) + assertThat(mergedAnnotation).isNotNull(); + + // NON-Synthesized Annotations + val fooFilter = mergedAnnotation.synthesize() + assertThat(fooFilter).isNotInstanceOf(SynthesizedAnnotation::class.java) + assertThat(fooFilter.value).isEqualTo("foo") + val filters = fooFilter.and + assertThat(filters.value).hasSize(2) + + val barFilter = filters.value[0] + assertThat(barFilter).isNotInstanceOf(SynthesizedAnnotation::class.java) + assertThat(barFilter.value).isEqualTo("bar") + assertThat(barFilter.and.value).isEmpty() + + val bazFilter = filters.value[1] + assertThat(bazFilter).isNotInstanceOf(SynthesizedAnnotation::class.java) + assertThat(bazFilter.value).isEqualTo("baz") + assertThat(bazFilter.and.value).isEmpty() + } + + @Test // gh-28012 + fun recursiveNestedAnnotationWithAttributeAliases() { + val method = javaClass.getMethod("synthesizableFilterMethod") + + // MergedAnnotations + val mergedAnnotations = MergedAnnotations.from(method) + assertThat(mergedAnnotations.isPresent(SynthesizableFilter::class.java)).isTrue(); + + // MergedAnnotation + val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(SynthesizableFilter::class.java)) + assertThat(mergedAnnotation).isNotNull(); + + // Synthesized Annotations + val fooFilter = mergedAnnotation.synthesize() + assertThat(fooFilter).isInstanceOf(SynthesizedAnnotation::class.java) + assertThat(fooFilter.value).isEqualTo("foo") + assertThat(fooFilter.name).isEqualTo("foo") + val filters = fooFilter.and + assertThat(filters.value).hasSize(2) + + val barFilter = filters.value[0] + assertThat(barFilter).isInstanceOf(SynthesizedAnnotation::class.java) + assertThat(barFilter.value).isEqualTo("bar") + assertThat(barFilter.name).isEqualTo("bar") + assertThat(barFilter.and.value).isEmpty() + + val bazFilter = filters.value[1] + assertThat(bazFilter).isInstanceOf(SynthesizedAnnotation::class.java) + assertThat(bazFilter.value).isEqualTo("baz") + assertThat(bazFilter.name).isEqualTo("baz") + assertThat(bazFilter.and.value).isEmpty() + } + + + @Person(name = "jane", friends = [Person(name = "john"), Person(name = "sally")]) + fun personMethod() { + } + + @SynthesizablePerson(name = "jane", friends = [SynthesizablePerson(name = "john"), SynthesizablePerson(name = "sally")]) + fun synthesizablePersonMethod() { + } + + @Filter("foo", and = Filters(Filter("bar"), Filter("baz"))) + fun filterMethod() { + } + + @SynthesizableFilter("foo", and = SynthesizableFilters(SynthesizableFilter("bar"), SynthesizableFilter("baz"))) + fun synthesizableFilterMethod() { + } + +} diff --git a/spring-core/src/test/kotlin/org/springframework/core/annotation/Person.kt b/spring-core/src/test/kotlin/org/springframework/core/annotation/Person.kt new file mode 100644 index 000000000000..4ae45e5feb0c --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/Person.kt @@ -0,0 +1,31 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation + +/** + * @author Sam Brannen + * @since 5.3.16 + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +public annotation class Person( + + val name: String = "", + + vararg val friends: Person = [] + +) diff --git a/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilter.kt b/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilter.kt new file mode 100644 index 000000000000..b10a970fdc6d --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilter.kt @@ -0,0 +1,35 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation + +/** + * @author Sam Brannen + * @since 5.3.16 + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +public annotation class SynthesizableFilter( + + @get:AliasFor("name") + val value: String = "", + + @get:AliasFor("value") + val name: String = "", + + val and: SynthesizableFilters = SynthesizableFilters() + +) diff --git a/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilters.kt b/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilters.kt new file mode 100644 index 000000000000..b961de1abe7a --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilters.kt @@ -0,0 +1,29 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation + +/** + * @author Sam Brannen + * @since 5.3.16 + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +public annotation class SynthesizableFilters( + + vararg val value: SynthesizableFilter + +) diff --git a/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizablePerson.kt b/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizablePerson.kt new file mode 100644 index 000000000000..01d6f870c7cb --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizablePerson.kt @@ -0,0 +1,35 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation + +/** + * @author Sam Brannen + * @since 5.3.16 + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +public annotation class SynthesizablePerson( + + @get:AliasFor("name") + val value: String = "", + + @get:AliasFor("value") + val name: String = "", + + vararg val friends: SynthesizablePerson = [] + +) diff --git a/spring-core/src/test/resources/org/springframework/util/xml/dtdWithNoComments.xml b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithNoComments.xml new file mode 100644 index 000000000000..299c52abef96 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithNoComments.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/dtdWithTrailingCommentAcrossMultipleLines.xml b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithTrailingCommentAcrossMultipleLines.xml new file mode 100644 index 000000000000..68776b4f61ff --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithTrailingCommentAcrossMultipleLines.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInComment.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInComment.xml new file mode 100644 index 000000000000..3f0fd05ed936 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInComment.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml new file mode 100644 index 000000000000..3d831932a9d9 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithMultipleComments.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithMultipleComments.xml new file mode 100644 index 000000000000..8843539e4ad4 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithMultipleComments.xml @@ -0,0 +1,10 @@ + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithNoComments.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithNoComments.xml new file mode 100644 index 000000000000..4f178c702f1c --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithNoComments.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java index 2de25448b470..5e4b50187f8c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java @@ -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. @@ -58,8 +58,18 @@ public class ReflectiveMethodExecutor implements MethodExecutor { * @param method the method to invoke */ public ReflectiveMethodExecutor(Method method) { + this(method, null); + } + + /** + * Create a new executor for the given method. + * @param method the method to invoke + * @param targetClass the target class to invoke the method on + * @since 5.3.16 + */ + public ReflectiveMethodExecutor(Method method, @Nullable Class targetClass) { this.originalMethod = method; - this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method); + this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method, targetClass); if (method.isVarArgs()) { this.varargsPosition = method.getParameterCount() - 1; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index 451aee275d99..86889dca73e2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -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. @@ -176,7 +176,7 @@ else if (paramCount == argumentTypes.size()) { } if (matchInfo != null) { if (matchInfo.isExactMatch()) { - return new ReflectiveMethodExecutor(method); + return new ReflectiveMethodExecutor(method, type); } else if (matchInfo.isCloseMatch()) { if (this.useDistance) { @@ -204,13 +204,13 @@ else if (matchInfo.isMatchRequiringConversion()) { } } if (closeMatch != null) { - return new ReflectiveMethodExecutor(closeMatch); + return new ReflectiveMethodExecutor(closeMatch, type); } else if (matchRequiringConversion != null) { if (multipleOptions) { throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name); } - return new ReflectiveMethodExecutor(matchRequiringConversion); + return new ReflectiveMethodExecutor(matchRequiringConversion, type); } else { return null; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 5fd48cdad88a..304645047db7 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -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. @@ -139,7 +139,7 @@ public boolean canRead(EvaluationContext context, @Nullable Object target, Strin // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -182,7 +182,7 @@ public TypedValue read(EvaluationContext context, @Nullable Object target, Strin // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); invoker = new InvokerPair(method, typeDescriptor); this.lastReadInvokerPair = invoker; this.readerCache.put(cacheKey, invoker); @@ -242,7 +242,7 @@ public boolean canWrite(EvaluationContext context, @Nullable Object target, Stri // Treat it like a property Property property = new Property(type, null, method); TypeDescriptor typeDescriptor = new TypeDescriptor(property); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); this.writerCache.put(cacheKey, method); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -291,7 +291,7 @@ public void write(EvaluationContext context, @Nullable Object target, String nam if (method == null) { method = findSetterForProperty(name, type, target); if (method != null) { - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); cachedMember = method; this.writerCache.put(cacheKey, cachedMember); } @@ -533,21 +533,21 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext context, @Nullab if (target == null) { return this; } - Class clazz = (target instanceof Class ? (Class) target : target.getClass()); - if (clazz.isArray()) { + Class type = (target instanceof Class ? (Class) target : target.getClass()); + if (type.isArray()) { return this; } - PropertyCacheKey cacheKey = new PropertyCacheKey(clazz, name, target instanceof Class); + PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); InvokerPair invocationTarget = this.readerCache.get(cacheKey); if (invocationTarget == null || invocationTarget.member instanceof Method) { Method method = (Method) (invocationTarget != null ? invocationTarget.member : null); if (method == null) { - method = findGetterForProperty(name, clazz, target); + method = findGetterForProperty(name, type, target); if (method != null) { TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1)); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); invocationTarget = new InvokerPair(method, typeDescriptor); ReflectionUtils.makeAccessible(method); this.readerCache.put(cacheKey, invocationTarget); @@ -561,7 +561,7 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext context, @Nullab if (invocationTarget == null || invocationTarget.member instanceof Field) { Field field = (invocationTarget != null ? (Field) invocationTarget.member : null); if (field == null) { - field = findField(name, clazz, target instanceof Class); + field = findField(name, type, target instanceof Class); if (field != null) { invocationTarget = new InvokerPair(field, new TypeDescriptor(field)); ReflectionUtils.makeAccessible(field); @@ -600,7 +600,7 @@ private static final class PropertyCacheKey implements Comparable clazz, String name, boolean targetIsClass) { this.clazz = clazz; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java index 446ef64c5e4a..5399e9c4741b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java @@ -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. @@ -273,7 +273,8 @@ protected String underscoreName(String name) { } StringBuilder result = new StringBuilder(); - for (int i = 0; i < name.length(); i++) { + result.append(Character.toLowerCase(name.charAt(0))); + for (int i = 1; i < name.length(); i++) { char c = name.charAt(i); if (Character.isUpperCase(c)) { result.append('_').append(Character.toLowerCase(c)); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java index 45262b8025ba..70e0083fb3f9 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java @@ -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. @@ -35,15 +35,17 @@ public interface DatabasePopulator { /** * Populate, initialize, or clean up the database using the provided JDBC * connection. + *

Warning: Concrete implementations should not close + * the provided {@link Connection}. *

Concrete implementations may throw an {@link SQLException} if * an error is encountered but are strongly encouraged to throw a * specific {@link ScriptException} instead. For example, Spring's * {@link ResourceDatabasePopulator} and {@link DatabasePopulatorUtils} wrap * all {@code SQLExceptions} in {@code ScriptExceptions}. - * @param connection the JDBC connection to use to populate the db; already - * configured and ready to use; never {@code null} + * @param connection the JDBC connection to use; already configured and + * ready to use; never {@code null} * @throws SQLException if an unrecoverable data access exception occurs - * during database population + * while interacting with the database * @throws ScriptException in all other error cases * @see DatabasePopulatorUtils#execute */ diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java index 601bbdfd7a1d..1c0a86ffed97 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java @@ -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. @@ -30,6 +30,7 @@ import org.springframework.jdbc.core.test.ConcretePerson; import org.springframework.jdbc.core.test.ConstructorPerson; import org.springframework.jdbc.core.test.DatePerson; +import org.springframework.jdbc.core.test.EmailPerson; import org.springframework.jdbc.core.test.Person; import org.springframework.jdbc.core.test.SpacePerson; import org.springframework.jdbc.datasource.SingleConnectionDataSource; @@ -97,8 +98,16 @@ private void verifyPersonViaBeanWrapper(Object person) { assertThat(bw.getPropertyValue("balance")).isEqualTo(new BigDecimal("1234.56")); } + protected void verifyPerson(EmailPerson 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.getBalance()).isEqualTo(new BigDecimal("1234.56")); + assertThat(person.getEMail()).isEqualTo("hello@world.info"); + } + - protected enum MockType {ONE, TWO, THREE}; + protected enum MockType {ONE, TWO, THREE} protected static class Mock { @@ -136,19 +145,22 @@ public Mock(MockType type) throws Exception { given(resultSet.getDate(3)).willReturn(new java.sql.Date(1221222L)); given(resultSet.getBigDecimal(4)).willReturn(new BigDecimal("1234.56")); given(resultSet.getObject(4)).willReturn(new BigDecimal("1234.56")); + given(resultSet.getString(5)).willReturn("hello@world.info"); given(resultSet.wasNull()).willReturn(type == MockType.TWO); - given(resultSetMetaData.getColumnCount()).willReturn(4); + given(resultSetMetaData.getColumnCount()).willReturn(5); 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(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); given(resultSet.findColumn("balance")).willReturn(4); + given(resultSet.findColumn("e_mail")).willReturn(5); jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(new SingleConnectionDataSource(connection, false)); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java index 6e1f84a632d5..99e9eb416274 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java @@ -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. @@ -19,85 +19,91 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.TypeMismatchException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.jdbc.core.test.ConcretePerson; import org.springframework.jdbc.core.test.DatePerson; +import org.springframework.jdbc.core.test.EmailPerson; import org.springframework.jdbc.core.test.ExtendedPerson; import org.springframework.jdbc.core.test.Person; import org.springframework.jdbc.core.test.SpacePerson; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; /** + * Tests for {@link BeanPropertyRowMapper}. + * * @author Thomas Risberg * @author Juergen Hoeller + * @author Sam Brannen */ -public class BeanPropertyRowMapperTests extends AbstractRowMapperTests { +class BeanPropertyRowMapperTests extends AbstractRowMapperTests { @Test @SuppressWarnings({"unchecked", "rawtypes"}) - public void testOverridingDifferentClassDefinedForMapping() { + void overridingDifferentClassDefinedForMapping() { BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class); assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> mapper.setMappedClass(Long.class)); } @Test - public void testOverridingSameClassDefinedForMapping() { + void overridingSameClassDefinedForMapping() { BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); - mapper.setMappedClass(Person.class); + assertThatNoException().isThrownBy(() -> mapper.setMappedClass(Person.class)); } @Test - public void testStaticQueryWithRowMapper() throws Exception { + void staticQueryWithRowMapper() throws Exception { Mock mock = new Mock(); List result = mock.getJdbcTemplate().query( "select name, age, birth_date, balance from people", new BeanPropertyRowMapper<>(Person.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); } @Test - public void testMappingWithInheritance() throws Exception { + void mappingWithInheritance() throws Exception { Mock mock = new Mock(); List result = mock.getJdbcTemplate().query( "select name, age, birth_date, balance from people", new BeanPropertyRowMapper<>(ConcretePerson.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); } @Test - public void testMappingWithNoUnpopulatedFieldsFound() throws Exception { + void mappingWithNoUnpopulatedFieldsFound() throws Exception { Mock mock = new Mock(); List result = mock.getJdbcTemplate().query( "select name, age, birth_date, balance from people", new BeanPropertyRowMapper<>(ConcretePerson.class, true)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); } @Test - public void testMappingWithUnpopulatedFieldsNotChecked() throws Exception { + void mappingWithUnpopulatedFieldsNotChecked() throws Exception { Mock mock = new Mock(); List result = mock.getJdbcTemplate().query( "select name, age, birth_date, balance from people", new BeanPropertyRowMapper<>(ExtendedPerson.class)); - assertThat(result.size()).isEqualTo(1); - ExtendedPerson bean = result.get(0); - verifyPerson(bean); + assertThat(result).hasSize(1); + verifyPerson(result.get(0)); mock.verifyClosed(); } @Test - public void testMappingWithUnpopulatedFieldsNotAccepted() throws Exception { + void mappingWithUnpopulatedFieldsNotAccepted() throws Exception { Mock mock = new Mock(); assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> mock.getJdbcTemplate().query("select name, age, birth_date, balance from people", @@ -105,7 +111,7 @@ public void testMappingWithUnpopulatedFieldsNotAccepted() throws Exception { } @Test - public void testMappingNullValue() throws Exception { + void mappingNullValue() throws Exception { BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); Mock mock = new Mock(MockType.TWO); assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() -> @@ -113,25 +119,50 @@ public void testMappingNullValue() throws Exception { } @Test - public void testQueryWithSpaceInColumnNameAndLocalDateTime() throws Exception { + void queryWithSpaceInColumnNameAndLocalDateTime() throws Exception { Mock mock = new Mock(MockType.THREE); List result = mock.getJdbcTemplate().query( "select last_name as \"Last Name\", age, birth_date, balance from people", new BeanPropertyRowMapper<>(SpacePerson.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); } @Test - public void testQueryWithSpaceInColumnNameAndLocalDate() throws Exception { + void queryWithSpaceInColumnNameAndLocalDate() throws Exception { Mock mock = new Mock(MockType.THREE); List result = mock.getJdbcTemplate().query( "select last_name as \"Last Name\", age, birth_date, balance from people", new BeanPropertyRowMapper<>(DatePerson.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); + verifyPerson(result.get(0)); + mock.verifyClosed(); + } + + @Test + void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception { + Mock mock = new Mock(); + List result = mock.getJdbcTemplate().query( + "select name, age, birth_date, balance, e_mail from people", + new BeanPropertyRowMapper<>(EmailPerson.class)); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); } + @ParameterizedTest + @CsvSource({ + "age, age", + "lastName, last_name", + "Name, name", + "FirstName, first_name", + "EMail, e_mail", + "URL, u_r_l", // likely undesirable, but that's the status quo + }) + void underscoreName(String input, String expected) { + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Object.class); + assertThat(mapper.underscoreName(input)).isEqualTo(expected); + } + } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java index 337f5f34a506..1470e9d6c977 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java @@ -22,7 +22,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; -import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.Map; @@ -160,12 +159,7 @@ public void testQueryForObjectWithRowMapper() throws Exception { String sql = "SELECT AGE FROM CUSTMR WHERE ID = 3"; given(this.resultSet.next()).willReturn(true, false); given(this.resultSet.getInt(1)).willReturn(22); - Object o = this.template.queryForObject(sql, new RowMapper() { - @Override - public Integer mapRow(ResultSet rs, int rowNum) throws SQLException { - return rs.getInt(1); - } - }); + Object o = this.template.queryForObject(sql, (RowMapper) (rs, rowNum) -> rs.getInt(1)); assertThat(o instanceof Integer).as("Correct result type").isTrue(); verify(this.resultSet).close(); verify(this.statement).close(); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java index 339bec58385f..456d59dd5bfd 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java @@ -152,12 +152,12 @@ public void testBogusUpdate() throws Exception { @Test public void testStringsWithStaticSql() throws Exception { - doTestStrings(null, null, null, null, (template, sql, rch) -> template.query(sql, rch)); + doTestStrings(null, null, null, null, JdbcTemplate::query); } @Test public void testStringsWithStaticSqlAndFetchSizeAndMaxRows() throws Exception { - doTestStrings(10, 20, 30, null, (template, sql, rch) -> template.query(sql, rch)); + doTestStrings(10, 20, 30, null, JdbcTemplate::query); } @Test @@ -268,28 +268,22 @@ public void testLeaveConnectionOpenOnRequest() throws Exception { @Test public void testConnectionCallback() throws Exception { - String result = this.template.execute(new ConnectionCallback() { - @Override - public String doInConnection(Connection con) { - assertThat(con instanceof ConnectionProxy).isTrue(); - assertThat(((ConnectionProxy) con).getTargetConnection()).isSameAs(JdbcTemplateTests.this.connection); - return "test"; - } + String result = this.template.execute((ConnectionCallback) con -> { + assertThat(con instanceof ConnectionProxy).isTrue(); + assertThat(((ConnectionProxy) con).getTargetConnection()).isSameAs(JdbcTemplateTests.this.connection); + return "test"; }); assertThat(result).isEqualTo("test"); } @Test public void testConnectionCallbackWithStatementSettings() throws Exception { - String result = this.template.execute(new ConnectionCallback() { - @Override - public String doInConnection(Connection con) throws SQLException { - PreparedStatement ps = con.prepareStatement("some SQL"); - ps.setFetchSize(10); - ps.setMaxRows(20); - ps.close(); - return "test"; - } + String result = this.template.execute((ConnectionCallback) con -> { + PreparedStatement ps = con.prepareStatement("some SQL"); + ps.setFetchSize(10); + ps.setMaxRows(20); + ps.close(); + return "test"; }); assertThat(result).isEqualTo("test"); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java index 25ccf1eaa870..1a2c61469908 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java @@ -20,7 +20,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; -import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; @@ -176,12 +175,7 @@ public void testQueryForObjectWithParamMapAndRowMapper() throws Exception { MapSqlParameterSource params = new MapSqlParameterSource(); params.addValue("id", 3); Object o = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id", - params, new RowMapper() { - @Override - public Object mapRow(ResultSet rs, int rowNum) throws SQLException { - return rs.getInt(1); - } - }); + params, (RowMapper) (rs, rowNum) -> rs.getInt(1)); boolean condition = o instanceof Integer; assertThat(condition).as("Correct result type").isTrue(); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/EmailPerson.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/EmailPerson.java new file mode 100644 index 000000000000..f9c6996f7ab8 --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/EmailPerson.java @@ -0,0 +1,78 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.core.test; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * @author Thomas Risberg + * @author Marten Deinum + */ +public class EmailPerson { + + private String name; + + private long age; + + private Date birth_date; + + private BigDecimal balance; + + private String eMail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getAge() { + return age; + } + + public void setAge(long age) { + this.age = age; + } + + public Date getBirth_date() { + return birth_date; + } + + public void setBirth_date(Date birth_date) { + this.birth_date = birth_date; + } + + public BigDecimal getBalance() { + return balance; + } + + public void setBalance(BigDecimal balance) { + this.balance = balance; + } + + public void setEMail(String email) { + this.eMail=email; + } + + public String getEMail() { + return this.eMail; + } + +} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java index ee7b13e1a73f..02441425f81f 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java @@ -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. @@ -46,9 +46,7 @@ import org.springframework.jdbc.core.SqlReturnResultSet; import org.springframework.jdbc.core.support.AbstractSqlTypeValue; import org.springframework.jdbc.datasource.ConnectionHolder; -import org.springframework.jdbc.support.SQLExceptionTranslator; import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; -import org.springframework.lang.Nullable; import org.springframework.transaction.support.TransactionSynchronizationManager; import static org.assertj.core.api.Assertions.assertThat; @@ -148,8 +146,7 @@ public void testAddInvoicesWithinTransaction() throws Exception { given(callableStatement.execute()).willReturn(false); given(callableStatement.getUpdateCount()).willReturn(-1); given(callableStatement.getObject(3)).willReturn(4); - given(connection.prepareCall("{call " + AddInvoice.SQL + "(?, ?, ?)}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + AddInvoice.SQL + "(?, ?, ?)}")).willReturn(callableStatement); TransactionSynchronizationManager.bindResource(dataSource, new ConnectionHolder(connection)); try { testAddInvoice(1106, 3); @@ -174,8 +171,7 @@ public void testStoredProcedureConfiguredViaJdbcTemplateWithCustomExceptionTrans given(callableStatement.execute()).willReturn(false); given(callableStatement.getUpdateCount()).willReturn(-1); given(callableStatement.getObject(2)).willReturn(5); - given(connection.prepareCall("{call " + StoredProcedureConfiguredViaJdbcTemplate.SQL + "(?, ?)}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureConfiguredViaJdbcTemplate.SQL + "(?, ?)}")).willReturn(callableStatement); class TestJdbcTemplate extends JdbcTemplate { @@ -210,8 +206,7 @@ public void testStoredProcedureConfiguredViaJdbcTemplate() throws Exception { given(callableStatement.execute()).willReturn(false); given(callableStatement.getUpdateCount()).willReturn(-1); given(callableStatement.getObject(2)).willReturn(4); - given(connection.prepareCall("{call " + StoredProcedureConfiguredViaJdbcTemplate.SQL + "(?, ?)}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureConfiguredViaJdbcTemplate.SQL + "(?, ?)}")).willReturn(callableStatement); JdbcTemplate t = new JdbcTemplate(); t.setDataSource(dataSource); StoredProcedureConfiguredViaJdbcTemplate sp = new StoredProcedureConfiguredViaJdbcTemplate(t); @@ -234,28 +229,24 @@ public void testNullArg() throws Exception { public void testUnnamedParameter() throws Exception { this.verifyClosedAfter = false; // Shouldn't succeed in creating stored procedure with unnamed parameter - assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> - new UnnamedParameterStoredProcedure(dataSource)); + assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) + .isThrownBy(() -> new UnnamedParameterStoredProcedure(dataSource)); } @Test public void testMissingParameter() throws Exception { this.verifyClosedAfter = false; MissingParameterStoredProcedure mp = new MissingParameterStoredProcedure(dataSource); - assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy( - mp::execute); + assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(mp::execute); } @Test public void testStoredProcedureExceptionTranslator() throws Exception { - SQLException sqlException = new SQLException( - "Syntax error or access violation exception", "42000"); + SQLException sqlException = new SQLException("Syntax error or access violation exception", "42000"); given(callableStatement.execute()).willThrow(sqlException); - given(connection.prepareCall("{call " + StoredProcedureExceptionTranslator.SQL + "()}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureExceptionTranslator.SQL + "()}")).willReturn(callableStatement); StoredProcedureExceptionTranslator sproc = new StoredProcedureExceptionTranslator(dataSource); - assertThatExceptionOfType(CustomDataException.class).isThrownBy( - sproc::execute); + assertThatExceptionOfType(CustomDataException.class).isThrownBy(sproc::execute); } @Test @@ -266,8 +257,7 @@ public void testStoredProcedureWithResultSet() throws Exception { given(callableStatement.getUpdateCount()).willReturn(-1); given(callableStatement.getResultSet()).willReturn(resultSet); given(callableStatement.getUpdateCount()).willReturn(-1); - given(connection.prepareCall("{call " + StoredProcedureWithResultSet.SQL + "()}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureWithResultSet.SQL + "()}")).willReturn(callableStatement); StoredProcedureWithResultSet sproc = new StoredProcedureWithResultSet(dataSource); sproc.execute(); assertThat(sproc.getCount()).isEqualTo(2); @@ -285,14 +275,11 @@ public void testStoredProcedureWithResultSetMapped() throws Exception { given(callableStatement.getResultSet()).willReturn(resultSet); given(callableStatement.getMoreResults()).willReturn(false); given(callableStatement.getUpdateCount()).willReturn(-1); - given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}")).willReturn(callableStatement); StoredProcedureWithResultSetMapped sproc = new StoredProcedureWithResultSetMapped(dataSource); Map res = sproc.execute(); List rs = (List) res.get("rs"); - assertThat(rs.size()).isEqualTo(2); - assertThat(rs.get(0)).isEqualTo("Foo"); - assertThat(rs.get(1)).isEqualTo("Bar"); + assertThat(rs).containsExactly("Foo", "Bar"); verify(resultSet).close(); } @@ -319,8 +306,7 @@ public void testStoredProcedureWithUndeclaredResults() throws Exception { given(callableStatement.getResultSet()).willReturn(resultSet1, resultSet2); given(callableStatement.getMoreResults()).willReturn(true, false, false); given(callableStatement.getUpdateCount()).willReturn(-1, -1, 0, -1); - given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}")).willReturn(callableStatement); StoredProcedureWithResultSetMapped sproc = new StoredProcedureWithResultSetMapped(dataSource); Map res = sproc.execute(); @@ -328,15 +314,12 @@ public void testStoredProcedureWithUndeclaredResults() throws Exception { assertThat(res.size()).as("incorrect number of returns").isEqualTo(3); List rs1 = (List) res.get("rs"); - assertThat(rs1.size()).isEqualTo(2); - assertThat(rs1.get(0)).isEqualTo("Foo"); - assertThat(rs1.get(1)).isEqualTo("Bar"); + assertThat(rs1).containsExactly("Foo", "Bar"); List rs2 = (List) res.get("#result-set-2"); assertThat(rs2.size()).isEqualTo(1); Object o2 = rs2.get(0); - boolean condition = o2 instanceof Map; - assertThat(condition).as("wron type returned for result set 2").isTrue(); + assertThat(o2).as("wron type returned for result set 2").isInstanceOf(Map.class); Map m2 = (Map) o2; assertThat(m2.get("spam")).isEqualTo("Spam"); assertThat(m2.get("eggs")).isEqualTo("Eggs"); @@ -351,12 +334,10 @@ public void testStoredProcedureWithUndeclaredResults() throws Exception { public void testStoredProcedureSkippingResultsProcessing() throws Exception { given(callableStatement.execute()).willReturn(true); given(callableStatement.getUpdateCount()).willReturn(-1); - given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}")).willReturn(callableStatement); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setSkipResultsProcessing(true); - StoredProcedureWithResultSetMapped sproc = new StoredProcedureWithResultSetMapped( - jdbcTemplate); + StoredProcedureWithResultSetMapped sproc = new StoredProcedureWithResultSetMapped(jdbcTemplate); Map res = sproc.execute(); assertThat(res.size()).as("incorrect number of returns").isEqualTo(0); } @@ -372,13 +353,11 @@ public void testStoredProcedureSkippingUndeclaredResults() throws Exception { given(callableStatement.getResultSet()).willReturn(resultSet); given(callableStatement.getMoreResults()).willReturn(true, false); given(callableStatement.getUpdateCount()).willReturn(-1, -1); - given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + StoredProcedureWithResultSetMapped.SQL + "()}")).willReturn(callableStatement); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setSkipUndeclaredResults(true); - StoredProcedureWithResultSetMapped sproc = new StoredProcedureWithResultSetMapped( - jdbcTemplate); + StoredProcedureWithResultSetMapped sproc = new StoredProcedureWithResultSetMapped(jdbcTemplate); Map res = sproc.execute(); assertThat(res.size()).as("incorrect number of returns").isEqualTo(1); @@ -394,8 +373,7 @@ public void testParameterMapper() throws Exception { given(callableStatement.execute()).willReturn(false); given(callableStatement.getUpdateCount()).willReturn(-1); given(callableStatement.getObject(2)).willReturn("OK"); - given(connection.prepareCall("{call " + ParameterMapperStoredProcedure.SQL + "(?, ?)}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + ParameterMapperStoredProcedure.SQL + "(?, ?)}")).willReturn(callableStatement); ParameterMapperStoredProcedure pmsp = new ParameterMapperStoredProcedure(dataSource); Map out = pmsp.executeTest(); @@ -411,8 +389,7 @@ public void testSqlTypeValue() throws Exception { given(callableStatement.execute()).willReturn(false); given(callableStatement.getUpdateCount()).willReturn(-1); given(callableStatement.getObject(2)).willReturn("OK"); - given(connection.prepareCall("{call " + SqlTypeValueStoredProcedure.SQL + "(?, ?)}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + SqlTypeValueStoredProcedure.SQL + "(?, ?)}")).willReturn(callableStatement); SqlTypeValueStoredProcedure stvsp = new SqlTypeValueStoredProcedure(dataSource); Map out = stvsp.executeTest(testVal); @@ -426,8 +403,7 @@ public void testNumericWithScale() throws Exception { given(callableStatement.execute()).willReturn(false); given(callableStatement.getUpdateCount()).willReturn(-1); given(callableStatement.getObject(1)).willReturn(new BigDecimal("12345.6789")); - given(connection.prepareCall("{call " + NumericWithScaleStoredProcedure.SQL + "(?)}") - ).willReturn(callableStatement); + given(connection.prepareCall("{call " + NumericWithScaleStoredProcedure.SQL + "(?)}")).willReturn(callableStatement); NumericWithScaleStoredProcedure nwssp = new NumericWithScaleStoredProcedure(dataSource); Map out = nwssp.executeTest(); assertThat(out.get("out")).isEqualTo(new BigDecimal("12345.6789")); @@ -686,12 +662,7 @@ private static class StoredProcedureExceptionTranslator extends StoredProcedure public StoredProcedureExceptionTranslator(DataSource ds) { setDataSource(ds); setSql(SQL); - getJdbcTemplate().setExceptionTranslator(new SQLExceptionTranslator() { - @Override - public DataAccessException translate(String task, @Nullable String sql, SQLException ex) { - return new CustomDataException(sql, ex); - } - }); + getJdbcTemplate().setExceptionTranslator((task, sql, ex) -> new CustomDataException(sql, ex)); compile(); } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java index 5cdfb7eda559..486ffbf9a8c2 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java @@ -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. @@ -87,7 +87,7 @@ public void errorCodeTranslation() { SQLException dupKeyEx = new SQLException("", "", 10); DataAccessException dksex = sext.translate("task", "SQL", dupKeyEx); - assertThat(DataIntegrityViolationException.class.isInstance(dksex)).as("Not instance of DataIntegrityViolationException").isTrue(); + assertThat(dksex).isInstanceOf(DataIntegrityViolationException.class); // Test fallback. We assume that no database will ever return this error code, // but 07xxx will be bad grammar picked up by the fallback SQLState translator diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java index 0a6287169a40..bbf71620798b 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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,7 @@ import org.springframework.jms.support.JmsUtils; import org.springframework.lang.Nullable; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.ResourceTransactionManager; @@ -248,7 +249,19 @@ protected boolean receiveAndExecute( rollbackOnException(this.transactionManager, status, ex); throw ex; } - this.transactionManager.commit(status); + try { + this.transactionManager.commit(status); + } + catch (TransactionException ex) { + // Propagate transaction system exceptions as infrastructure problems. + throw ex; + } + catch (RuntimeException ex) { + // Typically a late persistence exception from a listener-used resource + // -> handle it as listener exception, not as an infrastructure problem. + // E.g. a database locking failure should not lead to listener shutdown. + handleListenerException(ex); + } return messageReceived; } diff --git a/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java b/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java index 0690e7d349d8..34bf543bddcd 100644 --- a/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java @@ -200,12 +200,9 @@ void testSessionCallback() throws Exception { JmsTemplate template = createTemplate(); template.setConnectionFactory(this.connectionFactory); - template.execute(new SessionCallback() { - @Override - public Void doInJms(Session session) throws JMSException { - session.getTransacted(); - return null; - } + template.execute((SessionCallback) session -> { + session.getTransacted(); + return null; }); verify(this.session).close(); @@ -220,19 +217,13 @@ void testSessionCallbackWithinSynchronizedTransaction() throws Exception { TransactionSynchronizationManager.initSynchronization(); try { - template.execute(new SessionCallback() { - @Override - public Void doInJms(Session session) throws JMSException { - session.getTransacted(); - return null; - } + template.execute((SessionCallback) session -> { + session.getTransacted(); + return null; }); - template.execute(new SessionCallback() { - @Override - public Void doInJms(Session session) throws JMSException { - session.getTransacted(); - return null; - } + template.execute((SessionCallback) session -> { + session.getTransacted(); + return null; }); assertThat(ConnectionFactoryUtils.getTransactionalSession(scf, null, false)).isSameAs(this.session); @@ -374,29 +365,14 @@ private void doTestSendDestination( } if (useDefaultDestination) { - template.send(new MessageCreator() { - @Override - public Message createMessage(Session session) throws JMSException { - return session.createTextMessage("just testing"); - } - }); + template.send(session -> session.createTextMessage("just testing")); } else { if (explicitDestination) { - template.send(this.queue, new MessageCreator() { - @Override - public Message createMessage(Session session) throws JMSException { - return session.createTextMessage("just testing"); - } - }); + template.send(this.queue, (MessageCreator) session -> session.createTextMessage("just testing")); } else { - template.send(destinationName, new MessageCreator() { - @Override - public Message createMessage(Session session) throws JMSException { - return session.createTextMessage("just testing"); - } - }); + template.send(destinationName, (MessageCreator) session -> session.createTextMessage("just testing")); } } diff --git a/spring-jms/src/test/java/org/springframework/jms/listener/SimpleMessageListenerContainerTests.java b/spring-jms/src/test/java/org/springframework/jms/listener/SimpleMessageListenerContainerTests.java index 76bd754e2cc5..5d3a1846c40f 100644 --- a/spring-jms/src/test/java/org/springframework/jms/listener/SimpleMessageListenerContainerTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/listener/SimpleMessageListenerContainerTests.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.task.TaskExecutor; import org.springframework.jms.StubQueue; import org.springframework.lang.Nullable; import org.springframework.util.ErrorHandler; @@ -183,16 +182,13 @@ public void testCorrectSessionExposedForSessionAwareMessageListenerInvocation() this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); - this.container.setMessageListener(new SessionAwareMessageListener() { - @Override - public void onMessage(Message message, @Nullable Session sess) { - try { - // Check correct Session passed into SessionAwareMessageListener. - assertThat(session).isSameAs(sess); - } - catch (Throwable ex) { - failure.add("MessageListener execution failed: " + ex); - } + this.container.setMessageListener((SessionAwareMessageListener) (Message message, @Nullable Session sess) -> { + try { + // Check correct Session passed into SessionAwareMessageListener. + assertThat(session).isSameAs(sess); + } + catch (Throwable ex) { + failure.add("MessageListener execution failed: " + ex); } }); @@ -232,14 +228,11 @@ public void testTaskExecutorCorrectlyInvokedWhenSpecified() throws Exception { this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(listener); - this.container.setTaskExecutor(new TaskExecutor() { - @Override - public void execute(Runnable task) { - listener.executorInvoked = true; - assertThat(listener.listenerInvoked).isFalse(); - task.run(); - assertThat(listener.listenerInvoked).isTrue(); - } + this.container.setTaskExecutor(task -> { + listener.executorInvoked = true; + assertThat(listener.listenerInvoked).isFalse(); + task.run(); + assertThat(listener.listenerInvoked).isTrue(); }); this.container.afterPropertiesSet(); this.container.start(); @@ -279,11 +272,8 @@ public void testRegisteredExceptionListenerIsInvokedOnException() throws Excepti this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); - this.container.setMessageListener(new SessionAwareMessageListener() { - @Override - public void onMessage(Message message, @Nullable Session session) throws JMSException { - throw theException; - } + this.container.setMessageListener((SessionAwareMessageListener) (Message message, @Nullable Session session1) -> { + throw theException; }); ExceptionListener exceptionListener = mock(ExceptionListener.class); @@ -329,11 +319,8 @@ public void testRegisteredErrorHandlerIsInvokedOnException() throws Exception { this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); - this.container.setMessageListener(new SessionAwareMessageListener() { - @Override - public void onMessage(Message message, @Nullable Session session) throws JMSException { - throw theException; - } + this.container.setMessageListener((SessionAwareMessageListener) (Message message, @Nullable Session session1) -> { + throw theException; }); ErrorHandler errorHandler = mock(ErrorHandler.class); @@ -375,11 +362,8 @@ public void testNoRollbackOccursIfSessionIsNotTransactedAndThatExceptionsDo_NOT_ this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); - this.container.setMessageListener(new MessageListener() { - @Override - public void onMessage(Message message) { - throw new UnsupportedOperationException(); - } + this.container.setMessageListener((MessageListener) message -> { + throw new UnsupportedOperationException(); }); this.container.afterPropertiesSet(); this.container.start(); @@ -419,11 +403,8 @@ public void testTransactedSessionsGetRollbackLogicAppliedAndThatExceptionsStillD this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); - this.container.setMessageListener(new MessageListener() { - @Override - public void onMessage(Message message) { - throw new UnsupportedOperationException(); - } + this.container.setMessageListener((MessageListener) message -> { + throw new UnsupportedOperationException(); }); this.container.afterPropertiesSet(); this.container.start(); diff --git a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java index 4a984a256e37..4e0d7bfa363c 100644 --- a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java @@ -432,8 +432,8 @@ public void wrongParam(Integer i) { } } - interface Summary {}; - interface Full extends Summary {}; + interface Summary {} + interface Full extends Summary {} @SuppressWarnings("unused") private static class SampleResponse { diff --git a/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java index 41aed17f47aa..ad2531d3bc66 100644 --- a/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java @@ -30,8 +30,6 @@ import javax.jms.TextMessage; import org.junit.jupiter.api.Test; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.springframework.jms.support.converter.MessageConversionException; import org.springframework.jms.support.converter.SimpleMessageConverter; @@ -77,12 +75,7 @@ public void testByteArrayConversion() throws JMSException { given(session.createBytesMessage()).willReturn(message); given(message.getBodyLength()).willReturn((long) content.length); - given(message.readBytes(any(byte[].class))).willAnswer(new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - return byteArrayInputStream.read((byte[]) invocation.getArguments()[0]); - } - }); + given(message.readBytes(any(byte[].class))).willAnswer(invocation -> byteArrayInputStream.read((byte[]) invocation.getArguments()[0])); SimpleMessageConverter converter = new SimpleMessageConverter(); Message msg = converter.toMessage(content, session); diff --git a/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java index 4f428c270939..563ba5de605e 100644 --- a/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java @@ -300,9 +300,9 @@ public int hashCode() { } - private interface Summary {}; + private interface Summary {} - private interface Full extends Summary {}; + private interface Full extends Summary {} @SuppressWarnings("unused") diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java index 1e69ff4137f3..5fffc1657ffd 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java @@ -319,9 +319,9 @@ public void setArray(String[] array) { } - public interface MyJacksonView1 {}; + public interface MyJacksonView1 {} - public interface MyJacksonView2 {}; + public interface MyJacksonView2 {} public static class JacksonViewBean { diff --git a/spring-messaging/src/test/java/org/springframework/messaging/core/GenericMessagingTemplateTests.java b/spring-messaging/src/test/java/org/springframework/messaging/core/GenericMessagingTemplateTests.java index e2b3c4602b9b..0ef6a331686d 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/core/GenericMessagingTemplateTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/core/GenericMessagingTemplateTests.java @@ -29,7 +29,6 @@ import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.MessagingException; import org.springframework.messaging.StubMessageChannel; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.support.ExecutorSubscribableChannel; @@ -112,12 +111,9 @@ public void sendWithTimeoutMutable() { @Test public void sendAndReceive() { SubscribableChannel channel = new ExecutorSubscribableChannel(this.executor); - channel.subscribe(new MessageHandler() { - @Override - public void handleMessage(Message message) throws MessagingException { - MessageChannel replyChannel = (MessageChannel) message.getHeaders().getReplyChannel(); - replyChannel.send(new GenericMessage<>("response")); - } + channel.subscribe(message -> { + MessageChannel replyChannel = (MessageChannel) message.getHeaders().getReplyChannel(); + replyChannel.send(new GenericMessage<>("response")); }); String actual = this.template.convertSendAndReceive(channel, "request", String.class); @@ -126,7 +122,7 @@ public void handleMessage(Message message) throws MessagingException { @Test public void sendAndReceiveTimeout() throws InterruptedException { - final AtomicReference failure = new AtomicReference(); + final AtomicReference failure = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); this.template.setReceiveTimeout(1); @@ -152,7 +148,7 @@ public void sendAndReceiveTimeout() throws InterruptedException { @Test public void sendAndReceiveVariableTimeout() throws InterruptedException { - final AtomicReference failure = new AtomicReference(); + final AtomicReference failure = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); this.template.setSendTimeout(20_000); @@ -182,7 +178,7 @@ public void sendAndReceiveVariableTimeout() throws InterruptedException { @Test public void sendAndReceiveVariableTimeoutCustomHeaders() throws InterruptedException { - final AtomicReference failure = new AtomicReference(); + final AtomicReference failure = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); this.template.setSendTimeout(20_000); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/reactive/MessageMappingMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/reactive/MessageMappingMessageHandlerTests.java index fe4c46e742bf..8c270485413b 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/reactive/MessageMappingMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/reactive/MessageMappingMessageHandlerTests.java @@ -151,7 +151,7 @@ private MessageMappingMessageHandler initMesssageHandler() { } private Message message(String destination, String... content) { - Flux payload = Flux.fromIterable(Arrays.asList(content)).map(parts -> toDataBuffer(parts)); + Flux payload = Flux.fromIterable(Arrays.asList(content)).map(this::toDataBuffer); MessageHeaderAccessor headers = new MessageHeaderAccessor(); headers.setLeaveMutable(true); headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactoryTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactoryTests.java index f9290bf0faa9..3641cde73b70 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactoryTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactoryTests.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.support.StaticListableBeanFactory; import org.springframework.core.MethodParameter; -import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.messaging.Message; import org.springframework.messaging.converter.ByteArrayMessageConverter; @@ -59,12 +58,7 @@ public class DefaultMessageHandlerMethodFactoryTests { public void customConversion() throws Exception { DefaultMessageHandlerMethodFactory instance = createInstance(); GenericConversionService conversionService = new GenericConversionService(); - conversionService.addConverter(SampleBean.class, String.class, new Converter() { - @Override - public String convert(SampleBean source) { - return "foo bar"; - } - }); + conversionService.addConverter(SampleBean.class, String.class, source -> "foo bar"); instance.setConversionService(conversionService); instance.afterPropertiesSet(); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesContextHolderTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesContextHolderTests.java index ed76ea88e1e9..c42e8573924b 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesContextHolderTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesContextHolderTests.java @@ -131,8 +131,7 @@ public void currentAttributes() { @Test public void currentAttributesNone() { - assertThatIllegalStateException().isThrownBy(() -> - SimpAttributesContextHolder.currentAttributes()) + assertThatIllegalStateException().isThrownBy(SimpAttributesContextHolder::currentAttributes) .withMessageStartingWith("No thread-bound SimpAttributes found"); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java index 92eefda1fbb4..f30579fe0c2a 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java @@ -224,8 +224,8 @@ public JacksonViewBean getJsonView() { } - private interface MyJacksonView1 {}; - private interface MyJacksonView2 {}; + private interface MyJacksonView1 {} + private interface MyJacksonView2 {} private static class JacksonViewBean { diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java index 90985f35d79b..502969e4d5de 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java @@ -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,7 +36,6 @@ import org.springframework.messaging.simp.stomp.StompSession.Subscription; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.util.Assert; -import org.springframework.util.SocketUtils; import org.springframework.util.concurrent.ListenableFuture; import static org.assertj.core.api.Assertions.assertThat; @@ -61,7 +60,8 @@ public class ReactorNettyTcpStompClientTests { public void setup(TestInfo testInfo) throws Exception { logger.debug("Setting up before '" + testInfo.getTestMethod().get().getName() + "'"); - int port = SocketUtils.findAvailableTcpPort(61613); + @SuppressWarnings("deprecation") + int port = org.springframework.util.SocketUtils.findAvailableTcpPort(61613); this.activeMQBroker = new BrokerService(); this.activeMQBroker.addConnector("stomp://127.0.0.1:" + port); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java index c3c0d0293466..835cb47f769d 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java @@ -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. @@ -46,7 +46,6 @@ import org.springframework.messaging.support.ExecutorSubscribableChannel; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.Assert; -import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -75,10 +74,11 @@ public class StompBrokerRelayMessageHandlerIntegrationTests { @BeforeEach + @SuppressWarnings("deprecation") public void setup(TestInfo testInfo) throws Exception { logger.debug("Setting up before '" + testInfo.getTestMethod().get().getName() + "'"); - this.port = SocketUtils.findAvailableTcpPort(61613); + this.port = org.springframework.util.SocketUtils.findAvailableTcpPort(61613); this.responseChannel = new ExecutorSubscribableChannel(); this.responseHandler = new TestMessageHandler(); this.responseChannel.subscribe(this.responseHandler); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java index 70e35c35d969..3fc34442d7fd 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.Callable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -286,12 +285,7 @@ private Message message(StompCommand command, String sessionId, String u private static ListenableFutureTask getVoidFuture() { - ListenableFutureTask futureTask = new ListenableFutureTask<>(new Callable() { - @Override - public Void call() { - return null; - } - }); + ListenableFutureTask futureTask = new ListenableFutureTask<>(() -> null); futureTask.run(); return futureTask; } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java index d3b61969aac0..f9e482cd93f4 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java @@ -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. @@ -133,7 +133,9 @@ static void flush(Session session, boolean synch) throws DataAccessException { public static void closeSession(@Nullable Session session) { if (session != null) { try { - session.close(); + if (session.isOpen()) { + session.close(); + } } catch (Throwable ex) { logger.error("Failed to release Hibernate Session", ex); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java index 3a53b2cb9a1d..e0bc4a1da665 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/JpaTransactionManagerTests.java @@ -32,7 +32,6 @@ import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; -import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -100,13 +99,10 @@ public void testTransactionCommit() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } + Object result = tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; }); assertThat(result).isSameAs(l); @@ -135,13 +131,10 @@ public void testTransactionCommitWithRollbackException() { assertThat(condition2).isTrue(); try { - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } + Object result = tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; }); assertThat(result).isSameAs(l); } @@ -174,13 +167,10 @@ public void testTransactionRollback() { assertThat(condition2).isTrue(); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory); - throw new RuntimeException("some exception"); - } + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory); + throw new RuntimeException("some exception"); })).withMessage("some exception"); boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); @@ -205,13 +195,10 @@ public void testTransactionRollbackWithAlreadyRolledBack() { assertThat(condition2).isTrue(); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory); - throw new RuntimeException("some exception"); - } + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory); + throw new RuntimeException("some exception"); })); boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); @@ -235,16 +222,13 @@ public void testTransactionRollbackOnly() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - status.setRollbackOnly(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + status.setRollbackOnly(); - return l; - } + return l; }); boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); @@ -269,19 +253,13 @@ public void testParticipatingTransactionWithCommit() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - return tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } - }); - } + return tt.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; + }); }); boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); @@ -308,18 +286,12 @@ public void testParticipatingTransactionWithRollback() { assertThat(condition2).isTrue(); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - return tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory); - throw new RuntimeException("some exception"); - } - }); - } + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + return tt.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory); + throw new RuntimeException("some exception"); + }); })); boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); @@ -348,20 +320,14 @@ public void testParticipatingTransactionWithRollbackOnly() { assertThat(condition2).isTrue(); assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - - return tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - status.setRollbackOnly(); - return null; - } - }); - } + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + + return tt.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + status1.setRollbackOnly(); + return null; + }); })) .withCauseInstanceOf(RollbackException.class); @@ -391,18 +357,12 @@ public void testParticipatingTransactionWithRequiresNew() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - return tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } - }); - } + Object result = tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + return tt.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; + }); }); assertThat(result).isSameAs(l); @@ -433,20 +393,14 @@ public void testParticipatingTransactionWithRequiresNewAndPrebound() { TransactionSynchronizationManager.bindResource(factory, new EntityManagerHolder(manager)); try { - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory); + Object result = tt.execute(status -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory); - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - return tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } - }); - } + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + return tt.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; + }); }); assertThat(result).isSameAs(l); } @@ -479,20 +433,14 @@ public void testPropagationSupportsAndRequiresNew() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isFalse(); - TransactionTemplate tt2 = new TransactionTemplate(tm); - tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - return tt2.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } - }); - } + Object result = tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isFalse(); + TransactionTemplate tt2 = new TransactionTemplate(tm); + tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + return tt2.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; + }); }); assertThat(result).isSameAs(l); @@ -522,22 +470,16 @@ public void testPropagationSupportsAndRequiresNewAndEarlyAccess() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory); + Object result = tt.execute(status -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory); - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - TransactionTemplate tt2 = new TransactionTemplate(tm); - tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - return tt2.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } - }); - } + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + TransactionTemplate tt2 = new TransactionTemplate(tm); + tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + return tt2.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; + }); }); assertThat(result).isSameAs(l); @@ -568,24 +510,18 @@ public void testTransactionWithRequiresNewInAfterCompletion() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - @Override - public void afterCompletion(int status) { - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return null; - } - }); - } - }); - return null; - } + tt.execute(status -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCompletion(int status) { + tt.execute(status1 -> { + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return null; + }); + } + }); + return null; }); boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); @@ -616,17 +552,14 @@ public void testTransactionCommitWithPropagationSupports() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); - assertThat(condition1).isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); - boolean condition = !status.isNewTransaction(); - assertThat(condition).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } + Object result = tt.execute(status -> { + boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); + assertThat(condition1).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + boolean condition = !status.isNewTransaction(); + assertThat(condition).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; }); assertThat(result).isSameAs(l); @@ -650,18 +583,15 @@ public void testTransactionRollbackWithPropagationSupports() { boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); assertThat(condition2).isTrue(); - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); - assertThat(condition1).isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); - boolean condition = !status.isNewTransaction(); - assertThat(condition).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - status.setRollbackOnly(); - return null; - } + tt.execute(status -> { + boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); + assertThat(condition1).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + boolean condition = !status.isNewTransaction(); + assertThat(condition).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + status.setRollbackOnly(); + return null; }); boolean condition1 = !TransactionSynchronizationManager.hasResource(factory); @@ -687,14 +617,11 @@ public void testTransactionCommitWithPrebound() { TransactionSynchronizationManager.bindResource(factory, new EntityManagerHolder(manager)); try { - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory); - return l; - } + Object result = tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory); + return l; }); assertThat(result).isSameAs(l); @@ -722,15 +649,12 @@ public void testTransactionRollbackWithPrebound() { TransactionSynchronizationManager.bindResource(factory, new EntityManagerHolder(manager)); try { - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory); - status.setRollbackOnly(); - return null; - } + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory); + status.setRollbackOnly(); + return null; }); assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); @@ -760,16 +684,13 @@ public void testTransactionCommitWithPreboundAndPropagationSupports() { TransactionSynchronizationManager.bindResource(factory, new EntityManagerHolder(manager)); try { - Object result = tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); - boolean condition = !status.isNewTransaction(); - assertThat(condition).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - return l; - } + Object result = tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + boolean condition = !status.isNewTransaction(); + assertThat(condition).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + return l; }); assertThat(result).isSameAs(l); @@ -795,17 +716,14 @@ public void testTransactionRollbackWithPreboundAndPropagationSupports() { TransactionSynchronizationManager.bindResource(factory, new EntityManagerHolder(manager)); try { - tt.execute(new TransactionCallback() { - @Override - public Object doInTransaction(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); - boolean condition = !status.isNewTransaction(); - assertThat(condition).isTrue(); - EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); - status.setRollbackOnly(); - return null; - } + tt.execute(status -> { + assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + boolean condition = !status.isNewTransaction(); + assertThat(condition).isTrue(); + EntityManagerFactoryUtils.getTransactionalEntityManager(factory).flush(); + status.setRollbackOnly(); + return null; }); assertThat(TransactionSynchronizationManager.hasResource(factory)).isTrue(); diff --git a/spring-test/src/main/java/org/springframework/test/util/JsonExpectationsHelper.java b/spring-test/src/main/java/org/springframework/test/util/JsonExpectationsHelper.java index 4de0dba6eee3..7c6489a6da0d 100644 --- a/spring-test/src/main/java/org/springframework/test/util/JsonExpectationsHelper.java +++ b/spring-test/src/main/java/org/springframework/test/util/JsonExpectationsHelper.java @@ -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. @@ -32,8 +32,8 @@ public class JsonExpectationsHelper { /** * Parse the expected and actual strings as JSON and assert the two * are "similar" - i.e. they contain the same attribute-value pairs - * regardless of formatting with a lenient checking (extensible, and non-strict - * array ordering). + * regardless of formatting with lenient checking (extensible content and + * non-strict array ordering). * @param expected the expected JSON content * @param actual the actual JSON content * @since 4.1 @@ -47,14 +47,14 @@ public void assertJsonEqual(String expected, String actual) throws Exception { * Parse the expected and actual strings as JSON and assert the two * are "similar" - i.e. they contain the same attribute-value pairs * regardless of formatting. - *

Can compare in two modes, depending on {@code strict} parameter value: + *

Can compare in two modes, depending on the {@code strict} parameter value: *

    - *
  • {@code true}: strict checking. Not extensible, and strict array ordering.
  • - *
  • {@code false}: lenient checking. Extensible, and non-strict array ordering.
  • + *
  • {@code true}: strict checking. Not extensible and strict array ordering.
  • + *
  • {@code false}: lenient checking. Extensible and non-strict array ordering.
  • *
* @param expected the expected JSON content * @param actual the actual JSON content - * @param strict enables strict checking + * @param strict enables strict checking if {@code true} * @since 4.2 */ public void assertJsonEqual(String expected, String actual, boolean strict) throws Exception { diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 8628ef262261..a3cc4c2890e6 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -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. @@ -659,10 +659,10 @@ public EntityExchangeResult isEmpty() { } @Override - public BodyContentSpec json(String json) { + public BodyContentSpec json(String json, boolean strict) { this.result.assertWithDiagnostics(() -> { try { - new JsonExpectationsHelper().assertJsonEqual(json, getBodyAsString()); + new JsonExpectationsHelper().assertJsonEqual(json, getBodyAsString(), strict); } catch (Exception ex) { throw new AssertionError("JSON parsing error", ex); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 141428f171ab..ece5800cc5ec 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -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. @@ -526,14 +526,19 @@ interface Builder { interface UriSpec> { /** - * Specify the URI using an absolute, fully constructed {@link URI}. + * Specify the URI using an absolute, fully constructed {@link java.net.URI}. + *

If a {@link UriBuilderFactory} was configured for the client with + * a base URI, that base URI will not be applied to the + * supplied {@code java.net.URI}. If you wish to have a base URI applied to a + * {@code java.net.URI} you must invoke either {@link #uri(String, Object...)} + * or {@link #uri(String, Map)} — for example, {@code uri(myUri.toString())}. * @return spec to add headers or perform the exchange */ S uri(URI uri); /** * Specify the URI for the request using a URI template and URI variables. - * If a {@link UriBuilderFactory} was configured for the client (e.g. + *

If a {@link UriBuilderFactory} was configured for the client (e.g. * with a base URI) it will be used to expand the URI template. * @return spec to add headers or perform the exchange */ @@ -541,7 +546,7 @@ interface UriSpec> { /** * Specify the URI for the request using a URI template and URI variables. - * If a {@link UriBuilderFactory} was configured for the client (e.g. + *

If a {@link UriBuilderFactory} was configured for the client (e.g. * with a base URI) it will be used to expand the URI template. * @return spec to add headers or perform the exchange */ @@ -973,13 +978,37 @@ interface BodyContentSpec { /** * Parse the expected and actual response content as JSON and perform a - * "lenient" comparison verifying the same attribute-value pairs. - *

Use of this option requires the + * comparison verifying that they contain the same attribute-value pairs + * regardless of formatting with lenient checking (extensible + * and non-strict array ordering). + *

Use of this method requires the + * JSONassert library + * to be on the classpath. + * @param expectedJson the expected JSON content + * @see #json(String, boolean) + */ + default BodyContentSpec json(String expectedJson) { + return json(expectedJson, false); + } + + /** + * Parse the expected and actual response content as JSON and perform a + * comparison verifying that they contain the same attribute-value pairs + * regardless of formatting. + *

Can compare in two modes, depending on the {@code strict} parameter value: + *

    + *
  • {@code true}: strict checking. Not extensible and strict array ordering.
  • + *
  • {@code false}: lenient checking. Extensible and non-strict array ordering.
  • + *
+ *

Use of this method requires the * JSONassert library - * on to be on the classpath. - * @param expectedJson the expected JSON content. + * to be on the classpath. + * @param expectedJson the expected JSON content + * @param strict enables strict checking if {@code true} + * @since 5.3.16 + * @see #json(String) */ - BodyContentSpec json(String expectedJson); + BodyContentSpec json(String expectedJson, boolean strict); /** * Parse expected and actual response content as XML and assert that diff --git a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java index 7ed75786f8f3..5c409d880449 100644 --- a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java @@ -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. @@ -72,8 +72,8 @@ void resolveTestContextBootstrapperWithDoubleMetaBootstrapWithAnnotations() { assertThatIllegalStateException().isThrownBy(() -> resolveTestContextBootstrapper(bootstrapContext)) .withMessageContaining("Configuration error: found multiple declarations of @BootstrapWith") - .withMessageContaining(FooBootstrapper.class.getName()) - .withMessageContaining(BarBootstrapper.class.getName()); + .withMessageContaining(FooBootstrapper.class.getCanonicalName()) + .withMessageContaining(BarBootstrapper.class.getCanonicalName()); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java index f2637bc1709d..89a06defd642 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java @@ -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. @@ -37,13 +37,13 @@ * @author Rossen Stoyanchev * @since 5.0 */ -public class ErrorTests { +class ErrorTests { private final WebTestClient client = WebTestClient.bindToController(new TestController()).build(); @Test - public void notFound(){ + void notFound(){ this.client.get().uri("/invalid") .exchange() .expectStatus().isNotFound() @@ -51,7 +51,7 @@ public void notFound(){ } @Test - public void serverException() { + void serverException() { this.client.get().uri("/server-error") .exchange() .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) @@ -59,7 +59,7 @@ public void serverException() { } @Test // SPR-17363 - public void badRequestBeforeRequestBodyConsumed() { + void badRequestBeforeRequestBodyConsumed() { EntityExchangeResult result = this.client.post() .uri("/post") .contentType(MediaType.APPLICATION_JSON) diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java index d5dbbd597ce9..759770bf6528 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -18,7 +18,6 @@ import java.security.Principal; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -37,23 +36,18 @@ /** * Samples tests that demonstrate applying ServerWebExchange initialization. + * * @author Rossen Stoyanchev */ -public class ExchangeMutatorTests { - - private WebTestClient webTestClient; +class ExchangeMutatorTests { + private final WebTestClient webTestClient = WebTestClient.bindToController(new TestController()) + .apply(identity("Pablo")) + .build(); - @BeforeEach - public void setUp() throws Exception { - - this.webTestClient = WebTestClient.bindToController(new TestController()) - .apply(identity("Pablo")) - .build(); - } @Test - public void useGloballyConfiguredIdentity() throws Exception { + void useGloballyConfiguredIdentity() { this.webTestClient.get().uri("/userIdentity") .exchange() .expectStatus().isOk() @@ -61,8 +55,7 @@ public void useGloballyConfiguredIdentity() throws Exception { } @Test - public void useLocallyConfiguredIdentity() throws Exception { - + void useLocallyConfiguredIdentity() { this.webTestClient .mutateWith(identity("Giovanni")) .get().uri("/userIdentity") diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/GlobalEntityResultConsumerTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/GlobalEntityResultConsumerTests.java index 24bf92ecbb31..1a68d62ca1c0 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/GlobalEntityResultConsumerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/GlobalEntityResultConsumerTests.java @@ -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. @@ -96,4 +96,5 @@ List getPersons() { return Arrays.asList(new Person("Joe"), new Person("Joseph")); } } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderAndCookieTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderAndCookieTests.java index e42dff7c110d..a2b743c64255 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderAndCookieTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderAndCookieTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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,16 +29,17 @@ /** * Tests with headers and cookies. + * * @author Rossen Stoyanchev * @since 5.0 */ -public class HeaderAndCookieTests { +class HeaderAndCookieTests { private final WebTestClient client = WebTestClient.bindToController(new TestController()).build(); @Test - public void requestResponseHeaderPair() throws Exception { + void requestResponseHeaderPair() { this.client.get().uri("/header-echo").header("h1", "in") .exchange() .expectStatus().isOk() @@ -46,7 +47,7 @@ public void requestResponseHeaderPair() throws Exception { } @Test - public void headerMultipleValues() throws Exception { + void headerMultipleValues() { this.client.get().uri("/header-multi-value") .exchange() .expectStatus().isOk() @@ -54,7 +55,7 @@ public void headerMultipleValues() throws Exception { } @Test - public void setCookies() { + void setCookies() { this.client.get().uri("/cookie-echo") .cookies(cookies -> cookies.add("k1", "v1")) .exchange() diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java index 82d271c87775..e6a4ed779950 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java @@ -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. @@ -31,10 +31,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.Matchers.containsString; - - /** * Samples of tests using {@link WebTestClient} with serialized JSON content. * @@ -42,47 +41,76 @@ * @author Sam Brannen * @since 5.0 */ -public class JsonContentTests { +class JsonContentTests { private final WebTestClient client = WebTestClient.bindToController(new PersonController()).build(); @Test - public void jsonContent() { + void jsonContentWithDefaultLenientMode() { + this.client.get().uri("/persons") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectBody().json( + "[{\"firstName\":\"Jane\"}," + + "{\"firstName\":\"Jason\"}," + + "{\"firstName\":\"John\"}]"); + } + + @Test + void jsonContentWithStrictMode() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() - .expectBody().json("[{\"name\":\"Jane\"},{\"name\":\"Jason\"},{\"name\":\"John\"}]"); + .expectBody().json( + "[{\"firstName\":\"Jane\",\"lastName\":\"Williams\"}," + + "{\"firstName\":\"Jason\",\"lastName\":\"Johnson\"}," + + "{\"firstName\":\"John\",\"lastName\":\"Smith\"}]", + true); + } + + @Test + void jsonContentWithStrictModeAndMissingAttributes() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> this.client.get().uri("/persons") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectBody().json( + "[{\"firstName\":\"Jane\"}," + + "{\"firstName\":\"Jason\"}," + + "{\"firstName\":\"John\"}]", + true) + ); } @Test - public void jsonPathIsEqualTo() { + void jsonPathIsEqualTo() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectBody() - .jsonPath("$[0].name").isEqualTo("Jane") - .jsonPath("$[1].name").isEqualTo("Jason") - .jsonPath("$[2].name").isEqualTo("John"); + .jsonPath("$[0].firstName").isEqualTo("Jane") + .jsonPath("$[1].firstName").isEqualTo("Jason") + .jsonPath("$[2].firstName").isEqualTo("John"); } @Test - public void jsonPathMatches() { - this.client.get().uri("/persons/John") + void jsonPathMatches() { + this.client.get().uri("/persons/John/Smith") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectBody() - .jsonPath("$.name").value(containsString("oh")); + .jsonPath("$.firstName").value(containsString("oh")); } @Test - public void postJsonContent() { + void postJsonContent() { this.client.post().uri("/persons") .contentType(MediaType.APPLICATION_JSON) - .bodyValue("{\"name\":\"John\"}") + .bodyValue("{\"firstName\":\"John\",\"lastName\":\"Smith\"}") .exchange() .expectStatus().isCreated() .expectBody().isEmpty(); @@ -95,17 +123,38 @@ static class PersonController { @GetMapping Flux getPersons() { - return Flux.just(new Person("Jane"), new Person("Jason"), new Person("John")); + return Flux.just(new Person("Jane", "Williams"), new Person("Jason", "Johnson"), new Person("John", "Smith")); } - @GetMapping("/{name}") - Person getPerson(@PathVariable String name) { - return new Person(name); + @GetMapping("/{firstName}/{lastName}") + Person getPerson(@PathVariable String firstName, @PathVariable String lastName) { + return new Person(firstName, lastName); } @PostMapping ResponseEntity savePerson(@RequestBody Person person) { - return ResponseEntity.created(URI.create("/persons/" + person.getName())).build(); + return ResponseEntity.created(URI.create(String.format("/persons/%s/%s", person.getFirstName(), person.getLastName()))).build(); + } + } + + static class Person { + private String firstName; + private String lastName; + + public Person() { + } + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return this.firstName; + } + + public String getLastName() { + return this.lastName; } } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java index 3acbf66d9842..f1619d618819 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java @@ -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. @@ -49,7 +49,7 @@ * @author Rossen Stoyanchev * @since 5.0 */ -public class ResponseEntityTests { +class ResponseEntityTests { private final WebTestClient client = WebTestClient.bindToController(new PersonController()) .configureClient() @@ -58,7 +58,7 @@ public class ResponseEntityTests { @Test - public void entity() { + void entity() { this.client.get().uri("/John") .exchange() .expectStatus().isOk() @@ -67,7 +67,7 @@ public void entity() { } @Test - public void entityMatcher() { + void entityMatcher() { this.client.get().uri("/John") .exchange() .expectStatus().isOk() @@ -76,7 +76,7 @@ public void entityMatcher() { } @Test - public void entityWithConsumer() { + void entityWithConsumer() { this.client.get().uri("/John") .exchange() .expectStatus().isOk() @@ -86,8 +86,7 @@ public void entityWithConsumer() { } @Test - public void entityList() { - + void entityList() { List expected = Arrays.asList( new Person("Jane"), new Person("Jason"), new Person("John")); @@ -99,8 +98,7 @@ public void entityList() { } @Test - public void entityListWithConsumer() { - + void entityListWithConsumer() { this.client.get() .exchange() .expectStatus().isOk() @@ -111,8 +109,7 @@ public void entityListWithConsumer() { } @Test - public void entityMap() { - + void entityMap() { Map map = new LinkedHashMap<>(); map.put("Jane", new Person("Jane")); map.put("Jason", new Person("Jason")); @@ -125,8 +122,7 @@ public void entityMap() { } @Test - public void entityStream() { - + void entityStream() { FluxExchangeResult result = this.client.get() .accept(TEXT_EVENT_STREAM) .exchange() @@ -143,7 +139,7 @@ public void entityStream() { } @Test - public void postEntity() { + void postEntity() { this.client.post() .bodyValue(new Person("John")) .exchange() diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/XmlContentTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/XmlContentTests.java index 870c7231e056..a24eda5db21e 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/XmlContentTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/XmlContentTests.java @@ -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. @@ -41,15 +41,13 @@ import static org.hamcrest.Matchers.startsWith; - - /** * Samples of tests using {@link WebTestClient} with XML content. * * @author Eric Deandrea * @since 5.1 */ -public class XmlContentTests { +class XmlContentTests { private static final String persons_XML = "" @@ -64,7 +62,7 @@ public class XmlContentTests { @Test - public void xmlContent() { + void xmlContent() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_XML) .exchange() @@ -73,7 +71,7 @@ public void xmlContent() { } @Test - public void xpathIsEqualTo() { + void xpathIsEqualTo() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_XML) .exchange() @@ -89,7 +87,7 @@ public void xpathIsEqualTo() { } @Test - public void xpathMatches() { + void xpathMatches() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_XML) .exchange() @@ -99,7 +97,7 @@ public void xpathMatches() { } @Test - public void xpathContainsSubstringViaRegex() { + void xpathContainsSubstringViaRegex() { this.client.get().uri("/persons/John") .accept(MediaType.APPLICATION_XML) .exchange() @@ -109,8 +107,7 @@ public void xpathContainsSubstringViaRegex() { } @Test - public void postXmlContent() { - + void postXmlContent() { String content = "" + "John"; diff --git a/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java b/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java index 90b8214df344..b078744408f8 100644 --- a/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java +++ b/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java @@ -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. @@ -31,7 +31,6 @@ import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.core.task.TaskRejectedException; -import org.springframework.core.task.TaskTimeoutException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -143,6 +142,7 @@ public void scheduleWork(Work work, long startTimeout, @Nullable ExecutionContex * (or -1 if not applicable or not known) * @throws WorkException if the TaskExecutor did not accept the Work */ + @SuppressWarnings("deprecation") protected long executeWork(TaskExecutor taskExecutor, Work work, long startTimeout, boolean blockUntilStarted, @Nullable ExecutionContext executionContext, @Nullable WorkListener workListener) throws WorkException { @@ -164,7 +164,7 @@ protected long executeWork(TaskExecutor taskExecutor, Work work, long startTimeo taskExecutor.execute(workHandle); } } - catch (TaskTimeoutException ex) { + catch (org.springframework.core.task.TaskTimeoutException ex) { WorkException wex = new WorkRejectedException("TaskExecutor rejected Work because of timeout: " + work, ex); wex.setErrorCode(WorkException.START_TIMED_OUT); workListenerToUse.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, wex)); diff --git a/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java b/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java index e4fe06bfeb35..496d4ca80d88 100644 --- a/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java +++ b/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java @@ -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. @@ -33,7 +33,6 @@ import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskRejectedException; -import org.springframework.core.task.TaskTimeoutException; import org.springframework.jca.context.BootstrapContextAware; import org.springframework.jndi.JndiLocatorSupport; import org.springframework.lang.Nullable; @@ -218,11 +217,13 @@ private WorkManager obtainWorkManager() { // Implementation of the Spring SchedulingTaskExecutor interface //------------------------------------------------------------------------- + @SuppressWarnings("deprecation") @Override public void execute(Runnable task) { execute(task, TIMEOUT_INDEFINITE); } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { Work work = new DelegatingWork(this.taskDecorator != null ? this.taskDecorator.decorate(task) : task); @@ -254,7 +255,8 @@ else if (this.blockUntilStarted) { } catch (WorkRejectedException ex) { if (WorkException.START_TIMED_OUT.equals(ex.getErrorCode())) { - throw new TaskTimeoutException("JCA WorkManager rejected task because of timeout: " + task, ex); + throw new org.springframework.core.task.TaskTimeoutException( + "JCA WorkManager rejected task because of timeout: " + task, ex); } else { throw new TaskRejectedException("JCA WorkManager rejected task: " + task, ex); @@ -265,6 +267,7 @@ else if (this.blockUntilStarted) { } } + @SuppressWarnings("deprecation") @Override public Future submit(Runnable task) { FutureTask future = new FutureTask<>(task, null); @@ -272,6 +275,7 @@ public Future submit(Runnable task) { return future; } + @SuppressWarnings("deprecation") @Override public Future submit(Callable task) { FutureTask future = new FutureTask<>(task); @@ -279,6 +283,7 @@ public Future submit(Callable task) { return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Runnable task) { ListenableFutureTask future = new ListenableFutureTask<>(task, null); @@ -286,6 +291,7 @@ public ListenableFuture submitListenable(Runnable task) { return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Callable task) { ListenableFutureTask future = new ListenableFutureTask<>(task); diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java index d797b99f4b1b..ff1344424aa6 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java @@ -17,8 +17,12 @@ package org.springframework.http.codec.multipart; import java.nio.charset.Charset; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; import java.util.List; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -477,11 +481,14 @@ private final class BodyState implements State { private final DataBufferUtils.Matcher boundary; - private final AtomicReference previous = new AtomicReference<>(); + private final int boundaryLength; + + private final Deque queue = new ConcurrentLinkedDeque<>(); public BodyState() { - this.boundary = DataBufferUtils.matcher( - MultipartUtils.concat(CR_LF, TWO_HYPHENS, MultipartParser.this.boundary)); + byte[] delimiter = MultipartUtils.concat(CR_LF, TWO_HYPHENS, MultipartParser.this.boundary); + this.boundary = DataBufferUtils.matcher(delimiter); + this.boundaryLength = delimiter.length; } /** @@ -499,31 +506,38 @@ public void onNext(DataBuffer buffer) { if (logger.isTraceEnabled()) { logger.trace("Boundary found @" + endIdx + " in " + buffer); } - int len = endIdx - buffer.readPosition() - this.boundary.delimiter().length + 1; + int len = endIdx - buffer.readPosition() - this.boundaryLength + 1; if (len > 0) { - // buffer contains complete delimiter, let's slice it and flush it + // whole boundary in buffer. + // slice off the body part, and flush DataBuffer body = buffer.retainedSlice(buffer.readPosition(), len); enqueue(body); - enqueue(null); + flush(); } else if (len < 0) { - // buffer starts with the end of the delimiter, let's slice the previous buffer and flush it - DataBuffer previous = this.previous.get(); - int prevLen = previous.readableByteCount() + len; - if (prevLen > 0) { - DataBuffer body = previous.retainedSlice(previous.readPosition(), prevLen); - DataBufferUtils.release(previous); - this.previous.set(body); - enqueue(null); - } - else { - DataBufferUtils.release(previous); - this.previous.set(null); + // boundary spans multiple buffers, and we've just found the end + // iterate over buffers in reverse order + DataBuffer prev; + while ((prev = this.queue.pollLast()) != null) { + int prevLen = prev.readableByteCount() + len; + if (prevLen > 0) { + // slice body part of previous buffer, and flush it + DataBuffer body = prev.retainedSlice(prev.readPosition(), prevLen); + DataBufferUtils.release(prev); + enqueue(body); + flush(); + break; + } + else { + // previous buffer only contains boundary bytes + DataBufferUtils.release(prev); + len += prev.readableByteCount(); + } } } - else /* if (sliceLength == 0) */ { - // buffer starts with complete delimiter, flush out the previous buffer - enqueue(null); + else /* if (len == 0) */ { + // buffer starts with complete delimiter, flush out the previous buffers + flush(); } DataBuffer remainder = MultipartUtils.sliceFrom(buffer, endIdx); @@ -538,13 +552,32 @@ else if (len < 0) { } /** - * Stores the given buffer and sends out the previous buffer. + * Store the given buffer. Emit buffers that cannot contain boundary bytes, + * by iterating over the queue in reverse order, and summing buffer sizes. + * The first buffer that passes the boundary length and subsequent buffers + * are emitted (in the correct, non-reverse order). */ - private void enqueue(@Nullable DataBuffer buf) { - DataBuffer previous = this.previous.getAndSet(buf); - if (previous != null) { - emitBody(previous); + private void enqueue(DataBuffer buf) { + this.queue.add(buf); + + int len = 0; + Deque emit = new ArrayDeque<>(); + for (Iterator iterator = this.queue.descendingIterator(); iterator.hasNext(); ) { + DataBuffer previous = iterator.next(); + if (len > this.boundaryLength) { + // addFirst to negate iterating in reverse order + emit.addFirst(previous); + iterator.remove(); + } + len += previous.readableByteCount(); } + + emit.forEach(MultipartParser.this::emitBody); + } + + private void flush() { + this.queue.forEach(MultipartParser.this::emitBody); + this.queue.clear(); } @Override @@ -556,10 +589,8 @@ public void onComplete() { @Override public void dispose() { - DataBuffer previous = this.previous.getAndSet(null); - if (previous != null) { - DataBufferUtils.release(previous); - } + this.queue.forEach(DataBufferUtils::release); + this.queue.clear(); } @Override diff --git a/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java index 0dbfc02f098b..62a899f9b3aa 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java @@ -45,6 +45,7 @@ import org.springframework.http.StreamingHttpOutputMessage; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; /** @@ -207,6 +208,7 @@ public BufferedImage read(@Nullable Class clazz, HttpIn } private ImageInputStream createImageInputStream(InputStream is) throws IOException { + is = StreamUtils.nonClosing(is); if (this.cacheDir != null) { return new FileCacheImageInputStream(is, this.cacheDir); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java index 5a62697950cd..60d9ecc0220b 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java @@ -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. @@ -17,6 +17,7 @@ package org.springframework.http.converter.feed; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; @@ -35,6 +36,7 @@ import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; /** @@ -74,7 +76,8 @@ protected T readInternal(Class clazz, HttpInputMessage inputMessage Charset charset = (contentType != null && contentType.getCharset() != null ? contentType.getCharset() : DEFAULT_CHARSET); try { - Reader reader = new InputStreamReader(inputMessage.getBody(), charset); + InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody()); + Reader reader = new InputStreamReader(inputStream, charset); return (T) feedInput.build(reader); } catch (FeedException ex) { diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index 19b22fef2c81..b5273919e4d5 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -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. @@ -17,6 +17,7 @@ package org.springframework.http.converter.json; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; @@ -361,24 +362,25 @@ private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) th "UTF-16".equals(charset.name()) || "UTF-32".equals(charset.name()); try { + InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody()); if (inputMessage instanceof MappingJacksonInputMessage) { Class deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView(); if (deserializationView != null) { ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType); if (isUnicode) { - return objectReader.readValue(inputMessage.getBody()); + return objectReader.readValue(inputStream); } else { - Reader reader = new InputStreamReader(inputMessage.getBody(), charset); + Reader reader = new InputStreamReader(inputStream, charset); return objectReader.readValue(reader); } } } if (isUnicode) { - return objectMapper.readValue(inputMessage.getBody(), javaType); + return objectMapper.readValue(inputStream, javaType); } else { - Reader reader = new InputStreamReader(inputMessage.getBody(), charset); + Reader reader = new InputStreamReader(inputStream, charset); return objectMapper.readValue(reader, javaType); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractXmlHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractXmlHttpMessageConverter.java index 9da6bcb42c11..19d95e4bef3a 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractXmlHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractXmlHttpMessageConverter.java @@ -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. @@ -17,6 +17,7 @@ package org.springframework.http.converter.xml; import java.io.IOException; +import java.io.InputStream; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -33,6 +34,7 @@ import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.util.StreamUtils; /** * Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters} @@ -66,7 +68,8 @@ public final T readInternal(Class clazz, HttpInputMessage inputMess throws IOException, HttpMessageNotReadableException { try { - return readFromSource(clazz, inputMessage.getHeaders(), new StreamSource(inputMessage.getBody())); + InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody()); + return readFromSource(clazz, inputMessage.getHeaders(), new StreamSource(inputStream)); } catch (IOException | HttpMessageConversionException ex) { throw ex; diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java index 63c70e30c643..468152e2ce3f 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java @@ -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. @@ -146,7 +146,7 @@ public boolean supports(Class clazz) { protected T readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { - InputStream body = inputMessage.getBody(); + InputStream body = StreamUtils.nonClosing(inputMessage.getBody()); if (DOMSource.class == clazz) { return (T) readDOMSource(body, inputMessage); } diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index b136e3fbcf2f..82f722310c3b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -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. @@ -158,7 +158,9 @@ public HttpHeaders getHeaders() { String requestContentType = this.servletRequest.getContentType(); if (StringUtils.hasLength(requestContentType)) { contentType = MediaType.parseMediaType(requestContentType); - this.headers.setContentType(contentType); + if (contentType.isConcrete()) { + this.headers.setContentType(contentType); + } } } if (contentType != null && contentType.getCharset() == null) { diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index 5de3b4352b2e..97253608dfca 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -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. @@ -36,16 +36,27 @@ /** * {@link org.springframework.web.context.WebApplicationContext WebApplicationContext} * implementation which accepts component classes as input — in particular - * {@link org.springframework.context.annotation.Configuration @Configuration}-annotated + * {@link org.springframework.context.annotation.Configuration @Configuration} * classes, but also plain {@link org.springframework.stereotype.Component @Component} - * classes and JSR-330 compliant classes using {@code javax.inject} annotations. + * classes as well as JSR-330 compliant classes using {@code javax.inject} annotations. * *

Allows for registering classes one by one (specifying class names as config - * location) as well as for classpath scanning (specifying base packages as config location). + * locations) as well as via classpath scanning (specifying base packages as config + * locations). * *

This is essentially the equivalent of * {@link org.springframework.context.annotation.AnnotationConfigApplicationContext - * AnnotationConfigApplicationContext} for a web environment. + * AnnotationConfigApplicationContext} for a web environment. However, in contrast to + * {@code AnnotationConfigApplicationContext}, this class does not extend + * {@link org.springframework.context.support.GenericApplicationContext + * GenericApplicationContext} and therefore does not provide some of the convenient + * {@code registerBean(...)} methods available in a {@code GenericApplicationContext}. + * If you wish to register annotated component classes with a + * {@code GenericApplicationContext} in a web environment, you may use a + * {@code GenericWebApplicationContext} with an + * {@link org.springframework.context.annotation.AnnotatedBeanDefinitionReader + * AnnotatedBeanDefinitionReader}. See the Javadoc for {@link GenericWebApplicationContext} + * for details and an example. * *

To make use of this application context, the * {@linkplain ContextLoader#CONTEXT_CLASS_PARAM "contextClass"} context-param for @@ -80,8 +91,10 @@ * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 * @see org.springframework.context.annotation.AnnotationConfigApplicationContext + * @see org.springframework.web.context.support.GenericWebApplicationContext */ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext implements AnnotationConfigRegistry { diff --git a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java index c8a39cb2908b..fed8f1d01c9c 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java @@ -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. @@ -39,26 +39,42 @@ /** * Subclass of {@link GenericApplicationContext}, suitable for web environments. * - *

Implements {@link org.springframework.web.context.ConfigurableWebApplicationContext}, - * but is not intended for declarative setup in {@code web.xml}. Instead, it is designed - * for programmatic setup, for example for building nested contexts or for use within + *

Implements {@link ConfigurableWebApplicationContext}, but is not intended for + * declarative setup in {@code web.xml}. Instead, it is designed for programmatic setup, + * for example for building nested contexts or for use within * {@link org.springframework.web.WebApplicationInitializer WebApplicationInitializers}. * - *

If you intend to implement a WebApplicationContext that reads bean definitions - * from configuration files, consider deriving from AbstractRefreshableWebApplicationContext, - * reading the bean definitions in an implementation of the {@code loadBeanDefinitions} - * method. - * *

Interprets resource paths as servlet context resources, i.e. as paths beneath - * the web application root. Absolute paths, e.g. for files outside the web app root, - * can be accessed via "file:" URLs, as implemented by AbstractApplicationContext. + * the web application root. Absolute paths — for example, for files outside + * the web app root — can be accessed via {@code file:} URLs, as implemented + * by {@code AbstractApplicationContext}. * *

In addition to the special beans detected by - * {@link org.springframework.context.support.AbstractApplicationContext}, - * this class detects a ThemeSource bean in the context, with the name "themeSource". + * {@link org.springframework.context.support.AbstractApplicationContext AbstractApplicationContext}, + * this class detects a {@link ThemeSource} bean in the context, with the name "themeSource". + * + *

If you wish to register annotated component classes with a + * {@code GenericWebApplicationContext}, you can use an + * {@link org.springframework.context.annotation.AnnotatedBeanDefinitionReader + * AnnotatedBeanDefinitionReader}, as demonstrated in the following example. + * Component classes include in particular + * {@link org.springframework.context.annotation.Configuration @Configuration} + * classes but also plain {@link org.springframework.stereotype.Component @Component} + * classes as well as JSR-330 compliant classes using {@code javax.inject} annotations. + * + *

+ * GenericWebApplicationContext context = new GenericWebApplicationContext();
+ * AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(context);
+ * reader.register(AppConfig.class, UserController.class, UserRepository.class);
+ * + *

If you intend to implement a {@code WebApplicationContext} that reads bean definitions + * from configuration files, consider deriving from {@link AbstractRefreshableWebApplicationContext}, + * reading the bean definitions in an implementation of the {@code loadBeanDefinitions} + * method. * * @author Juergen Hoeller * @author Chris Beams + * @author Sam Brannen * @since 1.2 */ public class GenericWebApplicationContext extends GenericApplicationContext @@ -72,7 +88,7 @@ public class GenericWebApplicationContext extends GenericApplicationContext /** - * Create a new GenericWebApplicationContext. + * Create a new {@code GenericWebApplicationContext}. * @see #setServletContext * @see #registerBeanDefinition * @see #refresh @@ -82,8 +98,8 @@ public GenericWebApplicationContext() { } /** - * Create a new GenericWebApplicationContext for the given ServletContext. - * @param servletContext the ServletContext to run in + * Create a new {@code GenericWebApplicationContext} for the given {@link ServletContext}. + * @param servletContext the {@code ServletContext} to run in * @see #registerBeanDefinition * @see #refresh */ @@ -92,8 +108,8 @@ public GenericWebApplicationContext(ServletContext servletContext) { } /** - * Create a new GenericWebApplicationContext with the given DefaultListableBeanFactory. - * @param beanFactory the DefaultListableBeanFactory instance to use for this context + * Create a new {@code GenericWebApplicationContext} with the given {@link DefaultListableBeanFactory}. + * @param beanFactory the {@code DefaultListableBeanFactory} instance to use for this context * @see #setServletContext * @see #registerBeanDefinition * @see #refresh @@ -103,9 +119,10 @@ public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory) { } /** - * Create a new GenericWebApplicationContext with the given DefaultListableBeanFactory. - * @param beanFactory the DefaultListableBeanFactory instance to use for this context - * @param servletContext the ServletContext to run in + * Create a new {@code GenericWebApplicationContext} with the given {@link DefaultListableBeanFactory} + * and {@link ServletContext}. + * @param beanFactory the {@code DefaultListableBeanFactory} instance to use for this context + * @param servletContext the {@code ServletContext} to run in * @see #registerBeanDefinition * @see #refresh */ @@ -116,7 +133,7 @@ public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, Serv /** - * Set the ServletContext that this WebApplicationContext runs in. + * Set the {@link ServletContext} that this {@code WebApplicationContext} runs in. */ @Override public void setServletContext(@Nullable ServletContext servletContext) { @@ -143,8 +160,7 @@ protected ConfigurableEnvironment createEnvironment() { } /** - * Register ServletContextAwareProcessor. - * @see ServletContextAwareProcessor + * Register request/session scopes, environment beans, a {@link ServletContextAwareProcessor}, etc. */ @Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { @@ -157,7 +173,7 @@ protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactor } /** - * This implementation supports file paths beneath the root of the ServletContext. + * This implementation supports file paths beneath the root of the {@link ServletContext}. * @see ServletContextResource */ @Override @@ -236,7 +252,7 @@ public void setConfigLocation(String configLocation) { if (StringUtils.hasText(configLocation)) { throw new UnsupportedOperationException( "GenericWebApplicationContext does not support setConfigLocation(). " + - "Do you still have an 'contextConfigLocations' init-param set?"); + "Do you still have a 'contextConfigLocation' init-param set?"); } } @@ -245,7 +261,7 @@ public void setConfigLocations(String... configLocations) { if (!ObjectUtils.isEmpty(configLocations)) { throw new UnsupportedOperationException( "GenericWebApplicationContext does not support setConfigLocations(). " + - "Do you still have an 'contextConfigLocations' init-param set?"); + "Do you still have a 'contextConfigLocations' init-param set?"); } } diff --git a/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java b/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java index 1c4455dbbe99..43eda1c21d11 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java @@ -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. @@ -74,7 +74,7 @@ public static RequestPath parseAndCache(HttpServletRequest request) { */ public static RequestPath getParsedRequestPath(ServletRequest request) { RequestPath path = (RequestPath) request.getAttribute(PATH_ATTRIBUTE); - Assert.notNull(path, "Expected parsed RequestPath in request attribute \"" + PATH_ATTRIBUTE + "\"."); + Assert.notNull(path, () -> "Expected parsed RequestPath in request attribute \"" + PATH_ATTRIBUTE + "\"."); return path; } diff --git a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java index 82a3e79cb4c6..704fec2e1493 100644 --- a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java @@ -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. @@ -72,13 +72,7 @@ public void basic() throws Exception { @Test public void noExecution() throws Exception { List interceptors = new ArrayList<>(); - interceptors.add(new ClientHttpRequestInterceptor() { - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - return responseMock; - } - }); + interceptors.add((request, body, execution) -> responseMock); interceptors.add(new NoOpInterceptor()); requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, interceptors); @@ -97,15 +91,11 @@ public void changeHeaders() throws Exception { final String headerValue = "Bar"; final String otherValue = "Baz"; - ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { + ClientHttpRequestInterceptor interceptor = (request, body, execution) -> { HttpRequestWrapper wrapper = new HttpRequestWrapper(request); wrapper.getHeaders().add(headerName, otherValue); return execution.execute(wrapper, body); - } - }; + }; requestMock = new RequestMock() { @Override @@ -119,8 +109,7 @@ public ClientHttpResponse execute() throws IOException { }; requestMock.getHeaders().add(headerName, headerValue); - requestFactory = - new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); request.execute(); @@ -130,19 +119,13 @@ public ClientHttpResponse execute() throws IOException { public void changeURI() throws Exception { final URI changedUri = new URI("https://example.com/2"); - ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { + ClientHttpRequestInterceptor interceptor = (request, body, execution) -> execution.execute(new HttpRequestWrapper(request) { @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - return execution.execute(new HttpRequestWrapper(request) { - @Override - public URI getURI() { - return changedUri; - } - - }, body); + public URI getURI() { + return changedUri; } - }; + + }, body); requestFactoryMock = new RequestFactoryMock() { @Override @@ -152,8 +135,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO } }; - requestFactory = - new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); request.execute(); @@ -163,19 +145,13 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO public void changeMethod() throws Exception { final HttpMethod changedMethod = HttpMethod.POST; - ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { + ClientHttpRequestInterceptor interceptor = (request, body, execution) -> execution.execute(new HttpRequestWrapper(request) { @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - return execution.execute(new HttpRequestWrapper(request) { - @Override - public HttpMethod getMethod() { - return changedMethod; - } - - }, body); + public HttpMethod getMethod() { + return changedMethod; } - }; + + }, body); requestFactoryMock = new RequestFactoryMock() { @Override @@ -185,8 +161,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO } }; - requestFactory = - new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); request.execute(); @@ -196,16 +171,9 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO public void changeBody() throws Exception { final byte[] changedBody = "Foo".getBytes(); - ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - return execution.execute(request, changedBody); - } - }; + ClientHttpRequestInterceptor interceptor = (request, body, execution) -> execution.execute(request, changedBody); - requestFactory = - new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); request.execute(); diff --git a/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java index b91f6b429e34..477cd7138cda 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java @@ -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. @@ -19,6 +19,7 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import javax.imageio.ImageIO; @@ -33,6 +34,9 @@ import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Unit tests for BufferedImageHttpMessageConverter. @@ -65,11 +69,13 @@ public void canWrite() { public void read() throws IOException { Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class); byte[] body = FileCopyUtils.copyToByteArray(logo.getInputStream()); - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); + InputStream inputStream = spy(new ByteArrayInputStream(body)); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(new MediaType("image", "jpeg")); BufferedImage result = converter.read(BufferedImage.class, inputMessage); assertThat(result.getHeight()).as("Invalid height").isEqualTo(500); assertThat(result.getWidth()).as("Invalid width").isEqualTo(750); + verify(inputStream, never()).close(); } @Test @@ -84,6 +90,7 @@ public void write() throws IOException { BufferedImage result = ImageIO.read(new ByteArrayInputStream(outputMessage.getBodyAsBytes())); assertThat(result.getHeight()).as("Invalid height").isEqualTo(500); assertThat(result.getWidth()).as("Invalid width").isEqualTo(750); + verify(outputMessage.getBody(), never()).close(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java index d868e2666ee2..63c9b5aa3221 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java @@ -63,8 +63,10 @@ public void write() throws IOException { byte[] body = new byte[]{0x1, 0x2}; converter.write(body, null, outputMessage); assertThat(outputMessage.getBodyAsBytes()).as("Invalid result").isEqualTo(body); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "octet-stream")); - assertThat(outputMessage.getHeaders().getContentLength()).as("Invalid content-length").isEqualTo(2); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_OCTET_STREAM); + assertThat(outputMessage.getHeaders().getContentLength()) + .as("Invalid content-length").isEqualTo(2); } } diff --git a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java index 05669acc9962..1c3b8729abd8 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -147,9 +147,12 @@ public void writeForm() throws IOException { MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); this.converter.write(body, APPLICATION_FORM_URLENCODED, outputMessage); - assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).as("Invalid result").isEqualTo("name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"); - assertThat(outputMessage.getHeaders().getContentType().toString()).as("Invalid content-type").isEqualTo("application/x-www-form-urlencoded;charset=UTF-8"); - assertThat(outputMessage.getHeaders().getContentLength()).as("Invalid content-length").isEqualTo(outputMessage.getBodyAsBytes().length); + assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)) + .as("Invalid result").isEqualTo("name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"); + assertThat(outputMessage.getHeaders().getContentType().toString()) + .as("Invalid content-type").isEqualTo("application/x-www-form-urlencoded;charset=UTF-8"); + assertThat(outputMessage.getHeaders().getContentLength()) + .as("Invalid content-length").isEqualTo(outputMessage.getBodyAsBytes().length); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java index 3c98fe538d8d..3bf1b9d3e3eb 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java @@ -106,8 +106,10 @@ public void shouldWriteImageResource() throws IOException { Resource body = new ClassPathResource("logo.jpg", getClass()); converter.write(body, null, outputMessage); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(MediaType.IMAGE_JPEG); - assertThat(outputMessage.getHeaders().getContentLength()).as("Invalid content-length").isEqualTo(body.getFile().length()); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.IMAGE_JPEG); + assertThat(outputMessage.getHeaders().getContentLength()) + .as("Invalid content-length").isEqualTo(body.getFile().length()); } @Test // SPR-10848 diff --git a/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java index 856dcd787067..cd71fa9f3976 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java @@ -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. @@ -38,6 +38,9 @@ import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * @author Arjen Poutsma @@ -71,8 +74,8 @@ public void canWrite() { @Test public void read() throws IOException { - InputStream is = getClass().getResourceAsStream("atom.xml"); - MockHttpInputMessage inputMessage = new MockHttpInputMessage(is); + InputStream inputStream = spy(getClass().getResourceAsStream("atom.xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(ATOM_XML_UTF8); Feed result = converter.read(Feed.class, inputMessage); assertThat(result.getTitle()).isEqualTo("title"); @@ -87,6 +90,7 @@ public void read() throws IOException { Entry entry2 = (Entry) entries.get(1); assertThat(entry2.getId()).isEqualTo("id2"); assertThat(entry2.getTitle()).isEqualTo("title2"); + verify(inputStream, never()).close(); } @Test @@ -119,6 +123,7 @@ public void write() throws IOException { NodeMatcher nm = new DefaultNodeMatcher(ElementSelectors.byName); assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarToIgnoringWhitespace(expected, nm); + verify(outputMessage.getBody(), never()).close(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java index 5d1e01f32871..b537604ad3e6 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java @@ -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. @@ -34,6 +34,9 @@ import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * @author Arjen Poutsma @@ -56,8 +59,8 @@ public void canReadAndWrite() { @Test public void read() throws IOException { - InputStream is = getClass().getResourceAsStream("rss.xml"); - MockHttpInputMessage inputMessage = new MockHttpInputMessage(is); + InputStream inputStream = spy(getClass().getResourceAsStream("rss.xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(RSS_XML_UTF8); Channel result = converter.read(Channel.class, inputMessage); assertThat(result.getTitle()).isEqualTo("title"); @@ -72,6 +75,7 @@ public void read() throws IOException { Item item2 = (Item) items.get(1); assertThat(item2.getTitle()).isEqualTo("title2"); + verify(inputStream, never()).close(); } @Test @@ -105,6 +109,7 @@ public void write() throws IOException { ""; assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarToIgnoringWhitespace(expected); + verify(outputMessage.getBody(), never()).close(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java index aa2c777ae4cd..6d5cf1453dce 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java @@ -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. @@ -16,7 +16,9 @@ package org.springframework.http.converter.json; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.nio.charset.Charset; @@ -38,6 +40,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.within; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Gson 2.x converter tests. @@ -72,7 +77,8 @@ public void canReadAndWriteMicroformats() { public void readTyped() throws IOException { String body = "{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"]," + "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + InputStream inputStream = spy(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); MyBean result = (MyBean) this.converter.read(MyBean.class, inputMessage); @@ -83,6 +89,7 @@ public void readTyped() throws IOException { assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"}); assertThat(result.isBool()).isTrue(); assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2}); + verify(inputStream, never()).close(); } @Test @@ -131,7 +138,9 @@ public void write() throws IOException { assertThat(result.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); assertThat(result.contains("\"bool\":true")).isTrue(); assertThat(result.contains("\"bytes\":[1,2]")).isTrue(); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); + verify(outputMessage.getBody(), never()).close(); } @Test @@ -153,7 +162,8 @@ public void writeWithBaseType() throws IOException { assertThat(result.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); assertThat(result.contains("\"bool\":true")).isTrue(); assertThat(result.contains("\"bytes\":[1,2]")).isTrue(); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); } @Test @@ -169,7 +179,7 @@ public void writeUTF16() throws IOException { @Test public void readInvalidJson() throws IOException { String body = "FooBar"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> this.converter.read(MyBean.class, inputMessage)); diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java index 44e14ac57612..2519005f6672 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java @@ -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. @@ -16,7 +16,9 @@ package org.springframework.http.converter.json; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.nio.charset.Charset; @@ -38,6 +40,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.within; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Integration tests for the JSON Binding API, running against Apache Johnzon. @@ -72,7 +77,8 @@ public void canReadAndWriteMicroformats() { public void readTyped() throws IOException { String body = "{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"]," + "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); + InputStream inputStream = spy(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); MyBean result = (MyBean) this.converter.read(MyBean.class, inputMessage); @@ -83,6 +89,7 @@ public void readTyped() throws IOException { assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"}); assertThat(result.isBool()).isTrue(); assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2}); + verify(inputStream, never()).close(); } @Test @@ -131,7 +138,9 @@ public void write() throws IOException { assertThat(result.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); assertThat(result.contains("\"bool\":true")).isTrue(); assertThat(result.contains("\"bytes\":[1,2]")).isTrue(); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); + verify(outputMessage.getBody(), never()).close(); } @Test @@ -153,7 +162,8 @@ public void writeWithBaseType() throws IOException { assertThat(result.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); assertThat(result.contains("\"bool\":true")).isTrue(); assertThat(result.contains("\"bytes\":[1,2]")).isTrue(); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(new MediaType("application", "json", utf8)); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index cea6e8c7cf76..b06544ac239a 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -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. @@ -16,7 +16,9 @@ package org.springframework.http.converter.json; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -48,6 +50,7 @@ import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.within; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** @@ -134,7 +137,8 @@ public void readTyped() throws IOException { "\"string\":\"Foo\"," + "\"bool\":true," + "\"fraction\":42.0}"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + InputStream inputStream = spy(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); MyBean result = (MyBean) converter.read(MyBean.class, inputMessage); assertThat(result.getString()).isEqualTo("Foo"); @@ -143,6 +147,7 @@ public void readTyped() throws IOException { assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"}); assertThat(result.isBool()).isTrue(); assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2}); + verify(inputStream, never()).close(); } @Test @@ -155,7 +160,7 @@ public void readUntyped() throws IOException { "\"string\":\"Foo\"," + "\"bool\":true," + "\"fraction\":42.0}"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); HashMap result = (HashMap) converter.read(HashMap.class, inputMessage); assertThat(result.get("string")).isEqualTo("Foo"); @@ -187,7 +192,8 @@ public void write() throws IOException { assertThat(result.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); assertThat(result.contains("\"bool\":true")).isTrue(); assertThat(result.contains("\"bytes\":\"AQI=\"")).isTrue(); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON); verify(outputMessage.getBody(), never()).close(); } @@ -209,7 +215,8 @@ public void writeWithBaseType() throws IOException { assertThat(result.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); assertThat(result.contains("\"bool\":true")).isTrue(); assertThat(result.contains("\"bytes\":\"AQI=\"")).isTrue(); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON); } @Test @@ -225,16 +232,16 @@ public void writeUTF16() throws IOException { @Test public void readInvalidJson() throws IOException { String body = "FooBar"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); - assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> - converter.read(MyBean.class, inputMessage)); + assertThatExceptionOfType(HttpMessageNotReadableException.class) + .isThrownBy(() -> converter.read(MyBean.class, inputMessage)); } @Test public void readValidJsonWithUnknownProperty() throws IOException { String body = "{\"string\":\"string\",\"unknownProperty\":\"value\"}"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); converter.read(MyBean.class, inputMessage); // Assert no HttpMessageNotReadableException is thrown @@ -261,7 +268,7 @@ protected JavaType getJavaType(Type type, @Nullable Class contextClass) { "\"string\":\"Foo\"," + "\"bool\":true," + "\"fraction\":42.0}]"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); List results = (List) converter.read(List.class, inputMessage); @@ -275,7 +282,7 @@ protected JavaType getJavaType(Type type, @Nullable Class contextClass) { assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2}); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - converter.write(results, new MediaType("application", "json"), outputMessage); + converter.write(results, MediaType.APPLICATION_JSON, outputMessage); JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true); } @@ -291,8 +298,8 @@ public void readAndWriteParameterizedType() throws Exception { "\"string\":\"Foo\"," + "\"bool\":true," + "\"fraction\":42.0}]"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "json")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); List results = (List) converter.read(beansList.getType(), null, inputMessage); @@ -306,7 +313,7 @@ public void readAndWriteParameterizedType() throws Exception { assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2}); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - converter.write(results, beansList.getType(), new MediaType("application", "json"), outputMessage); + converter.write(results, beansList.getType(), MediaType.APPLICATION_JSON, outputMessage); JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true); } @@ -323,8 +330,8 @@ public void writeParameterizedBaseType() throws Exception { "\"string\":\"Foo\"," + "\"bool\":true," + "\"fraction\":42.0}]"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "json")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); List results = (List) converter.read(beansList.getType(), null, inputMessage); @@ -338,7 +345,7 @@ public void writeParameterizedBaseType() throws Exception { assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2}); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - converter.write(results, baseList.getType(), new MediaType("application", "json"), outputMessage); + converter.write(results, baseList.getType(), MediaType.APPLICATION_JSON, outputMessage); JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true); } @@ -484,7 +491,7 @@ public void writeSubTypeList() throws Exception { public void readWithNoDefaultConstructor() throws Exception { String body = "{\"property1\":\"foo\",\"property2\":\"bar\"}"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); - inputMessage.getHeaders().setContentType(new MediaType("application", "json")); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON); assertThatExceptionOfType(HttpMessageConversionException.class).isThrownBy(() -> converter.read(BeanWithNoDefaultConstructor.class, inputMessage)) .withMessageStartingWith("Type definition error:"); diff --git a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java index 26106e13d3d2..01dc97d84f94 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java @@ -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. @@ -16,8 +16,10 @@ package org.springframework.http.converter.protobuf; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.charset.Charset; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; @@ -34,6 +36,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -108,10 +112,12 @@ public void canWrite() { @Test public void read() throws IOException { byte[] body = this.testMsg.toByteArray(); + InputStream inputStream = spy(new ByteArrayInputStream(body)); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); inputMessage.getHeaders().setContentType(ProtobufHttpMessageConverter.PROTOBUF); Message result = this.converter.read(Msg.class, inputMessage); assertThat(result).isEqualTo(this.testMsg); + verify(inputStream, never()).close(); } @Test @@ -138,6 +144,7 @@ public void writeProtobuf() throws IOException { String schemaHeader = outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_SCHEMA_HEADER); assertThat(schemaHeader).isEqualTo("sample.proto"); + verify(outputMessage.getBody(), never()).close(); } @Test @@ -151,7 +158,7 @@ public void writeJsonWithGoogleProtobuf() throws IOException { assertThat(outputMessage.getHeaders().getContentType()).isEqualTo(contentType); - final String body = outputMessage.getBodyAsString(Charset.forName("UTF-8")); + final String body = outputMessage.getBodyAsString(StandardCharsets.UTF_8); assertThat(body.isEmpty()).as("body is empty").isFalse(); Msg.Builder builder = Msg.newBuilder(); @@ -175,7 +182,7 @@ public void writeJsonWithJavaFormat() throws IOException { assertThat(outputMessage.getHeaders().getContentType()).isEqualTo(contentType); - final String body = outputMessage.getBodyAsString(Charset.forName("UTF-8")); + final String body = outputMessage.getBodyAsString(StandardCharsets.UTF_8); assertThat(body.isEmpty()).as("body is empty").isFalse(); Msg.Builder builder = Msg.newBuilder(); @@ -190,7 +197,8 @@ public void writeJsonWithJavaFormat() throws IOException { @Test public void defaultContentType() throws Exception { - assertThat(this.converter.getDefaultContentType(this.testMsg)).isEqualTo(ProtobufHttpMessageConverter.PROTOBUF); + assertThat(this.converter.getDefaultContentType(this.testMsg)) + .isEqualTo(ProtobufHttpMessageConverter.PROTOBUF); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java index 296efbd8b3b1..c933c4262286 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java @@ -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. @@ -16,7 +16,9 @@ package org.springframework.http.converter.protobuf; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; @@ -32,6 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -88,10 +92,12 @@ public void canWrite() { @Test public void read() throws IOException { byte[] body = this.testMsg.toByteArray(); + InputStream inputStream = spy(new ByteArrayInputStream(body)); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); inputMessage.getHeaders().setContentType(ProtobufHttpMessageConverter.PROTOBUF); Message result = this.converter.read(Msg.class, inputMessage); assertThat(result).isEqualTo(this.testMsg); + verify(inputStream, never()).close(); } @Test @@ -118,6 +124,7 @@ public void write() throws IOException { String schemaHeader = outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_SCHEMA_HEADER); assertThat(schemaHeader).isEqualTo("sample.proto"); + verify(outputMessage.getBody(), never()).close(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java index 2cbb22bdef35..231d00306060 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java @@ -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. @@ -16,7 +16,9 @@ package org.springframework.http.converter.smile; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.smile.SmileFactory; @@ -28,6 +30,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Jackson 2.x Smile converter tests. @@ -63,7 +68,8 @@ public void read() throws IOException { body.setArray(new String[]{"Foo", "Bar"}); body.setBool(true); body.setBytes(new byte[]{0x1, 0x2}); - MockHttpInputMessage inputMessage = new MockHttpInputMessage(mapper.writeValueAsBytes(body)); + InputStream inputStream = spy(new ByteArrayInputStream(mapper.writeValueAsBytes(body))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(new MediaType("application", "x-jackson-smile")); MyBean result = (MyBean) converter.read(MyBean.class, inputMessage); assertThat(result.getString()).isEqualTo("Foo"); @@ -73,6 +79,7 @@ public void read() throws IOException { assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"}); assertThat(result.isBool()).isTrue(); assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2}); + verify(inputStream, never()).close(); } @Test @@ -87,7 +94,9 @@ public void write() throws IOException { body.setBytes(new byte[]{0x1, 0x2}); converter.write(body, null, outputMessage); assertThat(outputMessage.getBodyAsBytes()).isEqualTo(mapper.writeValueAsBytes(body)); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "x-jackson-smile")); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(new MediaType("application", "x-jackson-smile")); + verify(outputMessage.getBody(), never()).close(); } diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java index 0b02ce6ae1f2..399e2e0b4bc4 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java @@ -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. @@ -16,7 +16,10 @@ package org.springframework.http.converter.xml; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; import java.util.Set; @@ -38,6 +41,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Test fixture for {@link Jaxb2CollectionHttpMessageConverter}. @@ -79,19 +85,21 @@ public void canRead() { @SuppressWarnings("unchecked") public void readXmlRootElementList() throws Exception { String content = ""; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + InputStream inputStream = spy(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); List result = (List) converter.read(rootElementListType, null, inputMessage); assertThat(result.size()).as("Invalid result").isEqualTo(2); assertThat(result.get(0).type.s).as("Invalid result").isEqualTo("1"); assertThat(result.get(1).type.s).as("Invalid result").isEqualTo("2"); + verify(inputStream, never()).close(); } @Test @SuppressWarnings("unchecked") public void readXmlRootElementSet() throws Exception { String content = ""; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); Set result = (Set) converter.read(rootElementSetType, null, inputMessage); assertThat(result.size()).as("Invalid result").isEqualTo(2); @@ -103,7 +111,7 @@ public void readXmlRootElementSet() throws Exception { @SuppressWarnings("unchecked") public void readXmlTypeList() throws Exception { String content = ""; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); List result = (List) converter.read(typeListType, null, inputMessage); assertThat(result.size()).as("Invalid result").isEqualTo(2); @@ -115,7 +123,7 @@ public void readXmlTypeList() throws Exception { @SuppressWarnings("unchecked") public void readXmlTypeSet() throws Exception { String content = ""; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); Set result = (Set) converter.read(typeSetType, null, inputMessage); assertThat(result.size()).as("Invalid result").isEqualTo(2); @@ -131,7 +139,7 @@ public void readXmlRootElementExternalEntityDisabled() throws Exception { " \n" + " ]>" + " &ext;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); converter = new Jaxb2CollectionHttpMessageConverter>() { @Override @@ -160,7 +168,7 @@ public void readXmlRootElementExternalEntityEnabled() throws Exception { " \n" + " ]>" + " &ext;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); Jaxb2CollectionHttpMessageConverter c = new Jaxb2CollectionHttpMessageConverter>() { @Override @@ -195,10 +203,10 @@ public void testXmlBomb() throws Exception { " \n" + "]>\n" + "&lol9;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); - assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> - this.converter.read(this.rootElementListType, null, inputMessage)) - .withMessageContaining("\"lol9\""); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); + assertThatExceptionOfType(HttpMessageNotReadableException.class) + .isThrownBy(() -> this.converter.read(this.rootElementListType, null, inputMessage)) + .withMessageContaining("\"lol9\""); } diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java index cf5fa05d46cd..0324edcc10c5 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java @@ -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. @@ -16,6 +16,8 @@ package org.springframework.http.converter.xml; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import javax.xml.bind.Marshaller; @@ -44,6 +46,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.xmlunit.diff.ComparisonType.XML_STANDALONE; import static org.xmlunit.diff.DifferenceEvaluators.Default; import static org.xmlunit.diff.DifferenceEvaluators.chain; @@ -80,29 +85,37 @@ public void setup() { @Test public void canRead() { - assertThat(converter.canRead(RootElement.class, null)).as("Converter does not support reading @XmlRootElement").isTrue(); - assertThat(converter.canRead(Type.class, null)).as("Converter does not support reading @XmlType").isTrue(); + assertThat(converter.canRead(RootElement.class, null)) + .as("Converter does not support reading @XmlRootElement").isTrue(); + assertThat(converter.canRead(Type.class, null)) + .as("Converter does not support reading @XmlType").isTrue(); } @Test public void canWrite() { - assertThat(converter.canWrite(RootElement.class, null)).as("Converter does not support writing @XmlRootElement").isTrue(); - assertThat(converter.canWrite(RootElementSubclass.class, null)).as("Converter does not support writing @XmlRootElement subclass").isTrue(); - assertThat(converter.canWrite(rootElementCglib.getClass(), null)).as("Converter does not support writing @XmlRootElement subclass").isTrue(); - assertThat(converter.canWrite(Type.class, null)).as("Converter supports writing @XmlType").isFalse(); + assertThat(converter.canWrite(RootElement.class, null)) + .as("Converter does not support writing @XmlRootElement").isTrue(); + assertThat(converter.canWrite(RootElementSubclass.class, null)) + .as("Converter does not support writing @XmlRootElement subclass").isTrue(); + assertThat(converter.canWrite(rootElementCglib.getClass(), null)) + .as("Converter does not support writing @XmlRootElement subclass").isTrue(); + assertThat(converter.canWrite(Type.class, null)) + .as("Converter supports writing @XmlType").isFalse(); } @Test public void readXmlRootElement() throws Exception { - byte[] body = "".getBytes("UTF-8"); - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); + byte[] body = "".getBytes(StandardCharsets.UTF_8); + InputStream inputStream = spy(new ByteArrayInputStream(body)); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); RootElement result = (RootElement) converter.read(RootElement.class, inputMessage); assertThat(result.type.s).as("Invalid result").isEqualTo("Hello World"); + verify(inputStream, never()).close(); } @Test public void readXmlRootElementSubclass() throws Exception { - byte[] body = "".getBytes("UTF-8"); + byte[] body = "".getBytes(StandardCharsets.UTF_8); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); RootElementSubclass result = (RootElementSubclass) converter.read(RootElementSubclass.class, inputMessage); assertThat(result.getType().s).as("Invalid result").isEqualTo("Hello World"); @@ -110,7 +123,7 @@ public void readXmlRootElementSubclass() throws Exception { @Test public void readXmlType() throws Exception { - byte[] body = "".getBytes("UTF-8"); + byte[] body = "".getBytes(StandardCharsets.UTF_8); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); Type result = (Type) converter.read(Type.class, inputMessage); assertThat(result.s).as("Invalid result").isEqualTo("Hello World"); @@ -123,7 +136,7 @@ public void readXmlRootElementExternalEntityDisabled() throws Exception { " \n" + " ]>" + " &ext;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); converter.setSupportDtd(true); RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage); @@ -137,7 +150,7 @@ public void readXmlRootElementExternalEntityEnabled() throws Exception { " \n" + " ]>" + " &ext;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); this.converter.setProcessExternalEntities(true); RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage); @@ -163,7 +176,7 @@ public void testXmlBomb() throws Exception { " \n" + "]>\n" + "&lol9;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> this.converter.read(RootElement.class, inputMessage)) .withMessageContaining("DOCTYPE"); @@ -173,17 +186,20 @@ public void testXmlBomb() throws Exception { public void writeXmlRootElement() throws Exception { MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(rootElement, null, outputMessage); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_XML); DifferenceEvaluator ev = chain(Default, downgradeDifferencesToEqual(XML_STANDALONE)); assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarTo("", ev); + verify(outputMessage.getBody(), never()).close(); } @Test public void writeXmlRootElementSubclass() throws Exception { MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(rootElementCglib, null, outputMessage); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_XML); DifferenceEvaluator ev = chain(Default, downgradeDifferencesToEqual(XML_STANDALONE)); assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarTo("", ev); @@ -203,7 +219,7 @@ public void customizeMarshaller() throws Exception { @Test public void customizeUnmarshaller() throws Exception { - byte[] body = "a|||b".getBytes("UTF-8"); + byte[] body = "a|||b".getBytes(StandardCharsets.UTF_8); MyJaxb2RootElementHttpMessageConverter myConverter = new MyJaxb2RootElementHttpMessageConverter(); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); MyRootElement result = (MyRootElement) myConverter.read(MyRootElement.class, inputMessage); diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java index ab8c617b6f91..dcd4504e6f0e 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java @@ -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. @@ -16,7 +16,9 @@ package org.springframework.http.converter.xml; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -34,6 +36,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.within; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Jackson 2.x XML converter tests. @@ -74,7 +79,8 @@ public void read() throws IOException { "Bar" + "true" + "AQI="; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + InputStream inputStream = spy(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); MyBean result = (MyBean) converter.read(MyBean.class, inputMessage); assertThat(result.getString()).isEqualTo("Foo"); @@ -83,6 +89,7 @@ public void read() throws IOException { assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"}); assertThat(result.isBool()).isTrue(); assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2}); + verify(inputStream, never()).close(); } @Test @@ -103,14 +110,16 @@ public void write() throws IOException { assertThat(result.contains("FooBar")).isTrue(); assertThat(result.contains("true")).isTrue(); assertThat(result.contains("AQI=")).isTrue(); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml", StandardCharsets.UTF_8)); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(new MediaType("application", "xml", StandardCharsets.UTF_8)); + verify(outputMessage.getBody(), never()).close(); } @Test public void readInvalidXml() throws IOException { String body = "FooBar"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> converter.read(MyBean.class, inputMessage)); } @@ -118,8 +127,8 @@ public void readInvalidXml() throws IOException { @Test public void readValidXmlWithUnknownProperty() throws IOException { String body = "stringvalue"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); converter.read(MyBean.class, inputMessage); // Assert no HttpMessageNotReadableException is thrown } @@ -156,8 +165,8 @@ public void readWithExternalReference() throws IOException { new ClassPathResource("external.txt", getClass()).getURI() + "\" >]>&ext;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> this.converter.read(MyBean.class, inputMessage)); @@ -183,8 +192,8 @@ public void readWithXmlBomb() throws IOException { "]>\n" + "&lol9;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> this.converter.read(MyBean.class, inputMessage)); @@ -270,9 +279,9 @@ public void setArray(String[] array) { } - private interface MyJacksonView1 {}; + private interface MyJacksonView1 {} - private interface MyJacksonView2 {}; + private interface MyJacksonView2 {} @SuppressWarnings("unused") diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java index b3f95c9c81b1..3bb9c6f8877b 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java @@ -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. @@ -16,6 +16,10 @@ package org.springframework.http.converter.xml; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + import javax.xml.transform.Result; import javax.xml.transform.stream.StreamSource; @@ -40,6 +44,9 @@ import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Tests for {@link MarshallingHttpMessageConverter}. @@ -81,7 +88,8 @@ public void canWrite() { @Test public void read() throws Exception { String body = "Hello World"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + InputStream inputStream = spy(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); Unmarshaller unmarshaller = mock(Unmarshaller.class); given(unmarshaller.unmarshal(isA(StreamSource.class))).willReturn(body); @@ -91,6 +99,7 @@ public void read() throws Exception { String result = (String) converter.read(Object.class, inputMessage); assertThat(result).as("Invalid result").isEqualTo(body); + verify(inputStream, never()).close(); } @Test @@ -99,12 +108,12 @@ public void readWithTypeMismatchException() throws Exception { Marshaller marshaller = mock(Marshaller.class); Unmarshaller unmarshaller = mock(Unmarshaller.class); - given(unmarshaller.unmarshal(isA(StreamSource.class))).willReturn(Integer.valueOf(3)); + given(unmarshaller.unmarshal(isA(StreamSource.class))).willReturn(3); MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter(marshaller, unmarshaller); - assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> - converter.read(String.class, inputMessage)) - .withCauseInstanceOf(TypeMismatchException.class); + assertThatExceptionOfType(HttpMessageNotReadableException.class) + .isThrownBy(() -> converter.read(String.class, inputMessage)) + .withCauseInstanceOf(TypeMismatchException.class); } @Test @@ -118,9 +127,8 @@ public void readWithMarshallingFailureException() throws Exception { MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter(); converter.setUnmarshaller(unmarshaller); - assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> - converter.read(Object.class, inputMessage)) - .withCause(ex); + assertThatExceptionOfType(HttpMessageNotReadableException.class) + .isThrownBy(() -> converter.read(Object.class, inputMessage)).withCause(ex); } @Test @@ -134,7 +142,9 @@ public void write() throws Exception { MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter(marshaller); converter.write(body, null, outputMessage); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); + verify(outputMessage.getBody(), never()).close(); } @Test @@ -147,13 +157,12 @@ public void writeWithMarshallingFailureException() throws Exception { willThrow(ex).given(marshaller).marshal(eq(body), isA(Result.class)); MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter(marshaller); - assertThatExceptionOfType(HttpMessageNotWritableException.class).isThrownBy(() -> - converter.write(body, null, outputMessage)) - .withCause(ex); + assertThatExceptionOfType(HttpMessageNotWritableException.class) + .isThrownBy(() -> converter.write(body, null, outputMessage)).withCause(ex); } @Test - public void supports() throws Exception { + public void supports() { assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> new MarshallingHttpMessageConverter().supports(Object.class)); } diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java index a54e6895efb7..54f2fd1322cb 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java @@ -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. @@ -16,7 +16,9 @@ package org.springframework.http.converter.xml; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.nio.charset.StandardCharsets; @@ -50,6 +52,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * @author Arjen Poutsma @@ -90,17 +95,19 @@ public void canWrite() { @Test public void readDOMSource() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + InputStream inputStream = spy(new ByteArrayInputStream(BODY.getBytes(StandardCharsets.UTF_8))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage); Document document = (Document) result.getNode(); assertThat(document.getDocumentElement().getLocalName()).as("Invalid result").isEqualTo("root"); + verify(inputStream, never()).close(); } @Test public void readDOMSourceExternal() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); converter.setSupportDtd(true); DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage); Document document = (Document) result.getNode(); @@ -127,7 +134,7 @@ public void readDomSourceWithXmlBomb() throws Exception { " \n" + "]>\n" + "&lol9;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() -> this.converter.read(DOMSource.class, inputMessage)) @@ -136,8 +143,8 @@ public void readDomSourceWithXmlBomb() throws Exception { @Test public void readSAXSource() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage); InputSource inputSource = result.getInputSource(); String s = FileCopyUtils.copyToString(new InputStreamReader(inputSource.getByteStream())); @@ -146,8 +153,8 @@ public void readSAXSource() throws Exception { @Test public void readSAXSourceExternal() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); converter.setSupportDtd(true); SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage); InputSource inputSource = result.getInputSource(); @@ -182,20 +189,19 @@ public void readSAXSourceWithXmlBomb() throws Exception { "]>\n" + "&lol9;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); SAXSource result = (SAXSource) this.converter.read(SAXSource.class, inputMessage); InputSource inputSource = result.getInputSource(); XMLReader reader = result.getXMLReader(); - assertThatExceptionOfType(SAXException.class).isThrownBy(() -> - reader.parse(inputSource)) - .withMessageContaining("DOCTYPE"); + assertThatExceptionOfType(SAXException.class) + .isThrownBy(() -> reader.parse(inputSource)).withMessageContaining("DOCTYPE"); } @Test public void readStAXSource() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage); XMLStreamReader streamReader = result.getXMLStreamReader(); assertThat(streamReader.hasNext()).isTrue(); @@ -209,8 +215,8 @@ public void readStAXSource() throws Exception { @Test public void readStAXSourceExternal() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); converter.setSupportDtd(true); StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage); XMLStreamReader streamReader = result.getXMLStreamReader(); @@ -248,7 +254,7 @@ public void readStAXSourceWithXmlBomb() throws Exception { " \n" + "]>\n" + "&lol9;"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); StAXSource result = (StAXSource) this.converter.read(StAXSource.class, inputMessage); XMLStreamReader streamReader = result.getXMLStreamReader(); @@ -257,15 +263,14 @@ public void readStAXSourceWithXmlBomb() throws Exception { streamReader.next(); String s = streamReader.getLocalName(); assertThat(s).isEqualTo("root"); - assertThatExceptionOfType(XMLStreamException.class).isThrownBy(() -> - streamReader.getElementText()) - .withMessageContaining("\"lol9\""); + assertThatExceptionOfType(XMLStreamException.class) + .isThrownBy(streamReader::getElementText).withMessageContaining("\"lol9\""); } @Test public void readStreamSource() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); StreamSource result = (StreamSource) converter.read(StreamSource.class, inputMessage); String s = FileCopyUtils.copyToString(new InputStreamReader(result.getInputStream())); assertThat(XmlContent.of(s)).isSimilarTo(BODY); @@ -273,8 +278,8 @@ public void readStreamSource() throws Exception { @Test public void readSource() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML); converter.read(Source.class, inputMessage); } @@ -292,8 +297,11 @@ public void writeDOMSource() throws Exception { converter.write(domSource, null, outputMessage); assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarTo("Hello World"); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); - assertThat(outputMessage.getHeaders().getContentLength()).as("Invalid content-length").isEqualTo(outputMessage.getBodyAsBytes().length); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_XML); + assertThat(outputMessage.getHeaders().getContentLength()) + .as("Invalid content-length").isEqualTo(outputMessage.getBodyAsBytes().length); + verify(outputMessage.getBody(), never()).close(); } @Test @@ -305,7 +313,8 @@ public void writeSAXSource() throws Exception { converter.write(saxSource, null, outputMessage); assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarTo("Hello World"); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_XML); } @Test @@ -317,7 +326,8 @@ public void writeStreamSource() throws Exception { converter.write(streamSource, null, outputMessage); assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarTo("Hello World"); - assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); + assertThat(outputMessage.getHeaders().getContentType()) + .as("Invalid content-type").isEqualTo(MediaType.APPLICATION_XML); } } diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java index de0779a4c3ce..6b24f916134d 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java @@ -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. @@ -151,6 +151,13 @@ void getHeadersWithEmptyContentTypeAndEncoding() { assertThat(headers.getContentType()).isNull(); } + @Test // gh-27957 + void getHeadersWithWildcardContentType() { + mockRequest.setContentType("*/*"); + mockRequest.removeHeader("Content-Type"); + assertThat(request.getHeaders()).as("Invalid content-type should not raise exception").hasSize(0); + } + @Test void getBody() throws IOException { byte[] content = "Hello World".getBytes(StandardCharsets.UTF_8); diff --git a/spring-web/src/test/java/org/springframework/remoting/caucho/CauchoRemotingTests.java b/spring-web/src/test/java/org/springframework/remoting/caucho/CauchoRemotingTests.java index 58d1846bcf4a..e76b992d2c33 100644 --- a/spring-web/src/test/java/org/springframework/remoting/caucho/CauchoRemotingTests.java +++ b/spring-web/src/test/java/org/springframework/remoting/caucho/CauchoRemotingTests.java @@ -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. @@ -27,7 +27,6 @@ import org.springframework.beans.testfixture.beans.ITestBean; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.remoting.RemoteAccessException; -import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -109,7 +108,7 @@ public void hessianProxyFactoryBeanWithCustomProxyFactory() throws Exception { @Test @SuppressWarnings("deprecation") public void simpleHessianServiceExporter() throws IOException { - final int port = SocketUtils.findAvailableTcpPort(); + final int port = org.springframework.util.SocketUtils.findAvailableTcpPort(); TestBean tb = new TestBean("tb"); SimpleHessianServiceExporter exporter = new SimpleHessianServiceExporter(); diff --git a/spring-web/src/test/java/org/springframework/web/bind/ServletRequestDataBinderTests.java b/spring-web/src/test/java/org/springframework/web/bind/ServletRequestDataBinderTests.java index f6704d39e28c..9bf58625ccb1 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/ServletRequestDataBinderTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/ServletRequestDataBinderTests.java @@ -249,13 +249,13 @@ protected void doTestTony(PropertyValues pvs) throws Exception { m.put("forname", "Tony"); m.put("surname", "Blair"); m.put("age", "50"); - for (int i = 0; i < ps.length; i++) { - Object val = m.get(ps[i].getName()); + for (PropertyValue element : ps) { + Object val = m.get(element.getName()); assertThat(val != null).as("Can't have unexpected value").isTrue(); boolean condition = val instanceof String; assertThat(condition).as("Val i string").isTrue(); - assertThat(val.equals(ps[i].getValue())).as("val matches expected").isTrue(); - m.remove(ps[i].getName()); + assertThat(val.equals(element.getValue())).as("val matches expected").isTrue(); + m.remove(element.getName()); } assertThat(m.size() == 0).as("Map size is 0").isTrue(); } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java index 7a6f91b25f7e..8e7fff0f9cc2 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java @@ -16,8 +16,6 @@ package org.springframework.web.context.request.async; -import java.util.function.Consumer; - import org.junit.jupiter.api.Test; import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler; @@ -127,12 +125,7 @@ public void onError() throws Exception { DeferredResult result = new DeferredResult<>(null, "error result"); result.setResultHandler(handler); Exception e = new Exception(); - result.onError(new Consumer() { - @Override - public void accept(Throwable t) { - sb.append("error event"); - } - }); + result.onError(t -> sb.append("error event")); result.getInterceptor().handleError(null, null, e); diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerErrorTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerErrorTests.java index 6ecb5fd2fd54..74cd1ca0b6c8 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerErrorTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerErrorTests.java @@ -17,7 +17,6 @@ package org.springframework.web.context.request.async; import java.util.concurrent.Callable; -import java.util.function.Consumer; import javax.servlet.AsyncEvent; @@ -96,12 +95,7 @@ public void startCallableProcessingErrorAndResumeThroughCallback() throws Except StubCallable callable = new StubCallable(); WebAsyncTask webAsyncTask = new WebAsyncTask<>(callable); - webAsyncTask.onError(new Callable() { - @Override - public Object call() throws Exception { - return 7; - } - }); + webAsyncTask.onError(() -> 7); this.asyncManager.startCallableProcessing(webAsyncTask); @@ -202,12 +196,7 @@ public void startDeferredResultProcessingErrorAndResumeWithDefaultResult() throw public void startDeferredResultProcessingErrorAndResumeThroughCallback() throws Exception { final DeferredResult deferredResult = new DeferredResult<>(); - deferredResult.onError(new Consumer() { - @Override - public void accept(Throwable t) { - deferredResult.setResult(t); - } - }); + deferredResult.onError(t -> deferredResult.setResult(t)); this.asyncManager.startDeferredResultProcessing(deferredResult); diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java index e07100c00450..f463b1d6100f 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java @@ -97,12 +97,7 @@ public void startCallableProcessingTimeoutAndResumeThroughCallback() throws Exce StubCallable callable = new StubCallable(); WebAsyncTask webAsyncTask = new WebAsyncTask<>(callable); - webAsyncTask.onTimeout(new Callable() { - @Override - public Object call() throws Exception { - return 7; - } - }); + webAsyncTask.onTimeout(() -> 7); this.asyncManager.startCallableProcessing(webAsyncTask); diff --git a/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java index c35c86b60363..3ec794b1930d 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java @@ -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. @@ -20,8 +20,6 @@ import javax.servlet.FilterChain; import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; @@ -68,14 +66,9 @@ private void filterWithParameterForMethod(String methodParam, String expectedMet } MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = new FilterChain() { - - @Override - public void doFilter(ServletRequest filterRequest, - ServletResponse filterResponse) throws IOException, ServletException { - assertThat(((HttpServletRequest) filterRequest).getMethod()).as("Invalid method").isEqualTo(expectedMethod); - } - }; + FilterChain filterChain = (filterRequest, filterResponse) -> + assertThat(((HttpServletRequest) filterRequest).getMethod()) + .as("Invalid method").isEqualTo(expectedMethod); this.filter.doFilter(request, response, filterChain); } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryOrderingTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryOrderingTests.java index 9e4b9f3c0b37..005560c1f593 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryOrderingTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryOrderingTests.java @@ -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. @@ -54,27 +54,24 @@ * * @author Rossen Stoyanchev */ -public class ModelFactoryOrderingTests { +class ModelFactoryOrderingTests { private static final Log logger = LogFactory.getLog(ModelFactoryOrderingTests.class); - private NativeWebRequest webRequest; + private final NativeWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()); - private ModelAndViewContainer mavContainer; + private final ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - private SessionAttributeStore sessionAttributeStore; + private final SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore(); @BeforeEach - public void setup() { - this.sessionAttributeStore = new DefaultSessionAttributeStore(); - this.webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()); - this.mavContainer = new ModelAndViewContainer(); + void setup() { this.mavContainer.addAttribute("methods", new ArrayList()); } @Test - public void straightLineDependency() throws Exception { + void straightLineDependency() throws Exception { runTest(new StraightLineDependencyController()); assertInvokedBefore("getA", "getB1", "getB2", "getC1", "getC2", "getC3", "getC4"); assertInvokedBefore("getB1", "getB2", "getC1", "getC2", "getC3", "getC4"); @@ -85,7 +82,7 @@ public void straightLineDependency() throws Exception { } @Test - public void treeDependency() throws Exception { + void treeDependency() throws Exception { runTest(new TreeDependencyController()); assertInvokedBefore("getA", "getB1", "getB2", "getC1", "getC2", "getC3", "getC4"); assertInvokedBefore("getB1", "getC1", "getC2"); @@ -93,7 +90,7 @@ public void treeDependency() throws Exception { } @Test - public void InvertedTreeDependency() throws Exception { + void InvertedTreeDependency() throws Exception { runTest(new InvertedTreeDependencyController()); assertInvokedBefore("getC1", "getA", "getB1"); assertInvokedBefore("getC2", "getA", "getB1"); @@ -104,7 +101,7 @@ public void InvertedTreeDependency() throws Exception { } @Test - public void unresolvedDependency() throws Exception { + void unresolvedDependency() throws Exception { runTest(new UnresolvedDependencyController()); assertInvokedBefore("getA", "getC1", "getC2", "getC3", "getC4"); @@ -133,19 +130,16 @@ private void runTest(Object controller) throws Exception { ModelFactory factory = new ModelFactory(modelMethods, dataBinderFactory, sessionHandler); factory.initModel(this.webRequest, this.mavContainer, new HandlerMethod(controller, "handle")); if (logger.isDebugEnabled()) { - StringBuilder sb = new StringBuilder(); - for (String name : getInvokedMethods()) { - sb.append(" >> ").append(name); - } - logger.debug(sb); + logger.debug(String.join(" >> ", getInvokedMethods())); } } private void assertInvokedBefore(String beforeMethod, String... afterMethods) { List actual = getInvokedMethods(); for (String afterMethod : afterMethods) { - assertThat(actual.indexOf(beforeMethod) < actual.indexOf(afterMethod)).as(beforeMethod + " should be before " + afterMethod + ". Actual order: " + - actual.toString()).isTrue(); + assertThat(actual.indexOf(beforeMethod) < actual.indexOf(afterMethod)) + .as(beforeMethod + " should be before " + afterMethod + ". Actual order: " + actual.toString()) + .isTrue(); } } @@ -321,13 +315,8 @@ private static class C3 { } private static class C4 { } - private static final ReflectionUtils.MethodFilter METHOD_FILTER = new ReflectionUtils.MethodFilter() { - - @Override - public boolean matches(Method method) { - return ((AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) && - (AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null)); - } - }; + private static final ReflectionUtils.MethodFilter METHOD_FILTER = method -> + ((AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) && + (AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null)); } diff --git a/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java b/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java index 8f8ac90629bd..d9132561a26c 100644 --- a/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java @@ -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. @@ -16,8 +16,12 @@ package org.springframework.web.util; +import java.nio.charset.StandardCharsets; + import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.util.FileCopyUtils; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; @@ -29,16 +33,22 @@ */ public class ContentCachingRequestWrapperTests { - protected static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; + protected static final String FORM_CONTENT_TYPE = MediaType.APPLICATION_FORM_URLENCODED_VALUE; + + protected static final String CHARSET = StandardCharsets.UTF_8.name(); + + protected static final String GET = HttpMethod.GET.name(); + + protected static final String POST = HttpMethod.POST.name(); - protected static final String CHARSET = "UTF-8"; + protected static final int CONTENT_CACHE_LIMIT = 3; private final MockHttpServletRequest request = new MockHttpServletRequest(); @Test - public void cachedContent() throws Exception { - this.request.setMethod("GET"); + void cachedContent() throws Exception { + this.request.setMethod(GET); this.request.setCharacterEncoding(CHARSET); this.request.setContent("Hello World".getBytes(CHARSET)); @@ -48,24 +58,24 @@ public void cachedContent() throws Exception { } @Test - public void cachedContentWithLimit() throws Exception { - this.request.setMethod("GET"); + void cachedContentWithLimit() throws Exception { + this.request.setMethod(GET); this.request.setCharacterEncoding(CHARSET); this.request.setContent("Hello World".getBytes(CHARSET)); - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request, 3); + ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request, CONTENT_CACHE_LIMIT); byte[] response = FileCopyUtils.copyToByteArray(wrapper.getInputStream()); assertThat(response).isEqualTo("Hello World".getBytes(CHARSET)); assertThat(wrapper.getContentAsByteArray()).isEqualTo("Hel".getBytes(CHARSET)); } @Test - public void cachedContentWithOverflow() throws Exception { - this.request.setMethod("GET"); + void cachedContentWithOverflow() throws Exception { + this.request.setMethod(GET); this.request.setCharacterEncoding(CHARSET); this.request.setContent("Hello World".getBytes(CHARSET)); - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request, 3) { + ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request, CONTENT_CACHE_LIMIT) { @Override protected void handleContentOverflow(int contentCacheLimit) { throw new IllegalStateException(String.valueOf(contentCacheLimit)); @@ -78,8 +88,8 @@ protected void handleContentOverflow(int contentCacheLimit) { } @Test - public void requestParams() throws Exception { - this.request.setMethod("POST"); + void requestParams() throws Exception { + this.request.setMethod(POST); this.request.setContentType(FORM_CONTENT_TYPE); this.request.setCharacterEncoding(CHARSET); this.request.setParameter("first", "value"); @@ -94,8 +104,8 @@ public void requestParams() throws Exception { } @Test // SPR-12810 - public void inputStreamFormPostRequest() throws Exception { - this.request.setMethod("POST"); + void inputStreamFormPostRequest() throws Exception { + this.request.setMethod(POST); this.request.setContentType(FORM_CONTENT_TYPE); this.request.setCharacterEncoding(CHARSET); this.request.setParameter("first", "value"); diff --git a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java index 998af1c9eb16..074b9b5c5c8d 100644 --- a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java @@ -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. @@ -45,7 +45,7 @@ public class WebUtilsTests { @Test - public void findParameterValue() { + void findParameterValue() { Map params = new HashMap<>(); params.put("myKey1", "myValue1"); params.put("myKey2_myValue2", "xxx"); @@ -60,7 +60,7 @@ public void findParameterValue() { } @Test - public void parseMatrixVariablesString() { + void parseMatrixVariablesString() { MultiValueMap variables; variables = WebUtils.parseMatrixVariables(null); @@ -103,7 +103,7 @@ public void parseMatrixVariablesString() { } @Test - public void isValidOrigin() { + void isValidOrigin() { List allowed = Collections.emptyList(); assertThat(checkValidOrigin("mydomain1.example", -1, "http://mydomain1.example", allowed)).isTrue(); assertThat(checkValidOrigin("mydomain1.example", -1, "http://mydomain2.example", allowed)).isFalse(); @@ -117,7 +117,7 @@ public void isValidOrigin() { } @Test - public void isSameOrigin() { + void isSameOrigin() { assertThat(checkSameOrigin("http", "mydomain1.example", -1, "http://mydomain1.example")).isTrue(); assertThat(checkSameOrigin("http", "mydomain1.example", -1, "http://mydomain1.example:80")).isTrue(); assertThat(checkSameOrigin("https", "mydomain1.example", 443, "https://mydomain1.example")).isTrue(); @@ -156,7 +156,7 @@ public void isSameOrigin() { } @Test // SPR-16262 - public void isSameOriginWithXForwardedHeaders() throws Exception { + void isSameOriginWithXForwardedHeaders() throws Exception { String server = "mydomain1.example"; testWithXForwardedHeaders(server, -1, "https", null, -1, "https://mydomain1.example"); testWithXForwardedHeaders(server, 123, "https", null, -1, "https://mydomain1.example"); @@ -167,7 +167,7 @@ public void isSameOriginWithXForwardedHeaders() throws Exception { } @Test // SPR-16262 - public void isSameOriginWithForwardedHeader() throws Exception { + void isSameOriginWithForwardedHeader() throws Exception { String server = "mydomain1.example"; testWithForwardedHeader(server, -1, "proto=https", "https://mydomain1.example"); testWithForwardedHeader(server, 123, "proto=https", "https://mydomain1.example"); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java index 93f828bc3ddf..c5e3b3127b48 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java @@ -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. @@ -26,6 +26,7 @@ /** * @author Rossen Stoyanchev + * @author Sam Brannen */ public class JettyHttpServer extends AbstractHttpServer { @@ -73,7 +74,10 @@ protected void stopInternal() throws Exception { finally { try { if (this.jettyServer.isRunning()) { - this.jettyServer.setStopTimeout(5000); + // Do not configure a large stop timeout. For example, setting a stop timeout + // of 5000 adds an additional 1-2 seconds to the runtime of each test using + // the Jetty sever, resulting in 2-4 extra minutes of overall build time. + this.jettyServer.setStopTimeout(100); this.jettyServer.stop(); this.jettyServer.destroy(); } @@ -88,7 +92,10 @@ protected void stopInternal() throws Exception { protected void resetInternal() { try { if (this.jettyServer.isRunning()) { - this.jettyServer.setStopTimeout(5000); + // Do not configure a large stop timeout. For example, setting a stop timeout + // of 5000 adds an additional 1-2 seconds to the runtime of each test using + // the Jetty sever, resulting in 2-4 extra minutes of overall build time. + this.jettyServer.setStopTimeout(100); this.jettyServer.stop(); this.jettyServer.destroy(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/BodyExtractorsTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/BodyExtractorsTests.java index bf1e58c2e5d5..a67e5b0deca0 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/BodyExtractorsTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/BodyExtractorsTests.java @@ -111,7 +111,7 @@ public Map hints() { return hints; } }; - this.hints = new HashMap(); + this.hints = new HashMap<>(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java index a1f28b1b63fe..a67e7c352d87 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java @@ -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. @@ -46,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.springframework.web.reactive.function.server.RouterFunctions.route; /** @@ -94,10 +95,9 @@ void parts(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void transferTo(HttpServer httpServer) throws Exception { - // TODO: check why Undertow fails - if (httpServer instanceof UndertowHttpServer) { - return; - } + // TODO Determine why Undertow fails: https://github.com/spring-projects/spring-framework/issues/25310 + assumeFalse(httpServer instanceof UndertowHttpServer, "Undertow currently fails with transferTo"); + startServer(httpServer); Mono result = webClient diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java index e7fa002ff51b..4410e2102266 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java @@ -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. @@ -69,7 +69,6 @@ import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.util.SocketUtils; import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; import org.springframework.web.testfixture.xml.Pojo; @@ -1245,7 +1244,8 @@ void malformedResponseChunksOnEntityWithBody(ClientHttpConnector connector) { private Mono doMalformedChunkedResponseTest( ClientHttpConnector connector, Function> handler) { - int port = SocketUtils.findAvailableTcpPort(); + @SuppressWarnings("deprecation") + int port = org.springframework.util.SocketUtils.findAvailableTcpPort(); Thread serverThread = new Thread(() -> { // No way to simulate a malformed chunked response through MockWebServer. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java index ad6bfbdca9f7..b9d6fb39a425 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java @@ -36,7 +36,6 @@ import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse; import org.springframework.web.testfixture.server.MockServerWebExchange; @@ -295,12 +294,9 @@ public Mono writeTo(ServerWebExchange exchange, Context context) { public void toHttpHandlerWebFilter() { AtomicBoolean filterInvoked = new AtomicBoolean(); - WebFilter webFilter = new WebFilter() { - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - filterInvoked.set(true); - return chain.filter(exchange); - } + WebFilter webFilter = (exchange, chain) -> { + filterInvoked.set(true); + return chain.filter(exchange); }; HandlerFunction handlerFunction = request -> ServerResponse.accepted().build(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceUrlProviderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceUrlProviderTests.java index ec5d45344aaf..1b58da1e6ccc 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceUrlProviderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceUrlProviderTests.java @@ -164,7 +164,7 @@ void initializeOnCurrentContext() { private Condition pathPatternStringOf(String expected) { - return new Condition( + return new Condition<>( actual -> actual != null && actual.getPatternString().equals(expected), "Pattern %s", expected); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java index 1b49442ce2bd..b60587452ac1 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java @@ -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. @@ -60,6 +60,7 @@ import org.springframework.web.testfixture.http.server.reactive.bootstrap.UndertowHttpServer; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeFalse; class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTests { @@ -164,10 +165,9 @@ void filePartsMono(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void transferTo(HttpServer httpServer) throws Exception { - // TODO: check why Undertow fails - if (httpServer instanceof UndertowHttpServer) { - return; - } + // TODO Determine why Undertow fails: https://github.com/spring-projects/spring-framework/issues/25310 + assumeFalse(httpServer instanceof UndertowHttpServer, "Undertow currently fails with transferTo"); + startServer(httpServer); Flux result = webClient @@ -211,10 +211,9 @@ private MultiValueMap> generateBody() { private static void verifyContents(Path tempFile, Resource resource) { try { - byte[] tempBytes = Files.readAllBytes(tempFile); // Use FileCopyUtils since the resource might reside in a JAR instead of in the file system. byte[] resourceBytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); - assertThat(tempBytes).isEqualTo(resourceBytes); + assertThat(tempFile).hasBinaryContent(resourceBytes); } catch (IOException ex) { throw new AssertionError(ex); diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt index 8589ddc570fa..133046955275 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt @@ -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. @@ -97,6 +97,14 @@ class KotlinInvocableHandlerMethodTests { assertThat(this.exchange.response.headers.getFirst("foo")).isEqualTo("bar") } + @Test + fun privateController() { + this.resolvers.add(stubResolver("foo")) + val method = PrivateCoroutinesController::singleArg.javaMethod!! + val result = invoke(PrivateCoroutinesController(), method,"foo") + assertHandlerResultValue(result, "success:foo") + } + private fun invoke(handler: Any, method: Method, vararg providedArgs: Any?): Mono { val invocable = InvocableHandlerMethod(handler, method) invocable.setArgumentResolvers(this.resolvers) @@ -146,7 +154,13 @@ class KotlinInvocableHandlerMethodTests { delay(10) response.headers.add("foo", "bar") } + } + private class PrivateCoroutinesController { + suspend fun singleArg(q: String?): String { + delay(10) + return "success:$q" + } } } \ No newline at end of file diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java index 2f7be313ca98..812060198e63 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java @@ -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,7 +28,6 @@ import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; -import org.springframework.http.server.PathContainer; import org.springframework.http.server.RequestPath; import org.springframework.lang.Nullable; import org.springframework.util.AntPathMatcher; @@ -216,8 +215,9 @@ protected Object lookupHandler( handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); - PathContainer pathWithinMapping = pattern.extractPathWithinPattern(path.pathWithinApplication()); - return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping.value(), null); + String pathWithinMapping = pattern.extractPathWithinPattern(path.pathWithinApplication()).value(); + pathWithinMapping = UrlPathHelper.defaultInstance.removeSemicolonContent(pathWithinMapping); + return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping, null); } /** diff --git a/spring-webmvc/src/test/java/org/springframework/context/LifecycleContextBean.java b/spring-webmvc/src/test/java/org/springframework/context/LifecycleContextBean.java index 1f04eb182ccc..b05fcb658b76 100644 --- a/spring-webmvc/src/test/java/org/springframework/context/LifecycleContextBean.java +++ b/spring-webmvc/src/test/java/org/springframework/context/LifecycleContextBean.java @@ -33,21 +33,24 @@ public class LifecycleContextBean extends LifecycleBean implements ApplicationCo @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); - if (this.owningContext != null) + if (this.owningContext != null) { throw new RuntimeException("Factory called setBeanFactory after setApplicationContext"); + } } @Override public void afterPropertiesSet() { super.afterPropertiesSet(); - if (this.owningContext == null) + if (this.owningContext == null) { throw new RuntimeException("Factory didn't call setApplicationContext before afterPropertiesSet on lifecycle bean"); + } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - if (this.owningFactory == null) + if (this.owningFactory == null) { throw new RuntimeException("Factory called setApplicationContext before setBeanFactory"); + } this.owningContext = applicationContext; } diff --git a/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java b/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java index aa6de67cdf8a..0444cfc1ce86 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java @@ -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. @@ -25,9 +25,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.NoSuchMessageException; @@ -56,24 +54,15 @@ protected ConfigurableApplicationContext createContext() throws Exception { MockServletContext sc = new MockServletContext(""); root.setServletContext(sc); root.setConfigLocations("/org/springframework/web/context/WEB-INF/applicationContext.xml"); - root.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() { + root.addBeanFactoryPostProcessor(beanFactory -> beanFactory.addBeanPostProcessor(new BeanPostProcessor() { @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - beanFactory.addBeanPostProcessor(new BeanPostProcessor() { - @Override - public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException { - if (bean instanceof TestBean) { - ((TestBean) bean).getFriends().add("myFriend"); - } - return bean; - } - @Override - public Object postProcessAfterInitialization(Object bean, String name) throws BeansException { - return bean; - } - }); + public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException { + if (bean instanceof TestBean) { + ((TestBean) bean).getFriends().add("myFriend"); + } + return bean; } - }); + })); root.refresh(); XmlWebApplicationContext wac = new XmlWebApplicationContext(); wac.getEnvironment().addActiveProfile("wacProfile1"); @@ -109,7 +98,7 @@ protected void doTestEvents(TestApplicationListener listener, TestApplicationLis @Test @Override public void count() { - assertThat(this.applicationContext.getBeanDefinitionCount() == 14).as("should have 14 beans, not "+ this.applicationContext.getBeanDefinitionCount()).isTrue(); + assertThat(this.applicationContext.getBeanDefinitionCount()).as("should have 14 beans").isEqualTo(14); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/context/support/HttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/context/support/HttpRequestHandlerTests.java index 25880b6c1305..243249d6315c 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/context/support/HttpRequestHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/context/support/HttpRequestHandlerTests.java @@ -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. @@ -20,8 +20,6 @@ import javax.servlet.Servlet; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; @@ -46,24 +44,21 @@ public class HttpRequestHandlerTests { @Test public void testHttpRequestHandlerServletPassThrough() throws Exception { MockServletContext servletContext = new MockServletContext(); - final MockHttpServletRequest request = new MockHttpServletRequest(); - final MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); StaticWebApplicationContext wac = new StaticWebApplicationContext(); - wac.getBeanFactory().registerSingleton("myHandler", new HttpRequestHandler() { - @Override - public void handleRequest(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - assertThat(req).isSameAs(request); - assertThat(res).isSameAs(response); - String exception = request.getParameter("exception"); - if ("ServletException".equals(exception)) { - throw new ServletException("test"); - } - if ("IOException".equals(exception)) { - throw new IOException("test"); - } - res.getWriter().write("myResponse"); + wac.getBeanFactory().registerSingleton("myHandler", (HttpRequestHandler) (req, res) -> { + assertThat(req).isSameAs(request); + assertThat(res).isSameAs(response); + String exception = request.getParameter("exception"); + if ("ServletException".equals(exception)) { + throw new ServletException("test"); } + if ("IOException".equals(exception)) { + throw new IOException("test"); + } + res.getWriter().write("myResponse"); }); wac.setServletContext(servletContext); wac.refresh(); @@ -76,13 +71,13 @@ public void handleRequest(HttpServletRequest req, HttpServletResponse res) throw assertThat(response.getContentAsString()).isEqualTo("myResponse"); request.setParameter("exception", "ServletException"); - assertThatExceptionOfType(ServletException.class).isThrownBy(() -> - servlet.service(request, response)) + assertThatExceptionOfType(ServletException.class) + .isThrownBy(() -> servlet.service(request, response)) .withMessage("test"); request.setParameter("exception", "IOException"); - assertThatIOException().isThrownBy(() -> - servlet.service(request, response)) + assertThatIOException() + .isThrownBy(() -> servlet.service(request, response)) .withMessage("test"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java index be08292460d1..3b1d2c944630 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java @@ -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. @@ -158,9 +158,7 @@ public void refresh() throws BeansException { pvs = new MutablePropertyValues(); pvs.add("order", "0"); pvs.add("exceptionMappings", "java.lang.Exception=failed1"); - List mappedHandlers = new ManagedList<>(); - mappedHandlers.add(new RuntimeBeanReference("anotherLocaleHandler")); - pvs.add("mappedHandlers", mappedHandlers); + pvs.add("mappedHandlers", ManagedList.of(new RuntimeBeanReference("anotherLocaleHandler"))); pvs.add("defaultStatusCode", "500"); pvs.add("defaultErrorView", "failed2"); registerSingleton("handlerExceptionResolver", SimpleMappingExceptionResolver.class, pvs); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java index bcc0fe568d91..65c802cf5e09 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java @@ -29,7 +29,6 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.core.Ordered; -import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.FileSystemResourceLoader; import org.springframework.format.FormatterRegistry; import org.springframework.http.HttpStatus; @@ -360,12 +359,7 @@ private class TestWebMvcConfigurationSupport extends WebMvcConfigurationSupport @Override public void addFormatters(FormatterRegistry registry) { - registry.addConverter(new Converter() { - @Override - public String convert(TestBean source) { - return "converted"; - } - }); + registry.addConverter(TestBean.class, String.class, testBean -> "converted"); } @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java index 0d7553bb80d1..49e963f62231 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java @@ -36,7 +36,6 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.servlet.ModelAndView; @@ -50,13 +49,7 @@ */ public class DefaultEntityResponseBuilderTests { - static final ServerResponse.Context EMPTY_CONTEXT = new ServerResponse.Context() { - @Override - public List> messageConverters() { - return Collections.emptyList(); - } - - }; + static final ServerResponse.Context EMPTY_CONTEXT = () -> Collections.emptyList(); @Test public void fromObject() { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java index 56fec0a442b5..915e9ac0768b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java @@ -20,7 +20,6 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Collections; -import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; @@ -29,7 +28,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.servlet.ModelAndView; @@ -43,13 +41,7 @@ */ public class DefaultRenderingResponseTests { - static final ServerResponse.Context EMPTY_CONTEXT = new ServerResponse.Context() { - @Override - public List> messageConverters() { - return Collections.emptyList(); - } - - }; + static final ServerResponse.Context EMPTY_CONTEXT = () -> Collections.emptyList(); @Test public void create() throws Exception { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java index 07233b27ecec..931519a724d9 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java @@ -318,12 +318,7 @@ void session() { @Test void principal() { MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true); - Principal principal = new Principal() { - @Override - public String getName() { - return "foo"; - } - }; + Principal principal = () -> "foo"; servletRequest.setUserPrincipal(principal); DefaultServerRequest request = new DefaultServerRequest(servletRequest, diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java index 23621b73197c..e17601a0cb5d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java @@ -39,7 +39,6 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; @@ -55,14 +54,7 @@ */ public class DefaultServerResponseBuilderTests { - static final ServerResponse.Context EMPTY_CONTEXT = new ServerResponse.Context() { - @Override - public List> messageConverters() { - return Collections.emptyList(); - - } - - }; + static final ServerResponse.Context EMPTY_CONTEXT = () -> Collections.emptyList(); @Test public void status() { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java index d3cace663e37..c47a5e04485e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java @@ -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. @@ -102,7 +102,7 @@ void checkMappings(String beanName) throws Exception { assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.html"); assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(bean); - request = PathPatternsTestUtils.initRequest("GET", "/welcome.x", usePathPatterns); + request = PathPatternsTestUtils.initRequest("GET", "/welcome.x;jsessionid=123", usePathPatterns); chain = getHandler(hm, request); assertThat(chain.getHandler()).isSameAs(otherBean); assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome.x"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java index bade64bf142e..8f7fec3139dd 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java @@ -22,7 +22,6 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -949,12 +948,7 @@ private void assertStringArray() throws JspException, DocumentException { } private Map getCountryToLocaleMap() { - Map map = new TreeMap(new Comparator() { - @Override - public int compare(Object o1, Object o2) { - return ((Country)o1).getName().compareTo(((Country)o2).getName()); - } - }); + Map map = new TreeMap((o1, o2) -> ((Country)o1).getName().compareTo(((Country)o2).getName())); map.put(Country.COUNTRY_AT, LOCALE_AT); map.put(Country.COUNTRY_NL, LOCALE_NL); map.put(Country.COUNTRY_US, Locale.US); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java index ef566c7f8db1..9a2f75d1ba08 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java @@ -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. @@ -79,13 +79,7 @@ else if (sockJsSession.isClosed()) { if (logger.isDebugEnabled()) { logger.debug("Connection already closed (but not removed yet) for " + sockJsSession); } - SockJsFrame frame = SockJsFrame.closeFrameGoAway(); - try { - response.getBody().write(frame.getContentBytes()); - } - catch (IOException ex) { - throw new SockJsException("Failed to send " + frame, sockJsSession.getId(), ex); - } + writeFrame(SockJsFrame.closeFrameGoAway(), request, response, sockJsSession); } else if (!sockJsSession.isActive()) { if (logger.isTraceEnabled()) { @@ -97,13 +91,19 @@ else if (!sockJsSession.isActive()) { if (logger.isDebugEnabled()) { logger.debug("Another " + getTransportType() + " connection still open for " + sockJsSession); } - String formattedFrame = getFrameFormat(request).format(SockJsFrame.closeFrameAnotherConnectionOpen()); - try { - response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET)); - } - catch (IOException ex) { - throw new SockJsException("Failed to send " + formattedFrame, sockJsSession.getId(), ex); - } + writeFrame(SockJsFrame.closeFrameAnotherConnectionOpen(), request, response, sockJsSession); + } + } + + private void writeFrame(SockJsFrame frame, ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpSockJsSession sockJsSession) { + + String formattedFrame = getFrameFormat(request).format(frame); + try { + response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET)); + } + catch (IOException ex) { + throw new SockJsException("Failed to send " + formattedFrame, sockJsSession.getId(), ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java index a3d492ecf564..04372fb52bf5 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java @@ -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. @@ -257,7 +257,8 @@ public void handleSuccessiveRequest(ServerHttpRequest request, ServerHttpRespons synchronized (this.responseLock) { try { if (isClosed()) { - response.getBody().write(SockJsFrame.closeFrameGoAway().getContentBytes()); + String formattedFrame = frameFormat.format(SockJsFrame.closeFrameGoAway()); + response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET)); return; } this.response = response; diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistryTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistryTests.java index d7fa58dcff08..558b547c0927 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistryTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistryTests.java @@ -28,7 +28,6 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.user.SimpSubscription; -import org.springframework.messaging.simp.user.SimpSubscriptionMatcher; import org.springframework.messaging.simp.user.SimpUser; import org.springframework.messaging.support.MessageBuilder; import org.springframework.web.socket.CloseStatus; @@ -143,12 +142,7 @@ public void findSubscriptions() throws Exception { subscribeEvent = new SessionSubscribeEvent(this, message, user); registry.onApplicationEvent(subscribeEvent); - Set matches = registry.findSubscriptions(new SimpSubscriptionMatcher() { - @Override - public boolean match(SimpSubscription subscription) { - return subscription.getDestination().equals("/match"); - } - }); + Set matches = registry.findSubscriptions(subscription -> subscription.getDestination().equals("/match")); assertThat(matches.size()).isEqualTo(2); diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index 5a9318ef3cbe..f94de141f9ab 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -51,7 +51,7 @@ - + diff --git a/src/docs/asciidoc/core/core-aop.adoc b/src/docs/asciidoc/core/core-aop.adoc index 6479f329b5d9..3901669e6075 100644 --- a/src/docs/asciidoc/core/core-aop.adoc +++ b/src/docs/asciidoc/core/core-aop.adoc @@ -770,7 +770,7 @@ sub-packages: this(com.xyz.service.AccountService) ---- + -NOTE: 'this' is more commonly used in a binding form. See the section on <> +NOTE: `this` is more commonly used in a binding form. See the section on <> for how to make the proxy object available in the advice body. * Any join point (method execution only in Spring AOP) where the target object @@ -781,7 +781,7 @@ implements the `AccountService` interface: target(com.xyz.service.AccountService) ---- + -NOTE: 'target' is more commonly used in a binding form. See the <> section +NOTE: `target` is more commonly used in a binding form. See the <> section for how to make the target object available in the advice body. * Any join point (method execution only in Spring AOP) that takes a single parameter @@ -792,7 +792,7 @@ and where the argument passed at runtime is `Serializable`: args(java.io.Serializable) ---- + -NOTE: 'args' is more commonly used in a binding form. See the <> section +NOTE: `args` is more commonly used in a binding form. See the <> section for how to make the method arguments available in the advice body. + Note that the pointcut given in this example is different from `execution(* @@ -808,7 +808,7 @@ parameter of type `Serializable`. @target(org.springframework.transaction.annotation.Transactional) ---- + -NOTE: You can also use '@target' in a binding form. See the <> section for +NOTE: You can also use `@target` in a binding form. See the <> section for how to make the annotation object available in the advice body. * Any join point (method execution only in Spring AOP) where the declared type of the @@ -819,7 +819,7 @@ target object has an `@Transactional` annotation: @within(org.springframework.transaction.annotation.Transactional) ---- + -NOTE: You can also use '@within' in a binding form. See the <> section for +NOTE: You can also use `@within` in a binding form. See the <> section for how to make the annotation object available in the advice body. * Any join point (method execution only in Spring AOP) where the executing method has an @@ -830,7 +830,7 @@ how to make the annotation object available in the advice body. @annotation(org.springframework.transaction.annotation.Transactional) ---- + -NOTE: You can also use '@annotation' in a binding form. See the <> section +NOTE: You can also use `@annotation` in a binding form. See the <> section for how to make the annotation object available in the advice body. * Any join point (method execution only in Spring AOP) which takes a single parameter, @@ -841,7 +841,7 @@ and where the runtime type of the argument passed has the `@Classified` annotati @args(com.xyz.security.Classified) ---- + -NOTE: You can also use '@args' in a binding form. See the <> section +NOTE: You can also use `@args` in a binding form. See the <> section how to make the annotation object(s) available in the advice body. * Any join point (method execution only in Spring AOP) on a Spring bean named @@ -1213,33 +1213,60 @@ in contrast to `@AfterReturning` which only applies to successful normal returns [[aop-ataspectj-around-advice]] ==== Around Advice -The last kind of advice is around advice. Around advice runs "`around`" a matched +The last kind of advice is _around_ advice. Around advice runs "around" a matched method's execution. It has the opportunity to do work both before and after the method runs and to determine when, how, and even if the method actually gets to run at all. Around advice is often used if you need to share state before and after a method -execution in a thread-safe manner (starting and stopping a timer, for example). -Always use the least powerful form of advice that meets your requirements (that is, -do not use around advice if before advice would do). - -Around advice is declared by using the `@Around` annotation. The first parameter of the -advice method must be of type `ProceedingJoinPoint`. Within the body of the advice, -calling `proceed()` on the `ProceedingJoinPoint` causes the underlying method to run. -The `proceed` method can also pass in an `Object[]`. The values in the array are used -as the arguments to the method execution when it proceeds. - -NOTE: The behavior of `proceed` when called with an `Object[]` is a little different than -the behavior of `proceed` for around advice compiled by the AspectJ compiler. For around +execution in a thread-safe manner – for example, starting and stopping a timer. + +[TIP] +==== +Always use the least powerful form of advice that meets your requirements. + +For example, do not use _around_ advice if _before_ advice is sufficient for your needs. +==== + +Around advice is declared by annotating a method with the `@Around` annotation. The +method should declare `Object` as its return type, and the first parameter of the method +must be of type `ProceedingJoinPoint`. Within the body of the advice method, you must +invoke `proceed()` on the `ProceedingJoinPoint` in order for the underlying method to +run. Invoking `proceed()` without arguments will result in the caller's original +arguments being supplied to the underlying method when it is invoked. For advanced use +cases, there is an overloaded variant of the `proceed()` method which accepts an array of +arguments (`Object[]`). The values in the array will be used as the arguments to the +underlying method when it is invoked. + +[NOTE] +==== +The behavior of `proceed` when called with an `Object[]` is a little different than the +behavior of `proceed` for around advice compiled by the AspectJ compiler. For around advice written using the traditional AspectJ language, the number of arguments passed to `proceed` must match the number of arguments passed to the around advice (not the number of arguments taken by the underlying join point), and the value passed to proceed in a -given argument position supplants the original value at the join point for the entity -the value was bound to (do not worry if this does not make sense right now). The approach -taken by Spring is simpler and a better match to its proxy-based, execution-only -semantics. You only need to be aware of this difference if you compile @AspectJ -aspects written for Spring and use `proceed` with arguments with the AspectJ compiler -and weaver. There is a way to write such aspects that is 100% compatible across both -Spring AOP and AspectJ, and this is discussed in the -<>. +given argument position supplants the original value at the join point for the entity the +value was bound to (do not worry if this does not make sense right now). + +The approach taken by Spring is simpler and a better match to its proxy-based, +execution-only semantics. You only need to be aware of this difference if you compile +`@AspectJ` aspects written for Spring and use `proceed` with arguments with the AspectJ +compiler and weaver. There is a way to write such aspects that is 100% compatible across +both Spring AOP and AspectJ, and this is discussed in the +<>. +==== + +The value returned by the around advice is the return value seen by the caller of the +method. For example, a simple caching aspect could return a value from a cache if it has +one or invoke `proceed()` (and return that value) if it does not. Note that `proceed` +may be invoked once, many times, or not at all within the body of the around advice. All +of these are legal. + +WARNING: If you declare the return type of your around advice method as `void`, `null` +will always be returned to the caller, effectively ignoring the result of any invocation +of `proceed()`. It is therefore recommended that an around advice method declare a return +type of `Object`. The advice method should typically return the value returned from an +invocation of `proceed()`, even if the underlying method has a `void` return type. +However, the advice may optionally return a cached value, a wrapped value, or some other +value depending on the use case. The following example shows how to use around advice: @@ -1282,12 +1309,6 @@ The following example shows how to use around advice: } ---- -The value returned by the around advice is the return value seen by the caller of the -method. For example, a simple caching aspect could return a value from a cache if it -has one and invoke `proceed()` if it does not. Note that `proceed` may be invoked once, -many times, or not at all within the body of the around advice. All of these are legal. - - [[aop-ataspectj-advice-params]] ==== Advice Parameters @@ -1301,8 +1322,9 @@ write generic advice that can find out about the method the advice is currently ===== Access to the Current `JoinPoint` Any advice method may declare, as its first parameter, a parameter of type -`org.aspectj.lang.JoinPoint` (note that around advice is required to declare a first +`org.aspectj.lang.JoinPoint`. Note that around advice is required to declare a first parameter of type `ProceedingJoinPoint`, which is a subclass of `JoinPoint`. + The `JoinPoint` interface provides a number of useful methods: * `getArgs()`: Returns the method arguments. @@ -1319,7 +1341,7 @@ See the https://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lan We have already seen how to bind the returned value or exception value (using after returning and after throwing advice). To make argument values available to the advice body, you can use the binding form of `args`. If you use a parameter name in place of a -type name in an args expression, the value of the corresponding argument is passed as +type name in an `args` expression, the value of the corresponding argument is passed as the parameter value when the advice is invoked. An example should make this clearer. Suppose you want to advise the execution of DAO operations that take an `Account` object as the first parameter, and you need access to the account in the advice body. @@ -1348,7 +1370,7 @@ parameter, and the argument passed to that parameter is an instance of `Account` Second, it makes the actual `Account` object available to the advice through the `account` parameter. -Another way of writing this is to declare a pointcut that "`provides`" the `Account` +Another way of writing this is to declare a pointcut that "provides" the `Account` object value when it matches a join point, and then refer to the named pointcut from the advice. This would look as follows: @@ -1376,13 +1398,12 @@ from the advice. This would look as follows: } ---- -See the AspectJ programming guide for more -details. +See the AspectJ programming guide for more details. -The proxy object ( `this`), target object ( `target`), and annotations ( `@within`, -`@target`, `@annotation`, and `@args`) can all be bound in a similar fashion. The next two -examples show how to match the execution of methods annotated with an -`@Auditable` annotation and extract the audit code: +The proxy object (`this`), target object (`target`), and annotations (`@within`, +`@target`, `@annotation`, and `@args`) can all be bound in a similar fashion. The next +two examples show how to match the execution of methods annotated with an `@Auditable` +annotation and extract the audit code: The first of the two examples shows the definition of the `@Auditable` annotation: @@ -1448,7 +1469,7 @@ you have a generic type like the following: ---- You can restrict interception of method types to certain parameter types by -typing the advice parameter to the parameter type for which you want to intercept the method: +tying the advice parameter to the parameter type for which you want to intercept the method: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -1575,18 +1596,18 @@ the `argNames` attribute: } ---- -* Using the `'argNames'` attribute is a little clumsy, so if the `'argNames'` attribute +* Using the `argNames` attribute is a little clumsy, so if the `argNames` attribute has not been specified, Spring AOP looks at the debug information for the class and tries to determine the parameter names from the local variable table. This information is present as long as the classes have been compiled with debug - information ( `'-g:vars'` at a minimum). The consequences of compiling with this flag + information (`-g:vars` at a minimum). The consequences of compiling with this flag on are: (1) your code is slightly easier to understand (reverse engineer), (2) the class file sizes are very slightly bigger (typically inconsequential), (3) the - optimization to remove unused local variables is not applied by your compiler. In + optimization to remove unused local variables is not applied by your compiler. In other words, you should encounter no difficulties by building with this flag on. + -NOTE: If an @AspectJ aspect has been compiled by the AspectJ compiler (ajc) even without the -debug information, you need not add the `argNames` attribute, as the compiler +NOTE: If an @AspectJ aspect has been compiled by the AspectJ compiler (`ajc`) even +without the debug information, you need not add the `argNames` attribute, as the compiler retain the needed information. * If the code has been compiled without the necessary debug information, Spring AOP @@ -2315,20 +2336,30 @@ You can declare it by using the `after` element, as the following example shows: [[aop-schema-advice-around]] ==== Around Advice -The last kind of advice is around advice. Around advice runs "around" a matched method -execution. It has the opportunity to do work both before and after the method runs -and to determine when, how, and even if the method actually gets to run at all. -Around advice is often used to share state before and after a method execution in a -thread-safe manner (starting and stopping a timer, for example). Always use the least -powerful form of advice that meets your requirements. Do not use around advice if -before advice can do the job. - -You can declare around advice by using the `aop:around` element. The first parameter of -the advice method must be of type `ProceedingJoinPoint`. Within the body of the advice, -calling `proceed()` on the `ProceedingJoinPoint` causes the underlying method to run. -The `proceed` method may also be called with an `Object[]`. The values in the array -are used as the arguments to the method execution when it proceeds. -See <> for notes on calling `proceed` with an `Object[]`. +The last kind of advice is _around_ advice. Around advice runs "around" a matched +method's execution. It has the opportunity to do work both before and after the method +runs and to determine when, how, and even if the method actually gets to run at all. +Around advice is often used if you need to share state before and after a method +execution in a thread-safe manner – for example, starting and stopping a timer. + +[TIP] +==== +Always use the least powerful form of advice that meets your requirements. + +For example, do not use _around_ advice if _before_ advice is sufficient for your needs. +==== + +You can declare around advice by using the `aop:around` element. The advice method should +declare `Object` as its return type, and the first parameter of the method must be of +type `ProceedingJoinPoint`. Within the body of the advice method, you must invoke +`proceed()` on the `ProceedingJoinPoint` in order for the underlying method to run. +Invoking `proceed()` without arguments will result in the caller's original arguments +being supplied to the underlying method when it is invoked. For advanced use cases, there +is an overloaded variant of the `proceed()` method which accepts an array of arguments +(`Object[]`). The values in the array will be used as the arguments to the underlying +method when it is invoked. See <> for notes on calling +`proceed` with an `Object[]`. + The following example shows how to declare around advice in XML: [source,xml,indent=0,subs="verbatim,quotes"] @@ -2542,7 +2573,7 @@ With such a Boot class, we would get output similar to the following on standard [literal,subs="verbatim,quotes"] ---- -StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0 +StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0 ----------------------------------------- ms % Task name ----------------------------------------- @@ -3540,7 +3571,7 @@ with references to beans defined in the child (servlet-specific) contexts by usi When deploying multiple web applications within the same container, ensure that each web application loads the types in `spring-aspects.jar` by using its own classloader -(for example, by placing `spring-aspects.jar` in `'WEB-INF/lib'`). If `spring-aspects.jar` +(for example, by placing `spring-aspects.jar` in `WEB-INF/lib`). If `spring-aspects.jar` is added only to the container-wide classpath (and hence loaded by the shared parent classloader), all web applications share the same aspect instance (which is probably not what you want). diff --git a/src/docs/asciidoc/core/core-beans.adoc b/src/docs/asciidoc/core/core-beans.adoc index 72138867377e..1ef6724c1982 100644 --- a/src/docs/asciidoc/core/core-beans.adoc +++ b/src/docs/asciidoc/core/core-beans.adoc @@ -7490,7 +7490,7 @@ exact same way as when you use Spring annotations, as the following example show } ---- -NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `ManagedBean` +NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `@ManagedBean` annotations are not composable. You should use Spring's stereotype model for building custom component annotations. @@ -7900,6 +7900,10 @@ init-param): ---- +NOTE: For programmatic use cases, a `GenericWebApplicationContext` can be used as an +alternative to `AnnotationConfigWebApplicationContext`. See the +{api-spring-framework}/web/context/support/GenericWebApplicationContext.html[`GenericWebApplicationContext`] +javadoc for details. [[beans-java-bean-annotation]] diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index 9ea8f646d7d5..db96de5d1804 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -6573,7 +6573,7 @@ are made available by the manager. The following example shows how to do so: [source,xml,indent=0,subs="verbatim,quotes"] ---- - + default books diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 33b49bef56a5..b34860566c99 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -84,6 +84,11 @@ NOTE: In addition to using the ServletContext API directly, you can also extend `AbstractAnnotationConfigDispatcherServletInitializer` and override specific methods (see the example under <>). +NOTE: For programmatic use cases, a `GenericWebApplicationContext` can be used as an +alternative to `AnnotationConfigWebApplicationContext`. See the +{api-spring-framework}/web/context/support/GenericWebApplicationContext.html[`GenericWebApplicationContext`] +javadoc for details. + The following example of `web.xml` configuration registers and initializes the `DispatcherServlet`: [source,xml,indent=0,subs="verbatim,quotes"] @@ -4102,24 +4107,22 @@ as the following example shows: ---- HttpServletRequest request = ... - // Re-uses host, scheme, port, path and query string... + // Re-uses scheme, host, port, path, and query string... - ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request) - .replaceQueryParam("accountId", "{id}").build() - .expand("123") - .encode(); + URI uri = ServletUriComponentsBuilder.fromRequest(request) + .replaceQueryParam("accountId", "{id}") + .build("123"); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- val request: HttpServletRequest = ... - // Re-uses host, scheme, port, path and query string... + // Re-uses scheme, host, port, path, and query string... - val ucb = ServletUriComponentsBuilder.fromRequest(request) - .replaceQueryParam("accountId", "{id}").build() - .expand("123") - .encode() + val uri = ServletUriComponentsBuilder.fromRequest(request) + .replaceQueryParam("accountId", "{id}") + .build("123") ---- You can create URIs relative to the context path, as the following example shows: @@ -4127,18 +4130,26 @@ You can create URIs relative to the context path, as the following example shows [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - // Re-uses host, port and context path... + HttpServletRequest request = ... + + // Re-uses scheme, host, port, and context path... - ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request) - .path("/accounts").build() + URI uri = ServletUriComponentsBuilder.fromContextPath(request) + .path("/accounts") + .build() + .toUri(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - // Re-uses host, port and context path... + val request: HttpServletRequest = ... + + // Re-uses scheme, host, port, and context path... - val ucb = ServletUriComponentsBuilder.fromContextPath(request) - .path("/accounts").build() + val uri = ServletUriComponentsBuilder.fromContextPath(request) + .path("/accounts") + .build() + .toUri() ---- You can create URIs relative to a Servlet (for example, `/main/{asterisk}`), @@ -4147,18 +4158,26 @@ as the following example shows: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - // Re-uses host, port, context path, and Servlet prefix... + HttpServletRequest request = ... + + // Re-uses scheme, host, port, context path, and Servlet mapping prefix... - ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request) - .path("/accounts").build() + URI uri = ServletUriComponentsBuilder.fromServletMapping(request) + .path("/accounts") + .build() + .toUri(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - // Re-uses host, port, context path, and Servlet prefix... + val request: HttpServletRequest = ... + + // Re-uses scheme, host, port, context path, and Servlet mapping prefix... - val ucb = ServletUriComponentsBuilder.fromServletMapping(request) - .path("/accounts").build() + val uri = ServletUriComponentsBuilder.fromServletMapping(request) + .path("/accounts") + .build() + .toUri() ---- NOTE: As of 5.1, `ServletUriComponentsBuilder` ignores information from the `Forwarded` and diff --git a/src/eclipse/org.eclipse.jdt.ui.prefs b/src/eclipse/org.eclipse.jdt.ui.prefs index 1685154bbab8..ad75f7c7f2d3 100644 --- a/src/eclipse/org.eclipse.jdt.ui.prefs +++ b/src/eclipse/org.eclipse.jdt.ui.prefs @@ -63,4 +63,4 @@ org.eclipse.jdt.ui.keywordthis=false org.eclipse.jdt.ui.ondemandthreshold=9999 org.eclipse.jdt.ui.overrideannotation=true org.eclipse.jdt.ui.staticondemandthreshold=9999 -org.eclipse.jdt.ui.text.custom_code_templates= +org.eclipse.jdt.ui.text.custom_code_templates=