From c92b9bc7fe3c6a57cf5f15f53dd4afabd0b3e5ad Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 13 Jan 2022 17:55:37 +0100 Subject: [PATCH 001/998] Properly abort transferTo test for Undertow Instead of simply returning prematurely and allowing the tests to be marked as SUCCESS, this commit uses a failed assumption to abort the the trasferTo tests for Undertow, resulting in the parameterized test invocation properly being marked as ABORTED. See gh-25310 --- .../reactive/function/MultipartIntegrationTests.java | 10 +++++----- .../method/annotation/MultipartIntegrationTests.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) 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/result/method/annotation/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java index 1b49442ce2bd..98da50cd66fd 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 From 261bc2ad6acf974933267bbfcc2586c286cccad0 Mon Sep 17 00:00:00 2001 From: Marten Deinum Date: Mon, 17 Jan 2022 07:54:07 +0100 Subject: [PATCH 002/998] Fix regression in BeanPropertyRowMapper.underscoreName(String) Commit 6316a35 introduced a regression for property names starting with multiple uppercase letters (such as setEMail(...)). This commit fixes that regression and includes an additional test to cover this case. See gh-27929 Closes gh-27941 --- .../jdbc/core/BeanPropertyRowMapper.java | 5 +- .../jdbc/core/AbstractRowMapperTests.java | 16 +++- .../jdbc/core/BeanPropertyRowMapperTests.java | 14 +++- .../jdbc/core/test/EmailPerson.java | 78 +++++++++++++++++++ 4 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/core/test/EmailPerson.java 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/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java index 601bbdfd7a1d..bee0bade090f 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,6 +98,14 @@ 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}; @@ -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..c61bab0a297a 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. @@ -24,6 +24,7 @@ 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; @@ -134,4 +135,15 @@ public void testQueryWithSpaceInColumnNameAndLocalDate() throws Exception { mock.verifyClosed(); } + @Test + public void testQueryWithUnderscoreAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() 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.size()).isEqualTo(1); + verifyPerson(result.get(0)); + mock.verifyClosed(); + } + } 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; + } + +} From 420ff46b2acfc91a945a12a1ae0ea11f61b43e3e Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 17 Jan 2022 16:46:00 +0100 Subject: [PATCH 003/998] Polishing --- .../jdbc/core/BeanPropertyRowMapperTests.java | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) 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 c61bab0a297a..8cb5cd45d796 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 @@ -31,74 +31,77 @@ 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", @@ -106,7 +109,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(() -> @@ -114,34 +117,34 @@ 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 - public void testQueryWithUnderscoreAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception { + 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.size()).isEqualTo(1); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); } From 420c4f3df3809f5d394fd54a338ea0a1d91a14fe Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 17 Jan 2022 16:47:16 +0100 Subject: [PATCH 004/998] Explicitly test BeanPropertyRowMapper.underscoreName(String) See gh-27929 --- .../jdbc/core/BeanPropertyRowMapperTests.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 8cb5cd45d796..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 @@ -19,6 +19,8 @@ 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; @@ -149,4 +151,18 @@ void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLetter 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); + } + } From 71f74695788fda416b7aeba510c98180fb589187 Mon Sep 17 00:00:00 2001 From: Jerome Prinet Date: Mon, 17 Jan 2022 15:18:14 +0100 Subject: [PATCH 005/998] Upgrade to Gradle Enterprise 3.8.1 Closes gh-27942 --- settings.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index 0f97d1fcaa87..247d88ee6668 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,8 +6,8 @@ pluginManagement { } 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" From d02d5adb5465d51885d374c55bce8bbb8dbd6279 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 17 Jan 2022 18:03:27 +0100 Subject: [PATCH 006/998] Download gradle-enterprise-conventions plugin from "release" repo See gh-27942 --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 247d88ee6668..6913d57d47dd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,7 @@ pluginManagement { repositories { gradlePluginPortal() - maven { url "https://repo.spring.io/plugins-release" } + maven { url "https://repo.spring.io/release" } } } From 5c76ff5ef68c995852b82996501458a7defd914f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 18 Jan 2022 16:00:50 +0100 Subject: [PATCH 007/998] Ensure unresolvable placeholders can be ignored with @Value Prior to this commit, if a PropertySourcesPlaceholderConfigurer bean was configured with its ignoreUnresolvablePlaceholders flag set to true, unresolvable placeholders in an @Value annotation were not ignored, resulting in a BeanCreationException for the bean using @Value. For example, given a property declared as `my.app.var = ${var}` without a corresponding `var` property declared, an attempt to resolve `@Value("${my.app.var}")` resulted in the following exception. java.lang.IllegalArgumentException: Could not resolve placeholder 'var' in value "${var}" This commit fixes this by modifying PropertySourcesPlaceholderConfigurer's postProcessBeanFactory(...) method so that a local PropertyResolver is created if the ignoreUnresolvablePlaceholders flag is set to true. The local PropertyResolver then enforces that flag, since the Environment in the ApplicationContext is most likely not configured with ignoreUnresolvablePlaceholders set to true. Closes gh-27947 --- .../PropertySourcesPlaceholderConfigurer.java | 20 ++++- ...ertySourcesPlaceholderConfigurerTests.java | 73 ++++++++++++++++++- 2 files changed, 89 insertions(+), 4 deletions(-) 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/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; + } + } + } From 537aced28baff5b514dab2c6c59475c0f0e0f8f0 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 19 Jan 2022 13:53:38 +0100 Subject: [PATCH 008/998] Avoid message listener recovery in case of persistence exceptions on commit Closes gh-1807 --- ...AbstractPollingMessageListenerContainer.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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; } From 86be03945b5a3d887a22e055d9b12981238159c6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 19 Jan 2022 13:54:03 +0100 Subject: [PATCH 009/998] Polishing --- .../java/org/springframework/core/SimpleAliasRegistry.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 From 148eac0200ae4f1ab61034d133528ab6dad8ea1d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 19 Jan 2022 13:54:22 +0100 Subject: [PATCH 010/998] Upgrade to SLF4J 1.7.33, Netty 4.1.73, R2DBC Arabba-SR12 --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 6012af4ccc51..4ff69c724721 100644 --- a/build.gradle +++ b/build.gradle @@ -28,9 +28,9 @@ 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.netty:netty-bom:4.1.73.Final" mavenBom "io.projectreactor:reactor-bom:2020.0.15" - mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR11" + 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.jetbrains.kotlin:kotlin-bom:1.5.32" @@ -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.33" dependency("com.google.code.findbugs:findbugs:3.0.1") { exclude group: "dom4j", name: "dom4j" } From 72119120570c7607d34e77973726a5346184b083 Mon Sep 17 00:00:00 2001 From: shirohoo Date: Thu, 20 Jan 2022 14:06:13 +0900 Subject: [PATCH 011/998] Polish tests in spring-web This PR polishes trivial things in tests in spring-web. Closes gh-27958 --- .../ContentCachingRequestWrapperTests.java | 40 ++++++++++++------- .../web/util/WebUtilsTests.java | 14 +++---- 2 files changed, 32 insertions(+), 22 deletions(-) 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"); From a681d6af229146c73e4175b6c58292d677e8bc27 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 21 Jan 2022 15:52:09 +0100 Subject: [PATCH 012/998] Fix Javadoc links to JSR 305 annotations where feasible Prior to this commit, we only (indirectly) configured external Javadoc links for types in the javax.annotation package for JSR 250, since those types are part of Java 8 SE. However, an external Javadoc link for types from JSR 305 was not configured. To address this issue, this commit now configures an external Javadoc link for JSR 305 types. However, the external Javadoc link for JSR 305 must be configured 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. Instead, the Javadoc tool iterates over all external links, and the first external site that claims to support a given package (via the package-list file) wins. This means: - Javadoc for JSR 250 annotations is fully supported. - Javadoc for JSR 305 annotations in the javax.annotation package will continue to result in a 404 (page not found) error. - Javadoc for JSR 305 annotations in the javax.annotation.meta package is fully supported. For Spring Framework 6.0, the Javadoc for JSR 250 types in jakarta.annotation package is served from the JBoss Application Server Javadoc site instead of from Oracle's Java SE documentation site, since JSR 250 annotations are no longer included in Java SE. Closes gh-27904 --- build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4ff69c724721..d2b3fcf89fdd 100644 --- a/build.gradle +++ b/build.gradle @@ -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[] } From 316764cad04b247cbf69de6a5b3c974d642e5dd6 Mon Sep 17 00:00:00 2001 From: Antoine Rey Date: Fri, 21 Jan 2022 19:52:37 +0100 Subject: [PATCH 013/998] Fix CaffeineCacheManager configuration example in reference doc See gh-27967 --- src/docs/asciidoc/integration.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d698446f7ce0018bcd35c17fe579db13e42f04a5 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 24 Jan 2022 13:45:09 +0100 Subject: [PATCH 014/998] Polish ReflectionUtils[Tests] --- .../springframework/util/ReflectionUtils.java | 4 ++-- .../util/ReflectionUtilsTests.java | 23 +++++++------------ 2 files changed, 10 insertions(+), 17 deletions(-) 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..36dfaa1e5eb6 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -46,7 +46,7 @@ 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 */ @@ -354,7 +354,6 @@ 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. Method[] methods = getDeclaredMethods(clazz, false); for (Method method : methods) { if (mf != null && !mf.matches(method)) { @@ -367,6 +366,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/test/java/org/springframework/util/ReflectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java index 8ca69ddc9a05..06e2df61444a 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. @@ -184,27 +184,20 @@ 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 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 From d01dca14e94b6ead5d8ed98c99ba4c7dfed0fefb Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 24 Jan 2022 17:03:16 +0100 Subject: [PATCH 015/998] Filter methods in Object in ReflectionUtils.USER_DECLARED_METHODS Prior to this commit, the USER_DECLARED_METHODS MethodFilter in ReflectionUtils did not actually filter methods declared in java.lang.Object as stated in its Javadoc. Consequently, if ReflectionUtils.doWithMethods was invoked with USER_DECLARED_METHODS and Object.class as the class to introspect, all non-bridge non-synthetic methods declared in java.lang.Object were passed to the supplied MethodCallback, which breaks the contract of USER_DECLARED_METHODS. In addition, if USER_DECLARED_METHODS was composed with a custom MethodFilter using `USER_DECLARED_METHODS.and()`, that composed method filter allowed all non-bridge non-synthetic methods declared in java.lang.Object to be passed to the supplied MethodCallback, which also breaks the contract of USER_DECLARED_METHODS. This behavior resulted in regressions in code that had previously used USER_DECLARED_METHODS by itself and then began using USER_DECLARED_METHODS in a composed filter. For example, since commit c419ea7ba7 ReflectiveAspectJAdvisorFactory has incorrectly processed methods in java.lang.Object as candidates for advice methods. This commit fixes this bug and associated regressions by ensuring that USER_DECLARED_METHODS actually filters methods declared in java.lang.Object. In addition, ReflectionUtils.doWithMethods now aborts its search algorithm early if invoked with the USER_DECLARED_METHODS filter and Object.class as the class to introspect. Closes gh-27970 --- .../springframework/util/ReflectionUtils.java | 6 ++++- .../util/ReflectionUtilsTests.java | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) 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 36dfaa1e5eb6..274cb7188703 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -51,7 +51,7 @@ public abstract class ReflectionUtils { * @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,6 +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) { + 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)) { 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 06e2df61444a..ef0897552519 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -28,6 +28,7 @@ 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; @@ -193,6 +194,32 @@ void doWithMethodsUsingProtectedFilter() { .as("Public, not protected").doesNotContain("hashCode", "absquatulate"); } + @Test + 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(); From cb3fa89946858203fc84f6eb1a897cf353d34445 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 25 Jan 2022 09:57:36 +0100 Subject: [PATCH 016/998] Polish ReflectionUtilsTests --- .../util/ReflectionUtilsTests.java | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) 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 ef0897552519..f71d789f6cbc 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -228,7 +228,7 @@ void doWithMethodsFindsDuplicatesInClassHierarchy() { } @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(); @@ -236,7 +236,7 @@ void findMethod() throws Exception { @Disabled("[SPR-8644] findMethod() does not currently support var-args") @Test - void findMethodWithVarArgs() throws Exception { + void findMethodWithVarArgs() { assertThat(ReflectionUtils.findMethod(B.class, "add", int.class, int.class, int.class)).isNotNull(); } @@ -279,37 +279,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 @@ -326,16 +316,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 From 652c13a6eae11c3ce28410a6643f5860fe32b04d Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 25 Jan 2022 09:58:57 +0100 Subject: [PATCH 017/998] Enable ReflectionUtilsTests.findMethodWithVarArgs() See gh-13286 --- .../java/org/springframework/util/ReflectionUtilsTests.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 f71d789f6cbc..4109426e8fe7 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -24,7 +24,6 @@ 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; @@ -234,10 +233,9 @@ void findMethod() { assertThat(ReflectionUtils.findMethod(B.class, "getClass")).isNotNull(); } - @Disabled("[SPR-8644] findMethod() does not currently support var-args") @Test void findMethodWithVarArgs() { - assertThat(ReflectionUtils.findMethod(B.class, "add", int.class, int.class, int.class)).isNotNull(); + assertThat(ReflectionUtils.findMethod(B.class, "add", int[].class)).isNotNull(); } @Test From 0a92d84778dcb03dd5bf2860f2d997d1db2687f4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 25 Jan 2022 23:50:45 +0100 Subject: [PATCH 018/998] Check open status before close call (aligned with EntityManagerFactoryUtils) Closes gh-27972 --- .../springframework/orm/hibernate5/SessionFactoryUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); From 993b6d1351d6a92eb27b60e47d77cab8083b1b87 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 25 Jan 2022 23:51:22 +0100 Subject: [PATCH 019/998] Upgrade to Tomcat 9.0.58, Protobuf 3.19.3, Rome 1.18, H2 2.1.210, SLF4J 1.7.35, Mockito 4.3.1, HtmlUnit 2.57, XMLUnit 2.9, Checkstyle 9.2.1 --- build.gradle | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index d2b3fcf89fdd..0b73d65a8de0 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ configure(allprojects) { project -> entry 'log4j-jul' entry 'log4j-slf4j-impl' } - dependency "org.slf4j:slf4j-api:1.7.33" + dependency "org.slf4j:slf4j-api:1.7.35" dependency("com.google.code.findbugs:findbugs:3.0.1") { exclude group: "dom4j", name: "dom4j" } @@ -77,7 +77,7 @@ configure(allprojects) { project -> 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" @@ -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" @@ -128,14 +128,14 @@ configure(allprojects) { project -> dependency "org.webjars:webjars-locator-core:0.48" dependency "org.webjars:underscorejs:1.8.3" - dependencySet(group: 'org.apache.tomcat', version: '9.0.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' } @@ -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,7 +206,7 @@ configure(allprojects) { project -> } dependency "io.mockk:mockk:1.12.1" - dependency("net.sourceforge.htmlunit:htmlunit:2.56.0") { + dependency("net.sourceforge.htmlunit:htmlunit:2.57.0") { exclude group: "commons-logging", name: "commons-logging" } dependency("org.seleniumhq.selenium:htmlunit-driver:2.56.0") { @@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project -> } checkstyle { - toolVersion = "9.2" + toolVersion = "9.2.1" configDirectory.set(rootProject.file("src/checkstyle")) } From 1272cd557d14f1ecf28e518b6bbd6edcd62e0536 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 26 Jan 2022 00:02:12 +0100 Subject: [PATCH 020/998] Upgrade to SmallRye Mutiny 1.3.1, Aalto 1.3.1, Woodstox 6.2.8 --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 0b73d65a8de0..993049364999 100644 --- a/build.gradle +++ b/build.gradle @@ -68,12 +68,12 @@ 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" From bf1cf549b1f6d410d830945157ef2f4294f77783 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 26 Jan 2022 12:15:10 +0100 Subject: [PATCH 021/998] Delete unused XML config in AOP namespace tests See gh-22246 --- .../aop/config/AopNamespaceHandlerEventTests.java | 4 ++-- .../aop/config/AopNamespaceHandlerEventTests-context.xml | 6 ------ ...mespaceHandlerPointcutErrorTests-pointcutDuplication.xml | 4 ---- ...opNamespaceHandlerPointcutErrorTests-pointcutMissing.xml | 4 ---- 4 files changed, 2 insertions(+), 16 deletions(-) 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..5c5dd5689fce 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. @@ -153,7 +153,7 @@ public void testAdvisorEventsWithDirectPointcut() { public void testAspectEvent() { this.reader.loadBeanDefinitions(CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(5); + assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(2); boolean condition = componentDefinitions[0] instanceof CompositeComponentDefinition; assertThat(condition).as("No holder with nested components").isTrue(); 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 @@ - - - - From f4f5cee76cc9b9b5a7ba8efbfd37fd4b55dfe8a1 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 26 Jan 2022 12:29:30 +0100 Subject: [PATCH 022/998] Polish AOP namespace tests --- .../config/AopNamespaceHandlerEventTests.java | 82 ++++++++----------- ...AopNamespaceHandlerPointcutErrorTests.java | 8 +- .../aop/config/TopLevelAopTagTests.java | 6 +- 3 files changed, 43 insertions(+), 53 deletions(-) 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 5c5dd5689fce..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 @@ -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(2); + 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")); From 5c9fbcc23c59ae5b6f8f067198d8050a747a5a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ostro=C5=BEl=C3=ADk?= Date: Thu, 16 Dec 2021 11:15:23 +0100 Subject: [PATCH 023/998] Add CacheErrorHandler implementation that logs exceptions See gh-27826 --- .../interceptor/LoggingCacheErrorHandler.java | 111 ++++++++++++++++++ .../LoggingCacheErrorHandlerTests.java | 63 ++++++++++ 2 files changed, 174 insertions(+) create mode 100644 spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java create mode 100644 spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java 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..d1fefcafa420 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.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 simply logs error message. + * + * @author Adam Ostrožlík + * @since 5.3 + */ +public final class LoggingCacheErrorHandler implements CacheErrorHandler { + + private final Log logger; + private final boolean includeStacktrace; + + /** + * Construct new {@link LoggingCacheErrorHandler} that may log stacktraces. + * @param includeStacktrace whether to log or not log stacktraces. + * @param logger custom logger. + */ + private LoggingCacheErrorHandler(boolean includeStacktrace, Log logger) { + Assert.notNull(logger, "logger cannot be null"); + this.includeStacktrace = includeStacktrace; + this.logger = logger; + } + + @Override + public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { + logCacheError("Cache '" + cache.getName() + "' failed to get entry with key '" + key + "'", exception); + } + + @Override + public void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value) { + logCacheError("Cache '" + cache.getName() + "' failed to put entry with key '" + key + "'", exception); + } + + @Override + public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { + logCacheError("Cache '" + cache.getName() + "' failed to evict entry with key '" + key + "'", exception); + } + + @Override + public void handleCacheClearError(RuntimeException exception, Cache cache) { + logCacheError("Cache '" + cache.getName() + "' failed to clear itself", exception); + } + + private void logCacheError(String msg, RuntimeException ex) { + if (this.includeStacktrace) { + logger.warn(msg, ex); + } + else { + logger.warn(msg); + } + } + + /** + * Builder class for {@link LoggingCacheErrorHandler}. + */ + public static class Builder { + private Log logger; + private boolean includeStacktrace; + + /** + * Overrides default logger. + * @param logger new logger. + * @return this builder. + */ + public Builder setLogger(Log logger) { + this.logger = logger; + return this; + } + + /** + * Enable/disable logging of stacktraces. + * @param includeStacktrace true - include stacktraces; false otherwise. + * @return this builder. + */ + public Builder setIncludeStacktrace(boolean includeStacktrace) { + this.includeStacktrace = includeStacktrace; + return this; + } + + public LoggingCacheErrorHandler build() { + if (logger == null) { + return new LoggingCacheErrorHandler(this.includeStacktrace, LogFactory.getLog(LoggingCacheErrorHandler.class)); + } + return new LoggingCacheErrorHandler(this.includeStacktrace, logger); + } + } +} 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..f7618be9f402 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.interceptor; + +import org.apache.commons.logging.Log; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.cache.support.NoOpCache; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link LoggingCacheErrorHandler}. + * + * @author Adam Ostrožlík + */ +@ExtendWith(MockitoExtension.class) +public class LoggingCacheErrorHandlerTests { + + @Mock + Log logger; + + @Test + void givenHandlerWhenHandleGetErrorThenLogWithoutException() { + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler.Builder() + .setLogger(logger) + .build(); + handler.handleCacheGetError(new RuntimeException(), new NoOpCache("NOOP"), "key"); + verify(logger, times(1)).warn(anyString()); + } + + @Test + void givenHandlerWhenHandleGetErrorThenLogWithException() { + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler.Builder() + .setLogger(logger) + .setIncludeStacktrace(true) + .build(); + RuntimeException exception = new RuntimeException(); + handler.handleCacheGetError(exception, new NoOpCache("NOOP"), "key"); + verify(logger, times(1)).warn(anyString(), eq(exception)); + } + +} From 6a6c7df824675476a0f7b5bed80c503a9b279c01 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 26 Jan 2022 14:08:17 +0100 Subject: [PATCH 024/998] Polish "Add CacheErrorHandler implementation that logs exceptions" See gh-27826 --- .../interceptor/LoggingCacheErrorHandler.java | 98 +++++++++---------- .../LoggingCacheErrorHandlerTests.java | 59 ++++++----- 2 files changed, 82 insertions(+), 75 deletions(-) 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 index d1fefcafa420..e2f6c33127d4 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.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,88 +24,82 @@ import org.springframework.util.Assert; /** - * A {@link CacheErrorHandler} implementation that simply logs error message. + * A {@link CacheErrorHandler} implementation that logs error message. Can be + * used when underlying cache errors should be ignored. * * @author Adam Ostrožlík - * @since 5.3 + * @author Stephane Nicoll + * @since 5.3.16 */ -public final class LoggingCacheErrorHandler implements CacheErrorHandler { +public class LoggingCacheErrorHandler implements CacheErrorHandler { private final Log logger; - private final boolean includeStacktrace; + + private final boolean logStacktrace; + /** - * Construct new {@link LoggingCacheErrorHandler} that may log stacktraces. - * @param includeStacktrace whether to log or not log stacktraces. - * @param logger custom logger. + * Create an instance with the {@link Log logger} to use. + * @param logger the logger to use + * @param logStacktrace whether to log stack trace */ - private LoggingCacheErrorHandler(boolean includeStacktrace, Log logger) { - Assert.notNull(logger, "logger cannot be null"); - this.includeStacktrace = includeStacktrace; + 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("Cache '" + cache.getName() + "' failed to get entry with key '" + key + "'", exception); + 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("Cache '" + cache.getName() + "' failed to put entry with key '" + key + "'", exception); + logCacheError(logger, + createMessage(cache, "failed to put entry with key '" + key + "'"), + exception); } @Override public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { - logCacheError("Cache '" + cache.getName() + "' failed to evict entry with key '" + key + "'", exception); + logCacheError(logger, + createMessage(cache, "failed to evict entry with key '" + key + "'"), + exception); } @Override public void handleCacheClearError(RuntimeException exception, Cache cache) { - logCacheError("Cache '" + cache.getName() + "' failed to clear itself", exception); - } - - private void logCacheError(String msg, RuntimeException ex) { - if (this.includeStacktrace) { - logger.warn(msg, ex); - } - else { - logger.warn(msg); - } + logCacheError(logger, createMessage(cache, "failed to clear entries"), exception); } /** - * Builder class for {@link LoggingCacheErrorHandler}. + * Log the specified message. + * @param logger the logger + * @param message the message + * @param ex the exception */ - public static class Builder { - private Log logger; - private boolean includeStacktrace; - - /** - * Overrides default logger. - * @param logger new logger. - * @return this builder. - */ - public Builder setLogger(Log logger) { - this.logger = logger; - return this; + protected void logCacheError(Log logger, String message, RuntimeException ex) { + if (this.logStacktrace) { + logger.warn(message, ex); } - - /** - * Enable/disable logging of stacktraces. - * @param includeStacktrace true - include stacktraces; false otherwise. - * @return this builder. - */ - public Builder setIncludeStacktrace(boolean includeStacktrace) { - this.includeStacktrace = includeStacktrace; - return this; + else { + logger.warn(message); } + } - public LoggingCacheErrorHandler build() { - if (logger == null) { - return new LoggingCacheErrorHandler(this.includeStacktrace, LogFactory.getLog(LoggingCacheErrorHandler.class)); - } - return new LoggingCacheErrorHandler(this.includeStacktrace, logger); - } + private String createMessage(Cache cache, String reason) { + return String.format("Cache '%s' %s", cache.getName(), reason); } + } 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 index f7618be9f402..39525d715ae6 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.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. @@ -18,46 +18,59 @@ import org.apache.commons.logging.Log; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; + import org.springframework.cache.support.NoOpCache; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** * Tests for {@link LoggingCacheErrorHandler}. * * @author Adam Ostrožlík + * @author Stephane Nicoll */ -@ExtendWith(MockitoExtension.class) public class LoggingCacheErrorHandlerTests { - @Mock - Log logger; - @Test - void givenHandlerWhenHandleGetErrorThenLogWithoutException() { - LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler.Builder() - .setLogger(logger) - .build(); + void handleGetCacheErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); handler.handleCacheGetError(new RuntimeException(), new NoOpCache("NOOP"), "key"); - verify(logger, times(1)).warn(anyString()); + 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 givenHandlerWhenHandleGetErrorThenLogWithException() { - LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler.Builder() - .setLogger(logger) - .setIncludeStacktrace(true) - .build(); + 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, times(1)).warn(anyString(), eq(exception)); + verify(logger).warn("Cache 'NOOP' failed to get entry with key 'key'", exception); } } From 9095f1d584d16f9b50536e3a76c9628d0e4d0660 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 26 Jan 2022 17:32:07 +0100 Subject: [PATCH 025/998] Polish AspectJ documentation --- src/docs/asciidoc/core/core-aop.adoc | 50 ++++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/docs/asciidoc/core/core-aop.adoc b/src/docs/asciidoc/core/core-aop.adoc index 6479f329b5d9..7a539acff574 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,7 +1213,7 @@ 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 @@ -1301,8 +1301,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 +1320,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 +1349,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 +1377,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 +1448,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 +1575,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 @@ -2542,7 +2542,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 +3540,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). From 7f65e17ff2e728a2b1848da8db894f35fb09434f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 26 Jan 2022 17:32:36 +0100 Subject: [PATCH 026/998] Improve documentation for implementing AspectJ around advice Closes gh-27980 --- src/docs/asciidoc/core/core-aop.adoc | 111 +++++++++++++++++---------- 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/src/docs/asciidoc/core/core-aop.adoc b/src/docs/asciidoc/core/core-aop.adoc index 7a539acff574..3901669e6075 100644 --- a/src/docs/asciidoc/core/core-aop.adoc +++ b/src/docs/asciidoc/core/core-aop.adoc @@ -1217,29 +1217,56 @@ The last kind of advice is _around_ advice. Around advice runs "around" a matche 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 @@ -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"] From bd6d697395efceab68521e598964dca78995a60d Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 27 Jan 2022 09:52:25 +0100 Subject: [PATCH 027/998] Upgrade Java versions in CI image --- ci/images/get-jdk-url.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index e170d55171c3..530866224d1a 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,10 +3,10 @@ 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%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14_9.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" From e32f94bf8cbf5280d04434b0c5777fba73ab3e18 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 26 Jan 2022 20:21:13 +0100 Subject: [PATCH 028/998] Polish GenericWebApplicationContext and AnnotationConfigWebApplicationContext --- ...AnnotationConfigWebApplicationContext.java | 9 +-- .../support/GenericWebApplicationContext.java | 55 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) 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..635a2c76c2c0 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,12 +36,13 @@ /** * {@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 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..eb28a675e7a0 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,23 +39,24 @@ /** * 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 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 @@ -72,7 +73,7 @@ public class GenericWebApplicationContext extends GenericApplicationContext /** - * Create a new GenericWebApplicationContext. + * Create a new {@code GenericWebApplicationContext}. * @see #setServletContext * @see #registerBeanDefinition * @see #refresh @@ -82,8 +83,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 +93,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 +104,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 +118,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 +145,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 +158,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 +237,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 +246,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?"); } } From 6647023151771e6baa59e7fa923d24a009fcbb33 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 27 Jan 2022 15:47:21 +0100 Subject: [PATCH 029/998] Document how to register annotated classes in a GenericWebApplicationContext Closes gh-27778 --- .../AnnotationConfigWebApplicationContext.java | 14 +++++++++++++- .../support/GenericWebApplicationContext.java | 15 +++++++++++++++ src/docs/asciidoc/core/core-beans.adoc | 4 ++++ src/docs/asciidoc/web/webmvc.adoc | 5 +++++ 4 files changed, 37 insertions(+), 1 deletion(-) 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 635a2c76c2c0..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 @@ -46,7 +46,17 @@ * *

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 @@ -81,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 eb28a675e7a0..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 @@ -53,6 +53,20 @@ * {@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} @@ -60,6 +74,7 @@ * * @author Juergen Hoeller * @author Chris Beams + * @author Sam Brannen * @since 1.2 */ public class GenericWebApplicationContext extends GenericApplicationContext diff --git a/src/docs/asciidoc/core/core-beans.adoc b/src/docs/asciidoc/core/core-beans.adoc index 72138867377e..95881a0d2cc9 100644 --- a/src/docs/asciidoc/core/core-beans.adoc +++ b/src/docs/asciidoc/core/core-beans.adoc @@ -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/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 33b49bef56a5..7bca08dd9d2c 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"] From caa13690e8a7d726808e43394e92e000cc3b60d6 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 28 Jan 2022 13:41:51 +0100 Subject: [PATCH 030/998] Support multiple boundary buffers in MultipartParser In a small minority of cases, the multipart boundary can spread across three incoming buffers. Prior to this commit, MultipartParser.BodyState only supported two buffers. If the boundary is spread across three buffers, the first buffer of the three is sent as a whole, even though it contains the first bytes of the boundary. This commit fixes this bug, by enqueuing all prior buffers in a queue, and emitting the ones that cannot contain boundary bytes. Closes gh-27939 --- .../http/codec/multipart/MultipartParser.java | 91 +++++++++++++------ .../annotation/MultipartIntegrationTests.java | 3 +- 2 files changed, 62 insertions(+), 32 deletions(-) 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-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 98da50cd66fd..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 @@ -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); From fadfcf4e43725f4ce5180618a88c21010d55055a Mon Sep 17 00:00:00 2001 From: wkwkhautbois Date: Fri, 28 Jan 2022 23:06:32 +0900 Subject: [PATCH 031/998] Fix ServletUriComponentsBuilder examples in ref docs Closes gh-27984 --- src/docs/asciidoc/web/webmvc.adoc | 46 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 7bca08dd9d2c..52e6d0db4d4f 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -4109,10 +4109,9 @@ as the following example shows: // Re-uses host, scheme, 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 @@ -4121,10 +4120,9 @@ as the following example shows: // Re-uses host, scheme, 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: @@ -4132,18 +4130,22 @@ 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... + // Re-uses host, port, scheme 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... + // Re-uses host, port, scheme 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}`), @@ -4152,18 +4154,22 @@ as the following example shows: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - // Re-uses host, port, context path, and Servlet prefix... + // Re-uses host, port, scheme, context path, and Servlet 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... + // Re-uses host, port, scheme, context path, and Servlet 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 From 0e670b1c1546522b8f158bff6d1092b1b8035ea2 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 28 Jan 2022 16:15:21 +0100 Subject: [PATCH 032/998] Polish contribution See gh-27984 --- src/docs/asciidoc/web/webmvc.adoc | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 52e6d0db4d4f..b34860566c99 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -4107,7 +4107,7 @@ 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... URI uri = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}") @@ -4118,7 +4118,7 @@ as the following example shows: ---- val request: HttpServletRequest = ... - // Re-uses host, scheme, port, path and query string... + // Re-uses scheme, host, port, path, and query string... val uri = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}") @@ -4130,7 +4130,9 @@ 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, scheme and context path... + HttpServletRequest request = ... + + // Re-uses scheme, host, port, and context path... URI uri = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts") @@ -4140,7 +4142,9 @@ You can create URIs relative to the context path, as the following example shows [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - // Re-uses host, port, scheme and context path... + val request: HttpServletRequest = ... + + // Re-uses scheme, host, port, and context path... val uri = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts") @@ -4154,7 +4158,9 @@ as the following example shows: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - // Re-uses host, port, scheme, context path, and Servlet prefix... + HttpServletRequest request = ... + + // Re-uses scheme, host, port, context path, and Servlet mapping prefix... URI uri = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts") @@ -4164,7 +4170,9 @@ as the following example shows: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - // Re-uses host, port, scheme, context path, and Servlet prefix... + val request: HttpServletRequest = ... + + // Re-uses scheme, host, port, context path, and Servlet mapping prefix... val uri = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts") From fb312d0ed5d55752df5755be29833023e5a21258 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 28 Jan 2022 16:40:35 +0100 Subject: [PATCH 033/998] Improve Javadoc for DatabasePopulator See gh-27008 --- .../jdbc/datasource/init/DatabasePopulator.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 */ From dbeae27d3ca70eabd617538a09f4ac952f19126b Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 29 Jan 2022 12:41:39 +0100 Subject: [PATCH 034/998] Reduce build time by reducing shutdown wait period for Jetty tests --- .../server/reactive/bootstrap/JettyHttpServer.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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(); } From 7072aab53b765ccd57898bc54eec0f293fb4a700 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 29 Jan 2022 17:51:13 +0100 Subject: [PATCH 035/998] Improve log message when searching for default executor for async processing Closes gh-27983 --- .../aop/interceptor/AsyncExecutionAspectSupport.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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); } From a749a6bf54ad4b715bd99144acf75c40817797a5 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 28 Jan 2022 16:55:05 +0100 Subject: [PATCH 036/998] Stop applying Groovy plugin for the root Gradle project The root project does not rely on the Groovy plugin. See gh-27945 --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 993049364999..f9068aaac302 100644 --- a/build.gradle +++ b/build.gradle @@ -405,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' From 136bd2002e898d7692a8d4e9f5b0728128e53dca Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 31 Jan 2022 09:34:12 +0100 Subject: [PATCH 037/998] Upgrade to spring javaformat 0.0.30 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f9068aaac302..23f6a57a0126 100644 --- a/build.gradle +++ b/build.gradle @@ -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.30") } ext.javadocLinks = [ From 8f9a1cdc0c2ab2bee112dddad00a94bb501a3181 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 1 Feb 2022 13:51:40 +0100 Subject: [PATCH 038/998] Consider current date in "1W" cron expressions Prior to this commit, the QuartzCronField::weekdayNearestTo would elapse until the next month before checking if the current day matched. After this commit, the current day is checked before we elapse until the next month. Closes gh-27966 --- .../scheduling/support/QuartzCronField.java | 39 ++++++++++--------- .../support/CronExpressionTests.java | 25 ++++++++++-- 2 files changed, 43 insertions(+), 21 deletions(-) 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/test/java/org/springframework/scheduling/support/CronExpressionTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java index 3a21f984aa2d..5abce9e2de30 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 From 4effca35b500fda26f1f7d8322bc622468e5e93a Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 31 Jan 2022 13:51:40 +0100 Subject: [PATCH 039/998] Ignore Content-Type that is invalid (not concrete) Closes gh-27957 --- .../http/server/ServletServerHttpRequest.java | 6 ++++-- .../http/server/ServletServerHttpRequestTests.java | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) 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/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); From 8d5a6520ce033c38c57a9ec026102e17bd598acc Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 2 Feb 2022 15:32:21 +0000 Subject: [PATCH 040/998] Ensure all converters don't close InputStream Closes gh-27969 --- .../BufferedImageHttpMessageConverter.java | 2 ++ .../feed/AbstractWireFeedHttpMessageConverter.java | 7 +++++-- .../json/AbstractJackson2HttpMessageConverter.java | 12 +++++++----- .../xml/AbstractXmlHttpMessageConverter.java | 7 +++++-- .../converter/xml/SourceHttpMessageConverter.java | 4 ++-- .../BufferedImageHttpMessageConverterTests.java | 11 +++++++++-- .../feed/AtomFeedHttpMessageConverterTests.java | 11 ++++++++--- .../feed/RssChannelHttpMessageConverterTests.java | 11 ++++++++--- .../json/GsonHttpMessageConverterTests.java | 12 ++++++++++-- .../json/JsonbHttpMessageConverterTests.java | 12 ++++++++++-- .../MappingJackson2HttpMessageConverterTests.java | 9 +++++++-- .../ProtobufHttpMessageConverterTests.java | 9 ++++++++- ...rotobufJsonFormatHttpMessageConverterTests.java | 9 ++++++++- ...pingJackson2SmileHttpMessageConverterTests.java | 12 ++++++++++-- .../Jaxb2CollectionHttpMessageConverterTests.java | 12 ++++++++++-- .../Jaxb2RootElementHttpMessageConverterTests.java | 12 ++++++++++-- ...appingJackson2XmlHttpMessageConverterTests.java | 12 ++++++++++-- .../xml/MarshallingHttpMessageConverterTests.java | 14 ++++++++++++-- .../xml/SourceHttpMessageConverterTests.java | 12 ++++++++++-- 19 files changed, 151 insertions(+), 39 deletions(-) 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/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/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..60d7306c47ab 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 @@ -132,6 +139,7 @@ public void write() throws IOException { 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)); + verify(outputMessage.getBody(), never()).close(); } @Test 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..d2aad2f424e8 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 @@ -132,6 +139,7 @@ public void write() throws IOException { 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)); + verify(outputMessage.getBody(), never()).close(); } @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..f67a56433e45 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 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..f0b83166da1f 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,7 +16,9 @@ package org.springframework.http.converter.protobuf; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import com.google.protobuf.ExtensionRegistry; @@ -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 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..9d3e8470a7b6 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 @@ -88,6 +95,7 @@ public void write() throws IOException { 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")); + 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..9c0c08646f64 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,12 +85,14 @@ 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 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..202776f6856f 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; @@ -95,9 +100,11 @@ public void canWrite() { @Test public void readXmlRootElement() throws Exception { byte[] body = "".getBytes("UTF-8"); - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); + 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 @@ -177,6 +184,7 @@ public void writeXmlRootElement() throws Exception { DifferenceEvaluator ev = chain(Default, downgradeDifferencesToEqual(XML_STANDALONE)); assertThat(XmlContent.of(outputMessage.getBodyAsString(StandardCharsets.UTF_8))) .isSimilarTo("", ev); + verify(outputMessage.getBody(), never()).close(); } @Test 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..631ff4848e71 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 @@ -104,6 +111,7 @@ public void write() throws IOException { 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)); + verify(outputMessage.getBody(), never()).close(); } @Test 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..d79f9dc5ac8a 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 @@ -135,6 +144,7 @@ public void write() throws Exception { converter.write(body, null, outputMessage); assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(new MediaType("application", "xml")); + verify(outputMessage.getBody(), never()).close(); } @Test 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..c2341a9bed86 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,11 +95,13 @@ public void canWrite() { @Test public void readDOMSource() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); + InputStream inputStream = spy(new ByteArrayInputStream(BODY.getBytes("UTF-8"))); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(inputStream); inputMessage.getHeaders().setContentType(new 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 @@ -294,6 +301,7 @@ public void writeDOMSource() throws Exception { .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); + verify(outputMessage.getBody(), never()).close(); } @Test From c4e362500be18bd2e277535d568f45e8a059b0db Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 2 Feb 2022 17:09:04 +0000 Subject: [PATCH 041/998] Polishing tests --- .../ByteArrayHttpMessageConverterTests.java | 6 +- .../FormHttpMessageConverterTests.java | 9 ++- .../ResourceHttpMessageConverterTests.java | 6 +- .../json/GsonHttpMessageConverterTests.java | 8 ++- .../json/JsonbHttpMessageConverterTests.java | 6 +- ...pingJackson2HttpMessageConverterTests.java | 34 ++++++----- .../ProtobufHttpMessageConverterTests.java | 9 +-- ...ackson2SmileHttpMessageConverterTests.java | 3 +- ...b2CollectionHttpMessageConverterTests.java | 18 +++--- ...2RootElementHttpMessageConverterTests.java | 38 +++++++----- ...gJackson2XmlHttpMessageConverterTests.java | 19 +++--- .../MarshallingHttpMessageConverterTests.java | 23 ++++--- .../xml/SourceHttpMessageConverterTests.java | 60 ++++++++++--------- 13 files changed, 132 insertions(+), 107 deletions(-) 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/json/GsonHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java index 60d7306c47ab..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 @@ -138,7 +138,8 @@ 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(); } @@ -161,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 @@ -177,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 d2aad2f424e8..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 @@ -138,7 +138,8 @@ 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(); } @@ -161,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 f67a56433e45..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 @@ -160,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"); @@ -192,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(); } @@ -214,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 @@ -230,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 @@ -266,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); @@ -280,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); } @@ -296,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); @@ -311,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); } @@ -328,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); @@ -343,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); } @@ -489,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 f0b83166da1f..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 @@ -19,7 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; @@ -158,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(); @@ -182,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(); @@ -197,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/smile/MappingJackson2SmileHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java index 9d3e8470a7b6..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 @@ -94,7 +94,8 @@ 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 9c0c08646f64..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 @@ -99,7 +99,7 @@ public void readXmlRootElementList() throws Exception { @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); @@ -111,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); @@ -123,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); @@ -139,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 @@ -168,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 @@ -203,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 202776f6856f..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 @@ -85,21 +85,27 @@ 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"); + 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); @@ -109,7 +115,7 @@ public void readXmlRootElement() throws Exception { @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"); @@ -117,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"); @@ -130,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); @@ -144,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); @@ -170,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"); @@ -180,7 +186,8 @@ 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); @@ -191,7 +198,8 @@ public void writeXmlRootElement() throws Exception { 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); @@ -211,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 631ff4848e71..1deac303b701 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 @@ -110,15 +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)); } @@ -126,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 } @@ -164,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)); @@ -191,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)); 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 d79f9dc5ac8a..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 @@ -108,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 @@ -127,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 @@ -143,7 +142,8 @@ 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(); } @@ -157,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 c2341a9bed86..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 @@ -95,9 +95,9 @@ public void canWrite() { @Test public void readDOMSource() throws Exception { - InputStream inputStream = spy(new ByteArrayInputStream(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")); + 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"); @@ -106,8 +106,8 @@ public void readDOMSource() throws Exception { @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(); @@ -134,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)) @@ -143,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())); @@ -153,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(); @@ -189,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(); @@ -216,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(); @@ -255,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(); @@ -264,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); @@ -280,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); } @@ -299,8 +297,10 @@ 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(); } @@ -313,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 @@ -325,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); } } From e702c22da45016cdb15cdc4b47fb361c5063c8b0 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 3 Feb 2022 08:48:52 +0100 Subject: [PATCH 042/998] Upgrade Ubuntu version in CI image --- ci/images/ci-image/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile index df1be9f33457..c38b735bb7b7 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 From 82a2544918bac14301bece7c651114c278919f30 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 3 Feb 2022 08:49:16 +0100 Subject: [PATCH 043/998] Upgrade to spring javaformat 0.0.31 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 23f6a57a0126..dfb10e777ad1 100644 --- a/build.gradle +++ b/build.gradle @@ -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.30") + checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.31") } ext.javadocLinks = [ From f8a5a8d7be6040e60c21107c14fc394e67e75e6b Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 3 Feb 2022 14:50:10 +0100 Subject: [PATCH 044/998] Use modern language features in tests --- ...ionScannerJsr330ScopeIntegrationTests.java | 40 +-- .../ConcurrencyThrottleInterceptorTests.java | 11 +- .../beans/AbstractPropertyValuesTests.java | 8 +- .../beans/factory/BeanFactoryUtilsTests.java | 1 + .../DefaultListableBeanFactoryTests.java | 37 +- ...wiredAnnotationBeanPostProcessorTests.java | 8 +- .../config/CustomEditorConfigurerTests.java | 8 +- .../MethodInvokingFactoryBeanTests.java | 10 +- .../support/BeanFactoryGenericsTests.java | 24 +- .../DefaultSingletonBeanRegistryTests.java | 9 +- .../testfixture/beans/MustBeInitialized.java | 3 +- .../beans/testfixture/beans/Pet.java | 12 +- .../caffeine/CaffeineCacheManagerTests.java | 11 +- .../mail/javamail/JavaMailSenderTests.java | 34 +- .../AspectJAutoProxyCreatorTests.java | 9 +- .../aop/framework/AbstractAopProxyTests.java | 109 +++--- .../aop/framework/JdkDynamicProxyTests.java | 12 +- .../aop/framework/ProxyFactoryBeanTests.java | 16 +- .../autoproxy/AutoProxyCreatorTests.java | 13 +- .../target/CommonsPool2TargetSourceTests.java | 4 +- .../context/LifecycleContextBean.java | 9 +- ...notationConfigApplicationContextTests.java | 15 +- ...ommonAnnotationBeanPostProcessorTests.java | 7 +- .../ConfigurationClassAndBFPPTests.java | 16 +- .../ConfigurationClassWithConditionTests.java | 16 +- ...figurationClassAspectIntegrationTests.java | 5 +- .../ConfigurationClassProcessingTests.java | 10 +- .../event/ApplicationContextEventTests.java | 19 +- .../event/PayloadApplicationEventTests.java | 2 +- .../event/test/AbstractIdentifiable.java | 8 +- .../context/event/test/GenericEventPojo.java | 8 +- .../test/IdentifiableApplicationEvent.java | 8 +- .../ConversionServiceFactoryBeanTests.java | 5 +- .../format/number/NumberFormattingTests.java | 16 +- ...tingConversionServiceFactoryBeanTests.java | 16 +- .../export/MBeanExporterOperationsTests.java | 15 +- .../jmx/export/MBeanExporterTests.java | 7 +- .../jmx/export/NotificationListenerTests.java | 26 +- .../assembler/AbstractJmxAssemblerTests.java | 12 +- .../annotation/AsyncExecutionTests.java | 31 +- .../ExecutorBeanDefinitionParserTests.java | 8 +- .../scripting/groovy/MyBytecodeProcessor.java | 2 +- .../org/springframework/ui/ModelMapTests.java | 11 +- .../src/test/java/test/mixin/LockMixin.java | 3 +- .../context/testfixture/SimpleMapScope.java | 4 +- .../annotation/MergedAnnotationsTests.java | 4 +- .../converter/ConvertingComparatorTests.java | 2 +- .../util/AutoPopulatingListTests.java | 2 +- .../util/ConcurrentReferenceHashMapTests.java | 2 +- .../util/ReflectionUtilsTests.java | 4 +- .../util/StringUtilsTests.java | 10 +- .../jdbc/core/AbstractRowMapperTests.java | 2 +- .../jdbc/core/JdbcTemplateQueryTests.java | 8 +- .../jdbc/core/JdbcTemplateTests.java | 30 +- .../namedparam/NamedParameterQueryTests.java | 8 +- .../jdbc/object/StoredProcedureTests.java | 77 ++-- ...LErrorCodeSQLExceptionTranslatorTests.java | 4 +- .../jms/core/JmsTemplateTests.java | 48 +-- .../SimpleMessageListenerContainerTests.java | 59 ++-- .../MessagingMessageListenerAdapterTests.java | 4 +- .../support/SimpleMessageConverterTests.java | 9 +- .../MappingJackson2MessageConverterTests.java | 4 +- .../MappingJackson2MessageConverterTests.java | 4 +- .../core/GenericMessagingTemplateTests.java | 16 +- .../MessageMappingMessageHandlerTests.java | 2 +- ...faultMessageHandlerMethodFactoryTests.java | 8 +- .../SimpAttributesContextHolderTests.java | 3 +- ...criptionMethodReturnValueHandlerTests.java | 4 +- .../StompBrokerRelayMessageHandlerTests.java | 8 +- .../orm/jpa/JpaTransactionManagerTests.java | 334 +++++++----------- ...rceptingClientHttpRequestFactoryTests.java | 70 +--- ...gJackson2XmlHttpMessageConverterTests.java | 4 +- .../bind/ServletRequestDataBinderTests.java | 8 +- .../request/async/DeferredResultTests.java | 9 +- .../async/WebAsyncManagerErrorTests.java | 15 +- .../async/WebAsyncManagerTimeoutTests.java | 7 +- .../filter/HiddenHttpMethodFilterTests.java | 15 +- .../annotation/ModelFactoryOrderingTests.java | 45 +-- .../function/BodyExtractorsTests.java | 2 +- .../function/server/RouterFunctionsTests.java | 10 +- .../resource/ResourceUrlProviderTests.java | 2 +- .../context/LifecycleContextBean.java | 9 +- .../XmlWebApplicationContextTests.java | 29 +- .../support/HttpRequestHandlerTests.java | 39 +- ...MvcConfigurationSupportExtensionTests.java | 8 +- .../DefaultEntityResponseBuilderTests.java | 9 +- .../DefaultRenderingResponseTests.java | 10 +- .../function/DefaultServerRequestTests.java | 7 +- .../DefaultServerResponseBuilderTests.java | 10 +- .../web/servlet/tags/form/SelectTagTests.java | 8 +- .../DefaultSimpUserRegistryTests.java | 8 +- 91 files changed, 578 insertions(+), 1060 deletions(-) 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/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-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..d76579eaf218 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")); @@ -2718,8 +2709,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/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/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/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/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java index d841044f2650..91e58722fea2 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. @@ -557,12 +557,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 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/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/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/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/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/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/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index e9e3586bb6a4..0c7708eee4be 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 @@ -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( 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 4109426e8fe7..bbb05b743a86 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -391,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/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-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java index bee0bade090f..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 @@ -107,7 +107,7 @@ protected void verifyPerson(EmailPerson person) { } - protected enum MockType {ONE, TWO, THREE}; + protected enum MockType {ONE, TWO, THREE} protected static class Mock { 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/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/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/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/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-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/xml/MappingJackson2XmlHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java index 1deac303b701..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 @@ -279,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/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-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/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-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/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/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/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); From 05d3e820f9e1b20afbdbc352ee4d40c126474ff6 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 4 Feb 2022 08:20:45 +0300 Subject: [PATCH 045/998] Polish reference to ManagedBean annotation See gh-28004 --- src/docs/asciidoc/core/core-beans.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/asciidoc/core/core-beans.adoc b/src/docs/asciidoc/core/core-beans.adoc index 95881a0d2cc9..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. From 5d7a632965aff526cb6bbd0380b445f102819dca Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 4 Feb 2022 19:41:46 +0100 Subject: [PATCH 046/998] Ensure Spring AOP generates JDK dynamic proxies for lambdas Prior to this commit, if AOP proxy generation was configured with proxyTargetClass=true (which is the default behavior in recent versions of Spring Boot), beans implemented as lambda expressions or method references could not be proxied with CGLIB on Java 16 or higher without specifying `--add-opens java.base/java.lang=ALL-UNNAMED`. This commit addresses this shortcoming by ensuring that beans implemented as lambda expressions or method references are always proxied using a JDK dynamic proxy even if proxyTargetClass=true. Closes gh-27971 --- .../aop/framework/AopProxyUtils.java | 19 +++++- .../aop/framework/DefaultAopProxyFactory.java | 5 +- .../aop/framework/AopProxyUtilsTests.java | 61 ++++++++++++++++++- .../AspectJAutoProxyCreatorTests.java | 50 +++++++++++++++ src/checkstyle/checkstyle.xml | 2 +- 5 files changed, 131 insertions(+), 6 deletions(-) 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..b7c3de885c07 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.2.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/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-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 91e58722fea2..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 @@ -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. @@ -566,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/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 @@ - + From 132d8c7f453f6770ec5ca08e72446b65fc0c8528 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:19:06 +0100 Subject: [PATCH 047/998] Support for CGLIB BeanMap utility on JDK 17 Closes gh-27802 --- spring-core/spring-core.gradle | 1 + .../springframework/cglib/beans/BeanMap.java | 330 ++++++++++++++++++ .../cglib/beans/package-info.java | 10 + .../cglib/core/package-info.java | 2 +- .../cglib/proxy/package-info.java | 2 +- 5 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/beans/package-info.java 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..cfe843635991 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java @@ -0,0 +1,330 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package 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; From a71a45e71965acd9b4c5d9863bcd6f378990a465 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:21:00 +0100 Subject: [PATCH 048/998] Deprecate AsyncTaskExecutor.execute(Runnable task, long startTimeout) Closes gh-27959 --- .../commonj/WorkManagerTaskExecutor.java | 3 ++- .../quartz/SimpleThreadPoolTaskExecutor.java | 3 ++- .../concurrent/ConcurrentTaskExecutor.java | 3 ++- .../concurrent/ThreadPoolTaskExecutor.java | 3 ++- .../concurrent/ThreadPoolTaskScheduler.java | 3 ++- .../core/task/AsyncTaskExecutor.java | 20 ++++++++++++++----- .../core/task/SimpleAsyncTaskExecutor.java | 8 +++++++- .../core/task/TaskRejectedException.java | 3 +-- .../core/task/TaskTimeoutException.java | 5 +++-- .../task/support/TaskExecutorAdapter.java | 3 ++- .../jca/work/SimpleTaskWorkManager.java | 6 +++--- .../jca/work/WorkManagerTaskExecutor.java | 12 ++++++++--- 12 files changed, 50 insertions(+), 22 deletions(-) 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/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-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-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); From bc9cd9a687b598a3d567ebcd9fb4108e0342ce84 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:21:27 +0100 Subject: [PATCH 049/998] Find interface method even for late-bound interface declaration in subclass Closes gh-27995 --- .../AbstractAutowireCapableBeanFactory.java | 4 +- .../support/DisposableBeanAdapter.java | 10 ++-- .../org/springframework/util/ClassUtils.java | 60 +++++++++++++------ .../support/ReflectiveMethodExecutor.java | 14 ++++- .../support/ReflectiveMethodResolver.java | 8 +-- .../support/ReflectivePropertyAccessor.java | 24 ++++---- 6 files changed, 78 insertions(+), 42 deletions(-) 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-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-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; From a22feac8036393725113222055166eb9e7211c9a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:51:05 +0100 Subject: [PATCH 050/998] Update license header for https (nohttp rule) See gh-27802 --- .../src/main/java/org/springframework/cglib/beans/BeanMap.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index cfe843635991..3a6dd8e02057 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cglib.beans; import java.security.ProtectionDomain; From 5bbdd36e199dd53b237f168c34c81c7b4dcd8946 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:51:23 +0100 Subject: [PATCH 051/998] Upgrade to Checkstyle 9.3, HtmlUnit 2.58, Apache HttpClient 5.1.3 --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index dfb10e777ad1..58d99ed311c7 100644 --- a/build.gradle +++ b/build.gradle @@ -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" } @@ -206,10 +206,10 @@ configure(allprojects) { project -> } dependency "io.mockk:mockk:1.12.1" - dependency("net.sourceforge.htmlunit:htmlunit:2.57.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") { @@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project -> } checkstyle { - toolVersion = "9.2.1" + toolVersion = "9.3" configDirectory.set(rootProject.file("src/checkstyle")) } From 4ab03fede8aa6fd501f136803277db7e868d9b7c Mon Sep 17 00:00:00 2001 From: izeye Date: Sat, 5 Feb 2022 21:48:09 +0900 Subject: [PATCH 052/998] Fix Javadoc since for AopProxyUtils.isLambda() See gh-28010 --- .../java/org/springframework/aop/framework/AopProxyUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b7c3de885c07..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 @@ -252,7 +252,7 @@ static Object[] adaptArgumentsIfNecessary(Method method, @Nullable Object[] argu * 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.2.16 + * @since 5.3.16 */ static boolean isLambda(Class clazz) { return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) && From a13ad3e96932683feb6f1272503aaa30dae6685e Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 5 Feb 2022 19:43:45 +0100 Subject: [PATCH 053/998] Polishing --- .../test/util/JsonExpectationsHelper.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 { From 920be8e1b201fa947b07941eeace62ecf0f431a9 Mon Sep 17 00:00:00 2001 From: Gleidson Leopoldo Date: Tue, 1 Feb 2022 09:26:14 +0000 Subject: [PATCH 054/998] Add support for strict JSON comparison in WebTestClient Prior to this commit, WebTestClient only supported "lenient" comparison of the expected JSON body. This commit introduces an overloaded variant of `json()` in the BodyContentSpec that accepts an additional boolean flag to specify whether a "strict" comparison should be performed. This new feature is analogous to the existing support in MockMvc. Closes gh-27993 --- .../reactive/server/DefaultWebTestClient.java | 4 +- .../web/reactive/server/WebTestClient.java | 19 +++++++- .../server/samples/JsonContentTests.java | 46 ++++++++++++++++++- 3 files changed, 64 insertions(+), 5 deletions(-) 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..3c0a33cb67dc 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 @@ -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..b8115a2e549c 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 @@ -979,7 +979,24 @@ interface BodyContentSpec { * on to be on the classpath. * @param expectedJson the expected JSON content. */ - BodyContentSpec json(String expectedJson); + default BodyContentSpec json(String expectedJson) { + return json(expectedJson, false); + } + + /** + * Parse the expected and actual response content as JSON and perform a + * comparison in two modes, depending on {@code strict} parameter value, verifying the same attribute-value pairs. + *
    + *
  • {@code true}: strict checking. + *
  • {@code false}: lenient checking. + *
+ *

Use of this option requires the + * JSONassert library + * on to be on the classpath. + * @param expectedJson the expected JSON content. + * @param strict enables strict checking + */ + 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/web/reactive/server/samples/JsonContentTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/JsonContentTests.java index 82d271c87775..2df1ad44adc6 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 @@ -31,10 +31,10 @@ 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. * @@ -49,13 +49,32 @@ public class JsonContentTests { @Test public void jsonContent() { - this.client.get().uri("/persons") + this.client.get().uri("/persons/extended") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectBody().json("[{\"name\":\"Jane\"},{\"name\":\"Jason\"},{\"name\":\"John\"}]"); } + @Test + public void jsonContentStrictFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> this.client.get().uri("/persons/extended") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectBody().json("[{\"name\":\"Jane\"},{\"name\":\"Jason\"},{\"name\":\"John\"}]", true) + ); + } + + @Test + public void jsonContentStrict() { + this.client.get().uri("/persons/extended") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectBody().json("[{\"name\":\"Jane\",\"surname\":\"Williams\"},{\"name\":\"Jason\",\"surname\":\"Johnson\"},{\"name\":\"John\",\"surname\":\"Smith\"}]", true); + } + @Test public void jsonPathIsEqualTo() { this.client.get().uri("/persons") @@ -98,6 +117,11 @@ Flux getPersons() { return Flux.just(new Person("Jane"), new Person("Jason"), new Person("John")); } + @GetMapping("/extended") + Flux getExtendedPersons() { + return Flux.just(new ExtendedPerson("Jane", "Williams"), new ExtendedPerson("Jason", "Johnson"), new ExtendedPerson("John", "Smith")); + } + @GetMapping("/{name}") Person getPerson(@PathVariable String name) { return new Person(name); @@ -109,4 +133,22 @@ ResponseEntity savePerson(@RequestBody Person person) { } } + static class ExtendedPerson { + private String name; + private String surname; + + public ExtendedPerson(String name, String surname) { + this.name = name; + this.surname = surname; + } + + public String getName() { + return name; + } + + public String getSurname() { + return surname; + } + } + } From eb84c843736aaa555d9078a544280bd70b714fbb Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 5 Feb 2022 20:11:47 +0100 Subject: [PATCH 055/998] Polish contribution See gh-27993 --- .../reactive/server/DefaultWebTestClient.java | 2 +- .../web/reactive/server/WebTestClient.java | 31 ++++--- .../server/samples/JsonContentTests.java | 85 ++++++++++--------- 3 files changed, 66 insertions(+), 52 deletions(-) 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 3c0a33cb67dc..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. 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 b8115a2e549c..481a8d999e88 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. @@ -973,11 +973,14 @@ 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 - * on to be on the classpath. - * @param expectedJson the expected JSON content. + * to be on the classpath. + * @param expectedJson the expected JSON content + * @see #json(String, boolean) */ default BodyContentSpec json(String expectedJson) { return json(expectedJson, false); @@ -985,16 +988,20 @@ default BodyContentSpec json(String expectedJson) { /** * Parse the expected and actual response content as JSON and perform a - * comparison in two modes, depending on {@code strict} parameter value, verifying the same attribute-value pairs. + * 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. - *
  • {@code false}: lenient checking. + *
  • {@code true}: strict checking. Not extensible and strict array ordering.
  • + *
  • {@code false}: lenient checking. Extensible and non-strict array ordering.
  • *
- *

Use of this option requires the + *

Use of this method requires the * JSONassert library - * on to be on the classpath. - * @param expectedJson the expected JSON content. - * @param strict enables strict checking + * 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, boolean strict); 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 2df1ad44adc6..f038fb513d82 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. @@ -34,7 +34,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.Matchers.containsString; - /** * Samples of tests using {@link WebTestClient} with serialized JSON content. * @@ -48,31 +47,41 @@ public class JsonContentTests { @Test - public void jsonContent() { - this.client.get().uri("/persons/extended") + public void jsonContentWithDefaultLenientMode() { + this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() - .expectBody().json("[{\"name\":\"Jane\"},{\"name\":\"Jason\"},{\"name\":\"John\"}]"); + .expectBody().json( + "[{\"firstName\":\"Jane\"}," + + "{\"firstName\":\"Jason\"}," + + "{\"firstName\":\"John\"}]"); } @Test - public void jsonContentStrictFail() { - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> this.client.get().uri("/persons/extended") + public void jsonContentWithStrictMode() { + this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() - .expectBody().json("[{\"name\":\"Jane\"},{\"name\":\"Jason\"},{\"name\":\"John\"}]", true) - ); + .expectBody().json( + "[{\"firstName\":\"Jane\",\"lastName\":\"Williams\"}," + + "{\"firstName\":\"Jason\",\"lastName\":\"Johnson\"}," + + "{\"firstName\":\"John\",\"lastName\":\"Smith\"}]", + true); } @Test - public void jsonContentStrict() { - this.client.get().uri("/persons/extended") + public void jsonContentWithStrictModeAndMissingAttributes() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() - .expectStatus().isOk() - .expectBody().json("[{\"name\":\"Jane\",\"surname\":\"Williams\"},{\"name\":\"Jason\",\"surname\":\"Johnson\"},{\"name\":\"John\",\"surname\":\"Smith\"}]", true); + .expectBody().json( + "[{\"firstName\":\"Jane\"}," + + "{\"firstName\":\"Jason\"}," + + "{\"firstName\":\"John\"}]", + true) + ); } @Test @@ -82,26 +91,26 @@ public void jsonPathIsEqualTo() { .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") + 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() { this.client.post().uri("/persons") .contentType(MediaType.APPLICATION_JSON) - .bodyValue("{\"name\":\"John\"}") + .bodyValue("{\"firstName\":\"John\",\"lastName\":\"Smith\"}") .exchange() .expectStatus().isCreated() .expectBody().isEmpty(); @@ -114,40 +123,38 @@ static class PersonController { @GetMapping Flux getPersons() { - return Flux.just(new Person("Jane"), new Person("Jason"), new Person("John")); - } - - @GetMapping("/extended") - Flux getExtendedPersons() { - return Flux.just(new ExtendedPerson("Jane", "Williams"), new ExtendedPerson("Jason", "Johnson"), new ExtendedPerson("John", "Smith")); + 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 ExtendedPerson { - private String name; - private String surname; + static class Person { + private String firstName; + private String lastName; + + public Person() { + } - public ExtendedPerson(String name, String surname) { - this.name = name; - this.surname = surname; + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; } - public String getName() { - return name; + public String getFirstName() { + return this.firstName; } - public String getSurname() { - return surname; + public String getLastName() { + return this.lastName; } } From 038b88e2a1cc64c845aacc438d1b39979110bce5 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 5 Feb 2022 20:23:45 +0100 Subject: [PATCH 056/998] Polishing --- .../reactive/server/samples/ErrorTests.java | 10 ++++---- .../server/samples/ExchangeMutatorTests.java | 23 +++++++----------- .../GlobalEntityResultConsumerTests.java | 3 ++- .../server/samples/HeaderAndCookieTests.java | 11 +++++---- .../server/samples/JsonContentTests.java | 14 +++++------ .../server/samples/ResponseEntityTests.java | 24 ++++++++----------- .../server/samples/XmlContentTests.java | 17 ++++++------- 7 files changed, 45 insertions(+), 57 deletions(-) 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 f038fb513d82..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 @@ -41,13 +41,13 @@ * @author Sam Brannen * @since 5.0 */ -public class JsonContentTests { +class JsonContentTests { private final WebTestClient client = WebTestClient.bindToController(new PersonController()).build(); @Test - public void jsonContentWithDefaultLenientMode() { + void jsonContentWithDefaultLenientMode() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() @@ -59,7 +59,7 @@ public void jsonContentWithDefaultLenientMode() { } @Test - public void jsonContentWithStrictMode() { + void jsonContentWithStrictMode() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() @@ -72,7 +72,7 @@ public void jsonContentWithStrictMode() { } @Test - public void jsonContentWithStrictModeAndMissingAttributes() { + void jsonContentWithStrictModeAndMissingAttributes() { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() @@ -85,7 +85,7 @@ public void jsonContentWithStrictModeAndMissingAttributes() { } @Test - public void jsonPathIsEqualTo() { + void jsonPathIsEqualTo() { this.client.get().uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchange() @@ -97,7 +97,7 @@ public void jsonPathIsEqualTo() { } @Test - public void jsonPathMatches() { + void jsonPathMatches() { this.client.get().uri("/persons/John/Smith") .accept(MediaType.APPLICATION_JSON) .exchange() @@ -107,7 +107,7 @@ public void jsonPathMatches() { } @Test - public void postJsonContent() { + void postJsonContent() { this.client.post().uri("/persons") .contentType(MediaType.APPLICATION_JSON) .bodyValue("{\"firstName\":\"John\",\"lastName\":\"Smith\"}") 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"; From 669b05dc1d925cf88a084a1de6e582b6f0a773e1 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 8 Feb 2022 14:04:11 +0100 Subject: [PATCH 057/998] Allow AutowiredAnnotationBeanPostProcessor to compile on JDK 11 --- .../annotation/AutowiredAnnotationBeanPostProcessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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()))); } /** From 7139a877f41f864b3a318c0dadd3450fa67deb19 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 8 Feb 2022 13:22:44 +0100 Subject: [PATCH 058/998] Ensure toString() for synthesized annotations is source code compatible Since the introduction of synthesized annotation support in Spring Framework 4.2 (a.k.a., merged annotations), the toString() implementation attempted to align with the formatting used by the JDK itself. However, Class annotation attributes were formatted using Class#getName in Spring; whereas, the JDK used Class#toString up until JDK 9. In addition, JDK 9 introduced new formatting for toString() for annotations, apparently intended to align with the syntax used in the source code declaration of the annotation. However, JDK 9+ formats enum annotation attributes using Enum#toString instead of Enum#name, which can lead to issues if toString() is overridden in an enum. This commit updates the formatting used for synthesized annotations by ensuring that toString() generates a string that is compatible with the syntax of the originating source code, going beyond the changes made in JDK 9 by using Enum#name instead of Enum#toString. Closes gh-28015 --- ...izedMergedAnnotationInvocationHandler.java | 14 ++++-- .../annotation/MergedAnnotationsTests.java | 45 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) 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..6838dc377973 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. @@ -195,18 +195,24 @@ private String annotationToString() { } private String toString(Object value) { + if (value instanceof String) { + return '"' + value.toString() + '"'; + } + if (value instanceof Enum) { + return ((Enum) value).name(); + } if (value instanceof Class) { - return ((Class) value).getName(); + return ((Class) value).getName() + ".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); 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 0c7708eee4be..0b92547863ed 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. @@ -38,6 +38,7 @@ import javax.annotation.Resource; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.JRE; import org.springframework.core.Ordered; import org.springframework.core.annotation.MergedAnnotation.Adapt; @@ -1861,20 +1862,41 @@ 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()); + + if (JRE.currentVersion().ordinal() > JRE.JAVA_8.ordinal()) { + // The unsynthesized annotation for handleMappedWithSamePathAndValueAttributes() + // should produce the same toString() results as synthesized annotations for + // handleMappedWithPathAttribute() on Java 9 or higher + 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(")"); + String string = webMapping.toString(); + + // Formatting common to Spring and JDK 9+ + assertThat(string) + .startsWith("@" + RequestMapping.class.getName() + "(") + .contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"", "clazz=java.lang.Object.class") + .endsWith(")"); + + if (webMapping instanceof SynthesizedAnnotation) { + assertThat(string).as("Spring uses Enum#name()").contains("method={GET, POST}"); + } + else { + assertThat(string).as("JDK uses Enum#toString()").contains("method={method: get, method: post}"); + } } @Test @@ -2941,7 +2963,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 +2988,9 @@ enum RequestMethod { String[] path() default ""; RequestMethod[] method() default {}; + + // clazz is only used for testing annotation toString() implementations + Class clazz() default Object.class; } @Retention(RetentionPolicy.RUNTIME) From d2c7dfb79ee62900a0c5ded12768fc18458d765a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 10 Feb 2022 12:37:19 +0100 Subject: [PATCH 059/998] Add convenience factory method for Managed[List|Set|Map] Closes gh-28026 --- .../beans/factory/support/ManagedList.java | 17 +++++++++++- .../beans/factory/support/ManagedMap.java | 22 +++++++++++++++- .../beans/factory/support/ManagedSet.java | 17 +++++++++++- .../DefaultListableBeanFactoryTests.java | 8 ++---- .../PropertyResourceConfigurerTests.java | 21 +++++++-------- .../factory/support/ManagedListTests.java | 26 ++++++------------- .../factory/support/ManagedMapTests.java | 24 +++++++---------- .../factory/support/ManagedSetTests.java | 25 ++++++------------ .../servlet/ComplexWebApplicationContext.java | 6 ++--- 9 files changed, 92 insertions(+), 74 deletions(-) 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/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index d76579eaf218..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 @@ -2298,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); @@ -2316,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"); 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/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-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); From 97582fd0a1f786c2cde12ba2af8196e423d47d87 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 9 Feb 2022 15:56:17 +0100 Subject: [PATCH 060/998] Update Eclipse template to @since 5.3.16 --- src/eclipse/org.eclipse.jdt.ui.prefs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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= From ce87285be5d4be7ab9d981273acac8a8624883dc Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 10 Feb 2022 14:16:34 +0100 Subject: [PATCH 061/998] Use canonical names for types in synthesized annotation toString My proposal for the same change in the JDK is currently targeted for JDK 19. - https://bugs.openjdk.java.net/browse/JDK-8281462 - https://bugs.openjdk.java.net/browse/JDK-8281568 - https://github.com/openjdk/jdk/pull/7418 See gh-28015 --- ...izedMergedAnnotationInvocationHandler.java | 9 +++++++-- .../annotation/MergedAnnotationsTests.java | 20 +++++++++++++------ .../test/context/BootstrapUtilsTests.java | 6 +++--- 3 files changed, 24 insertions(+), 11 deletions(-) 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 6838dc377973..8cbfcc6f5a48 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 @@ -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) { @@ -202,7 +202,7 @@ private String toString(Object value) { return ((Enum) value).name(); } if (value instanceof Class) { - return ((Class) value).getName() + ".class"; + return getName((Class) value) + ".class"; } if (value.getClass().isArray()) { StringBuilder builder = new StringBuilder("{"); @@ -277,6 +277,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/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index 0b92547863ed..a6ff5a0f07a6 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 @@ -1887,15 +1887,22 @@ private void assertToStringForWebMappingWithPathAndValue(RequestMapping webMappi // Formatting common to Spring and JDK 9+ assertThat(string) - .startsWith("@" + RequestMapping.class.getName() + "(") - .contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"", "clazz=java.lang.Object.class") + .contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"") .endsWith(")"); if (webMapping instanceof SynthesizedAnnotation) { - assertThat(string).as("Spring uses Enum#name()").contains("method={GET, POST}"); + assertThat(string).as("Spring formatting") + .startsWith("@org.springframework.core.annotation.MergedAnnotationsTests.RequestMapping(") + .contains("method={GET, POST}", + "clazz=org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class", + "classes={org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class}"); } else { - assertThat(string).as("JDK uses Enum#toString()").contains("method={method: get, method: post}"); + assertThat(string).as("JDK 9-18 formatting") + .startsWith("@org.springframework.core.annotation.MergedAnnotationsTests$RequestMapping(") + .contains("method={method: get, method: post}", + "clazz=org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod.class", + "classes={org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod.class}"); } } @@ -2989,8 +2996,9 @@ public String toString() { RequestMethod[] method() default {}; - // clazz is only used for testing annotation toString() implementations - Class clazz() default Object.class; + Class clazz() default RequestMethod.class; + + Class[] classes() default {RequestMethod.class}; } @Retention(RetentionPolicy.RUNTIME) 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 From 2fd39839f83e8923277cb89bcff0b5dc5a18abfc Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 10 Feb 2022 16:58:36 +0100 Subject: [PATCH 062/998] Improve toString() for synthesized annotations Although the initial report in gh-28015 only covered inconsistencies for arrays and strings in the toString() implementations for annotations between the JDK (after Java 9) and Spring, it has since come to our attention that there was further room for improvement. This commit therefore addresses the following in toString() output for synthesized annotations. - characters are now wrapped in single quotes. - bytes are now properly formatted as "(byte) 0x##". - long, float, and double values are now appended with "L", "f", and "d", respectively. The use of lowercase for "f" and "d" is solely to align with the choice made by the JDK team. However, this commit 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. Closes gh-28015 --- ...izedMergedAnnotationInvocationHandler.java | 28 +++++++++++ .../annotation/MergedAnnotationsTests.java | 50 +++++++++++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) 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 8cbfcc6f5a48..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 @@ -194,10 +194,38 @@ 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(); } 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 a6ff5a0f07a6..acd8fc9abaf6 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 @@ -1887,7 +1887,7 @@ private void assertToStringForWebMappingWithPathAndValue(RequestMapping webMappi // Formatting common to Spring and JDK 9+ assertThat(string) - .contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"") + .contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"", "ch='X'", "chars={'X'}") .endsWith(")"); if (webMapping instanceof SynthesizedAnnotation) { @@ -1895,14 +1895,36 @@ private void assertToStringForWebMappingWithPathAndValue(RequestMapping webMappi .startsWith("@org.springframework.core.annotation.MergedAnnotationsTests.RequestMapping(") .contains("method={GET, POST}", "clazz=org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class", - "classes={org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class}"); + "classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod[].class}", + "byteValue=(byte) 0xFF", "bytes={(byte) 0xFF}", + "shortValue=9876", "shorts={9876}", + "longValue=42L", "longs={42L}", + "floatValue=3.14f", "floats={3.14f}", + "doubleValue=99.999d", "doubles={99.999d}" + ); } else { assertThat(string).as("JDK 9-18 formatting") .startsWith("@org.springframework.core.annotation.MergedAnnotationsTests$RequestMapping(") .contains("method={method: get, method: post}", "clazz=org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod.class", - "classes={org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod.class}"); + "classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod[].class}", + "shortValue=9876", "shorts={9876}", + "floatValue=3.14f", "floats={3.14f}", + "doubleValue=99.999", "doubles={99.999}" + ); + if (JRE.currentVersion().ordinal() < JRE.JAVA_14.ordinal()) { + assertThat(string).as("JDK 9-13 formatting") + .contains("longValue=42", "longs={42}", + "byteValue=-1", "bytes={-1}" + ); + } + else { + assertThat(string).as("JDK 14+ formatting") + .contains("longValue=42L", "longs={42L}", + "byteValue=(byte)0xff", "bytes={(byte)0xff}" + ); + } } } @@ -2996,9 +3018,29 @@ public String toString() { 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}; - Class[] classes() default {RequestMethod.class}; + double doubleValue() default 99.999D; + double[] doubles() default {99.999D}; } @Retention(RetentionPolicy.RUNTIME) From b60340b102ac1858c1527faab4c848561a860898 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 11 Feb 2022 15:33:14 +0100 Subject: [PATCH 063/998] Simplify tests for synthesized annotation toString() See gh-28015 --- .../annotation/MergedAnnotationsTests.java | 79 +++++++------------ 1 file changed, 30 insertions(+), 49 deletions(-) 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 acd8fc9abaf6..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 @@ -38,7 +38,6 @@ import javax.annotation.Resource; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.JRE; import org.springframework.core.Ordered; import org.springframework.core.annotation.MergedAnnotation.Adapt; @@ -1872,60 +1871,42 @@ void toStringForSynthesizedAnnotations() throws Exception { assertThat(webMappingWithAliases.toString()).isNotEqualTo(synthesizedWebMapping1.toString()); - if (JRE.currentVersion().ordinal() > JRE.JAVA_8.ordinal()) { - // The unsynthesized annotation for handleMappedWithSamePathAndValueAttributes() - // should produce the same toString() results as synthesized annotations for - // handleMappedWithPathAttribute() on Java 9 or higher - assertToStringForWebMappingWithPathAndValue(webMappingWithPathAndValue); - } + // 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 string = webMapping.toString(); - - // Formatting common to Spring and JDK 9+ - assertThat(string) - .contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"", "ch='X'", "chars={'X'}") + 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(")"); - - if (webMapping instanceof SynthesizedAnnotation) { - assertThat(string).as("Spring formatting") - .startsWith("@org.springframework.core.annotation.MergedAnnotationsTests.RequestMapping(") - .contains("method={GET, POST}", - "clazz=org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class", - "classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod[].class}", - "byteValue=(byte) 0xFF", "bytes={(byte) 0xFF}", - "shortValue=9876", "shorts={9876}", - "longValue=42L", "longs={42L}", - "floatValue=3.14f", "floats={3.14f}", - "doubleValue=99.999d", "doubles={99.999d}" - ); - } - else { - assertThat(string).as("JDK 9-18 formatting") - .startsWith("@org.springframework.core.annotation.MergedAnnotationsTests$RequestMapping(") - .contains("method={method: get, method: post}", - "clazz=org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod.class", - "classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod[].class}", - "shortValue=9876", "shorts={9876}", - "floatValue=3.14f", "floats={3.14f}", - "doubleValue=99.999", "doubles={99.999}" - ); - if (JRE.currentVersion().ordinal() < JRE.JAVA_14.ordinal()) { - assertThat(string).as("JDK 9-13 formatting") - .contains("longValue=42", "longs={42}", - "byteValue=-1", "bytes={-1}" - ); - } - else { - assertThat(string).as("JDK 14+ formatting") - .contains("longValue=42L", "longs={42L}", - "byteValue=(byte)0xff", "bytes={(byte)0xff}" - ); - } - } } @Test From 420147954621a4b1d653b6f441cbf59ad493173b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 11 Feb 2022 15:41:49 +0100 Subject: [PATCH 064/998] Start building against Reactor 2020.0.16 snapshots See gh-28039 --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 58d99ed311c7..f80d6519774c 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ configure(allprojects) { project -> imports { mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6" mavenBom "io.netty:netty-bom:4.1.73.Final" - mavenBom "io.projectreactor:reactor-bom:2020.0.15" + mavenBom "io.projectreactor:reactor-bom:2020.0.16-SNAPSHOT" 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" @@ -292,6 +292,7 @@ configure(allprojects) { project -> repositories { mavenCentral() maven { url "https://repo.spring.io/libs-spring-framework-build" } + maven { url "https://repo.spring.io/snapshot" } // reactor } } configurations.all { From eca755ec88a6f9c91d335f4d0931cb79668164ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Fri, 11 Feb 2022 17:42:06 +0100 Subject: [PATCH 065/998] Upgrade to Dokka 1.6.10 Closes gh-28040 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f80d6519774c..e126405cff45 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' From 4eaee1e7381d5f3d8cd6e3ab77c8cfcf7ef2d716 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 11 Feb 2022 20:41:23 +0100 Subject: [PATCH 066/998] Short circuit if-conditions in AttributeMethods --- .../springframework/core/annotation/AttributeMethods.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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); From 3ec612aaf8611d2ad6e6f7f3d5868feee5024477 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 11 Feb 2022 20:51:19 +0100 Subject: [PATCH 067/998] Support recursive annotations in merged annotations Although Java does not allow the definition of recursive annotations, Kotlin does, and prior to this commit an attempt to synthesize a merged annotation using the MergedAnnotation API resulted in a StackOverflowError if there was a recursive cycle in the annotation definitions. This commit addresses this issue by tracking which annotations have already been visited and short circuits the recursive algorithm if a cycle is detected. Closes gh-28012 --- .../annotation/AnnotationTypeMapping.java | 25 +++-- .../annotation/AnnotationTypeMappings.java | 77 +++++++++---- .../AnnotationTypeMappingsTests.java | 4 +- .../springframework/core/annotation/Filter.kt | 35 ++++++ .../core/annotation/Filters.kt | 29 +++++ .../KotlinMergedAnnotationsTests.kt | 105 ++++++++++++++++++ .../springframework/core/annotation/Person.kt | 35 ++++++ 7 files changed, 279 insertions(+), 31 deletions(-) create mode 100644 spring-core/src/test/kotlin/org/springframework/core/annotation/Filter.kt create mode 100644 spring-core/src/test/kotlin/org/springframework/core/annotation/Filters.kt create mode 100644 spring-core/src/test/kotlin/org/springframework/core/annotation/KotlinMergedAnnotationsTests.kt create mode 100644 spring-core/src/test/kotlin/org/springframework/core/annotation/Person.kt 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/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/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..2755ba4d9b2f --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/Filter.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 Filter( + + @get:AliasFor("name") + val value: String = "", + + @get:AliasFor("value") + val name: 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..dedd3ced4166 --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/KotlinMergedAnnotationsTests.kt @@ -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.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(); + + // 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(); + + // 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("jane", friends = [Person("john"), Person("sally")]) + fun personMethod() { + } + + @Filter("foo", and = Filters(Filter("bar"), Filter("baz"))) + fun filterMethod() { + } + +} 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..28d67c4a2364 --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/Person.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 Person( + + @get:AliasFor("name") + val value: String = "", + + @get:AliasFor("value") + val name: String = "", + + vararg val friends: Person = [] + +) From 2f78abd56e39eb95c32fd211ca738359f8eec3e5 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 14 Feb 2022 10:29:45 +0100 Subject: [PATCH 068/998] Upgrade CI pipeline This commit upgrades the CI pipeline with the following: * replace JDK16 with JDK17 as build variant * upgrade all JDK versions * replace docker-image with registry-image resource for CI image --- ci/images/ci-image/Dockerfile | 2 +- ci/images/get-jdk-url.sh | 9 ++++++--- ci/parameters.yml | 3 --- ci/pipeline.yml | 16 ++++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile index c38b735bb7b7..edc9eac649b0 100644 --- a/ci/images/ci-image/Dockerfile +++ b/ci/images/ci-image/Dockerfile @@ -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 530866224d1a..d090494f6627 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -6,10 +6,13 @@ case "$1" in 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.14%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14_9.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/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..03545186aea5 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -87,7 +87,7 @@ resources: branch: ((branch)) paths: ["ci/images/*"] - name: ci-image - type: docker-image + type: registry-image icon: docker source: <<: *docker-resource-source @@ -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" From 8eb618b480896092cae04d1c5e412f7a5dba0c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 14 Feb 2022 10:08:42 +0100 Subject: [PATCH 069/998] Make Kotlin functions accessible in CoroutinesUtils In order to allow using private classes like in Java for example. Closes gh-23840 --- .../springframework/core/CoroutinesUtils.java | 6 +++++- .../result/KotlinInvocableHandlerMethodTests.kt | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) 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-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 From 0ab054c7b943d65bb9034d1d7987f556e9d54d05 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 14 Feb 2022 10:55:07 +0100 Subject: [PATCH 070/998] Upgrade concourse-release-scripts in CI This commit also reverts the change of resource type for the publication of the CI image and fixes a bug in the CI image setup with available JDKs. --- ci/images/setup.sh | 4 ++-- ci/pipeline.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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/pipeline.yml b/ci/pipeline.yml index 03545186aea5..eb8de81ea1e1 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -87,7 +87,7 @@ resources: branch: ((branch)) paths: ["ci/images/*"] - name: ci-image - type: registry-image + type: docker-image icon: docker source: <<: *docker-resource-source From 88f73bffa0e98c1e14db065c86d17c5d6295cee0 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 14 Feb 2022 16:21:41 +0100 Subject: [PATCH 071/998] Make assertion message lazy in ServletRequestPathUtils Closes gh-27946 --- .../org/springframework/web/util/ServletRequestPathUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; } From f004bb1b64cd9f39d58adc181607a1d5f3034b29 Mon Sep 17 00:00:00 2001 From: Ivan Zbykovskyi <> Date: Thu, 3 Feb 2022 13:34:02 +0200 Subject: [PATCH 072/998] Add formatting for SockJS GoAway frame Prevents infinite loop for xhr-polling and xhr-streaming transports. See gh-28000 --- .../handler/AbstractHttpSendingTransportHandler.java | 4 +++- .../sockjs/transport/session/AbstractHttpSockJsSession.java | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) 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..73aac7449e4d 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 @@ -79,9 +79,11 @@ else if (sockJsSession.isClosed()) { if (logger.isDebugEnabled()) { logger.debug("Connection already closed (but not removed yet) for " + sockJsSession); } + SockJsFrameFormat frameFormat = this.getFrameFormat(request); SockJsFrame frame = SockJsFrame.closeFrameGoAway(); + String formattedFrame = frameFormat.format(frame); try { - response.getBody().write(frame.getContentBytes()); + response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET)); } catch (IOException ex) { throw new SockJsException("Failed to send " + frame, 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..ab90363c7f1c 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 @@ -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; From 11cb93823270e8c7003c6bf21909bef0601f062d Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 14 Feb 2022 16:34:40 +0000 Subject: [PATCH 073/998] Polishing contribution Closes gh-28000 --- .../AbstractHttpSendingTransportHandler.java | 32 +++++++++---------- .../session/AbstractHttpSockJsSession.java | 2 +- 2 files changed, 16 insertions(+), 18 deletions(-) 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 73aac7449e4d..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,15 +79,7 @@ else if (sockJsSession.isClosed()) { if (logger.isDebugEnabled()) { logger.debug("Connection already closed (but not removed yet) for " + sockJsSession); } - SockJsFrameFormat frameFormat = this.getFrameFormat(request); - SockJsFrame frame = SockJsFrame.closeFrameGoAway(); - String formattedFrame = frameFormat.format(frame); - try { - response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET)); - } - 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()) { @@ -99,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 ab90363c7f1c..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. From ec03e8830ed0edb3cffbda1395c491127c0353b6 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 14 Feb 2022 20:50:38 +0000 Subject: [PATCH 074/998] Remove path variables from pathWithinMapping Closes gh-27913 --- .../web/servlet/handler/AbstractUrlHandlerMapping.java | 8 ++++---- .../web/servlet/handler/SimpleUrlHandlerMappingTests.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) 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/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"); From 3ea540adbce6fd61ee745b7b7a103f3a16ef95ec Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 15 Feb 2022 13:37:08 +0100 Subject: [PATCH 075/998] Upgrade to Reactor 2020.0.16 Closes gh-28039 --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e126405cff45..7ef36356d006 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ configure(allprojects) { project -> imports { mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6" mavenBom "io.netty:netty-bom:4.1.73.Final" - mavenBom "io.projectreactor:reactor-bom:2020.0.16-SNAPSHOT" + 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" @@ -292,7 +292,6 @@ configure(allprojects) { project -> repositories { mavenCentral() maven { url "https://repo.spring.io/libs-spring-framework-build" } - maven { url "https://repo.spring.io/snapshot" } // reactor } } configurations.all { From 3188c0f7db54cffdb95e7127e2adde959dd078f9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 15 Feb 2022 13:47:03 +0100 Subject: [PATCH 076/998] Ensure fix for gh-28012 is actually tested In 3ec612aaf8, I accidentally removed tests that verified support for non-synthesizable merged annotations for recursive annotations in Kotlin. This commit reinstates those non-synthesizable tests while retaining the synthesizable tests. --- .../springframework/core/annotation/Filter.kt | 4 -- .../KotlinMergedAnnotationsTests.kt | 68 ++++++++++++++++++- .../springframework/core/annotation/Person.kt | 4 -- .../core/annotation/SynthesizableFilter.kt | 35 ++++++++++ .../core/annotation/SynthesizableFilters.kt | 29 ++++++++ .../core/annotation/SynthesizablePerson.kt | 35 ++++++++++ 6 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilter.kt create mode 100644 spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizableFilters.kt create mode 100644 spring-core/src/test/kotlin/org/springframework/core/annotation/SynthesizablePerson.kt 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 index 2755ba4d9b2f..9a05b5ba1adc 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/annotation/Filter.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/Filter.kt @@ -24,12 +24,8 @@ package org.springframework.core.annotation @Retention(AnnotationRetention.RUNTIME) public annotation class Filter( - @get:AliasFor("name") val value: String = "", - @get:AliasFor("value") - val name: String = "", - val and: Filters = Filters() ) 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 index dedd3ced4166..fa32cfe3650d 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/annotation/KotlinMergedAnnotationsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/KotlinMergedAnnotationsTests.kt @@ -41,6 +41,34 @@ class KotlinMergedAnnotationsTests { 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) @@ -72,6 +100,36 @@ class KotlinMergedAnnotationsTests { 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) @@ -94,12 +152,20 @@ class KotlinMergedAnnotationsTests { } - @Person("jane", friends = [Person("john"), Person("sally")]) + @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 index 28d67c4a2364..4ae45e5feb0c 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/annotation/Person.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/annotation/Person.kt @@ -24,10 +24,6 @@ package org.springframework.core.annotation @Retention(AnnotationRetention.RUNTIME) public annotation class Person( - @get:AliasFor("name") - val value: String = "", - - @get:AliasFor("value") 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 = [] + +) From 685a195ba17be18479ab682f808b14d8c586b3fa Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 15 Feb 2022 14:28:50 +0100 Subject: [PATCH 077/998] Deprecate SocketUtils 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 SocketUtils to find an available local port for a server, it is recommended that users 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, the user should query the server for the port it is currently using. SocketUtils is now deprecated in 5.3.16 and will be removed in 6.0. Closes gh-28052 --- .../access/MBeanClientInterceptorTests.java | 6 +- .../RemoteMBeanClientInterceptorTests.java | 7 +-- .../ConnectorServerFactoryBeanTests.java | 6 +- ...MBeanServerConnectionFactoryBeanTests.java | 6 +- .../org/springframework/util/SocketUtils.java | 14 ++++- .../util/SocketUtilsTests.java | 63 ++++++++++--------- .../ReactorNettyTcpStompClientTests.java | 6 +- ...erRelayMessageHandlerIntegrationTests.java | 6 +- .../remoting/caucho/CauchoRemotingTests.java | 5 +- .../client/WebClientIntegrationTests.java | 6 +- 10 files changed, 69 insertions(+), 56 deletions(-) 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/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-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/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-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-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-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. From 7276752e7cbc87aead678ae84ec8a83e7603a141 Mon Sep 17 00:00:00 2001 From: vikey Date: Sat, 12 Feb 2022 18:51:24 +0800 Subject: [PATCH 078/998] Fix CronExpression issue with DST This commit fixes an issue with CronExpression fails to calculate next execution on the day of daylight saving time. Closes gh-28038 --- .../springframework/scheduling/support/CronField.java | 9 +++++++-- .../scheduling/support/CronExpressionTests.java | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) 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..e01e00b63229 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 @@ -260,7 +260,7 @@ public > T elapseUntil(T temporal, in * Roll forward the give temporal until it reaches the next higher * order field. Calling this method is equivalent to calling * {@link #elapseUntil(Temporal, int)} with goal set to the - * minimum value of this field's range. + * minimum value of this field's range, except for daylight saving. * @param temporal the temporal to roll forward * @param the type of temporal * @return the rolled forward temporal @@ -269,7 +269,12 @@ 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); + //adjust daylight saving + if (get(result) != range.getMinimum()) { + result = this.field.adjustInto(result,result.range(this.field).getMinimum()); + } + return result; } /** 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 5abce9e2de30..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 @@ -1336,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 From 5ab966fbdefc33840ff2ac4c12b26a6c306b59cf Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Wed, 16 Feb 2022 11:16:27 +0100 Subject: [PATCH 079/998] Polish contribution See gh-28038 --- .../scheduling/support/CronField.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 e01e00b63229..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 @@ -260,7 +260,7 @@ public > T elapseUntil(T temporal, in * Roll forward the give temporal until it reaches the next higher * order field. Calling this method is equivalent to calling * {@link #elapseUntil(Temporal, int)} with goal set to the - * minimum value of this field's range, except for daylight saving. + * minimum value of this field's range. * @param temporal the temporal to roll forward * @param the type of temporal * @return the rolled forward temporal @@ -269,10 +269,12 @@ public > T rollForward(T temporal) { int current = get(temporal); ValueRange range = temporal.range(this.field); long amount = range.getMaximum() - current + 1; - T result = this.field.getBaseUnit().addTo(temporal, amount); - //adjust daylight saving - if (get(result) != range.getMinimum()) { - result = this.field.adjustInto(result,result.range(this.field).getMinimum()); + 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; } From e3ceb9b23dacbed3db0811b7535cbe07ccda58cb Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 16 Feb 2022 12:01:14 +0100 Subject: [PATCH 080/998] Polish @Target declarations for stereotype annotations --- .../main/java/org/springframework/stereotype/Controller.java | 4 ++-- .../main/java/org/springframework/stereotype/Repository.java | 4 ++-- .../src/main/java/org/springframework/stereotype/Service.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) 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 From 3ac60147f3b9a37f136f25fcd60d145c49110251 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 16 Feb 2022 12:11:33 +0100 Subject: [PATCH 081/998] Improve documentation for uri(URI) method in WebTestClient Prior to this commit, it was not clear that a configured base URI would not be applied when invoking uri(URI). This commit adds a note to the Javadoc to clarify that behavior. Closes gh-28058 --- .../test/web/reactive/server/WebTestClient.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 481a8d999e88..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 @@ -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 */ From 36dc4e4c5f3827cf9f13a691c8007cc5f2a46f17 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 15 Feb 2022 15:06:43 -0800 Subject: [PATCH 082/998] Add mavenCentral() to pluginManagement repositories Update the pluginManagement repositories used by Grade to include `mavenCentral()` since `gradlePluginPortal()` has been suffering from timeouts recently. --- settings.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle b/settings.gradle index 6913d57d47dd..e1638d26c9ad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ pluginManagement { repositories { + mavenCentral() gradlePluginPortal() maven { url "https://repo.spring.io/release" } } From 1166577d2c7f1a2b2dba84ba3f1026f5b3ca9b2a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 16 Feb 2022 20:04:27 +0100 Subject: [PATCH 083/998] Upgrade to Netty 4.1.74, Jetty 9.4.45, Undertow 2.2.16, Hibernate Validator 6.2.2, Apache Johnzon 1.2.16, EclipseLink 2.7.10 --- build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 7ef36356d006..58352ec87409 100644 --- a/build.gradle +++ b/build.gradle @@ -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.73.Final" + 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" @@ -83,7 +83,7 @@ configure(allprojects) { project -> 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" } @@ -124,7 +124,7 @@ 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" @@ -139,7 +139,7 @@ configure(allprojects) { project -> 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" @@ -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" From f8a59c267f318e06522fdfa968c9b251cf1b07d9 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 16 Feb 2022 20:04:51 +0100 Subject: [PATCH 084/998] Polishing --- .../annotation/AbstractAspectJAdvisorFactoryTests.java | 10 +++++----- .../aop/aspectj/annotation/AspectMetadataTests.java | 7 +++---- 2 files changed, 8 insertions(+), 9 deletions(-) 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())"); } } From 4571626839a00c924d7e38b8d0b02cb2518345b5 Mon Sep 17 00:00:00 2001 From: Spring Builds Date: Thu, 17 Feb 2022 07:45:38 +0000 Subject: [PATCH 085/998] Next development version (v5.3.17-SNAPSHOT) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ce5199c44137..c51960841a5e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=5.3.16-SNAPSHOT +version=5.3.17-SNAPSHOT org.gradle.jvmargs=-Xmx1536M org.gradle.caching=true org.gradle.parallel=true From 2ffefbb21116dda66973b920218c79ffe13cbac8 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 17 Feb 2022 09:44:22 +0100 Subject: [PATCH 086/998] Downgrade to concourse-release-scripts 0.3.2 This commit reverts partially "0ab054c7b943d65bb9034d1d7987f556e9d54d05" as 0.3.3 is breaking promition. --- ci/images/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/images/setup.sh b/ci/images/setup.sh index 3e1e0bc05c2d..6c02f65ef665 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.3/concourse-release-scripts-0.3.3.jar +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 ########################################################### # JAVA From ff20a06876209e6dafd519e732801f292230f9d9 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 17 Feb 2022 11:51:14 +0100 Subject: [PATCH 087/998] Added .sdkmanrc file This commit adds a .sdkmanrc file, so that we can automatically switch to JDK 8 when building the 5.3. branch. --- .sdkmanrc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .sdkmanrc diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 000000000000..a59545673245 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,3 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=8.0.322-librca From 94af2ca06bf711de03286999b1c64a703d6de552 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 18 Feb 2022 15:31:59 +0100 Subject: [PATCH 088/998] Recover from error during SpEL MIXED mode compilation Prior to this commit, SpEL was able to recover from an error that occurred while running a CompiledExpression; however, SpEL was not able to recover from an error that occurred while compiling the expression (such as a java.lang.VerifyError). The latter can occur when multiple threads concurrently change types involved in the expression, such as the concrete type of a custom variable registered via EvaluationContext.setVariable(...), which can result in SpEL generating invalid bytecode. This commit addresses this issue by catching exceptions thrown while compiling an expression and updating the `failedAttempts` and `interpretedCount` counters accordingly. If an exception is caught while operating in SpelCompilerMode.IMMEDIATE mode, the exception will be propagated via a SpelEvaluationException with a new SpelMessage.EXCEPTION_COMPILING_EXPRESSION error category. Closes gh-28043 --- .../expression/spel/SpelMessage.java | 11 ++++-- .../spel/standard/SpelCompiler.java | 5 ++- .../spel/standard/SpelExpression.java | 36 ++++++++++++----- .../spel/standard/SpelCompilerTests.java | 39 +++++++++++++++++++ 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index 3a03cfd9a1a4..b8f5f92d0554 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.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. @@ -27,10 +27,11 @@ *

When a message is formatted, it will have this kind of form, capturing the prefix * and the error kind: * - *

EL1004E: Type cannot be found 'String'
+ *
EL1005E: Type cannot be found 'String'
* * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public enum SpelMessage { @@ -255,7 +256,11 @@ public enum SpelMessage { /** @since 4.3.17 */ FLAWED_PATTERN(Kind.ERROR, 1073, - "Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"); + "Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"), + + /** @since 5.3.16 */ + EXCEPTION_COMPILING_EXPRESSION(Kind.ERROR, 1074, + "An exception occurred while compiling an expression"); private final Kind kind; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java index f2a225952a0d..d94c18ea5c7f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.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. @@ -110,7 +110,8 @@ public CompiledExpression compile(SpelNodeImpl expression) { return ReflectionUtils.accessibleConstructor(clazz).newInstance(); } catch (Throwable ex) { - throw new IllegalStateException("Failed to instantiate CompiledExpression", ex); + throw new IllegalStateException("Failed to instantiate CompiledExpression for expression: " + + expression.toStringAST(), ex); } } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java index 86ed383c3892..660fb23ddc11 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.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. @@ -44,6 +44,7 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class SpelExpression implements Expression { @@ -522,17 +523,34 @@ public boolean compileExpression() { // Compiled by another thread before this thread got into the sync block return true; } - SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader()); - compiledAst = compiler.compile(this.ast); - if (compiledAst != null) { - // Successfully compiled - this.compiledAst = compiledAst; - return true; + try { + SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader()); + compiledAst = compiler.compile(this.ast); + if (compiledAst != null) { + // Successfully compiled + this.compiledAst = compiledAst; + return true; + } + else { + // Failed to compile + this.failedAttempts.incrementAndGet(); + return false; + } } - else { + catch (Exception ex) { // Failed to compile this.failedAttempts.incrementAndGet(); - return false; + + // If running in mixed mode, revert to interpreted + if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { + this.compiledAst = null; + this.interpretedCount.set(0); + return false; + } + else { + // Running in SpelCompilerMode.immediate mode - propagate exception to caller + throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_COMPILING_EXPRESSION); + } } } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java index 5046f58189aa..a3ee9cb08355 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java @@ -76,6 +76,21 @@ void defaultMethodInvocation() { assertThat(expression.getValue(context)).asInstanceOf(BOOLEAN).isTrue(); } + @Test // gh-28043 + void changingRegisteredVariableTypeDoesNotResultInFailureInMixedMode() { + SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.MIXED, null); + SpelExpressionParser parser = new SpelExpressionParser(config); + Expression sharedExpression = parser.parseExpression("#bean.value"); + StandardEvaluationContext context = new StandardEvaluationContext(); + + Object[] beans = new Object[] {new Bean1(), new Bean2(), new Bean3(), new Bean4()}; + + IntStream.rangeClosed(1, 1_000_000).parallel().forEach(count -> { + context.setVariable("bean", beans[count % 4]); + assertThat(sharedExpression.getValue(context)).asString().startsWith("1"); + }); + } + static class OrderedComponent implements Ordered { @@ -121,4 +136,28 @@ default boolean isEditable2() { boolean hasSomeProperty(); } + public static class Bean1 { + public String getValue() { + return "11"; + } + } + + public static class Bean2 { + public Integer getValue() { + return 111; + } + } + + public static class Bean3 { + public Float getValue() { + return 1.23f; + } + } + + public static class Bean4 { + public Character getValue() { + return '1'; + } + } + } From 071c2988d55b1ad823b79fd4be8908666fc466a8 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 18 Feb 2022 16:18:13 +0100 Subject: [PATCH 089/998] Suppress deprecation warnings in tests in build --- .../orm/jpa/support/OpenEntityManagerInViewTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java index 70360c26dea6..6d8c3157bf46 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.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. @@ -419,6 +419,7 @@ private static class TestTaskExecutor extends SimpleAsyncTaskExecutor { private final CountDownLatch latch = new CountDownLatch(1); @Override + @SuppressWarnings("deprecation") public void execute(Runnable task, long startTimeout) { Runnable decoratedTask = () -> { try { From 5689395678f57fe967a3b21ed7d9087cfec7b622 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 19 Feb 2022 16:51:00 +0100 Subject: [PATCH 090/998] Deprecate "enclosing classes" search strategy for MergedAnnotations The TYPE_HIERARCHY_AND_ENCLOSING_CLASSES search strategy for MergedAnnotations was originally introduced to support @Nested test classes in JUnit Jupiter (see #23378). However, while implementing #19930, we determined that the TYPE_HIERARCHY_AND_ENCLOSING_CLASSES search strategy unfortunately could not be used since it does not allow the user to control when to recurse up the enclosing class hierarchy. For example, this search strategy will automatically search on enclosing classes for static nested classes as well as for inner classes, when the user probably only wants one such category of "enclosing class" to be searched. Consequently, TestContextAnnotationUtils was introduced in the Spring TestContext Framework to address the shortcomings of the TYPE_HIERARCHY_AND_ENCLOSING_CLASSES search strategy. Since this search strategy is unlikely to be useful to general users, the team has decided to deprecate this search strategy in Spring Framework 5.3.x and remove it in 6.0. Closes gh-28079 --- .../springframework/core/annotation/AnnotationsScanner.java | 5 ++++- .../springframework/core/annotation/MergedAnnotations.java | 2 ++ .../core/annotation/AnnotationsScannerTests.java | 5 ++++- .../core/annotation/MergedAnnotationsTests.java | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java index 626413838c9b..70efaeea41c7 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.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. @@ -92,6 +92,7 @@ private static R process(C context, AnnotatedElement source, } @Nullable + @SuppressWarnings("deprecation") private static R processClass(C context, Class source, SearchStrategy searchStrategy, AnnotationsProcessor processor) { @@ -235,6 +236,7 @@ private static R processClassHierarchy(C context, int[] aggregateIndex, C } @Nullable + @SuppressWarnings("deprecation") private static R processMethod(C context, Method source, SearchStrategy searchStrategy, AnnotationsProcessor processor) { @@ -510,6 +512,7 @@ static boolean hasPlainJavaAnnotationsOnly(Class type) { return (type.getName().startsWith("java.") || type == Ordered.class); } + @SuppressWarnings("deprecation") private static boolean isWithoutHierarchy(AnnotatedElement source, SearchStrategy searchStrategy) { if (source == Object.class) { return true; diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java index 55dff9086f74..28f7cf009a99 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java @@ -482,7 +482,9 @@ enum SearchStrategy { * need to be meta-annotated with {@link Inherited @Inherited}. When * searching a {@link Method} source, this strategy is identical to * {@link #TYPE_HIERARCHY}. + * @deprecated as of Spring Framework 5.3.17; to be removed in Spring Framework 6.0 */ + @Deprecated TYPE_HIERARCHY_AND_ENCLOSING_CLASSES } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java index 83518d9b3846..e848090b1241 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.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. @@ -421,6 +421,7 @@ void typeHierarchyStrategyOnMethodWithGenericParameterNonOverrideScansAnnotation } @Test + @SuppressWarnings("deprecation") void typeHierarchyWithEnclosedStrategyOnEnclosedStaticClassScansAnnotations() { Class source = AnnotationEnclosingClassSample.EnclosedStatic.EnclosedStaticStatic.class; assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)) @@ -428,6 +429,7 @@ void typeHierarchyWithEnclosedStrategyOnEnclosedStaticClassScansAnnotations() { } @Test + @SuppressWarnings("deprecation") void typeHierarchyWithEnclosedStrategyOnEnclosedInnerClassScansAnnotations() { Class source = AnnotationEnclosingClassSample.EnclosedInner.EnclosedInnerInner.class; assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)) @@ -435,6 +437,7 @@ void typeHierarchyWithEnclosedStrategyOnEnclosedInnerClassScansAnnotations() { } @Test + @SuppressWarnings("deprecation") void typeHierarchyWithEnclosedStrategyOnMethodHierarchyUsesTypeHierarchyScan() { Method source = methodFrom(WithHierarchy.class); assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)).containsExactly( 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 02ec8f0980e9..e175e6e030da 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 @@ -712,6 +712,7 @@ void streamTypeHierarchyFromClassWithInterface() throws Exception { } @Test + @SuppressWarnings("deprecation") void streamTypeHierarchyAndEnclosingClassesFromNonAnnotatedInnerClassWithAnnotatedEnclosingClass() { Stream> classes = MergedAnnotations.from(AnnotatedClass.NonAnnotatedInnerClass.class, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES).stream().map(MergedAnnotation::getType); @@ -719,6 +720,7 @@ void streamTypeHierarchyAndEnclosingClassesFromNonAnnotatedInnerClassWithAnnotat } @Test + @SuppressWarnings("deprecation") void streamTypeHierarchyAndEnclosingClassesFromNonAnnotatedStaticNestedClassWithAnnotatedEnclosingClass() { Stream> classes = MergedAnnotations.from(AnnotatedClass.NonAnnotatedStaticNestedClass.class, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES).stream().map(MergedAnnotation::getType); From 84b4cebb3913ddd4a803939fdc8dde1b0401ff35 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 19 Feb 2022 16:54:16 +0100 Subject: [PATCH 091/998] Fix (@)since tag in SpelMessage See gh-28043 --- .../java/org/springframework/expression/spel/SpelMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index b8f5f92d0554..9c08841158ed 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -258,7 +258,7 @@ public enum SpelMessage { FLAWED_PATTERN(Kind.ERROR, 1073, "Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"), - /** @since 5.3.16 */ + /** @since 5.3.17 */ EXCEPTION_COMPILING_EXPRESSION(Kind.ERROR, 1074, "An exception occurred while compiling an expression"); From 453c6d41f71acc54bb3928f9c7b9787d29e84a43 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 24 Feb 2022 10:54:52 +0100 Subject: [PATCH 092/998] Fix Objenesis version See gh-28100 --- src/docs/dist/license.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docs/dist/license.txt b/src/docs/dist/license.txt index c68e1154b170..0eb8edb06324 100644 --- a/src/docs/dist/license.txt +++ b/src/docs/dist/license.txt @@ -255,10 +255,10 @@ CGLIB 3.3 is licensed under the Apache License, version 2.0, the text of which is included above. ->>> Objenesis 3.1 (org.objenesis:objenesis:3.1): +>>> Objenesis 3.2 (org.objenesis:objenesis:3.2): Per the LICENSE file in the Objenesis ZIP distribution downloaded from -http://objenesis.org/download.html, Objenesis 3.1 is licensed under the +http://objenesis.org/download.html, Objenesis 3.2 is licensed under the Apache License, version 2.0, the text of which is included above. Per the NOTICE file in the Objenesis ZIP distribution downloaded from From 7e2106b850ea65866dc97d24d108c7fe1ea64c8c Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 24 Feb 2022 14:46:26 +0100 Subject: [PATCH 093/998] Refactor roll forward in CronField Before this commit, CronField.Type::rollForward added temporal units to reach the higher order field. This caused issues with DST, where the added amount of hours was either too small or too large. This commit refactors the implementation so that it now adds one to the higher order field, and reset the current field to the minimum value. Closes gh-28095 --- .../scheduling/support/CronField.java | 34 ++++++++----------- .../support/CronExpressionTests.java | 8 +++++ 2 files changed, 23 insertions(+), 19 deletions(-) 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 adc2c2ffe5d6..f794645d654f 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 @@ -18,6 +18,7 @@ import java.time.DateTimeException; import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; import java.time.temporal.ValueRange; import java.util.function.BiFunction; @@ -168,22 +169,25 @@ protected static > T cast(Temporal te * day-of-month, month, day-of-week. */ protected enum Type { - NANO(ChronoField.NANO_OF_SECOND), - SECOND(ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), - MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), - HOUR(ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), - DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), - MONTH(ChronoField.MONTH_OF_YEAR, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), - DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND); + NANO(ChronoField.NANO_OF_SECOND, ChronoUnit.SECONDS), + SECOND(ChronoField.SECOND_OF_MINUTE, ChronoUnit.MINUTES, ChronoField.NANO_OF_SECOND), + MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoUnit.HOURS, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + HOUR(ChronoField.HOUR_OF_DAY, ChronoUnit.DAYS, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoUnit.MONTHS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + MONTH(ChronoField.MONTH_OF_YEAR, ChronoUnit.YEARS, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoUnit.WEEKS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND); private final ChronoField field; + private final ChronoUnit higherOrder; + private final ChronoField[] lowerOrders; - Type(ChronoField field, ChronoField... lowerOrders) { + Type(ChronoField field, ChronoUnit higherOrder, ChronoField... lowerOrders) { this.field = field; + this.higherOrder = higherOrder; this.lowerOrders = lowerOrders; } @@ -266,17 +270,9 @@ public > T elapseUntil(T temporal, in * @return the rolled forward temporal */ public > T rollForward(T temporal) { - int current = get(temporal); - ValueRange range = temporal.range(this.field); - long amount = range.getMaximum() - current + 1; - 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; + T result = this.higherOrder.addTo(temporal, 1); + ValueRange range = result.range(this.field); + return this.field.adjustInto(result, range.getMinimum()); } /** 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 5de5362fc1dd..5dd837e0379f 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 @@ -1344,6 +1344,14 @@ public void daylightSaving() { actual = cronExpression.next(last); assertThat(actual).isNotNull(); assertThat(actual).isEqualTo(expected); + + cronExpression = CronExpression.parse("0 5 0 * * *"); + + last = ZonedDateTime.parse("2019-10-27T01:05+02:00[Europe/Amsterdam]"); + expected = ZonedDateTime.parse("2019-10-28T00:05+01:00[Europe/Amsterdam]"); + actual = cronExpression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); } @Test From 6f41180cc5e2f96b11950d483a41454d384defc9 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 28 Feb 2022 16:37:07 +0100 Subject: [PATCH 094/998] Align AsyncRestTemplate error logging with RestTemplate Prior to this commit, `AsyncRestTemplate` would log errors (including simple 404s) with WARN level. Such errors are quite common and should not clutter logs. This commit aligns the logging strategy with RestTemplate, using the DEBUG level for such cases. Fixes gh-28049 --- .../org/springframework/web/client/AsyncRestTemplate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java index f94359de4aeb..fcdc0e483232 100644 --- a/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java @@ -559,9 +559,9 @@ private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse re } private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException { - if (logger.isWarnEnabled()) { + if (logger.isDebugEnabled()) { try { - logger.warn("Async " + method.name() + " request for \"" + url + "\" resulted in " + + logger.debug("Async " + method.name() + " request for \"" + url + "\" resulted in " + response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler"); } catch (IOException ex) { From beab8ab4e752fa7da27a1df3904fe4f628236e06 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 28 Feb 2022 14:19:29 +0100 Subject: [PATCH 095/998] Test claims regarding SpEL support for T(Character) See gh-28112 --- .../springframework/expression/spel/EvaluationTests.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 98b9704ea336..a1c1a0eedaee 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -420,7 +420,11 @@ public void testIndexerError() { @Test public void testStaticRef02() { - evaluate("T(java.awt.Color).green.getRGB()!=0", "true", Boolean.class); + evaluate("T(java.awt.Color).green.getRGB() != 0", true, Boolean.class); + evaluate("(T(java.lang.Math).random() * 100.0 ) > 0", true, Boolean.class); + evaluate("(T(Math).random() * 100.0) > 0", true, Boolean.class); + evaluate("T(Character).isUpperCase('Test'.charAt(0)) ? 'uppercase' : 'lowercase'", "uppercase", String.class); + evaluate("T(Character).isUpperCase('Test'.charAt(1)) ? 'uppercase' : 'lowercase'", "lowercase", String.class); } // variables and functions From 84de100fc64cbb96719d7ac18e977e7aa668074a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 28 Feb 2022 17:15:35 +0100 Subject: [PATCH 096/998] Polishing --- .../expression/MethodResolver.java | 6 +- .../expression/spel/ast/MethodReference.java | 3 +- .../expression/spel/EvaluationTests.java | 2390 +++++++++-------- .../spel/SpelDocumentationTests.java | 130 +- .../expression/spel/TestScenarioCreator.java | 2 +- 5 files changed, 1270 insertions(+), 1261 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java index db555ce7b238..d4216d2a55c3 100644 --- a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ import org.springframework.lang.Nullable; /** - * A method resolver attempts locate a method and returns a command executor that can be - * used to invoke that method. The command executor will be cached but if it 'goes stale' + * A method resolver attempts to locate a method and returns a command executor that can be + * used to invoke that method. The command executor will be cached, but if it 'goes stale' * the resolvers will be called again. * * @author Andy Clement diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index cbaf1c8d1add..1ecb6187001a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -200,8 +200,7 @@ private MethodExecutor findAccessorForMethod(List argumentTypes, EvaluationContext evaluationContext) throws SpelEvaluationException { AccessException accessException = null; - List methodResolvers = evaluationContext.getMethodResolvers(); - for (MethodResolver methodResolver : methodResolvers) { + for (MethodResolver methodResolver : evaluationContext.getMethodResolvers()) { try { MethodExecutor methodExecutor = methodResolver.resolve( evaluationContext, targetObject, this.name, argumentTypes); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index a1c1a0eedaee..097daf526da6 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -16,25 +16,23 @@ package org.springframework.expression.spel; -import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.BeanResolver; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; -import org.springframework.expression.MethodExecutor; import org.springframework.expression.MethodFilter; -import org.springframework.expression.MethodResolver; import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -57,1305 +55,1333 @@ * @author Giovanni Dall'Oglio Risso * @since 3.0 */ -public class EvaluationTests extends AbstractExpressionTests { - - @Test - public void testCreateListsOnAttemptToIndexNull01() throws EvaluationException, ParseException { - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e = parser.parseExpression("list[0]"); - TestClass testClass = new TestClass(); - - Object o = e.getValue(new StandardEvaluationContext(testClass)); - assertThat(o).isEqualTo(""); - o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass)); - assertThat(o).isEqualTo(""); - assertThat(testClass.list.size()).isEqualTo(4); - - assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> - parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass))); - - o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass)); - assertThat(o).isEqualTo(""); - assertThat(testClass.getFoo().size()).isEqualTo(4); - } +class EvaluationTests extends AbstractExpressionTests { - @Test - public void testCreateMapsOnAttemptToIndexNull01() { - TestClass testClass = new TestClass(); - StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + @Nested + class MiscellaneousTests { - Object o = parser.parseExpression("map['a']").getValue(ctx); - assertThat(o).isNull(); - o = parser.parseExpression("map").getValue(ctx); - assertThat(o).isNotNull(); + @Test + void createListsOnAttemptToIndexNull01() throws EvaluationException, ParseException { + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e = parser.parseExpression("list[0]"); + TestClass testClass = new TestClass(); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - parser.parseExpression("map2['a']").getValue(ctx)); - // map2 should be null, there is no setter - } + Object o = e.getValue(new StandardEvaluationContext(testClass)); + assertThat(o).isEqualTo(""); + o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass)); + assertThat(o).isEqualTo(""); + assertThat(testClass.list.size()).isEqualTo(4); - // wibble2 should be null (cannot be initialized dynamically), there is no setter - @Test - public void testCreateObjectsOnAttemptToReferenceNull() { - TestClass testClass = new TestClass(); - StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> + parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass))); - Object o = parser.parseExpression("wibble.bar").getValue(ctx); - assertThat(o).isEqualTo("hello"); - o = parser.parseExpression("wibble").getValue(ctx); - assertThat(o).isNotNull(); + o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass)); + assertThat(o).isEqualTo(""); + assertThat(testClass.getFoo().size()).isEqualTo(4); + } - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - parser.parseExpression("wibble2.bar").getValue(ctx)); - } + @Test + void createMapsOnAttemptToIndexNull() { + TestClass testClass = new TestClass(); + StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - @Test - public void testElvis01() { - evaluate("'Andy'?:'Dave'", "Andy", String.class); - evaluate("null?:'Dave'", "Dave", String.class); - } + Object o = parser.parseExpression("map['a']").getValue(ctx); + assertThat(o).isNull(); + o = parser.parseExpression("map").getValue(ctx); + assertThat(o).isNotNull(); - @Test - public void testSafeNavigation() { - evaluate("null?.null?.null", null, null); - } + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + parser.parseExpression("map2['a']").getValue(ctx)); + // map2 should be null, there is no setter + } - @Test - public void testRelOperatorGT01() { - evaluate("3 > 6", "false", Boolean.class); - } + // wibble2 should be null (cannot be initialized dynamically), there is no setter + @Test + void createObjectsOnAttemptToReferenceNull() { + TestClass testClass = new TestClass(); + StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - @Test - public void testRelOperatorLT01() { - evaluate("3 < 6", "true", Boolean.class); - } + Object o = parser.parseExpression("wibble.bar").getValue(ctx); + assertThat(o).isEqualTo("hello"); + o = parser.parseExpression("wibble").getValue(ctx); + assertThat(o).isNotNull(); - @Test - public void testRelOperatorLE01() { - evaluate("3 <= 6", "true", Boolean.class); - } + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + parser.parseExpression("wibble2.bar").getValue(ctx)); + } - @Test - public void testRelOperatorGE01() { - evaluate("3 >= 6", "false", Boolean.class); - } + @Test + void elvisOperator() { + evaluate("'Andy'?:'Dave'", "Andy", String.class); + evaluate("null?:'Dave'", "Dave", String.class); + } - @Test - public void testRelOperatorGE02() { - evaluate("3 >= 3", "true", Boolean.class); - } + @Test + void safeNavigation() { + evaluate("null?.null?.null", null, null); + } - @Test - public void testRelOperatorsInstanceof01() { - evaluate("'xyz' instanceof T(int)", "false", Boolean.class); - } + @Test // SPR-16731 + void matchesWithPatternAccessThreshold() { + String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$"; + String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches \'" + pattern + "\'"; + Expression expr = parser.parseExpression(expression); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(expr::getValue) + .withCauseInstanceOf(IllegalStateException.class) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.FLAWED_PATTERN)); + } - @Test - public void testRelOperatorsInstanceof04() { - evaluate("null instanceof T(String)", "false", Boolean.class); - } + // mixing operators + @Test + void mixingOperators() { + evaluate("true and 5>3", "true", Boolean.class); + } - @Test - public void testRelOperatorsInstanceof05() { - evaluate("null instanceof T(Integer)", "false", Boolean.class); - } + // assignment + @Test + void assignmentToVariables() { + evaluate("#var1='value1'", "value1", String.class); + } - @Test - public void testRelOperatorsInstanceof06() { - evaluateAndCheckError("'A' instanceof null", SpelMessage.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND, 15, "null"); - } + @Test + void operatorVariants() { + SpelExpression e = (SpelExpression) parser.parseExpression("#a < #b"); + EvaluationContext ctx = new StandardEvaluationContext(); + ctx.setVariable("a", (short) 3); + ctx.setVariable("b", (short) 6); + assertThat(e.getValue(ctx, Boolean.class)).isTrue(); + ctx.setVariable("b", (byte) 6); + assertThat(e.getValue(ctx, Boolean.class)).isTrue(); + ctx.setVariable("a", (byte) 9); + ctx.setVariable("b", (byte) 6); + assertThat(e.getValue(ctx, Boolean.class)).isFalse(); + ctx.setVariable("a", 10L); + ctx.setVariable("b", (short) 30); + assertThat(e.getValue(ctx, Boolean.class)).isTrue(); + ctx.setVariable("a", (byte) 3); + ctx.setVariable("b", (short) 30); + assertThat(e.getValue(ctx, Boolean.class)).isTrue(); + ctx.setVariable("a", (byte) 3); + ctx.setVariable("b", 30L); + assertThat(e.getValue(ctx, Boolean.class)).isTrue(); + ctx.setVariable("a", (byte) 3); + ctx.setVariable("b", 30f); + assertThat(e.getValue(ctx, Boolean.class)).isTrue(); + ctx.setVariable("a", new BigInteger("10")); + ctx.setVariable("b", new BigInteger("20")); + assertThat(e.getValue(ctx, Boolean.class)).isTrue(); + } - @Test - public void testRelOperatorsMatches01() { - evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class); - } + @Test + void indexer03() { + evaluate("'christian'[8]", "n", String.class); + } - @Test - public void testRelOperatorsMatches02() { - evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class); - } + @Test + void indexerError() { + evaluateAndCheckError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]", + SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); + } - @Test - public void testRelOperatorsMatches03() { - evaluateAndCheckError("null matches '^.*$'", SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, 0, null); - } + @Test + void stringType() { + evaluateAndAskForReturnType("getPlaceOfBirth().getCity()", "SmilJan", String.class); + } - @Test - public void testRelOperatorsMatches04() { - evaluateAndCheckError("'abc' matches null", SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, 14, null); - } + @Test + void numbers() { + evaluateAndAskForReturnType("3*4+5", 17, Integer.class); + evaluateAndAskForReturnType("3*4+5", 17L, Long.class); + evaluateAndAskForReturnType("65", 'A', Character.class); + evaluateAndAskForReturnType("3*4+5", (short) 17, Short.class); + evaluateAndAskForReturnType("3*4+5", "17", String.class); + } - @Test - public void testRelOperatorsMatches05() { - evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string - } + @Test + void advancedNumerics() { + int twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Integer.class); + assertThat(twentyFour).isEqualTo(24); + double one = parser.parseExpression("8.0 / 5e0 % 2").getValue(Double.class); + assertThat((float) one).isCloseTo((float) 1.6d, within((float) 0d)); + int o = parser.parseExpression("8.0 / 5e0 % 2").getValue(Integer.class); + assertThat(o).isEqualTo(1); + int sixteen = parser.parseExpression("-2 ^ 4").getValue(Integer.class); + assertThat(sixteen).isEqualTo(16); + int minusFortyFive = parser.parseExpression("1+2-3*8^2/2/2").getValue(Integer.class); + assertThat(minusFortyFive).isEqualTo(-45); + } - @Test // SPR-16731 - public void testMatchesWithPatternAccessThreshold() { - String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$"; - String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches \'" + pattern + "\'"; - Expression expr = parser.parseExpression(expression); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy( - expr::getValue) - .withCauseInstanceOf(IllegalStateException.class) - .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.FLAWED_PATTERN)); - } + @Test + void comparison() { + EvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); + boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue( + context, Boolean.class); + assertThat(trueValue).isTrue(); + } - // mixing operators - @Test - public void testMixingOperators01() { - evaluate("true and 5>3", "true", Boolean.class); - } + @Test + void resolvingList() { + StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); + assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> + parser.parseExpression("T(List)!=null").getValue(context, Boolean.class)); + ((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util"); + assertThat(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class)).isTrue(); + } + + @Test + void resolvingString() { + Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); + assertThat(stringClass).isEqualTo(String.class); + } + + /** + * SPR-6984: attempting to index a collection on write using an index that + * doesn't currently exist in the collection (address.crossStreets[0] below) + */ + @Test + void initializingCollectionElementsOnWrite() { + TestPerson person = new TestPerson(); + EvaluationContext context = new StandardEvaluationContext(person); + SpelParserConfiguration config = new SpelParserConfiguration(true, true); + ExpressionParser parser = new SpelExpressionParser(config); + Expression e = parser.parseExpression("name"); + e.setValue(context, "Oleg"); + assertThat(person.getName()).isEqualTo("Oleg"); + + e = parser.parseExpression("address.street"); + e.setValue(context, "123 High St"); + assertThat(person.getAddress().getStreet()).isEqualTo("123 High St"); + + e = parser.parseExpression("address.crossStreets[0]"); + e.setValue(context, "Blah"); + assertThat(person.getAddress().getCrossStreets().get(0)).isEqualTo("Blah"); + + e = parser.parseExpression("address.crossStreets[3]"); + e.setValue(context, "Wibble"); + assertThat(person.getAddress().getCrossStreets().get(0)).isEqualTo("Blah"); + assertThat(person.getAddress().getCrossStreets().get(3)).isEqualTo("Wibble"); + } + + /** + * Verifies behavior requested in SPR-9613. + */ + @Test + void caseInsensitiveNullLiterals() { + ExpressionParser parser = new SpelExpressionParser(); + + Expression e = parser.parseExpression("null"); + assertThat(e.getValue()).isNull(); + + e = parser.parseExpression("NULL"); + assertThat(e.getValue()).isNull(); + + e = parser.parseExpression("NuLl"); + assertThat(e.getValue()).isNull(); + } + + /** + * Verifies behavior requested in SPR-9621. + */ + @Test + void customMethodFilter() { + StandardEvaluationContext context = new StandardEvaluationContext(); + + // Register a custom MethodResolver... + context.setMethodResolvers(Arrays.asList((evaluationContext, targetObject, name, argumentTypes) -> null)); + + // or simply... + // context.setMethodResolvers(new ArrayList()); + + // Register a custom MethodFilter... + MethodFilter methodFilter = methods -> null; + assertThatIllegalStateException() + .isThrownBy(() -> context.registerMethodFilter(String.class, methodFilter)) + .withMessage("Method filter cannot be set as the reflective method resolver is not in use"); + } + + /** + * This test is checking that with the changes for 9751 that the refactoring in Indexer is + * coping correctly for references beyond collection boundaries. + */ + @Test + void collectionGrowingViaIndexer() { + Spr9751 instance = new Spr9751(); + + // Add a new element to the list + StandardEvaluationContext ctx = new StandardEvaluationContext(instance); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e = parser.parseExpression("listOfStrings[++index3]='def'"); + e.getValue(ctx); + assertThat(instance.listOfStrings.size()).isEqualTo(2); + assertThat(instance.listOfStrings.get(1)).isEqualTo("def"); + + // Check reference beyond end of collection + ctx = new StandardEvaluationContext(instance); + parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + e = parser.parseExpression("listOfStrings[0]"); + String value = e.getValue(ctx, String.class); + assertThat(value).isEqualTo("abc"); + e = parser.parseExpression("listOfStrings[1]"); + value = e.getValue(ctx, String.class); + assertThat(value).isEqualTo("def"); + e = parser.parseExpression("listOfStrings[2]"); + value = e.getValue(ctx, String.class); + assertThat(value).isEqualTo(""); + + // Now turn off growing and reference off the end + StandardEvaluationContext failCtx = new StandardEvaluationContext(instance); + parser = new SpelExpressionParser(new SpelParserConfiguration(false, false)); + Expression failExp = parser.parseExpression("listOfStrings[3]"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + failExp.getValue(failCtx, String.class)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS)); + } + + @Test + void limitCollectionGrowing() { + TestClass instance = new TestClass(); + StandardEvaluationContext ctx = new StandardEvaluationContext(instance); + SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(true, true, 3)); + Expression e = parser.parseExpression("foo[2]"); + e.setValue(ctx, "2"); + assertThat(instance.getFoo().size()).isEqualTo(3); + e = parser.parseExpression("foo[3]"); + try { + e.setValue(ctx, "3"); + } + catch (SpelEvaluationException see) { + assertThat(see.getMessageCode()).isEqualTo(SpelMessage.UNABLE_TO_GROW_COLLECTION); + assertThat(instance.getFoo().size()).isEqualTo(3); + } + } - // property access - @Test - public void testPropertyField01() { - evaluate("name", "Nikola Tesla", String.class, false); - // not writable because (1) name is private (2) there is no setter, only a getter - evaluateAndCheckError("madeup", SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, 0, "madeup", - "org.springframework.expression.spel.testresources.Inventor"); } - @Test - public void testPropertyField02_SPR7100() { - evaluate("_name", "Nikola Tesla", String.class); - evaluate("_name_", "Nikola Tesla", String.class); + @Nested + class RelationalOperatorTests { + + @Test + void relOperatorGT() { + evaluate("3 > 6", "false", Boolean.class); + } + + @Test + void relOperatorLT() { + evaluate("3 < 6", "true", Boolean.class); + } + + @Test + void relOperatorLE() { + evaluate("3 <= 6", "true", Boolean.class); + } + + @Test + void relOperatorGE01() { + evaluate("3 >= 6", "false", Boolean.class); + } + + @Test + void relOperatorGE02() { + evaluate("3 >= 3", "true", Boolean.class); + } + + @Test + void relOperatorsInstanceof01() { + evaluate("'xyz' instanceof T(int)", "false", Boolean.class); + } + + @Test + void relOperatorsInstanceof04() { + evaluate("null instanceof T(String)", "false", Boolean.class); + } + + @Test + void relOperatorsInstanceof05() { + evaluate("null instanceof T(Integer)", "false", Boolean.class); + } + + @Test + void relOperatorsInstanceof06() { + evaluateAndCheckError("'A' instanceof null", SpelMessage.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND, 15, "null"); + } + + @Test + void relOperatorsMatches01() { + evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class); + } + + @Test + void relOperatorsMatches02() { + evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class); + } + + @Test + void relOperatorsMatches03() { + evaluateAndCheckError("null matches '^.*$'", SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, 0, null); + } + + @Test + void relOperatorsMatches04() { + evaluateAndCheckError("'abc' matches null", SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, 14, null); + } + + @Test + void relOperatorsMatches05() { + evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string + } + } - @Test - public void testRogueTrailingDotCausesNPE_SPR6866() { - assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> - new SpelExpressionParser().parseExpression("placeOfBirth.foo.")) + @Nested + class PropertyAccessTests { + + @Test + void propertyField() { + evaluate("name", "Nikola Tesla", String.class, false); + // not writable because (1) name is private (2) there is no setter, only a getter + evaluateAndCheckError("madeup", SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, 0, "madeup", + "org.springframework.expression.spel.testresources.Inventor"); + } + + @Test + void propertyField_SPR7100() { + evaluate("_name", "Nikola Tesla", String.class); + evaluate("_name_", "Nikola Tesla", String.class); + } + + @Test + void rogueTrailingDotCausesNPE_SPR6866() { + assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> + new SpelExpressionParser().parseExpression("placeOfBirth.foo.")) .satisfies(ex -> { assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.OOD); assertThat(ex.getPosition()).isEqualTo(16); }); - } + } - // nested properties - @Test - public void testPropertiesNested01() { - evaluate("placeOfBirth.city", "SmilJan", String.class, true); - } + @Nested + class NestedPropertiesTests { - @Test - public void testPropertiesNested02() { - evaluate("placeOfBirth.doubleIt(12)", "24", Integer.class); - } - - @Test - public void testPropertiesNested03() throws ParseException { - assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> - new SpelExpressionParser().parseRaw("placeOfBirth.23")) - .satisfies(ex -> { - assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.UNEXPECTED_DATA_AFTER_DOT); - assertThat(ex.getInserts()[0]).isEqualTo("23"); - }); - } + // nested properties + @Test + void propertiesNested01() { + evaluate("placeOfBirth.city", "SmilJan", String.class, true); + } - // methods - @Test - public void testMethods01() { - evaluate("echo(12)", "12", String.class); - } + @Test + void propertiesNested02() { + evaluate("placeOfBirth.doubleIt(12)", "24", Integer.class); + } - @Test - public void testMethods02() { - evaluate("echo(name)", "Nikola Tesla", String.class); - } + @Test + void propertiesNested03() throws ParseException { + assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> + new SpelExpressionParser().parseRaw("placeOfBirth.23")) + .satisfies(ex -> { + assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.UNEXPECTED_DATA_AFTER_DOT); + assertThat(ex.getInserts()[0]).isEqualTo("23"); + }); + } - // constructors - @Test - public void testConstructorInvocation01() { - evaluate("new String('hello')", "hello", String.class); - } + } - @Test - public void testConstructorInvocation05() { - evaluate("new java.lang.String('foobar')", "foobar", String.class); } - @Test - public void testConstructorInvocation06() { - // repeated evaluation to drive use of cached executor - SpelExpression e = (SpelExpression) parser.parseExpression("new String('wibble')"); - String newString = e.getValue(String.class); - assertThat(newString).isEqualTo("wibble"); - newString = e.getValue(String.class); - assertThat(newString).isEqualTo("wibble"); - - // not writable - assertThat(e.isWritable(new StandardEvaluationContext())).isFalse(); + @Nested + class MethodAndConstructorTests { - // ast - assertThat(e.toStringAST()).isEqualTo("new String('wibble')"); - } + @Test + void methods01() { + evaluate("echo(12)", "12", String.class); + } - // unary expressions - @Test - public void testUnaryMinus01() { - evaluate("-5", "-5", Integer.class); - } + @Test + void methods02() { + evaluate("echo(name)", "Nikola Tesla", String.class); + } - @Test - public void testUnaryPlus01() { - evaluate("+5", "5", Integer.class); - } + @Test + void constructorInvocation01() { + evaluate("new String('hello')", "hello", String.class); + } - @Test - public void testUnaryNot01() { - evaluate("!true", "false", Boolean.class); - } + @Test + void constructorInvocation05() { + evaluate("new java.lang.String('foobar')", "foobar", String.class); + } - @Test - public void testUnaryNot02() { - evaluate("!false", "true", Boolean.class); - } + @Test + void constructorInvocation06() { + // repeated evaluation to drive use of cached executor + SpelExpression e = (SpelExpression) parser.parseExpression("new String('wibble')"); + String newString = e.getValue(String.class); + assertThat(newString).isEqualTo("wibble"); + newString = e.getValue(String.class); + assertThat(newString).isEqualTo("wibble"); - @Test - public void testUnaryNotWithNullValue() { - assertThatExceptionOfType(EvaluationException.class).isThrownBy( - parser.parseExpression("!null")::getValue); - } + // not writable + assertThat(e.isWritable(new StandardEvaluationContext())).isFalse(); - @Test - public void testAndWithNullValueOnLeft() { - assertThatExceptionOfType(EvaluationException.class).isThrownBy( - parser.parseExpression("null and true")::getValue); - } + // ast + assertThat(e.toStringAST()).isEqualTo("new String('wibble')"); + } - @Test - public void testAndWithNullValueOnRight() { - assertThatExceptionOfType(EvaluationException.class).isThrownBy( - parser.parseExpression("true and null")::getValue); } - @Test - public void testOrWithNullValueOnLeft() { - assertThatExceptionOfType(EvaluationException.class).isThrownBy( - parser.parseExpression("null or false")::getValue); - } + @Nested + class UnaryOperatorTests { - @Test - public void testOrWithNullValueOnRight() { - assertThatExceptionOfType(EvaluationException.class).isThrownBy( - parser.parseExpression("false or null")::getValue); - } + @Test + void unaryMinus() { + evaluate("-5", "-5", Integer.class); + } - // assignment - @Test - public void testAssignmentToVariables01() { - evaluate("#var1='value1'", "value1", String.class); - } + @Test + void unaryPlus() { + evaluate("+5", "5", Integer.class); + } - @Test - public void testTernaryOperator01() { - evaluate("2>4?1:2", 2, Integer.class); - } + @Test + void unaryNot01() { + evaluate("!true", "false", Boolean.class); + } - @Test - public void testTernaryOperator02() { - evaluate("'abc'=='abc'?1:2", 1, Integer.class); - } + @Test + void unaryNot02() { + evaluate("!false", "true", Boolean.class); + } - @Test - public void testTernaryOperator03() { - // cannot convert String to boolean - evaluateAndCheckError("'hello'?1:2", SpelMessage.TYPE_CONVERSION_ERROR); - } + @Test + void unaryNotWithNullValue() { + assertThatExceptionOfType(EvaluationException.class) + .isThrownBy(parser.parseExpression("!null")::getValue); + } - @Test - public void testTernaryOperator04() { - Expression e = parser.parseExpression("1>2?3:4"); - assertThat(e.isWritable(context)).isFalse(); } - @Test - public void testTernaryOperator05() { - evaluate("1>2?#var=4:#var=5", 5, Integer.class); - evaluate("3?:#var=5", 3, Integer.class); - evaluate("null?:#var=5", 5, Integer.class); - evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class); - } + @Nested + class BinaryOperatorTests { - @Test - public void testTernaryOperatorWithNullValue() { - assertThatExceptionOfType(EvaluationException.class).isThrownBy( - parser.parseExpression("null ? 0 : 1")::getValue); - } + @Test + void andWithNullValueOnLeft() { + assertThatExceptionOfType(EvaluationException.class).isThrownBy( + parser.parseExpression("null and true")::getValue); + } - @Test - public void methodCallWithRootReferenceThroughParameter() { - evaluate("placeOfBirth.doubleIt(inventions.length)", 18, Integer.class); - } + @Test + void andWithNullValueOnRight() { + assertThatExceptionOfType(EvaluationException.class).isThrownBy( + parser.parseExpression("true and null")::getValue); + } - @Test - public void ctorCallWithRootReferenceThroughParameter() { - evaluate("new org.springframework.expression.spel.testresources.PlaceOfBirth(inventions[0].toString()).city", - "Telephone repeater", String.class); - } + @Test + void orWithNullValueOnLeft() { + assertThatExceptionOfType(EvaluationException.class).isThrownBy( + parser.parseExpression("null or false")::getValue); + } - @Test - public void fnCallWithRootReferenceThroughParameter() { - evaluate("#reverseInt(inventions.length, inventions.length, inventions.length)", "int[3]{9,9,9}", int[].class); - } + @Test + void orWithNullValueOnRight() { + assertThatExceptionOfType(EvaluationException.class).isThrownBy( + parser.parseExpression("false or null")::getValue); + } - @Test - public void methodCallWithRootReferenceThroughParameterThatIsAFunctionCall() { - evaluate("placeOfBirth.doubleIt(#reverseInt(inventions.length,2,3)[2])", 18, Integer.class); } - @Test - public void testIndexer03() { - evaluate("'christian'[8]", "n", String.class); - } + @Nested + class TernaryOperatorTests { - @Test - public void testIndexerError() { - evaluateAndCheckError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]", - SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); - } + @Test + void ternaryOperator01() { + evaluate("2>4?1:2", 2, Integer.class); + } - @Test - public void testStaticRef02() { - evaluate("T(java.awt.Color).green.getRGB() != 0", true, Boolean.class); - evaluate("(T(java.lang.Math).random() * 100.0 ) > 0", true, Boolean.class); - evaluate("(T(Math).random() * 100.0) > 0", true, Boolean.class); - evaluate("T(Character).isUpperCase('Test'.charAt(0)) ? 'uppercase' : 'lowercase'", "uppercase", String.class); - evaluate("T(Character).isUpperCase('Test'.charAt(1)) ? 'uppercase' : 'lowercase'", "lowercase", String.class); - } + @Test + void ternaryOperator02() { + evaluate("'abc'=='abc'?1:2", 1, Integer.class); + } - // variables and functions - @Test - public void testVariableAccess01() { - evaluate("#answer", "42", Integer.class, true); - } + @Test + void ternaryOperator03() { + // cannot convert String to boolean + evaluateAndCheckError("'hello'?1:2", SpelMessage.TYPE_CONVERSION_ERROR); + } - @Test - public void testFunctionAccess01() { - evaluate("#reverseInt(1,2,3)", "int[3]{3,2,1}", int[].class); - } + @Test + void ternaryOperator04() { + Expression e = parser.parseExpression("1>2?3:4"); + assertThat(e.isWritable(context)).isFalse(); + } - @Test - public void testFunctionAccess02() { - evaluate("#reverseString('hello')", "olleh", String.class); - } + @Test + void ternaryOperator05() { + evaluate("1>2?#var=4:#var=5", 5, Integer.class); + evaluate("3?:#var=5", 3, Integer.class); + evaluate("null?:#var=5", 5, Integer.class); + evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class); + } - // type references - @Test - public void testTypeReferences01() { - evaluate("T(java.lang.String)", "class java.lang.String", Class.class); - } + @Test + void ternaryOperatorWithNullValue() { + assertThatExceptionOfType(EvaluationException.class).isThrownBy( + parser.parseExpression("null ? 0 : 1")::getValue); + } - @Test - public void testTypeReferencesAndQualifiedIdentifierCaching() { - SpelExpression e = (SpelExpression) parser.parseExpression("T(java.lang.String)"); - assertThat(e.isWritable(new StandardEvaluationContext())).isFalse(); - assertThat(e.toStringAST()).isEqualTo("T(java.lang.String)"); - assertThat(e.getValue(Class.class)).isEqualTo(String.class); - // use cached QualifiedIdentifier: - assertThat(e.toStringAST()).isEqualTo("T(java.lang.String)"); - assertThat(e.getValue(Class.class)).isEqualTo(String.class); } - @Test - public void operatorVariants() { - SpelExpression e = (SpelExpression)parser.parseExpression("#a < #b"); - EvaluationContext ctx = new StandardEvaluationContext(); - ctx.setVariable("a", (short) 3); - ctx.setVariable("b", (short) 6); - assertThat(e.getValue(ctx, Boolean.class)).isTrue(); - ctx.setVariable("b", (byte) 6); - assertThat(e.getValue(ctx, Boolean.class)).isTrue(); - ctx.setVariable("a", (byte) 9); - ctx.setVariable("b", (byte) 6); - assertThat(e.getValue(ctx, Boolean.class)).isFalse(); - ctx.setVariable("a", 10L); - ctx.setVariable("b", (short) 30); - assertThat(e.getValue(ctx, Boolean.class)).isTrue(); - ctx.setVariable("a", (byte) 3); - ctx.setVariable("b", (short) 30); - assertThat(e.getValue(ctx, Boolean.class)).isTrue(); - ctx.setVariable("a", (byte) 3); - ctx.setVariable("b", 30L); - assertThat(e.getValue(ctx, Boolean.class)).isTrue(); - ctx.setVariable("a", (byte) 3); - ctx.setVariable("b", 30f); - assertThat(e.getValue(ctx, Boolean.class)).isTrue(); - ctx.setVariable("a", new BigInteger("10")); - ctx.setVariable("b", new BigInteger("20")); - assertThat(e.getValue(ctx, Boolean.class)).isTrue(); - } + @Nested + class MethodConstructorAndFunctionInvocationTests { - @Test - public void testTypeReferencesPrimitive() { - evaluate("T(int)", "int", Class.class); - evaluate("T(byte)", "byte", Class.class); - evaluate("T(char)", "char", Class.class); - evaluate("T(boolean)", "boolean", Class.class); - evaluate("T(long)", "long", Class.class); - evaluate("T(short)", "short", Class.class); - evaluate("T(double)", "double", Class.class); - evaluate("T(float)", "float", Class.class); - } + @Test + void methodCallWithRootReferenceThroughParameter() { + evaluate("placeOfBirth.doubleIt(inventions.length)", 18, Integer.class); + } - @Test - public void testTypeReferences02() { - evaluate("T(String)", "class java.lang.String", Class.class); - } + @Test + void ctorCallWithRootReferenceThroughParameter() { + evaluate("new org.springframework.expression.spel.testresources.PlaceOfBirth(inventions[0].toString()).city", + "Telephone repeater", String.class); + } - @Test - public void testStringType() { - evaluateAndAskForReturnType("getPlaceOfBirth().getCity()", "SmilJan", String.class); - } + @Test + void fnCallWithRootReferenceThroughParameter() { + evaluate("#reverseInt(inventions.length, inventions.length, inventions.length)", "int[3]{9,9,9}", int[].class); + } - @Test - public void testNumbers01() { - evaluateAndAskForReturnType("3*4+5", 17, Integer.class); - evaluateAndAskForReturnType("3*4+5", 17L, Long.class); - evaluateAndAskForReturnType("65", 'A', Character.class); - evaluateAndAskForReturnType("3*4+5", (short) 17, Short.class); - evaluateAndAskForReturnType("3*4+5", "17", String.class); - } + @Test + void methodCallWithRootReferenceThroughParameterThatIsAFunctionCall() { + evaluate("placeOfBirth.doubleIt(#reverseInt(inventions.length,2,3)[2])", 18, Integer.class); + } - @Test - public void testAdvancedNumerics() { - int twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Integer.class); - assertThat(twentyFour).isEqualTo(24); - double one = parser.parseExpression("8.0 / 5e0 % 2").getValue(Double.class); - assertThat((float) one).isCloseTo((float) 1.6d, within((float) 0d)); - int o = parser.parseExpression("8.0 / 5e0 % 2").getValue(Integer.class); - assertThat(o).isEqualTo(1); - int sixteen = parser.parseExpression("-2 ^ 4").getValue(Integer.class); - assertThat(sixteen).isEqualTo(16); - int minusFortyFive = parser.parseExpression("1+2-3*8^2/2/2").getValue(Integer.class); - assertThat(minusFortyFive).isEqualTo(-45); } - @Test - public void testComparison() { - EvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); - boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue( - context, Boolean.class); - assertThat(trueValue).isTrue(); - } + @Nested + class VariableAndFunctionAccessTests { - @Test - public void testResolvingList() { - StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); - assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> - parser.parseExpression("T(List)!=null").getValue(context, Boolean.class)); - ((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util"); - assertThat(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class)).isTrue(); - } + @Test + void variableAccess() { + evaluate("#answer", "42", Integer.class, true); + } - @Test - public void testResolvingString() { - Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); - assertThat(stringClass).isEqualTo(String.class); - } + @Test + void functionAccess() { + evaluate("#reverseInt(1,2,3)", "int[3]{3,2,1}", int[].class); + evaluate("#reverseString('hello')", "olleh", String.class); + } - /** - * SPR-6984: attempting to index a collection on write using an index that - * doesn't currently exist in the collection (address.crossStreets[0] below) - */ - @Test - public void initializingCollectionElementsOnWrite() { - TestPerson person = new TestPerson(); - EvaluationContext context = new StandardEvaluationContext(person); - SpelParserConfiguration config = new SpelParserConfiguration(true, true); - ExpressionParser parser = new SpelExpressionParser(config); - Expression e = parser.parseExpression("name"); - e.setValue(context, "Oleg"); - assertThat(person.getName()).isEqualTo("Oleg"); - - e = parser.parseExpression("address.street"); - e.setValue(context, "123 High St"); - assertThat(person.getAddress().getStreet()).isEqualTo("123 High St"); - - e = parser.parseExpression("address.crossStreets[0]"); - e.setValue(context, "Blah"); - assertThat(person.getAddress().getCrossStreets().get(0)).isEqualTo("Blah"); - - e = parser.parseExpression("address.crossStreets[3]"); - e.setValue(context, "Wibble"); - assertThat(person.getAddress().getCrossStreets().get(0)).isEqualTo("Blah"); - assertThat(person.getAddress().getCrossStreets().get(3)).isEqualTo("Wibble"); } - /** - * Verifies behavior requested in SPR-9613. - */ - @Test - public void caseInsensitiveNullLiterals() { - ExpressionParser parser = new SpelExpressionParser(); + @Nested + class TypeReferenceTests { - Expression e = parser.parseExpression("null"); - assertThat(e.getValue()).isNull(); + @Test + void typeReferences() { + evaluate("T(java.lang.String)", "class java.lang.String", Class.class); + evaluate("T(String)", "class java.lang.String", Class.class); + } - e = parser.parseExpression("NULL"); - assertThat(e.getValue()).isNull(); + @Test + void typeReferencesAndQualifiedIdentifierCaching() { + SpelExpression e = (SpelExpression) parser.parseExpression("T(java.lang.String)"); + assertThat(e.isWritable(new StandardEvaluationContext())).isFalse(); + assertThat(e.toStringAST()).isEqualTo("T(java.lang.String)"); + assertThat(e.getValue(Class.class)).isEqualTo(String.class); + // use cached QualifiedIdentifier: + assertThat(e.toStringAST()).isEqualTo("T(java.lang.String)"); + assertThat(e.getValue(Class.class)).isEqualTo(String.class); + } - e = parser.parseExpression("NuLl"); - assertThat(e.getValue()).isNull(); - } + @Test + void typeReferencesPrimitive() { + evaluate("T(int)", "int", Class.class); + evaluate("T(byte)", "byte", Class.class); + evaluate("T(char)", "char", Class.class); + evaluate("T(boolean)", "boolean", Class.class); + evaluate("T(long)", "long", Class.class); + evaluate("T(short)", "short", Class.class); + evaluate("T(double)", "double", Class.class); + evaluate("T(float)", "float", Class.class); + } - /** - * Verifies behavior requested in SPR-9621. - */ - @Test - public void customMethodFilter() { - StandardEvaluationContext context = new StandardEvaluationContext(); - - // Register a custom MethodResolver... - List customResolvers = new ArrayList<>(); - customResolvers.add(new CustomMethodResolver()); - context.setMethodResolvers(customResolvers); - - // or simply... - // context.setMethodResolvers(new ArrayList()); - - // Register a custom MethodFilter... - MethodFilter filter = new CustomMethodFilter(); - assertThatIllegalStateException().isThrownBy(() -> - context.registerMethodFilter(String.class, filter)) - .withMessage("Method filter cannot be set as the reflective method resolver is not in use"); - } + @Test + void staticMethodReferences() { + evaluate("T(java.awt.Color).green.getRGB() != 0", true, Boolean.class); + evaluate("(T(java.lang.Math).random() * 100.0 ) > 0", true, Boolean.class); + evaluate("(T(Math).random() * 100.0) > 0", true, Boolean.class); + evaluate("T(Character).isUpperCase('Test'.charAt(0)) ? 'uppercase' : 'lowercase'", "uppercase", String.class); + evaluate("T(Character).isUpperCase('Test'.charAt(1)) ? 'uppercase' : 'lowercase'", "lowercase", String.class); + } - /** - * This test is checking that with the changes for 9751 that the refactoring in Indexer is - * coping correctly for references beyond collection boundaries. - */ - @Test - public void collectionGrowingViaIndexer() { - Spr9751 instance = new Spr9751(); - - // Add a new element to the list - StandardEvaluationContext ctx = new StandardEvaluationContext(instance); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e = parser.parseExpression("listOfStrings[++index3]='def'"); - e.getValue(ctx); - assertThat(instance.listOfStrings.size()).isEqualTo(2); - assertThat(instance.listOfStrings.get(1)).isEqualTo("def"); - - // Check reference beyond end of collection - ctx = new StandardEvaluationContext(instance); - parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - e = parser.parseExpression("listOfStrings[0]"); - String value = e.getValue(ctx, String.class); - assertThat(value).isEqualTo("abc"); - e = parser.parseExpression("listOfStrings[1]"); - value = e.getValue(ctx, String.class); - assertThat(value).isEqualTo("def"); - e = parser.parseExpression("listOfStrings[2]"); - value = e.getValue(ctx, String.class); - assertThat(value).isEqualTo(""); - - // Now turn off growing and reference off the end - StandardEvaluationContext failCtx = new StandardEvaluationContext(instance); - parser = new SpelExpressionParser(new SpelParserConfiguration(false, false)); - Expression failExp = parser.parseExpression("listOfStrings[3]"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - failExp.getValue(failCtx, String.class)) - .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS)); } - @Test - public void limitCollectionGrowing() { - TestClass instance = new TestClass(); - StandardEvaluationContext ctx = new StandardEvaluationContext(instance); - SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(true, true, 3)); - Expression e = parser.parseExpression("foo[2]"); - e.setValue(ctx, "2"); - assertThat(instance.getFoo().size()).isEqualTo(3); - e = parser.parseExpression("foo[3]"); - try { - e.setValue(ctx, "3"); - } - catch (SpelEvaluationException see) { - assertThat(see.getMessageCode()).isEqualTo(SpelMessage.UNABLE_TO_GROW_COLLECTION); - assertThat(instance.getFoo().size()).isEqualTo(3); - } - } + @Nested + class IncrementAndDecrementTests { - // For now I am making #this not assignable - @Test - public void increment01root() { - Integer i = 42; - StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e = parser.parseExpression("#this++"); - assertThat(i.intValue()).isEqualTo(42); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e.getValue(ctx, Integer.class)) + // For now I am making #this not assignable + @Test + void increment01root() { + Integer i = 42; + StandardEvaluationContext ctx = new StandardEvaluationContext(i); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e = parser.parseExpression("#this++"); + assertThat(i.intValue()).isEqualTo(42); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e.getValue(ctx, Integer.class)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.NOT_ASSIGNABLE)); - } + } - @Test - public void increment02postfix() { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e; - - // BigDecimal - e = parser.parseExpression("bd++"); - assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); - BigDecimal return_bd = e.getValue(ctx, BigDecimal.class); - assertThat(new BigDecimal("2").equals(return_bd)).isTrue(); - assertThat(new BigDecimal("3").equals(helper.bd)).isTrue(); - - // double - e = parser.parseExpression("ddd++"); - assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); - double return_ddd = e.getValue(ctx, Double.TYPE); - assertThat((float) return_ddd).isCloseTo((float) 2.0d, within((float) 0d)); - assertThat((float) helper.ddd).isCloseTo((float) 3.0d, within((float) 0d)); - - // float - e = parser.parseExpression("fff++"); - assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); - float return_fff = e.getValue(ctx, Float.TYPE); - assertThat(return_fff).isCloseTo(3.0f, within((float) 0d)); - assertThat(helper.fff).isCloseTo(4.0f, within((float) 0d)); - - // long - e = parser.parseExpression("lll++"); - assertThat(helper.lll).isEqualTo(66666L); - long return_lll = e.getValue(ctx, Long.TYPE); - assertThat(return_lll).isEqualTo(66666L); - assertThat(helper.lll).isEqualTo(66667L); - - // int - e = parser.parseExpression("iii++"); - assertThat(helper.iii).isEqualTo(42); - int return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(42); - assertThat(helper.iii).isEqualTo(43); - return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(43); - assertThat(helper.iii).isEqualTo(44); - - // short - e = parser.parseExpression("sss++"); - assertThat(helper.sss).isEqualTo((short) 15); - short return_sss = e.getValue(ctx, Short.TYPE); - assertThat(return_sss).isEqualTo((short) 15); - assertThat(helper.sss).isEqualTo((short) 16); - } + @Test + void increment02postfix() { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e; + + // BigDecimal + e = parser.parseExpression("bd++"); + assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); + BigDecimal return_bd = e.getValue(ctx, BigDecimal.class); + assertThat(new BigDecimal("2").equals(return_bd)).isTrue(); + assertThat(new BigDecimal("3").equals(helper.bd)).isTrue(); + + // double + e = parser.parseExpression("ddd++"); + assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); + double return_ddd = e.getValue(ctx, Double.TYPE); + assertThat((float) return_ddd).isCloseTo((float) 2.0d, within((float) 0d)); + assertThat((float) helper.ddd).isCloseTo((float) 3.0d, within((float) 0d)); + + // float + e = parser.parseExpression("fff++"); + assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); + float return_fff = e.getValue(ctx, Float.TYPE); + assertThat(return_fff).isCloseTo(3.0f, within((float) 0d)); + assertThat(helper.fff).isCloseTo(4.0f, within((float) 0d)); + + // long + e = parser.parseExpression("lll++"); + assertThat(helper.lll).isEqualTo(66666L); + long return_lll = e.getValue(ctx, Long.TYPE); + assertThat(return_lll).isEqualTo(66666L); + assertThat(helper.lll).isEqualTo(66667L); + + // int + e = parser.parseExpression("iii++"); + assertThat(helper.iii).isEqualTo(42); + int return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(42); + assertThat(helper.iii).isEqualTo(43); + return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(43); + assertThat(helper.iii).isEqualTo(44); + + // short + e = parser.parseExpression("sss++"); + assertThat(helper.sss).isEqualTo((short) 15); + short return_sss = e.getValue(ctx, Short.TYPE); + assertThat(return_sss).isEqualTo((short) 15); + assertThat(helper.sss).isEqualTo((short) 16); + } - @Test - public void increment02prefix() { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e; - - - // BigDecimal - e = parser.parseExpression("++bd"); - assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); - BigDecimal return_bd = e.getValue(ctx, BigDecimal.class); - assertThat(new BigDecimal("3").equals(return_bd)).isTrue(); - assertThat(new BigDecimal("3").equals(helper.bd)).isTrue(); - - // double - e = parser.parseExpression("++ddd"); - assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); - double return_ddd = e.getValue(ctx, Double.TYPE); - assertThat((float) return_ddd).isCloseTo((float) 3.0d, within((float) 0d)); - assertThat((float) helper.ddd).isCloseTo((float) 3.0d, within((float) 0d)); - - // float - e = parser.parseExpression("++fff"); - assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); - float return_fff = e.getValue(ctx, Float.TYPE); - assertThat(return_fff).isCloseTo(4.0f, within((float) 0d)); - assertThat(helper.fff).isCloseTo(4.0f, within((float) 0d)); - - // long - e = parser.parseExpression("++lll"); - assertThat(helper.lll).isEqualTo(66666L); - long return_lll = e.getValue(ctx, Long.TYPE); - assertThat(return_lll).isEqualTo(66667L); - assertThat(helper.lll).isEqualTo(66667L); - - // int - e = parser.parseExpression("++iii"); - assertThat(helper.iii).isEqualTo(42); - int return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(43); - assertThat(helper.iii).isEqualTo(43); - return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(44); - assertThat(helper.iii).isEqualTo(44); - - // short - e = parser.parseExpression("++sss"); - assertThat(helper.sss).isEqualTo((short) 15); - int return_sss = (Integer) e.getValue(ctx); - assertThat(return_sss).isEqualTo((short) 16); - assertThat(helper.sss).isEqualTo((short) 16); - } + @Test + void increment02prefix() { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e; + + // BigDecimal + e = parser.parseExpression("++bd"); + assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); + BigDecimal return_bd = e.getValue(ctx, BigDecimal.class); + assertThat(new BigDecimal("3").equals(return_bd)).isTrue(); + assertThat(new BigDecimal("3").equals(helper.bd)).isTrue(); + + // double + e = parser.parseExpression("++ddd"); + assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); + double return_ddd = e.getValue(ctx, Double.TYPE); + assertThat((float) return_ddd).isCloseTo((float) 3.0d, within((float) 0d)); + assertThat((float) helper.ddd).isCloseTo((float) 3.0d, within((float) 0d)); + + // float + e = parser.parseExpression("++fff"); + assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); + float return_fff = e.getValue(ctx, Float.TYPE); + assertThat(return_fff).isCloseTo(4.0f, within((float) 0d)); + assertThat(helper.fff).isCloseTo(4.0f, within((float) 0d)); + + // long + e = parser.parseExpression("++lll"); + assertThat(helper.lll).isEqualTo(66666L); + long return_lll = e.getValue(ctx, Long.TYPE); + assertThat(return_lll).isEqualTo(66667L); + assertThat(helper.lll).isEqualTo(66667L); + + // int + e = parser.parseExpression("++iii"); + assertThat(helper.iii).isEqualTo(42); + int return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(43); + assertThat(helper.iii).isEqualTo(43); + return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(44); + assertThat(helper.iii).isEqualTo(44); + + // short + e = parser.parseExpression("++sss"); + assertThat(helper.sss).isEqualTo((short) 15); + int return_sss = (Integer) e.getValue(ctx); + assertThat(return_sss).isEqualTo((short) 16); + assertThat(helper.sss).isEqualTo((short) 16); + } - @Test - public void increment03() { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + @Test + void increment03() { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e1 = parser.parseExpression("m()++"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e1.getValue(ctx, Double.TYPE)) + Expression e1 = parser.parseExpression("m()++"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e1.getValue(ctx, Double.TYPE)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.OPERAND_NOT_INCREMENTABLE)); - Expression e2 = parser.parseExpression("++m()"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e2.getValue(ctx, Double.TYPE)) + Expression e2 = parser.parseExpression("++m()"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e2.getValue(ctx, Double.TYPE)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.OPERAND_NOT_INCREMENTABLE)); - } + } - @Test - public void increment04() { - Integer i = 42; - StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e1 = parser.parseExpression("++1"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e1.getValue(ctx, Double.TYPE)) + @Test + void increment04() { + Integer i = 42; + StandardEvaluationContext ctx = new StandardEvaluationContext(i); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e1 = parser.parseExpression("++1"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e1.getValue(ctx, Double.TYPE)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.NOT_ASSIGNABLE)); - Expression e2 = parser.parseExpression("1++"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e2.getValue(ctx, Double.TYPE)) + Expression e2 = parser.parseExpression("1++"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e2.getValue(ctx, Double.TYPE)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.NOT_ASSIGNABLE)); - } + } - @Test - public void decrement01root() { - Integer i = 42; - StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e = parser.parseExpression("#this--"); - assertThat(i.intValue()).isEqualTo(42); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e.getValue(ctx, Integer.class)) + @Test + void decrement01root() { + Integer i = 42; + StandardEvaluationContext ctx = new StandardEvaluationContext(i); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e = parser.parseExpression("#this--"); + assertThat(i.intValue()).isEqualTo(42); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e.getValue(ctx, Integer.class)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.NOT_ASSIGNABLE)); - } + } - @Test - public void decrement02postfix() { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e; - - // BigDecimal - e = parser.parseExpression("bd--"); - assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); - BigDecimal return_bd = e.getValue(ctx,BigDecimal.class); - assertThat(new BigDecimal("2").equals(return_bd)).isTrue(); - assertThat(new BigDecimal("1").equals(helper.bd)).isTrue(); - - // double - e = parser.parseExpression("ddd--"); - assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); - double return_ddd = e.getValue(ctx, Double.TYPE); - assertThat((float) return_ddd).isCloseTo((float) 2.0d, within((float) 0d)); - assertThat((float) helper.ddd).isCloseTo((float) 1.0d, within((float) 0d)); - - // float - e = parser.parseExpression("fff--"); - assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); - float return_fff = e.getValue(ctx, Float.TYPE); - assertThat(return_fff).isCloseTo(3.0f, within((float) 0d)); - assertThat(helper.fff).isCloseTo(2.0f, within((float) 0d)); - - // long - e = parser.parseExpression("lll--"); - assertThat(helper.lll).isEqualTo(66666L); - long return_lll = e.getValue(ctx, Long.TYPE); - assertThat(return_lll).isEqualTo(66666L); - assertThat(helper.lll).isEqualTo(66665L); - - // int - e = parser.parseExpression("iii--"); - assertThat(helper.iii).isEqualTo(42); - int return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(42); - assertThat(helper.iii).isEqualTo(41); - return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(41); - assertThat(helper.iii).isEqualTo(40); - - // short - e = parser.parseExpression("sss--"); - assertThat(helper.sss).isEqualTo((short) 15); - short return_sss = e.getValue(ctx, Short.TYPE); - assertThat(return_sss).isEqualTo((short) 15); - assertThat(helper.sss).isEqualTo((short) 14); - } + @Test + void decrement02postfix() { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e; + + // BigDecimal + e = parser.parseExpression("bd--"); + assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); + BigDecimal return_bd = e.getValue(ctx,BigDecimal.class); + assertThat(new BigDecimal("2").equals(return_bd)).isTrue(); + assertThat(new BigDecimal("1").equals(helper.bd)).isTrue(); + + // double + e = parser.parseExpression("ddd--"); + assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); + double return_ddd = e.getValue(ctx, Double.TYPE); + assertThat((float) return_ddd).isCloseTo((float) 2.0d, within((float) 0d)); + assertThat((float) helper.ddd).isCloseTo((float) 1.0d, within((float) 0d)); + + // float + e = parser.parseExpression("fff--"); + assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); + float return_fff = e.getValue(ctx, Float.TYPE); + assertThat(return_fff).isCloseTo(3.0f, within((float) 0d)); + assertThat(helper.fff).isCloseTo(2.0f, within((float) 0d)); + + // long + e = parser.parseExpression("lll--"); + assertThat(helper.lll).isEqualTo(66666L); + long return_lll = e.getValue(ctx, Long.TYPE); + assertThat(return_lll).isEqualTo(66666L); + assertThat(helper.lll).isEqualTo(66665L); + + // int + e = parser.parseExpression("iii--"); + assertThat(helper.iii).isEqualTo(42); + int return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(42); + assertThat(helper.iii).isEqualTo(41); + return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(41); + assertThat(helper.iii).isEqualTo(40); + + // short + e = parser.parseExpression("sss--"); + assertThat(helper.sss).isEqualTo((short) 15); + short return_sss = e.getValue(ctx, Short.TYPE); + assertThat(return_sss).isEqualTo((short) 15); + assertThat(helper.sss).isEqualTo((short) 14); + } - @Test - public void decrement02prefix() { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e; - - // BigDecimal - e = parser.parseExpression("--bd"); - assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); - BigDecimal return_bd = e.getValue(ctx,BigDecimal.class); - assertThat(new BigDecimal("1").equals(return_bd)).isTrue(); - assertThat(new BigDecimal("1").equals(helper.bd)).isTrue(); - - // double - e = parser.parseExpression("--ddd"); - assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); - double return_ddd = e.getValue(ctx, Double.TYPE); - assertThat((float) return_ddd).isCloseTo((float) 1.0d, within((float) 0d)); - assertThat((float) helper.ddd).isCloseTo((float) 1.0d, within((float) 0d)); - - // float - e = parser.parseExpression("--fff"); - assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); - float return_fff = e.getValue(ctx, Float.TYPE); - assertThat(return_fff).isCloseTo(2.0f, within((float) 0d)); - assertThat(helper.fff).isCloseTo(2.0f, within((float) 0d)); - - // long - e = parser.parseExpression("--lll"); - assertThat(helper.lll).isEqualTo(66666L); - long return_lll = e.getValue(ctx, Long.TYPE); - assertThat(return_lll).isEqualTo(66665L); - assertThat(helper.lll).isEqualTo(66665L); - - // int - e = parser.parseExpression("--iii"); - assertThat(helper.iii).isEqualTo(42); - int return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(41); - assertThat(helper.iii).isEqualTo(41); - return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(40); - assertThat(helper.iii).isEqualTo(40); - - // short - e = parser.parseExpression("--sss"); - assertThat(helper.sss).isEqualTo((short) 15); - int return_sss = (Integer)e.getValue(ctx); - assertThat(return_sss).isEqualTo(14); - assertThat(helper.sss).isEqualTo((short) 14); - } + @Test + void decrement02prefix() { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e; + + // BigDecimal + e = parser.parseExpression("--bd"); + assertThat(new BigDecimal("2").equals(helper.bd)).isTrue(); + BigDecimal return_bd = e.getValue(ctx,BigDecimal.class); + assertThat(new BigDecimal("1").equals(return_bd)).isTrue(); + assertThat(new BigDecimal("1").equals(helper.bd)).isTrue(); + + // double + e = parser.parseExpression("--ddd"); + assertThat((float) helper.ddd).isCloseTo((float) 2.0d, within((float) 0d)); + double return_ddd = e.getValue(ctx, Double.TYPE); + assertThat((float) return_ddd).isCloseTo((float) 1.0d, within((float) 0d)); + assertThat((float) helper.ddd).isCloseTo((float) 1.0d, within((float) 0d)); + + // float + e = parser.parseExpression("--fff"); + assertThat(helper.fff).isCloseTo(3.0f, within((float) 0d)); + float return_fff = e.getValue(ctx, Float.TYPE); + assertThat(return_fff).isCloseTo(2.0f, within((float) 0d)); + assertThat(helper.fff).isCloseTo(2.0f, within((float) 0d)); + + // long + e = parser.parseExpression("--lll"); + assertThat(helper.lll).isEqualTo(66666L); + long return_lll = e.getValue(ctx, Long.TYPE); + assertThat(return_lll).isEqualTo(66665L); + assertThat(helper.lll).isEqualTo(66665L); + + // int + e = parser.parseExpression("--iii"); + assertThat(helper.iii).isEqualTo(42); + int return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(41); + assertThat(helper.iii).isEqualTo(41); + return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(40); + assertThat(helper.iii).isEqualTo(40); + + // short + e = parser.parseExpression("--sss"); + assertThat(helper.sss).isEqualTo((short) 15); + int return_sss = (Integer)e.getValue(ctx); + assertThat(return_sss).isEqualTo(14); + assertThat(helper.sss).isEqualTo((short) 14); + } - @Test - public void decrement03() { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + @Test + void decrement03() { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e1 = parser.parseExpression("m()--"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e1.getValue(ctx, Double.TYPE)) + Expression e1 = parser.parseExpression("m()--"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e1.getValue(ctx, Double.TYPE)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.OPERAND_NOT_DECREMENTABLE)); - Expression e2 = parser.parseExpression("--m()"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e2.getValue(ctx, Double.TYPE)) + Expression e2 = parser.parseExpression("--m()"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e2.getValue(ctx, Double.TYPE)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.OPERAND_NOT_DECREMENTABLE)); - } - + } - @Test - public void decrement04() { - Integer i = 42; - StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e1 = parser.parseExpression("--1"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e1.getValue(ctx, Integer.class)) + @Test + void decrement04() { + Integer i = 42; + StandardEvaluationContext ctx = new StandardEvaluationContext(i); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e1 = parser.parseExpression("--1"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e1.getValue(ctx, Integer.class)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.NOT_ASSIGNABLE)); - Expression e2 = parser.parseExpression("1--"); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> - e2.getValue(ctx, Integer.class)) + Expression e2 = parser.parseExpression("1--"); + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + e2.getValue(ctx, Integer.class)) .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.NOT_ASSIGNABLE)); - } - - @Test - public void incdecTogether() { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e; - - // index1 is 2 at the start - the 'intArray[#root.index1++]' should not be evaluated twice! - // intArray[2] is 3 - e = parser.parseExpression("intArray[#root.index1++]++"); - e.getValue(ctx, Integer.class); - assertThat(helper.index1).isEqualTo(3); - assertThat(helper.intArray[2]).isEqualTo(4); - - // index1 is 3 intArray[3] is 4 - e = parser.parseExpression("intArray[#root.index1++]--"); - assertThat(e.getValue(ctx, Integer.class).intValue()).isEqualTo(4); - assertThat(helper.index1).isEqualTo(4); - assertThat(helper.intArray[3]).isEqualTo(3); - - // index1 is 4, intArray[3] is 3 - e = parser.parseExpression("intArray[--#root.index1]++"); - assertThat(e.getValue(ctx, Integer.class).intValue()).isEqualTo(3); - assertThat(helper.index1).isEqualTo(3); - assertThat(helper.intArray[3]).isEqualTo(4); - } - - - // Verify how all the nodes behave with assignment (++, --, =) - @Test - public void incrementAllNodeTypes() throws SecurityException, NoSuchMethodException { - Spr9751 helper = new Spr9751(); - StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - Expression e; - - // BooleanLiteral - expectFailNotAssignable(parser, ctx, "true++"); - expectFailNotAssignable(parser, ctx, "--false"); - expectFailSetValueNotSupported(parser, ctx, "true=false"); - - // IntLiteral - expectFailNotAssignable(parser, ctx, "12++"); - expectFailNotAssignable(parser, ctx, "--1222"); - expectFailSetValueNotSupported(parser, ctx, "12=16"); - - // LongLiteral - expectFailNotAssignable(parser, ctx, "1.0d++"); - expectFailNotAssignable(parser, ctx, "--3.4d"); - expectFailSetValueNotSupported(parser, ctx, "1.0d=3.2d"); - - // NullLiteral - expectFailNotAssignable(parser, ctx, "null++"); - expectFailNotAssignable(parser, ctx, "--null"); - expectFailSetValueNotSupported(parser, ctx, "null=null"); - expectFailSetValueNotSupported(parser, ctx, "null=123"); - - // OpAnd - expectFailNotAssignable(parser, ctx, "(true && false)++"); - expectFailNotAssignable(parser, ctx, "--(false AND true)"); - expectFailSetValueNotSupported(parser, ctx, "(true && false)=(false && true)"); - - // OpDivide - expectFailNotAssignable(parser, ctx, "(3/4)++"); - expectFailNotAssignable(parser, ctx, "--(2/5)"); - expectFailSetValueNotSupported(parser, ctx, "(1/2)=(3/4)"); - - // OpEq - expectFailNotAssignable(parser, ctx, "(3==4)++"); - expectFailNotAssignable(parser, ctx, "--(2==5)"); - expectFailSetValueNotSupported(parser, ctx, "(1==2)=(3==4)"); - - // OpGE - expectFailNotAssignable(parser, ctx, "(3>=4)++"); - expectFailNotAssignable(parser, ctx, "--(2>=5)"); - expectFailSetValueNotSupported(parser, ctx, "(1>=2)=(3>=4)"); - - // OpGT - expectFailNotAssignable(parser, ctx, "(3>4)++"); - expectFailNotAssignable(parser, ctx, "--(2>5)"); - expectFailSetValueNotSupported(parser, ctx, "(1>2)=(3>4)"); - - // OpLE - expectFailNotAssignable(parser, ctx, "(3<=4)++"); - expectFailNotAssignable(parser, ctx, "--(2<=5)"); - expectFailSetValueNotSupported(parser, ctx, "(1<=2)=(3<=4)"); - - // OpLT - expectFailNotAssignable(parser, ctx, "(3<4)++"); - expectFailNotAssignable(parser, ctx, "--(2<5)"); - expectFailSetValueNotSupported(parser, ctx, "(1<2)=(3<4)"); - - // OpMinus - expectFailNotAssignable(parser, ctx, "(3-4)++"); - expectFailNotAssignable(parser, ctx, "--(2-5)"); - expectFailSetValueNotSupported(parser, ctx, "(1-2)=(3-4)"); - - // OpModulus - expectFailNotAssignable(parser, ctx, "(3%4)++"); - expectFailNotAssignable(parser, ctx, "--(2%5)"); - expectFailSetValueNotSupported(parser, ctx, "(1%2)=(3%4)"); - - // OpMultiply - expectFailNotAssignable(parser, ctx, "(3*4)++"); - expectFailNotAssignable(parser, ctx, "--(2*5)"); - expectFailSetValueNotSupported(parser, ctx, "(1*2)=(3*4)"); - - // OpNE - expectFailNotAssignable(parser, ctx, "(3!=4)++"); - expectFailNotAssignable(parser, ctx, "--(2!=5)"); - expectFailSetValueNotSupported(parser, ctx, "(1!=2)=(3!=4)"); - - // OpOr - expectFailNotAssignable(parser, ctx, "(true || false)++"); - expectFailNotAssignable(parser, ctx, "--(false OR true)"); - expectFailSetValueNotSupported(parser, ctx, "(true || false)=(false OR true)"); - - // OpPlus - expectFailNotAssignable(parser, ctx, "(3+4)++"); - expectFailNotAssignable(parser, ctx, "--(2+5)"); - expectFailSetValueNotSupported(parser, ctx, "(1+2)=(3+4)"); - - // RealLiteral - expectFailNotAssignable(parser, ctx, "1.0d++"); - expectFailNotAssignable(parser, ctx, "--2.0d"); - expectFailSetValueNotSupported(parser, ctx, "(1.0d)=(3.0d)"); - expectFailNotAssignable(parser, ctx, "1.0f++"); - expectFailNotAssignable(parser, ctx, "--2.0f"); - expectFailSetValueNotSupported(parser, ctx, "(1.0f)=(3.0f)"); - - // StringLiteral - expectFailNotAssignable(parser, ctx, "'abc'++"); - expectFailNotAssignable(parser, ctx, "--'def'"); - expectFailSetValueNotSupported(parser, ctx, "'abc'='def'"); - - // Ternary - expectFailNotAssignable(parser, ctx, "(true?true:false)++"); - expectFailNotAssignable(parser, ctx, "--(true?true:false)"); - expectFailSetValueNotSupported(parser, ctx, "(true?true:false)=(true?true:false)"); - - // TypeReference - expectFailNotAssignable(parser, ctx, "T(String)++"); - expectFailNotAssignable(parser, ctx, "--T(Integer)"); - expectFailSetValueNotSupported(parser, ctx, "T(String)=T(Integer)"); - - // OperatorBetween - expectFailNotAssignable(parser, ctx, "(3 between {1,5})++"); - expectFailNotAssignable(parser, ctx, "--(3 between {1,5})"); - expectFailSetValueNotSupported(parser, ctx, "(3 between {1,5})=(3 between {1,5})"); - - // OperatorInstanceOf - expectFailNotAssignable(parser, ctx, "(type instanceof T(String))++"); - expectFailNotAssignable(parser, ctx, "--(type instanceof T(String))"); - expectFailSetValueNotSupported(parser, ctx, "(type instanceof T(String))=(type instanceof T(String))"); - - // Elvis - expectFailNotAssignable(parser, ctx, "(true?:false)++"); - expectFailNotAssignable(parser, ctx, "--(true?:false)"); - expectFailSetValueNotSupported(parser, ctx, "(true?:false)=(true?:false)"); - - // OpInc - expectFailNotAssignable(parser, ctx, "(iii++)++"); - expectFailNotAssignable(parser, ctx, "--(++iii)"); - expectFailSetValueNotSupported(parser, ctx, "(iii++)=(++iii)"); - - // OpDec - expectFailNotAssignable(parser, ctx, "(iii--)++"); - expectFailNotAssignable(parser, ctx, "--(--iii)"); - expectFailSetValueNotSupported(parser, ctx, "(iii--)=(--iii)"); - - // OperatorNot - expectFailNotAssignable(parser, ctx, "(!true)++"); - expectFailNotAssignable(parser, ctx, "--(!false)"); - expectFailSetValueNotSupported(parser, ctx, "(!true)=(!false)"); - - // OperatorPower - expectFailNotAssignable(parser, ctx, "(iii^2)++"); - expectFailNotAssignable(parser, ctx, "--(iii^2)"); - expectFailSetValueNotSupported(parser, ctx, "(iii^2)=(iii^3)"); - - // Assign - // iii=42 - e = parser.parseExpression("iii=iii++"); - assertThat(helper.iii).isEqualTo(42); - int return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(helper.iii).isEqualTo(42); - assertThat(return_iii).isEqualTo(42); - - // Identifier - e = parser.parseExpression("iii++"); - assertThat(helper.iii).isEqualTo(42); - return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(42); - assertThat(helper.iii).isEqualTo(43); - - e = parser.parseExpression("--iii"); - assertThat(helper.iii).isEqualTo(43); - return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(42); - assertThat(helper.iii).isEqualTo(42); - - e = parser.parseExpression("iii=99"); - assertThat(helper.iii).isEqualTo(42); - return_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_iii).isEqualTo(99); - assertThat(helper.iii).isEqualTo(99); - - // CompoundExpression - // foo.iii == 99 - e = parser.parseExpression("foo.iii++"); - assertThat(helper.foo.iii).isEqualTo(99); - int return_foo_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_foo_iii).isEqualTo(99); - assertThat(helper.foo.iii).isEqualTo(100); - - e = parser.parseExpression("--foo.iii"); - assertThat(helper.foo.iii).isEqualTo(100); - return_foo_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_foo_iii).isEqualTo(99); - assertThat(helper.foo.iii).isEqualTo(99); - - e = parser.parseExpression("foo.iii=999"); - assertThat(helper.foo.iii).isEqualTo(99); - return_foo_iii = e.getValue(ctx, Integer.TYPE); - assertThat(return_foo_iii).isEqualTo(999); - assertThat(helper.foo.iii).isEqualTo(999); - - // ConstructorReference - expectFailNotAssignable(parser, ctx, "(new String('abc'))++"); - expectFailNotAssignable(parser, ctx, "--(new String('abc'))"); - expectFailSetValueNotSupported(parser, ctx, "(new String('abc'))=(new String('abc'))"); - - // MethodReference - expectFailNotIncrementable(parser, ctx, "m()++"); - expectFailNotDecrementable(parser, ctx, "--m()"); - expectFailSetValueNotSupported(parser, ctx, "m()=m()"); - - // OperatorMatches - expectFailNotAssignable(parser, ctx, "('abc' matches '^a..')++"); - expectFailNotAssignable(parser, ctx, "--('abc' matches '^a..')"); - expectFailSetValueNotSupported(parser, ctx, "('abc' matches '^a..')=('abc' matches '^a..')"); - - // Selection - ctx.registerFunction("isEven", Spr9751.class.getDeclaredMethod("isEven", Integer.TYPE)); - - expectFailNotIncrementable(parser, ctx, "({1,2,3}.?[#isEven(#this)])++"); - expectFailNotDecrementable(parser, ctx, "--({1,2,3}.?[#isEven(#this)])"); - expectFailNotAssignable(parser, ctx, "({1,2,3}.?[#isEven(#this)])=({1,2,3}.?[#isEven(#this)])"); - - // slightly diff here because return value isn't a list, it is a single entity - expectFailNotAssignable(parser, ctx, "({1,2,3}.^[#isEven(#this)])++"); - expectFailNotAssignable(parser, ctx, "--({1,2,3}.^[#isEven(#this)])"); - expectFailNotAssignable(parser, ctx, "({1,2,3}.^[#isEven(#this)])=({1,2,3}.^[#isEven(#this)])"); - - expectFailNotAssignable(parser, ctx, "({1,2,3}.$[#isEven(#this)])++"); - expectFailNotAssignable(parser, ctx, "--({1,2,3}.$[#isEven(#this)])"); - expectFailNotAssignable(parser, ctx, "({1,2,3}.$[#isEven(#this)])=({1,2,3}.$[#isEven(#this)])"); - - // FunctionReference - expectFailNotAssignable(parser, ctx, "#isEven(3)++"); - expectFailNotAssignable(parser, ctx, "--#isEven(4)"); - expectFailSetValueNotSupported(parser, ctx, "#isEven(3)=#isEven(5)"); - - // VariableReference - ctx.setVariable("wibble", "hello world"); - expectFailNotIncrementable(parser, ctx, "#wibble++"); - expectFailNotDecrementable(parser, ctx, "--#wibble"); - e = parser.parseExpression("#wibble=#wibble+#wibble"); - String s = e.getValue(ctx, String.class); - assertThat(s).isEqualTo("hello worldhello world"); - assertThat(ctx.lookupVariable("wibble")).isEqualTo("hello worldhello world"); - - ctx.setVariable("wobble", 3); - e = parser.parseExpression("#wobble++"); - assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(3); - int r = e.getValue(ctx, Integer.TYPE); - assertThat(r).isEqualTo(3); - assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(4); - - e = parser.parseExpression("--#wobble"); - assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(4); - r = e.getValue(ctx, Integer.TYPE); - assertThat(r).isEqualTo(3); - assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(3); - - e = parser.parseExpression("#wobble=34"); - assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(3); - r = e.getValue(ctx, Integer.TYPE); - assertThat(r).isEqualTo(34); - assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(34); - - // Projection - expectFailNotIncrementable(parser, ctx, "({1,2,3}.![#isEven(#this)])++"); // projection would be {false,true,false} - expectFailNotDecrementable(parser, ctx, "--({1,2,3}.![#isEven(#this)])"); // projection would be {false,true,false} - expectFailNotAssignable(parser, ctx, "({1,2,3}.![#isEven(#this)])=({1,2,3}.![#isEven(#this)])"); - - // InlineList - expectFailNotAssignable(parser, ctx, "({1,2,3})++"); - expectFailNotAssignable(parser, ctx, "--({1,2,3})"); - expectFailSetValueNotSupported(parser, ctx, "({1,2,3})=({1,2,3})"); - - // InlineMap - expectFailNotAssignable(parser, ctx, "({'a':1,'b':2,'c':3})++"); - expectFailNotAssignable(parser, ctx, "--({'a':1,'b':2,'c':3})"); - expectFailSetValueNotSupported(parser, ctx, "({'a':1,'b':2,'c':3})=({'a':1,'b':2,'c':3})"); - - // BeanReference - ctx.setBeanResolver(new MyBeanResolver()); - expectFailNotAssignable(parser, ctx, "@foo++"); - expectFailNotAssignable(parser, ctx, "--@foo"); - expectFailSetValueNotSupported(parser, ctx, "@foo=@bar"); - - // PropertyOrFieldReference - helper.iii = 42; - e = parser.parseExpression("iii++"); - assertThat(helper.iii).isEqualTo(42); - r = e.getValue(ctx, Integer.TYPE); - assertThat(r).isEqualTo(42); - assertThat(helper.iii).isEqualTo(43); - - e = parser.parseExpression("--iii"); - assertThat(helper.iii).isEqualTo(43); - r = e.getValue(ctx, Integer.TYPE); - assertThat(r).isEqualTo(42); - assertThat(helper.iii).isEqualTo(42); - - e = parser.parseExpression("iii=100"); - assertThat(helper.iii).isEqualTo(42); - r = e.getValue(ctx, Integer.TYPE); - assertThat(r).isEqualTo(100); - assertThat(helper.iii).isEqualTo(100); - } - - private void expectFailNotAssignable(ExpressionParser parser, EvaluationContext eContext, String expressionString) { - expectFail(parser, eContext, expressionString, SpelMessage.NOT_ASSIGNABLE); - } - - private void expectFailSetValueNotSupported(ExpressionParser parser, EvaluationContext eContext, String expressionString) { - expectFail(parser, eContext, expressionString, SpelMessage.SETVALUE_NOT_SUPPORTED); - } - - private void expectFailNotIncrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) { - expectFail(parser, eContext, expressionString, SpelMessage.OPERAND_NOT_INCREMENTABLE); - } + } - private void expectFailNotDecrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) { - expectFail(parser, eContext, expressionString, SpelMessage.OPERAND_NOT_DECREMENTABLE); - } + @Test + void incrementAndDecrementTogether() { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e; + + // index1 is 2 at the start - the 'intArray[#root.index1++]' should not be evaluated twice! + // intArray[2] is 3 + e = parser.parseExpression("intArray[#root.index1++]++"); + e.getValue(ctx, Integer.class); + assertThat(helper.index1).isEqualTo(3); + assertThat(helper.intArray[2]).isEqualTo(4); + + // index1 is 3 intArray[3] is 4 + e = parser.parseExpression("intArray[#root.index1++]--"); + assertThat(e.getValue(ctx, Integer.class).intValue()).isEqualTo(4); + assertThat(helper.index1).isEqualTo(4); + assertThat(helper.intArray[3]).isEqualTo(3); + + // index1 is 4, intArray[3] is 3 + e = parser.parseExpression("intArray[--#root.index1]++"); + assertThat(e.getValue(ctx, Integer.class).intValue()).isEqualTo(3); + assertThat(helper.index1).isEqualTo(3); + assertThat(helper.intArray[3]).isEqualTo(4); + } - private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) { - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> { - Expression e = parser.parseExpression(expressionString); - SpelUtilities.printAbstractSyntaxTree(System.out, e); - e.getValue(eContext); - }).satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(messageCode)); - } + // Verify how all the nodes behave with assignment (++, --, =) + @Test + void incrementAllNodeTypes() throws SecurityException, NoSuchMethodException { + Spr9751 helper = new Spr9751(); + StandardEvaluationContext ctx = new StandardEvaluationContext(helper); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression e; + + // BooleanLiteral + expectFailNotAssignable(parser, ctx, "true++"); + expectFailNotAssignable(parser, ctx, "--false"); + expectFailSetValueNotSupported(parser, ctx, "true=false"); + + // IntLiteral + expectFailNotAssignable(parser, ctx, "12++"); + expectFailNotAssignable(parser, ctx, "--1222"); + expectFailSetValueNotSupported(parser, ctx, "12=16"); + + // LongLiteral + expectFailNotAssignable(parser, ctx, "1.0d++"); + expectFailNotAssignable(parser, ctx, "--3.4d"); + expectFailSetValueNotSupported(parser, ctx, "1.0d=3.2d"); + + // NullLiteral + expectFailNotAssignable(parser, ctx, "null++"); + expectFailNotAssignable(parser, ctx, "--null"); + expectFailSetValueNotSupported(parser, ctx, "null=null"); + expectFailSetValueNotSupported(parser, ctx, "null=123"); + + // OpAnd + expectFailNotAssignable(parser, ctx, "(true && false)++"); + expectFailNotAssignable(parser, ctx, "--(false AND true)"); + expectFailSetValueNotSupported(parser, ctx, "(true && false)=(false && true)"); + + // OpDivide + expectFailNotAssignable(parser, ctx, "(3/4)++"); + expectFailNotAssignable(parser, ctx, "--(2/5)"); + expectFailSetValueNotSupported(parser, ctx, "(1/2)=(3/4)"); + + // OpEq + expectFailNotAssignable(parser, ctx, "(3==4)++"); + expectFailNotAssignable(parser, ctx, "--(2==5)"); + expectFailSetValueNotSupported(parser, ctx, "(1==2)=(3==4)"); + + // OpGE + expectFailNotAssignable(parser, ctx, "(3>=4)++"); + expectFailNotAssignable(parser, ctx, "--(2>=5)"); + expectFailSetValueNotSupported(parser, ctx, "(1>=2)=(3>=4)"); + + // OpGT + expectFailNotAssignable(parser, ctx, "(3>4)++"); + expectFailNotAssignable(parser, ctx, "--(2>5)"); + expectFailSetValueNotSupported(parser, ctx, "(1>2)=(3>4)"); + + // OpLE + expectFailNotAssignable(parser, ctx, "(3<=4)++"); + expectFailNotAssignable(parser, ctx, "--(2<=5)"); + expectFailSetValueNotSupported(parser, ctx, "(1<=2)=(3<=4)"); + + // OpLT + expectFailNotAssignable(parser, ctx, "(3<4)++"); + expectFailNotAssignable(parser, ctx, "--(2<5)"); + expectFailSetValueNotSupported(parser, ctx, "(1<2)=(3<4)"); + + // OpMinus + expectFailNotAssignable(parser, ctx, "(3-4)++"); + expectFailNotAssignable(parser, ctx, "--(2-5)"); + expectFailSetValueNotSupported(parser, ctx, "(1-2)=(3-4)"); + + // OpModulus + expectFailNotAssignable(parser, ctx, "(3%4)++"); + expectFailNotAssignable(parser, ctx, "--(2%5)"); + expectFailSetValueNotSupported(parser, ctx, "(1%2)=(3%4)"); + + // OpMultiply + expectFailNotAssignable(parser, ctx, "(3*4)++"); + expectFailNotAssignable(parser, ctx, "--(2*5)"); + expectFailSetValueNotSupported(parser, ctx, "(1*2)=(3*4)"); + + // OpNE + expectFailNotAssignable(parser, ctx, "(3!=4)++"); + expectFailNotAssignable(parser, ctx, "--(2!=5)"); + expectFailSetValueNotSupported(parser, ctx, "(1!=2)=(3!=4)"); + + // OpOr + expectFailNotAssignable(parser, ctx, "(true || false)++"); + expectFailNotAssignable(parser, ctx, "--(false OR true)"); + expectFailSetValueNotSupported(parser, ctx, "(true || false)=(false OR true)"); + + // OpPlus + expectFailNotAssignable(parser, ctx, "(3+4)++"); + expectFailNotAssignable(parser, ctx, "--(2+5)"); + expectFailSetValueNotSupported(parser, ctx, "(1+2)=(3+4)"); + + // RealLiteral + expectFailNotAssignable(parser, ctx, "1.0d++"); + expectFailNotAssignable(parser, ctx, "--2.0d"); + expectFailSetValueNotSupported(parser, ctx, "(1.0d)=(3.0d)"); + expectFailNotAssignable(parser, ctx, "1.0f++"); + expectFailNotAssignable(parser, ctx, "--2.0f"); + expectFailSetValueNotSupported(parser, ctx, "(1.0f)=(3.0f)"); + + // StringLiteral + expectFailNotAssignable(parser, ctx, "'abc'++"); + expectFailNotAssignable(parser, ctx, "--'def'"); + expectFailSetValueNotSupported(parser, ctx, "'abc'='def'"); + + // Ternary + expectFailNotAssignable(parser, ctx, "(true?true:false)++"); + expectFailNotAssignable(parser, ctx, "--(true?true:false)"); + expectFailSetValueNotSupported(parser, ctx, "(true?true:false)=(true?true:false)"); + + // TypeReference + expectFailNotAssignable(parser, ctx, "T(String)++"); + expectFailNotAssignable(parser, ctx, "--T(Integer)"); + expectFailSetValueNotSupported(parser, ctx, "T(String)=T(Integer)"); + + // OperatorBetween + expectFailNotAssignable(parser, ctx, "(3 between {1,5})++"); + expectFailNotAssignable(parser, ctx, "--(3 between {1,5})"); + expectFailSetValueNotSupported(parser, ctx, "(3 between {1,5})=(3 between {1,5})"); + + // OperatorInstanceOf + expectFailNotAssignable(parser, ctx, "(type instanceof T(String))++"); + expectFailNotAssignable(parser, ctx, "--(type instanceof T(String))"); + expectFailSetValueNotSupported(parser, ctx, "(type instanceof T(String))=(type instanceof T(String))"); + + // Elvis + expectFailNotAssignable(parser, ctx, "(true?:false)++"); + expectFailNotAssignable(parser, ctx, "--(true?:false)"); + expectFailSetValueNotSupported(parser, ctx, "(true?:false)=(true?:false)"); + + // OpInc + expectFailNotAssignable(parser, ctx, "(iii++)++"); + expectFailNotAssignable(parser, ctx, "--(++iii)"); + expectFailSetValueNotSupported(parser, ctx, "(iii++)=(++iii)"); + + // OpDec + expectFailNotAssignable(parser, ctx, "(iii--)++"); + expectFailNotAssignable(parser, ctx, "--(--iii)"); + expectFailSetValueNotSupported(parser, ctx, "(iii--)=(--iii)"); + + // OperatorNot + expectFailNotAssignable(parser, ctx, "(!true)++"); + expectFailNotAssignable(parser, ctx, "--(!false)"); + expectFailSetValueNotSupported(parser, ctx, "(!true)=(!false)"); + + // OperatorPower + expectFailNotAssignable(parser, ctx, "(iii^2)++"); + expectFailNotAssignable(parser, ctx, "--(iii^2)"); + expectFailSetValueNotSupported(parser, ctx, "(iii^2)=(iii^3)"); + + // Assign + // iii=42 + e = parser.parseExpression("iii=iii++"); + assertThat(helper.iii).isEqualTo(42); + int return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(helper.iii).isEqualTo(42); + assertThat(return_iii).isEqualTo(42); + + // Identifier + e = parser.parseExpression("iii++"); + assertThat(helper.iii).isEqualTo(42); + return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(42); + assertThat(helper.iii).isEqualTo(43); + + e = parser.parseExpression("--iii"); + assertThat(helper.iii).isEqualTo(43); + return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(42); + assertThat(helper.iii).isEqualTo(42); + + e = parser.parseExpression("iii=99"); + assertThat(helper.iii).isEqualTo(42); + return_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_iii).isEqualTo(99); + assertThat(helper.iii).isEqualTo(99); + + // CompoundExpression + // foo.iii == 99 + e = parser.parseExpression("foo.iii++"); + assertThat(helper.foo.iii).isEqualTo(99); + int return_foo_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_foo_iii).isEqualTo(99); + assertThat(helper.foo.iii).isEqualTo(100); + + e = parser.parseExpression("--foo.iii"); + assertThat(helper.foo.iii).isEqualTo(100); + return_foo_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_foo_iii).isEqualTo(99); + assertThat(helper.foo.iii).isEqualTo(99); + + e = parser.parseExpression("foo.iii=999"); + assertThat(helper.foo.iii).isEqualTo(99); + return_foo_iii = e.getValue(ctx, Integer.TYPE); + assertThat(return_foo_iii).isEqualTo(999); + assertThat(helper.foo.iii).isEqualTo(999); + + // ConstructorReference + expectFailNotAssignable(parser, ctx, "(new String('abc'))++"); + expectFailNotAssignable(parser, ctx, "--(new String('abc'))"); + expectFailSetValueNotSupported(parser, ctx, "(new String('abc'))=(new String('abc'))"); + + // MethodReference + expectFailNotIncrementable(parser, ctx, "m()++"); + expectFailNotDecrementable(parser, ctx, "--m()"); + expectFailSetValueNotSupported(parser, ctx, "m()=m()"); + + // OperatorMatches + expectFailNotAssignable(parser, ctx, "('abc' matches '^a..')++"); + expectFailNotAssignable(parser, ctx, "--('abc' matches '^a..')"); + expectFailSetValueNotSupported(parser, ctx, "('abc' matches '^a..')=('abc' matches '^a..')"); + + // Selection + ctx.registerFunction("isEven", Spr9751.class.getDeclaredMethod("isEven", Integer.TYPE)); + + expectFailNotIncrementable(parser, ctx, "({1,2,3}.?[#isEven(#this)])++"); + expectFailNotDecrementable(parser, ctx, "--({1,2,3}.?[#isEven(#this)])"); + expectFailNotAssignable(parser, ctx, "({1,2,3}.?[#isEven(#this)])=({1,2,3}.?[#isEven(#this)])"); + + // slightly diff here because return value isn't a list, it is a single entity + expectFailNotAssignable(parser, ctx, "({1,2,3}.^[#isEven(#this)])++"); + expectFailNotAssignable(parser, ctx, "--({1,2,3}.^[#isEven(#this)])"); + expectFailNotAssignable(parser, ctx, "({1,2,3}.^[#isEven(#this)])=({1,2,3}.^[#isEven(#this)])"); + + expectFailNotAssignable(parser, ctx, "({1,2,3}.$[#isEven(#this)])++"); + expectFailNotAssignable(parser, ctx, "--({1,2,3}.$[#isEven(#this)])"); + expectFailNotAssignable(parser, ctx, "({1,2,3}.$[#isEven(#this)])=({1,2,3}.$[#isEven(#this)])"); + + // FunctionReference + expectFailNotAssignable(parser, ctx, "#isEven(3)++"); + expectFailNotAssignable(parser, ctx, "--#isEven(4)"); + expectFailSetValueNotSupported(parser, ctx, "#isEven(3)=#isEven(5)"); + + // VariableReference + ctx.setVariable("wibble", "hello world"); + expectFailNotIncrementable(parser, ctx, "#wibble++"); + expectFailNotDecrementable(parser, ctx, "--#wibble"); + e = parser.parseExpression("#wibble=#wibble+#wibble"); + String s = e.getValue(ctx, String.class); + assertThat(s).isEqualTo("hello worldhello world"); + assertThat(ctx.lookupVariable("wibble")).isEqualTo("hello worldhello world"); + + ctx.setVariable("wobble", 3); + e = parser.parseExpression("#wobble++"); + assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(3); + int r = e.getValue(ctx, Integer.TYPE); + assertThat(r).isEqualTo(3); + assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(4); + + e = parser.parseExpression("--#wobble"); + assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(4); + r = e.getValue(ctx, Integer.TYPE); + assertThat(r).isEqualTo(3); + assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(3); + + e = parser.parseExpression("#wobble=34"); + assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(3); + r = e.getValue(ctx, Integer.TYPE); + assertThat(r).isEqualTo(34); + assertThat(((Integer) ctx.lookupVariable("wobble")).intValue()).isEqualTo(34); + + // Projection + expectFailNotIncrementable(parser, ctx, "({1,2,3}.![#isEven(#this)])++"); // projection would be {false,true,false} + expectFailNotDecrementable(parser, ctx, "--({1,2,3}.![#isEven(#this)])"); // projection would be {false,true,false} + expectFailNotAssignable(parser, ctx, "({1,2,3}.![#isEven(#this)])=({1,2,3}.![#isEven(#this)])"); + + // InlineList + expectFailNotAssignable(parser, ctx, "({1,2,3})++"); + expectFailNotAssignable(parser, ctx, "--({1,2,3})"); + expectFailSetValueNotSupported(parser, ctx, "({1,2,3})=({1,2,3})"); + + // InlineMap + expectFailNotAssignable(parser, ctx, "({'a':1,'b':2,'c':3})++"); + expectFailNotAssignable(parser, ctx, "--({'a':1,'b':2,'c':3})"); + expectFailSetValueNotSupported(parser, ctx, "({'a':1,'b':2,'c':3})=({'a':1,'b':2,'c':3})"); + + // BeanReference + BeanResolver beanResolver = (context, beanName) -> { + if (beanName.equals("foo") || beanName.equals("bar")) { + return new Spr9751_2(); + } + throw new AccessException("unknown bean " + beanName); + }; + ctx.setBeanResolver(beanResolver); + expectFailNotAssignable(parser, ctx, "@foo++"); + expectFailNotAssignable(parser, ctx, "--@foo"); + expectFailSetValueNotSupported(parser, ctx, "@foo=@bar"); + + // PropertyOrFieldReference + helper.iii = 42; + e = parser.parseExpression("iii++"); + assertThat(helper.iii).isEqualTo(42); + r = e.getValue(ctx, Integer.TYPE); + assertThat(r).isEqualTo(42); + assertThat(helper.iii).isEqualTo(43); + + e = parser.parseExpression("--iii"); + assertThat(helper.iii).isEqualTo(43); + r = e.getValue(ctx, Integer.TYPE); + assertThat(r).isEqualTo(42); + assertThat(helper.iii).isEqualTo(42); + + e = parser.parseExpression("iii=100"); + assertThat(helper.iii).isEqualTo(42); + r = e.getValue(ctx, Integer.TYPE); + assertThat(r).isEqualTo(100); + assertThat(helper.iii).isEqualTo(100); + } - static class CustomMethodResolver implements MethodResolver { + private void expectFailNotAssignable(ExpressionParser parser, EvaluationContext eContext, String expressionString) { + expectFail(parser, eContext, expressionString, SpelMessage.NOT_ASSIGNABLE); + } - @Override - public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, - List argumentTypes) throws AccessException { - return null; + private void expectFailSetValueNotSupported(ExpressionParser parser, EvaluationContext eContext, String expressionString) { + expectFail(parser, eContext, expressionString, SpelMessage.SETVALUE_NOT_SUPPORTED); } - } + private void expectFailNotIncrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) { + expectFail(parser, eContext, expressionString, SpelMessage.OPERAND_NOT_INCREMENTABLE); + } - static class CustomMethodFilter implements MethodFilter { + private void expectFailNotDecrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) { + expectFail(parser, eContext, expressionString, SpelMessage.OPERAND_NOT_DECREMENTABLE); + } - @Override - public List filter(List methods) { - return null; + private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) { + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> { + Expression e = parser.parseExpression(expressionString); + SpelUtilities.printAbstractSyntaxTree(System.out, e); + e.getValue(eContext); + }).satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(messageCode)); } } @@ -1433,16 +1459,4 @@ static class Spr9751_2 { public int iii = 99; } - - static class MyBeanResolver implements BeanResolver { - - @Override - public Object resolve(EvaluationContext context, String beanName) throws AccessException { - if (beanName.equals("foo") || beanName.equals("bar")) { - return new Spr9751_2(); - } - throw new AccessException("not heard of " + beanName); - } - } - } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java index dcc4511f4cd8..280b452007b0 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,16 +41,17 @@ /** * Test the examples specified in the documentation. * - * NOTE: any outgoing changes from this file upon synchronizing with the repo may indicate that + *

NOTE: any outgoing changes from this file upon synchronizing with the repo may indicate that * you need to update the documentation too ! * * @author Andy Clement */ @SuppressWarnings("rawtypes") -public class SpelDocumentationTests extends AbstractExpressionTests { +class SpelDocumentationTests extends AbstractExpressionTests { - static Inventor tesla ; - static Inventor pupin ; + static Inventor tesla; + + static Inventor pupin; static { GregorianCalendar c = new GregorianCalendar(); @@ -65,53 +66,24 @@ public class SpelDocumentationTests extends AbstractExpressionTests { pupin.setPlaceOfBirth(new PlaceOfBirth("Idvor")); } - static class IEEE { - private String name; - - - public Inventor[] Members = new Inventor[1]; - public List Members2 = new ArrayList(); - public Map officers = new HashMap<>(); - - public List> reverse = new ArrayList<>(); - - @SuppressWarnings("unchecked") - IEEE() { - officers.put("president",pupin); - List linv = new ArrayList(); - linv.add(tesla); - officers.put("advisors",linv); - Members2.add(tesla); - Members2.add(pupin); - - reverse.add(officers); - } - - public boolean isMember(String name) { - return true; - } - - public String getName() { return name; } - public void setName(String n) { this.name = n; } - } @Test - public void testMethodInvocation() { + void methodInvocation() { evaluate("'Hello World'.concat('!')","Hello World!",String.class); } @Test - public void testBeanPropertyAccess() { + void beanPropertyAccess() { evaluate("new String('Hello World'.bytes)","Hello World",String.class); } @Test - public void testArrayLengthAccess() { + void arrayLengthAccess() { evaluate("'Hello World'.bytes.length",11,Integer.class); } @Test - public void testRootObject() throws Exception { + void rootObject() throws Exception { GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); @@ -129,7 +101,7 @@ public void testRootObject() throws Exception { } @Test - public void testEqualityCheck() throws Exception { + void equalityCheck() throws Exception { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -143,13 +115,13 @@ public void testEqualityCheck() throws Exception { // Section 7.4.1 @Test - public void testXMLBasedConfig() { + void xmlBasedConfig() { evaluate("(T(java.lang.Math).random() * 100.0 )>0",true,Boolean.class); } // Section 7.5 @Test - public void testLiterals() throws Exception { + void literals() throws Exception { ExpressionParser parser = new SpelExpressionParser(); String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); // evals to "Hello World" @@ -169,7 +141,7 @@ public void testLiterals() throws Exception { } @Test - public void testPropertyAccess() throws Exception { + void propertyAccess() throws Exception { EvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); // 1856 assertThat(year).isEqualTo(1856); @@ -179,12 +151,12 @@ public void testPropertyAccess() throws Exception { } @Test - public void testPropertyNavigation() throws Exception { + void propertyNavigation() throws Exception { ExpressionParser parser = new SpelExpressionParser(); // Inventions Array StandardEvaluationContext teslaContext = TestScenarioCreator.getTestEvaluationContext(); -// teslaContext.setRootObject(tesla); + // teslaContext.setRootObject(tesla); // evaluates to "Induction motor" String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class); @@ -206,9 +178,8 @@ public void testPropertyNavigation() throws Exception { assertThat(invention).isEqualTo("Wireless communication"); } - @Test - public void testDictionaryAccess() throws Exception { + void dictionaryAccess() throws Exception { StandardEvaluationContext societyContext = new StandardEvaluationContext(); societyContext.setRootObject(new IEEE()); // Officer's Dictionary @@ -233,7 +204,7 @@ public void testDictionaryAccess() throws Exception { // 7.5.3 @Test - public void testMethodInvocation2() throws Exception { + void methodInvocation2() throws Exception { // string literal, evaluates to "bc" String c = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class); assertThat(c).isEqualTo("bc"); @@ -248,7 +219,7 @@ public void testMethodInvocation2() throws Exception { // 7.5.4.1 @Test - public void testRelationalOperators() throws Exception { + void relationalOperators() throws Exception { boolean result = parser.parseExpression("2 == 2").getValue(Boolean.class); assertThat(result).isTrue(); // evaluates to false @@ -261,7 +232,7 @@ public void testRelationalOperators() throws Exception { } @Test - public void testOtherOperators() throws Exception { + void otherOperators() throws Exception { // evaluates to false boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); assertThat(falseValue).isFalse(); @@ -278,7 +249,7 @@ public void testOtherOperators() throws Exception { // 7.5.4.2 @Test - public void testLogicalOperators() throws Exception { + void logicalOperators() throws Exception { StandardEvaluationContext societyContext = new StandardEvaluationContext(); societyContext.setRootObject(new IEEE()); @@ -319,7 +290,7 @@ public void testLogicalOperators() throws Exception { // 7.5.4.3 @Test - public void testNumericalOperators() throws Exception { + void numericalOperators() throws Exception { // Addition int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 assertThat(two).isEqualTo(2); @@ -363,7 +334,7 @@ public void testNumericalOperators() throws Exception { // 7.5.5 @Test - public void testAssignment() throws Exception { + void assignment() throws Exception { Inventor inventor = new Inventor(); StandardEvaluationContext inventorContext = new StandardEvaluationContext(); inventorContext.setRootObject(inventor); @@ -381,7 +352,7 @@ public void testAssignment() throws Exception { // 7.5.6 @Test - public void testTypes() throws Exception { + void types() throws Exception { Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); assertThat(dateClass).isEqualTo(Date.class); boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class); @@ -391,7 +362,7 @@ public void testTypes() throws Exception { // 7.5.7 @Test - public void testConstructors() throws Exception { + void constructors() throws Exception { StandardEvaluationContext societyContext = new StandardEvaluationContext(); societyContext.setRootObject(new IEEE()); Inventor einstein = @@ -404,7 +375,7 @@ public void testConstructors() throws Exception { // 7.5.8 @Test - public void testVariables() throws Exception { + void variables() throws Exception { Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("newName", "Mike Tesla"); @@ -416,9 +387,9 @@ public void testVariables() throws Exception { assertThat(tesla.getFoo()).isEqualTo("Mike Tesla"); } - @SuppressWarnings("unchecked") @Test - public void testSpecialVariables() throws Exception { + @SuppressWarnings("unchecked") + void specialVariables() throws Exception { // create an array of integers List primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17); @@ -435,7 +406,7 @@ public void testSpecialVariables() throws Exception { // 7.5.9 @Test - public void testFunctions() throws Exception { + void functions() throws Exception { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.registerFunction("reverseString", StringUtils.class.getDeclaredMethod("reverseString", String.class)); @@ -447,7 +418,7 @@ public void testFunctions() throws Exception { // 7.5.10 @Test - public void testTernary() throws Exception { + void ternary() throws Exception { String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class); assertThat(falseString).isEqualTo("falseExp"); @@ -468,9 +439,9 @@ public void testTernary() throws Exception { // 7.5.11 - @SuppressWarnings("unchecked") @Test - public void testSelection() throws Exception { + @SuppressWarnings("unchecked") + void selection() throws Exception { StandardEvaluationContext societyContext = new StandardEvaluationContext(); societyContext.setRootObject(new IEEE()); List list = (List) parser.parseExpression("Members2.?[nationality == 'Serbian']").getValue(societyContext); @@ -481,7 +452,7 @@ public void testSelection() throws Exception { // 7.5.12 @Test - public void testTemplating() throws Exception { + void templating() throws Exception { String randomPhrase = parser.parseExpression("random number is ${T(java.lang.Math).random()}", new TemplatedParserContext()).getValue(String.class); assertThat(randomPhrase.startsWith("random number")).isTrue(); @@ -505,14 +476,39 @@ public boolean isTemplate() { } } + static class IEEE { + private String name; + + public Inventor[] Members = new Inventor[1]; + public List Members2 = new ArrayList(); + public Map officers = new HashMap<>(); + + public List> reverse = new ArrayList<>(); + + @SuppressWarnings("unchecked") + IEEE() { + officers.put("president",pupin); + List linv = new ArrayList(); + linv.add(tesla); + officers.put("advisors",linv); + Members2.add(tesla); + Members2.add(pupin); + + reverse.add(officers); + } + + public boolean isMember(String name) { + return true; + } + + public String getName() { return name; } + public void setName(String n) { this.name = n; } + } + static class StringUtils { public static String reverseString(String input) { - StringBuilder backwards = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { - backwards.append(input.charAt(input.length() - 1 - i)); - } - return backwards.toString(); + return new StringBuilder(input).reverse().toString(); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java index 4bb3f7da0cc3..ebeebcf7e0ce 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java @@ -30,7 +30,7 @@ *

  • The root context object is an Inventor instance {@link Inventor} * */ -public class TestScenarioCreator { +class TestScenarioCreator { public static StandardEvaluationContext getTestEvaluationContext() { StandardEvaluationContext testContext = new StandardEvaluationContext(); From a7d5fbfbea1b1c4948a8f8cc5541f90bd2a1d972 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 1 Mar 2022 13:27:37 +0100 Subject: [PATCH 097/998] Fix log messages for init/destroy method registration --- .../annotation/InitDestroyAnnotationBeanPostProcessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java index f76a03b8dc9d..97e65f155816 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.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. @@ -302,7 +302,7 @@ public void checkConfigMembers(RootBeanDefinition beanDefinition) { beanDefinition.registerExternallyManagedInitMethod(methodIdentifier); checkedInitMethods.add(element); if (logger.isTraceEnabled()) { - logger.trace("Registered init method on class [" + this.targetClass.getName() + "]: " + element); + logger.trace("Registered init method on class [" + this.targetClass.getName() + "]: " + methodIdentifier); } } } @@ -313,7 +313,7 @@ public void checkConfigMembers(RootBeanDefinition beanDefinition) { beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier); checkedDestroyMethods.add(element); if (logger.isTraceEnabled()) { - logger.trace("Registered destroy method on class [" + this.targetClass.getName() + "]: " + element); + logger.trace("Registered destroy method on class [" + this.targetClass.getName() + "]: " + methodIdentifier); } } } From f96872404d9ab495a2821955b649fec9a28f92ed Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 1 Mar 2022 15:02:57 +0100 Subject: [PATCH 098/998] Ensure private init/destroy method is invoked only once Closes gh-28083 --- .../AbstractAutowireCapableBeanFactory.java | 4 +- .../support/DisposableBeanAdapter.java | 5 +- .../factory/support/RootBeanDefinition.java | 63 ++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) 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 7a94a91c86a8..c1e764b22a5f 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 @@ -1844,7 +1844,7 @@ protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBea throws Throwable { boolean isInitializingBean = (bean instanceof InitializingBean); - if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { + if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) { if (logger.isTraceEnabled()) { logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } @@ -1868,7 +1868,7 @@ protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBea String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && - !mbd.isExternallyManagedInitMethod(initMethodName)) { + !mbd.hasAnyExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } 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 b5fdef4dd813..4317ae901c9b 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 @@ -52,6 +52,7 @@ * @author Juergen Hoeller * @author Costin Leau * @author Stephane Nicoll + * @author Sam Brannen * @since 2.0 * @see AbstractBeanFactory * @see org.springframework.beans.factory.DisposableBean @@ -109,12 +110,12 @@ public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition be this.beanName = beanName; this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed(); this.invokeDisposableBean = (bean instanceof DisposableBean && - !beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME)); + !beanDefinition.hasAnyExternallyManagedDestroyMethod(DESTROY_METHOD_NAME)); String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition); if (destroyMethodName != null && !(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodName)) && - !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { + !beanDefinition.hasAnyExternallyManagedDestroyMethod(destroyMethodName)) { this.invokeAutoCloseable = (bean instanceof AutoCloseable && CLOSE_METHOD_NAME.equals(destroyMethodName)); if (!this.invokeAutoCloseable) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index f4fdafa3767e..13638768efa1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.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. @@ -49,6 +49,7 @@ * * @author Rod Johnson * @author Juergen Hoeller + * @author Sam Brannen * @see GenericBeanDefinition * @see ChildBeanDefinition */ @@ -479,6 +480,36 @@ public boolean isExternallyManagedInitMethod(String initMethod) { } } + /** + * Determine if the given method name indicates an externally managed + * initialization method, regardless of method visibility. + *

    In contrast to {@link #isExternallyManagedInitMethod(String)}, this + * method also returns {@code true} if there is a {@code private} external + * init method that has been + * {@linkplain #registerExternallyManagedInitMethod(String) registered} + * using a fully qualified method name instead of a simple method name. + * @since 5.3.17 + */ + boolean hasAnyExternallyManagedInitMethod(String initMethod) { + synchronized (this.postProcessingLock) { + if (isExternallyManagedInitMethod(initMethod)) { + return true; + } + if (this.externallyManagedInitMethods != null) { + for (String candidate : this.externallyManagedInitMethods) { + int indexOfDot = candidate.lastIndexOf("."); + if (indexOfDot >= 0) { + String methodName = candidate.substring(indexOfDot + 1); + if (methodName.equals(initMethod)) { + return true; + } + } + } + } + return false; + } + } + /** * Return all externally managed initialization methods (as an immutable Set). * @since 5.3.11 @@ -513,6 +544,36 @@ public boolean isExternallyManagedDestroyMethod(String destroyMethod) { } } + /** + * Determine if the given method name indicates an externally managed + * destruction method, regardless of method visibility. + *

    In contrast to {@link #isExternallyManagedDestroyMethod(String)}, this + * method also returns {@code true} if there is a {@code private} external + * destroy method that has been + * {@linkplain #registerExternallyManagedDestroyMethod(String) registered} + * using a fully qualified method name instead of a simple method name. + * @since 5.3.17 + */ + boolean hasAnyExternallyManagedDestroyMethod(String destroyMethod) { + synchronized (this.postProcessingLock) { + if (isExternallyManagedDestroyMethod(destroyMethod)) { + return true; + } + if (this.externallyManagedDestroyMethods != null) { + for (String candidate : this.externallyManagedDestroyMethods) { + int indexOfDot = candidate.lastIndexOf("."); + if (indexOfDot >= 0) { + String methodName = candidate.substring(indexOfDot + 1); + if (methodName.equals(destroyMethod)) { + return true; + } + } + } + } + return false; + } + } + /** * Return all externally managed destruction methods (as an immutable Set). * @since 5.3.11 From af14eea1ef76576acd07ba90cd0a656bd6b31969 Mon Sep 17 00:00:00 2001 From: Vikey Chen Date: Tue, 1 Mar 2022 00:49:43 +0800 Subject: [PATCH 099/998] Introduce tests for gh-28083 --- .../Spr3775InitDestroyLifecycleTests.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java index 0fb249595f43..8fb07dc306f9 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java @@ -149,6 +149,30 @@ public void testJsr250AnnotationsWithShadowedMethods() { bean.destroyMethods); } + @Test + public void testJsr250AnnotationsWithCustomPrivateInitDestroyMethods() { + Class beanClass = CustomAnnotatedPrivateInitDestroyBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); + CustomAnnotatedPrivateInitDestroyBean bean = + (CustomAnnotatedPrivateInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); + assertMethodOrdering("init-methods", Arrays.asList("privateCustomInit1","afterPropertiesSet"), bean.initMethods); + beanFactory.destroySingletons(); + assertMethodOrdering("destroy-methods", Arrays.asList("privateCustomDestroy1","destroy"), bean.destroyMethods); + } + + @Test + public void testJsr250AnnotationsWithCustomSameMethodNames() { + Class beanClass = CustomAnnotatedPrivateSameNameInitDestroyBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); + CustomAnnotatedPrivateSameNameInitDestroyBean bean = + (CustomAnnotatedPrivateSameNameInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); + assertMethodOrdering("init-methods", + Arrays.asList("privateCustomInit1","afterPropertiesSet","sameNameCustomInit1"), bean.initMethods); + beanFactory.destroySingletons(); + assertMethodOrdering("destroy-methods", + Arrays.asList("privateCustomDestroy1","destroy","sameNameCustomDestroy1"), bean.destroyMethods); + } + @Test public void testAllLifecycleMechanismsAtOnce() { final Class beanClass = AllInOneBean.class; @@ -205,6 +229,31 @@ public void customDestroy() throws Exception { } } + public static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean{ + + @PostConstruct + private void customInit1() throws Exception { + this.initMethods.add("privateCustomInit1"); + } + + @PreDestroy + private void customDestroy1() throws Exception { + this.destroyMethods.add("privateCustomDestroy1"); + } + + } + + public static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean { + + private void customInit1() throws Exception { + this.initMethods.add("sameNameCustomInit1"); + } + + private void customDestroy1() throws Exception { + this.destroyMethods.add("sameNameCustomDestroy1"); + } + + } public static class CustomInitializingDisposableBean extends CustomInitDestroyBean implements InitializingBean, DisposableBean { From a524857bd548dbb3245771a374e9ef609dfae7c0 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 1 Mar 2022 15:22:34 +0100 Subject: [PATCH 100/998] Fix init/destroy lifecycle method tests See gh-28083 --- .../Spr3775InitDestroyLifecycleTests.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java index 8fb07dc306f9..4de6652e65a3 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.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. @@ -155,9 +155,9 @@ public void testJsr250AnnotationsWithCustomPrivateInitDestroyMethods() { DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); CustomAnnotatedPrivateInitDestroyBean bean = (CustomAnnotatedPrivateInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering("init-methods", Arrays.asList("privateCustomInit1","afterPropertiesSet"), bean.initMethods); + assertMethodOrdering(beanClass, "init-methods", Arrays.asList("@PostConstruct.privateCustomInit1", "afterPropertiesSet"), bean.initMethods); beanFactory.destroySingletons(); - assertMethodOrdering("destroy-methods", Arrays.asList("privateCustomDestroy1","destroy"), bean.destroyMethods); + assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("@PreDestroy.privateCustomDestroy1", "destroy"), bean.destroyMethods); } @Test @@ -166,11 +166,11 @@ public void testJsr250AnnotationsWithCustomSameMethodNames() { DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); CustomAnnotatedPrivateSameNameInitDestroyBean bean = (CustomAnnotatedPrivateSameNameInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering("init-methods", - Arrays.asList("privateCustomInit1","afterPropertiesSet","sameNameCustomInit1"), bean.initMethods); + assertMethodOrdering(beanClass, "init-methods", + Arrays.asList("@PostConstruct.privateCustomInit1", "@PostConstruct.sameNameCustomInit1", "afterPropertiesSet"), bean.initMethods); beanFactory.destroySingletons(); - assertMethodOrdering("destroy-methods", - Arrays.asList("privateCustomDestroy1","destroy","sameNameCustomDestroy1"), bean.destroyMethods); + assertMethodOrdering(beanClass, "destroy-methods", + Arrays.asList("@PreDestroy.sameNameCustomDestroy1", "@PreDestroy.privateCustomDestroy1", "destroy"), bean.destroyMethods); } @Test @@ -229,28 +229,32 @@ public void customDestroy() throws Exception { } } - public static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean{ + public static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean { @PostConstruct private void customInit1() throws Exception { - this.initMethods.add("privateCustomInit1"); + this.initMethods.add("@PostConstruct.privateCustomInit1"); } @PreDestroy private void customDestroy1() throws Exception { - this.destroyMethods.add("privateCustomDestroy1"); + this.destroyMethods.add("@PreDestroy.privateCustomDestroy1"); } } public static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean { + @PostConstruct + @SuppressWarnings("unused") private void customInit1() throws Exception { - this.initMethods.add("sameNameCustomInit1"); + this.initMethods.add("@PostConstruct.sameNameCustomInit1"); } + @PreDestroy + @SuppressWarnings("unused") private void customDestroy1() throws Exception { - this.destroyMethods.add("sameNameCustomDestroy1"); + this.destroyMethods.add("@PreDestroy.sameNameCustomDestroy1"); } } From dcdea986f6316344705aa0b70d79a4c60ab96121 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 1 Mar 2022 15:43:25 +0100 Subject: [PATCH 101/998] Polish init/destroy lifecycle method tests See gh-28083 --- .../InitDestroyMethodLifecycleTests.java | 279 +++++++++++++++ .../Spr3775InitDestroyLifecycleTests.java | 325 ------------------ 2 files changed, 279 insertions(+), 325 deletions(-) create mode 100644 spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java delete mode 100644 spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java diff --git a/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java new file mode 100644 index 000000000000..034452bc0512 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java @@ -0,0 +1,279 @@ +/* + * 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.context.annotation; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests which verify expected init and destroy bean lifecycle + * behavior as requested in + * SPR-3775. + * + *

    Specifically, combinations of the following are tested: + *

      + *
    • {@link InitializingBean} & {@link DisposableBean} interfaces
    • + *
    • Custom {@link RootBeanDefinition#getInitMethodName() init} & + * {@link RootBeanDefinition#getDestroyMethodName() destroy} methods
    • + *
    • JSR 250's {@link javax.annotation.PostConstruct @PostConstruct} & + * {@link javax.annotation.PreDestroy @PreDestroy} annotations
    • + *
    + * + * @author Sam Brannen + * @since 2.5 + */ +class InitDestroyMethodLifecycleTests { + + @Test + void initDestroyMethods() { + Class beanClass = InitDestroyBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "afterPropertiesSet", "destroy"); + InitDestroyBean bean = beanFactory.getBean(InitDestroyBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("afterPropertiesSet"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("destroy"); + } + + @Test + void initializingDisposableInterfaces() { + Class beanClass = CustomInitializingDisposableBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", "customDestroy"); + CustomInitializingDisposableBean bean = beanFactory.getBean(CustomInitializingDisposableBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("afterPropertiesSet", "customInit"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("destroy", "customDestroy"); + } + + @Test + void initializingDisposableInterfacesWithShadowedMethods() { + Class beanClass = InitializingDisposableWithShadowedMethodsBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "afterPropertiesSet", "destroy"); + InitializingDisposableWithShadowedMethodsBean bean = beanFactory.getBean(InitializingDisposableWithShadowedMethodsBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("InitializingBean.afterPropertiesSet"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("DisposableBean.destroy"); + } + + @Test + void jsr250Annotations() { + Class beanClass = CustomAnnotatedInitDestroyBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", "customDestroy"); + CustomAnnotatedInitDestroyBean bean = beanFactory.getBean(CustomAnnotatedInitDestroyBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("postConstruct", "afterPropertiesSet", "customInit"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("preDestroy", "destroy", "customDestroy"); + } + + @Test + void jsr250AnnotationsWithShadowedMethods() { + Class beanClass = CustomAnnotatedInitDestroyWithShadowedMethodsBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", "customDestroy"); + CustomAnnotatedInitDestroyWithShadowedMethodsBean bean = beanFactory.getBean(CustomAnnotatedInitDestroyWithShadowedMethodsBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("@PostConstruct.afterPropertiesSet", "customInit"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("@PreDestroy.destroy", "customDestroy"); + } + + @Test + void jsr250AnnotationsWithCustomPrivateInitDestroyMethods() { + Class beanClass = CustomAnnotatedPrivateInitDestroyBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); + CustomAnnotatedPrivateInitDestroyBean bean = beanFactory.getBean(CustomAnnotatedPrivateInitDestroyBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("@PostConstruct.privateCustomInit1", "afterPropertiesSet"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("@PreDestroy.privateCustomDestroy1", "destroy"); + } + + @Test + void jsr250AnnotationsWithCustomSameMethodNames() { + Class beanClass = CustomAnnotatedPrivateSameNameInitDestroyBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); + CustomAnnotatedPrivateSameNameInitDestroyBean bean = beanFactory.getBean(CustomAnnotatedPrivateSameNameInitDestroyBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("@PostConstruct.privateCustomInit1", "@PostConstruct.sameNameCustomInit1", "afterPropertiesSet"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("@PreDestroy.sameNameCustomDestroy1", "@PreDestroy.privateCustomDestroy1", "destroy"); + } + + @Test + void allLifecycleMechanismsAtOnce() { + Class beanClass = AllInOneBean.class; + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "afterPropertiesSet", "destroy"); + AllInOneBean bean = beanFactory.getBean(AllInOneBean.class); + assertThat(bean.initMethods).as("init-methods").containsExactly("afterPropertiesSet"); + beanFactory.destroySingletons(); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("destroy"); + } + + + private static DefaultListableBeanFactory createBeanFactoryAndRegisterBean(Class beanClass, + String initMethodName, String destroyMethodName) { + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); + beanDefinition.setInitMethodName(initMethodName); + beanDefinition.setDestroyMethodName(destroyMethodName); + beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); + beanFactory.registerBeanDefinition("lifecycleTestBean", beanDefinition); + return beanFactory; + } + + + static class InitDestroyBean { + + final List initMethods = new ArrayList<>(); + final List destroyMethods = new ArrayList<>(); + + + public void afterPropertiesSet() throws Exception { + this.initMethods.add("afterPropertiesSet"); + } + + public void destroy() throws Exception { + this.destroyMethods.add("destroy"); + } + } + + static class InitializingDisposableWithShadowedMethodsBean extends InitDestroyBean implements + InitializingBean, DisposableBean { + + @Override + public void afterPropertiesSet() throws Exception { + this.initMethods.add("InitializingBean.afterPropertiesSet"); + } + + @Override + public void destroy() throws Exception { + this.destroyMethods.add("DisposableBean.destroy"); + } + } + + + static class CustomInitDestroyBean { + + final List initMethods = new ArrayList<>(); + final List destroyMethods = new ArrayList<>(); + + public void customInit() throws Exception { + this.initMethods.add("customInit"); + } + + public void customDestroy() throws Exception { + this.destroyMethods.add("customDestroy"); + } + } + + static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean { + + @PostConstruct + private void customInit1() throws Exception { + this.initMethods.add("@PostConstruct.privateCustomInit1"); + } + + @PreDestroy + private void customDestroy1() throws Exception { + this.destroyMethods.add("@PreDestroy.privateCustomDestroy1"); + } + } + + static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean { + + @PostConstruct + @SuppressWarnings("unused") + private void customInit1() throws Exception { + this.initMethods.add("@PostConstruct.sameNameCustomInit1"); + } + + @PreDestroy + @SuppressWarnings("unused") + private void customDestroy1() throws Exception { + this.destroyMethods.add("@PreDestroy.sameNameCustomDestroy1"); + } + } + + static class CustomInitializingDisposableBean extends CustomInitDestroyBean + implements InitializingBean, DisposableBean { + + @Override + public void afterPropertiesSet() throws Exception { + this.initMethods.add("afterPropertiesSet"); + } + + @Override + public void destroy() throws Exception { + this.destroyMethods.add("destroy"); + } + } + + static class CustomAnnotatedInitDestroyBean extends CustomInitializingDisposableBean { + + @PostConstruct + public void postConstruct() throws Exception { + this.initMethods.add("postConstruct"); + } + + @PreDestroy + public void preDestroy() throws Exception { + this.destroyMethods.add("preDestroy"); + } + } + + static class CustomAnnotatedInitDestroyWithShadowedMethodsBean extends CustomInitializingDisposableBean { + + @PostConstruct + @Override + public void afterPropertiesSet() throws Exception { + this.initMethods.add("@PostConstruct.afterPropertiesSet"); + } + + @PreDestroy + @Override + public void destroy() throws Exception { + this.destroyMethods.add("@PreDestroy.destroy"); + } + } + + static class AllInOneBean implements InitializingBean, DisposableBean { + + final List initMethods = new ArrayList<>(); + final List destroyMethods = new ArrayList<>(); + + @PostConstruct + @Override + public void afterPropertiesSet() throws Exception { + this.initMethods.add("afterPropertiesSet"); + } + + @PreDestroy + @Override + public void destroy() throws Exception { + this.destroyMethods.add("destroy"); + } + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java deleted file mode 100644 index 4de6652e65a3..000000000000 --- a/spring-context/src/test/java/org/springframework/context/annotation/Spr3775InitDestroyLifecycleTests.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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.context.annotation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.util.ObjectUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - *

    - * JUnit-3.8-based unit test which verifies expected init and - * destroy bean lifecycle behavior as requested in SPR-3775. - *

    - *

    - * Specifically, combinations of the following are tested: - *

    - *
      - *
    • {@link InitializingBean} & {@link DisposableBean} interfaces
    • - *
    • Custom {@link RootBeanDefinition#getInitMethodName() init} & - * {@link RootBeanDefinition#getDestroyMethodName() destroy} methods
    • - *
    • JSR 250's {@link javax.annotation.PostConstruct @PostConstruct} & - * {@link javax.annotation.PreDestroy @PreDestroy} annotations
    • - *
    - * - * @author Sam Brannen - * @since 2.5 - */ -public class Spr3775InitDestroyLifecycleTests { - - private static final Log logger = LogFactory.getLog(Spr3775InitDestroyLifecycleTests.class); - - /** LIFECYCLE_TEST_BEAN. */ - private static final String LIFECYCLE_TEST_BEAN = "lifecycleTestBean"; - - - private void debugMethods(Class clazz, String category, List methodNames) { - if (logger.isDebugEnabled()) { - logger.debug(clazz.getSimpleName() + ": " + category + ": " + methodNames); - } - } - - private void assertMethodOrdering(Class clazz, String category, List expectedMethods, - List actualMethods) { - debugMethods(clazz, category, actualMethods); - assertThat(ObjectUtils.nullSafeEquals(expectedMethods, actualMethods)).as("Verifying " + category + ": expected<" + expectedMethods + "> but got<" + actualMethods + ">.").isTrue(); - } - - private DefaultListableBeanFactory createBeanFactoryAndRegisterBean(final Class beanClass, - final String initMethodName, final String destroyMethodName) { - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); - beanDefinition.setInitMethodName(initMethodName); - beanDefinition.setDestroyMethodName(destroyMethodName); - beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); - beanFactory.registerBeanDefinition(LIFECYCLE_TEST_BEAN, beanDefinition); - return beanFactory; - } - - @Test - public void testInitDestroyMethods() { - final Class beanClass = InitDestroyBean.class; - final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, - "afterPropertiesSet", "destroy"); - final InitDestroyBean bean = (InitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy"), bean.destroyMethods); - } - - @Test - public void testInitializingDisposableInterfaces() { - final Class beanClass = CustomInitializingDisposableBean.class; - final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", - "customDestroy"); - final CustomInitializingDisposableBean bean = (CustomInitializingDisposableBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet", "customInit"), - bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy", "customDestroy"), - bean.destroyMethods); - } - - @Test - public void testInitializingDisposableInterfacesWithShadowedMethods() { - final Class beanClass = InitializingDisposableWithShadowedMethodsBean.class; - final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, - "afterPropertiesSet", "destroy"); - final InitializingDisposableWithShadowedMethodsBean bean = (InitializingDisposableWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", Arrays.asList("InitializingBean.afterPropertiesSet"), - bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("DisposableBean.destroy"), bean.destroyMethods); - } - - @Test - public void testJsr250Annotations() { - final Class beanClass = CustomAnnotatedInitDestroyBean.class; - final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", - "customDestroy"); - final CustomAnnotatedInitDestroyBean bean = (CustomAnnotatedInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", Arrays.asList("postConstruct", "afterPropertiesSet", - "customInit"), bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("preDestroy", "destroy", "customDestroy"), - bean.destroyMethods); - } - - @Test - public void testJsr250AnnotationsWithShadowedMethods() { - final Class beanClass = CustomAnnotatedInitDestroyWithShadowedMethodsBean.class; - final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", - "customDestroy"); - final CustomAnnotatedInitDestroyWithShadowedMethodsBean bean = (CustomAnnotatedInitDestroyWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", - Arrays.asList("@PostConstruct.afterPropertiesSet", "customInit"), bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("@PreDestroy.destroy", "customDestroy"), - bean.destroyMethods); - } - - @Test - public void testJsr250AnnotationsWithCustomPrivateInitDestroyMethods() { - Class beanClass = CustomAnnotatedPrivateInitDestroyBean.class; - DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); - CustomAnnotatedPrivateInitDestroyBean bean = - (CustomAnnotatedPrivateInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", Arrays.asList("@PostConstruct.privateCustomInit1", "afterPropertiesSet"), bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("@PreDestroy.privateCustomDestroy1", "destroy"), bean.destroyMethods); - } - - @Test - public void testJsr250AnnotationsWithCustomSameMethodNames() { - Class beanClass = CustomAnnotatedPrivateSameNameInitDestroyBean.class; - DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); - CustomAnnotatedPrivateSameNameInitDestroyBean bean = - (CustomAnnotatedPrivateSameNameInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", - Arrays.asList("@PostConstruct.privateCustomInit1", "@PostConstruct.sameNameCustomInit1", "afterPropertiesSet"), bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", - Arrays.asList("@PreDestroy.sameNameCustomDestroy1", "@PreDestroy.privateCustomDestroy1", "destroy"), bean.destroyMethods); - } - - @Test - public void testAllLifecycleMechanismsAtOnce() { - final Class beanClass = AllInOneBean.class; - final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, - "afterPropertiesSet", "destroy"); - final AllInOneBean bean = (AllInOneBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); - assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods); - beanFactory.destroySingletons(); - assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy"), bean.destroyMethods); - } - - - public static class InitDestroyBean { - - final List initMethods = new ArrayList<>(); - final List destroyMethods = new ArrayList<>(); - - - public void afterPropertiesSet() throws Exception { - this.initMethods.add("afterPropertiesSet"); - } - - public void destroy() throws Exception { - this.destroyMethods.add("destroy"); - } - } - - public static class InitializingDisposableWithShadowedMethodsBean extends InitDestroyBean implements - InitializingBean, DisposableBean { - - @Override - public void afterPropertiesSet() throws Exception { - this.initMethods.add("InitializingBean.afterPropertiesSet"); - } - - @Override - public void destroy() throws Exception { - this.destroyMethods.add("DisposableBean.destroy"); - } - } - - - public static class CustomInitDestroyBean { - - final List initMethods = new ArrayList<>(); - final List destroyMethods = new ArrayList<>(); - - public void customInit() throws Exception { - this.initMethods.add("customInit"); - } - - public void customDestroy() throws Exception { - this.destroyMethods.add("customDestroy"); - } - } - - public static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean { - - @PostConstruct - private void customInit1() throws Exception { - this.initMethods.add("@PostConstruct.privateCustomInit1"); - } - - @PreDestroy - private void customDestroy1() throws Exception { - this.destroyMethods.add("@PreDestroy.privateCustomDestroy1"); - } - - } - - public static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean { - - @PostConstruct - @SuppressWarnings("unused") - private void customInit1() throws Exception { - this.initMethods.add("@PostConstruct.sameNameCustomInit1"); - } - - @PreDestroy - @SuppressWarnings("unused") - private void customDestroy1() throws Exception { - this.destroyMethods.add("@PreDestroy.sameNameCustomDestroy1"); - } - - } - - public static class CustomInitializingDisposableBean extends CustomInitDestroyBean - implements InitializingBean, DisposableBean { - - @Override - public void afterPropertiesSet() throws Exception { - this.initMethods.add("afterPropertiesSet"); - } - - @Override - public void destroy() throws Exception { - this.destroyMethods.add("destroy"); - } - } - - - public static class CustomAnnotatedInitDestroyBean extends CustomInitializingDisposableBean { - - @PostConstruct - public void postConstruct() throws Exception { - this.initMethods.add("postConstruct"); - } - - @PreDestroy - public void preDestroy() throws Exception { - this.destroyMethods.add("preDestroy"); - } - } - - - public static class CustomAnnotatedInitDestroyWithShadowedMethodsBean extends CustomInitializingDisposableBean { - - @PostConstruct - @Override - public void afterPropertiesSet() throws Exception { - this.initMethods.add("@PostConstruct.afterPropertiesSet"); - } - - @PreDestroy - @Override - public void destroy() throws Exception { - this.destroyMethods.add("@PreDestroy.destroy"); - } - } - - - public static class AllInOneBean implements InitializingBean, DisposableBean { - - final List initMethods = new ArrayList<>(); - final List destroyMethods = new ArrayList<>(); - - @Override - @PostConstruct - public void afterPropertiesSet() throws Exception { - this.initMethods.add("afterPropertiesSet"); - } - - @Override - @PreDestroy - public void destroy() throws Exception { - this.destroyMethods.add("destroy"); - } - } - -} From d67034f99b5dad82e2daecc1e31ce99b1c551593 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 1 Mar 2022 16:18:46 +0100 Subject: [PATCH 102/998] Document semantics for externally managed init/destroy methods This commit introduces Javadoc to explain the difference between init/destroy method names when such methods are private, namely that a private method is registered via its qualified method name; whereas, a non-private method is registered via its simple name. See gh-28083 --- .../factory/support/RootBeanDefinition.java | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 13638768efa1..f93c08f3d548 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -437,7 +437,7 @@ public void registerExternallyManagedConfigMember(Member configMember) { } /** - * Check whether the given method or field is an externally managed configuration member. + * Determine if the given method or field is an externally managed configuration member. */ public boolean isExternallyManagedConfigMember(Member configMember) { synchronized (this.postProcessingLock) { @@ -447,7 +447,7 @@ public boolean isExternallyManagedConfigMember(Member configMember) { } /** - * Return all externally managed configuration methods and fields (as an immutable Set). + * Get all externally managed configuration methods and fields (as an immutable Set). * @since 5.3.11 */ public Set getExternallyManagedConfigMembers() { @@ -459,7 +459,15 @@ public Set getExternallyManagedConfigMembers() { } /** - * Register an externally managed configuration initialization method. + * Register an externally managed configuration initialization method — + * for example, a method annotated with JSR-250's + * {@link javax.annotation.PostConstruct} annotation. + *

    The supplied {@code initMethod} may be the + * {@linkplain Method#getName() simple method name} for non-private methods or the + * {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method) + * qualified method name} for {@code private} methods. A qualified name is + * necessary for {@code private} methods in order to disambiguate between + * multiple private methods with the same name within a class hierarchy. */ public void registerExternallyManagedInitMethod(String initMethod) { synchronized (this.postProcessingLock) { @@ -471,7 +479,10 @@ public void registerExternallyManagedInitMethod(String initMethod) { } /** - * Check whether the given method name indicates an externally managed initialization method. + * Determine if the given method name indicates an externally managed + * initialization method. + *

    See {@link #registerExternallyManagedInitMethod} for details + * regarding the format for the supplied {@code initMethod}. */ public boolean isExternallyManagedInitMethod(String initMethod) { synchronized (this.postProcessingLock) { @@ -484,10 +495,10 @@ public boolean isExternallyManagedInitMethod(String initMethod) { * Determine if the given method name indicates an externally managed * initialization method, regardless of method visibility. *

    In contrast to {@link #isExternallyManagedInitMethod(String)}, this - * method also returns {@code true} if there is a {@code private} external - * init method that has been + * method also returns {@code true} if there is a {@code private} externally + * managed initialization method that has been * {@linkplain #registerExternallyManagedInitMethod(String) registered} - * using a fully qualified method name instead of a simple method name. + * using a qualified method name instead of a simple method name. * @since 5.3.17 */ boolean hasAnyExternallyManagedInitMethod(String initMethod) { @@ -512,6 +523,8 @@ boolean hasAnyExternallyManagedInitMethod(String initMethod) { /** * Return all externally managed initialization methods (as an immutable Set). + *

    See {@link #registerExternallyManagedInitMethod} for details + * regarding the format for the initialization methods in the returned set. * @since 5.3.11 */ public Set getExternallyManagedInitMethods() { @@ -523,7 +536,15 @@ public Set getExternallyManagedInitMethods() { } /** - * Register an externally managed configuration destruction method. + * Register an externally managed configuration destruction method — + * for example, a method annotated with JSR-250's + * {@link javax.annotation.PreDestroy} annotation. + *

    The supplied {@code destroyMethod} may be the + * {@linkplain Method#getName() simple method name} for non-private methods or the + * {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method) + * qualified method name} for {@code private} methods. A qualified name is + * necessary for {@code private} methods in order to disambiguate between + * multiple private methods with the same name within a class hierarchy. */ public void registerExternallyManagedDestroyMethod(String destroyMethod) { synchronized (this.postProcessingLock) { @@ -535,7 +556,10 @@ public void registerExternallyManagedDestroyMethod(String destroyMethod) { } /** - * Check whether the given method name indicates an externally managed destruction method. + * Determine if the given method name indicates an externally managed + * destruction method. + *

    See {@link #registerExternallyManagedDestroyMethod} for details + * regarding the format for the supplied {@code destroyMethod}. */ public boolean isExternallyManagedDestroyMethod(String destroyMethod) { synchronized (this.postProcessingLock) { @@ -548,10 +572,10 @@ public boolean isExternallyManagedDestroyMethod(String destroyMethod) { * Determine if the given method name indicates an externally managed * destruction method, regardless of method visibility. *

    In contrast to {@link #isExternallyManagedDestroyMethod(String)}, this - * method also returns {@code true} if there is a {@code private} external - * destroy method that has been + * method also returns {@code true} if there is a {@code private} externally + * managed destruction method that has been * {@linkplain #registerExternallyManagedDestroyMethod(String) registered} - * using a fully qualified method name instead of a simple method name. + * using a qualified method name instead of a simple method name. * @since 5.3.17 */ boolean hasAnyExternallyManagedDestroyMethod(String destroyMethod) { @@ -575,7 +599,9 @@ boolean hasAnyExternallyManagedDestroyMethod(String destroyMethod) { } /** - * Return all externally managed destruction methods (as an immutable Set). + * Get all externally managed destruction methods (as an immutable Set). + *

    See {@link #registerExternallyManagedDestroyMethod} for details + * regarding the format for the destruction methods in the returned set. * @since 5.3.11 */ public Set getExternallyManagedDestroyMethods() { From 67b91b239091afe169045cf0dafa800aaa5884aa Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 1 Mar 2022 19:10:33 +0100 Subject: [PATCH 103/998] Polish RollbackRuleTests See gh-28098 --- .../interceptor/RollbackRuleTests.java | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleTests.java b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleTests.java index 8445de81bb51..47f73dee2743 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleTests.java @@ -37,67 +37,81 @@ class RollbackRuleTests { @Test - void foundImmediatelyWithString() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName()); - assertThat(rr.getDepth(new Exception())).isEqualTo(0); + void constructorArgumentMustBeThrowableClassWithNonThrowableType() { + assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute(Object.class)); } @Test - void foundImmediatelyWithClass() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class); - assertThat(rr.getDepth(new Exception())).isEqualTo(0); + void constructorArgumentMustBeThrowableClassWithNullThrowableType() { + assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((Class) null)); + } + + @Test + void constructorArgumentMustBeStringWithNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((String) null)); } @Test void notFound() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(java.io.IOException.class.getName()); + RollbackRuleAttribute rr = new RollbackRuleAttribute(IOException.class); assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(-1); } @Test - void ancestry() { + void foundImmediatelyWithString() { RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName()); - // Exception -> Runtime -> NestedRuntime -> MyRuntimeException - assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(3); + assertThat(rr.getDepth(new Exception())).isEqualTo(0); } @Test - void alwaysTrueForThrowable() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class.getName()); - assertThat(rr.getDepth(new MyRuntimeException(""))).isGreaterThan(0); - assertThat(rr.getDepth(new IOException())).isGreaterThan(0); - assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0); - assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0); + void foundImmediatelyWithClass() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class); + assertThat(rr.getDepth(new Exception())).isEqualTo(0); } @Test - void ctorArgMustBeAThrowableClassWithNonThrowableType() { - assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute(Object.class)); + void foundInSuperclassHierarchy() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class); + // Exception -> RuntimeException -> NestedRuntimeException -> MyRuntimeException + assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(3); } @Test - void ctorArgMustBeAThrowableClassWithNullThrowableType() { - assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((Class) null)); + void alwaysFoundForThrowable() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class); + assertThat(rr.getDepth(new MyRuntimeException(""))).isGreaterThan(0); + assertThat(rr.getDepth(new IOException())).isGreaterThan(0); + assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0); + assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0); } @Test - void ctorArgExceptionStringNameVersionWithNull() { - assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((String) null)); + void foundNestedExceptionInEnclosingException() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class); + assertThat(rr.getDepth(new EnclosingException.NestedException())).isEqualTo(0); } @Test - void foundEnclosedExceptionWithEnclosingException() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class); - assertThat(rr.getDepth(new EnclosingException.EnclosedException())).isEqualTo(0); + void foundWhenNameOfExceptionThrownStartsWithTheNameOfTheRegisteredExceptionType() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(MyException.class); + assertThat(rr.getDepth(new MyException2())).isEqualTo(0); } + @SuppressWarnings("serial") static class EnclosingException extends RuntimeException { @SuppressWarnings("serial") - static class EnclosedException extends RuntimeException { - + static class NestedException extends RuntimeException { } } + static class MyException extends RuntimeException { + } + + // Name intentionally starts with MyException (including package) but does + // NOT extend MyException. + static class MyException2 extends RuntimeException { + } + } From 25aa295c2c78e5d4047bf2f4fa1229c2ad0862d8 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 2 Mar 2022 17:25:37 +0100 Subject: [PATCH 104/998] Rename test class to adhere to conventions --- .../{RollbackRuleTests.java => RollbackRuleAttributeTests.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename spring-tx/src/test/java/org/springframework/transaction/interceptor/{RollbackRuleTests.java => RollbackRuleAttributeTests.java} (99%) diff --git a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleTests.java b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java similarity index 99% rename from spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleTests.java rename to spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java index 47f73dee2743..488ca914518a 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java @@ -34,7 +34,7 @@ * @author Sam Brannen * @since 09.04.2003 */ -class RollbackRuleTests { +class RollbackRuleAttributeTests { @Test void constructorArgumentMustBeThrowableClassWithNonThrowableType() { From 340f41af6d52b8a780ffc813727333ad10247382 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 2 Mar 2022 17:28:45 +0100 Subject: [PATCH 105/998] Suppress warnings in Gradle build --- .../transaction/interceptor/RollbackRuleAttributeTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java index 488ca914518a..fd05ff675531 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java @@ -106,11 +106,13 @@ static class NestedException extends RuntimeException { } } + @SuppressWarnings("serial") static class MyException extends RuntimeException { } // Name intentionally starts with MyException (including package) but does // NOT extend MyException. + @SuppressWarnings("serial") static class MyException2 extends RuntimeException { } From b3e5f86277e73c91990a85d23779509225085d63 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 3 Mar 2022 16:20:13 +0100 Subject: [PATCH 106/998] Polish rollback rule support --- .../interceptor/RollbackRuleAttribute.java | 78 +++++----- .../interceptor/MyRuntimeException.java | 8 +- .../RollbackRuleAttributeTests.java | 139 ++++++++++++------ .../RuleBasedTransactionAttributeTests.java | 32 ++-- .../TransactionAttributeEditorTests.java | 74 ++++------ 5 files changed, 188 insertions(+), 143 deletions(-) diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java index 07a59cc1131b..4c3d4ec53c23 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.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. @@ -22,13 +22,13 @@ import org.springframework.util.Assert; /** - * Rule determining whether or not a given exception (and any subclasses) - * should cause a rollback. + * Rule determining whether or not a given exception should cause a rollback. * *

    Multiple such rules can be applied to determine whether a transaction * should commit or rollback after an exception has been thrown. * * @author Rod Johnson + * @author Sam Brannen * @since 09.04.2003 * @see NoRollbackRuleAttribute */ @@ -36,7 +36,7 @@ public class RollbackRuleAttribute implements Serializable{ /** - * The {@link RollbackRuleAttribute rollback rule} for + * The {@linkplain RollbackRuleAttribute rollback rule} for * {@link RuntimeException RuntimeExceptions}. */ public static final RollbackRuleAttribute ROLLBACK_ON_RUNTIME_EXCEPTIONS = @@ -48,30 +48,31 @@ public class RollbackRuleAttribute implements Serializable{ * This way does multiple string comparisons, but how often do we decide * whether to roll back a transaction following an exception? */ - private final String exceptionName; + private final String exceptionPattern; /** - * Create a new instance of the {@code RollbackRuleAttribute} class. + * Create a new instance of the {@code RollbackRuleAttribute} class + * for the given {@code exceptionType}. *

    This is the preferred way to construct a rollback rule that matches - * the supplied {@link Exception} class, its subclasses, and its nested classes. - * @param clazz throwable class; must be {@link Throwable} or a subclass + * the supplied exception type, its subclasses, and its nested classes. + * @param exceptionType exception type; must be {@link Throwable} or a subclass * of {@code Throwable} - * @throws IllegalArgumentException if the supplied {@code clazz} is + * @throws IllegalArgumentException if the supplied {@code exceptionType} is * not a {@code Throwable} type or is {@code null} */ - public RollbackRuleAttribute(Class clazz) { - Assert.notNull(clazz, "'clazz' cannot be null"); - if (!Throwable.class.isAssignableFrom(clazz)) { + public RollbackRuleAttribute(Class exceptionType) { + Assert.notNull(exceptionType, "'exceptionType' cannot be null"); + if (!Throwable.class.isAssignableFrom(exceptionType)) { throw new IllegalArgumentException( - "Cannot construct rollback rule from [" + clazz.getName() + "]: it's not a Throwable"); + "Cannot construct rollback rule from [" + exceptionType.getName() + "]: it's not a Throwable"); } - this.exceptionName = clazz.getName(); + this.exceptionPattern = exceptionType.getName(); } /** * Create a new instance of the {@code RollbackRuleAttribute} class - * for the given {@code exceptionName}. + * for the given {@code exceptionPattern}. *

    This can be a substring, with no wildcard support at present. A value * of "ServletException" would match * {@code javax.servlet.ServletException} and subclasses, for example. @@ -79,40 +80,49 @@ public RollbackRuleAttribute(Class clazz) { * whether to include package information (which is not mandatory). For * example, "Exception" will match nearly anything, and will probably hide * other rules. "java.lang.Exception" would be correct if "Exception" was - * meant to define a rule for all checked exceptions. With more unusual + * meant to define a rule for all checked exceptions. With more unique * exception names such as "BaseBusinessException" there's no need to use a * fully package-qualified name. - * @param exceptionName the exception name pattern; can also be a fully + * @param exceptionPattern the exception name pattern; can also be a fully * package-qualified class name - * @throws IllegalArgumentException if the supplied - * {@code exceptionName} is {@code null} or empty + * @throws IllegalArgumentException if the supplied {@code exceptionPattern} + * is {@code null} or empty */ - public RollbackRuleAttribute(String exceptionName) { - Assert.hasText(exceptionName, "'exceptionName' cannot be null or empty"); - this.exceptionName = exceptionName; + public RollbackRuleAttribute(String exceptionPattern) { + Assert.hasText(exceptionPattern, "'exceptionPattern' cannot be null or empty"); + this.exceptionPattern = exceptionPattern; } /** - * Return the pattern for the exception name. + * Get the configured exception name pattern that this rule uses for matching. + * @see #getDepth(Throwable) */ public String getExceptionName() { - return this.exceptionName; + return this.exceptionPattern; } /** - * Return the depth of the superclass matching. - *

    {@code 0} means {@code ex} matches exactly. Returns - * {@code -1} if there is no match. Otherwise, returns depth with the - * lowest depth winning. + * Return the depth of the superclass matching, with the following semantics. + *

      + *
    • {@code -1} means this rule does not match the supplied {@code exception}.
    • + *
    • {@code 0} means this rule matches the supplied {@code exception} exactly.
    • + *
    • Any other positive value means this rule matches the supplied {@code exception} + * within the superclass hierarchy, where the value is the number of levels in the + * class hierarchy between the supplied {@code exception} and the exception against + * which this rule matches directly.
    • + *
    + *

    When comparing roll back rules that match against a given exception, a rule + * with a lower matching depth wins. For example, a direct match ({@code depth == 0}) + * wins over a match in the superclass hierarchy ({@code depth > 0}). */ - public int getDepth(Throwable ex) { - return getDepth(ex.getClass(), 0); + public int getDepth(Throwable exception) { + return getDepth(exception.getClass(), 0); } private int getDepth(Class exceptionClass, int depth) { - if (exceptionClass.getName().contains(this.exceptionName)) { + if (exceptionClass.getName().contains(this.exceptionPattern)) { // Found it! return depth; } @@ -133,17 +143,17 @@ public boolean equals(@Nullable Object other) { return false; } RollbackRuleAttribute rhs = (RollbackRuleAttribute) other; - return this.exceptionName.equals(rhs.exceptionName); + return this.exceptionPattern.equals(rhs.exceptionPattern); } @Override public int hashCode() { - return this.exceptionName.hashCode(); + return this.exceptionPattern.hashCode(); } @Override public String toString() { - return "RollbackRuleAttribute with pattern [" + this.exceptionName + "]"; + return "RollbackRuleAttribute with pattern [" + this.exceptionPattern + "]"; } } diff --git a/spring-tx/src/test/java/org/springframework/transaction/interceptor/MyRuntimeException.java b/spring-tx/src/test/java/org/springframework/transaction/interceptor/MyRuntimeException.java index 80affe6bc24f..5b512e1eb8e8 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/interceptor/MyRuntimeException.java +++ b/spring-tx/src/test/java/org/springframework/transaction/interceptor/MyRuntimeException.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,13 @@ */ @SuppressWarnings("serial") class MyRuntimeException extends NestedRuntimeException { + + public MyRuntimeException() { + super(""); + } + public MyRuntimeException(String msg) { super(msg); } + } diff --git a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java index fd05ff675531..f210659af3b6 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java @@ -18,6 +18,7 @@ import java.io.IOException; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.FatalBeanException; @@ -36,65 +37,105 @@ */ class RollbackRuleAttributeTests { - @Test - void constructorArgumentMustBeThrowableClassWithNonThrowableType() { - assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute(Object.class)); - } + @Nested + class ExceptionPatternTests { - @Test - void constructorArgumentMustBeThrowableClassWithNullThrowableType() { - assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((Class) null)); - } + @Test + void constructorPreconditions() { + assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((String) null)); + } - @Test - void constructorArgumentMustBeStringWithNull() { - assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((String) null)); - } + @Test + void notFound() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(IOException.class.getName()); + assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(-1); + } - @Test - void notFound() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(IOException.class); - assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(-1); - } + @Test + void alwaysFoundForThrowable() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class.getName()); + assertThat(rr.getDepth(new MyRuntimeException())).isGreaterThan(0); + assertThat(rr.getDepth(new IOException())).isGreaterThan(0); + assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0); + assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0); + } - @Test - void foundImmediatelyWithString() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName()); - assertThat(rr.getDepth(new Exception())).isEqualTo(0); - } + @Test + void foundImmediatelyWhenDirectMatch() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName()); + assertThat(rr.getDepth(new Exception())).isEqualTo(0); + } - @Test - void foundImmediatelyWithClass() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class); - assertThat(rr.getDepth(new Exception())).isEqualTo(0); - } + @Test + void foundImmediatelyWhenExceptionThrownIsNestedTypeOfRegisteredException() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class.getName()); + assertThat(rr.getDepth(new EnclosingException.NestedException())).isEqualTo(0); + } - @Test - void foundInSuperclassHierarchy() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class); - // Exception -> RuntimeException -> NestedRuntimeException -> MyRuntimeException - assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(3); - } + @Test + void foundImmediatelyWhenNameOfExceptionThrownStartsWithNameOfRegisteredException() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(MyException.class.getName()); + assertThat(rr.getDepth(new MyException2())).isEqualTo(0); + } - @Test - void alwaysFoundForThrowable() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class); - assertThat(rr.getDepth(new MyRuntimeException(""))).isGreaterThan(0); - assertThat(rr.getDepth(new IOException())).isGreaterThan(0); - assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0); - assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0); - } + @Test + void foundInSuperclassHierarchy() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName()); + // Exception -> RuntimeException -> NestedRuntimeException -> MyRuntimeException + assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(3); + } - @Test - void foundNestedExceptionInEnclosingException() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class); - assertThat(rr.getDepth(new EnclosingException.NestedException())).isEqualTo(0); } - @Test - void foundWhenNameOfExceptionThrownStartsWithTheNameOfTheRegisteredExceptionType() { - RollbackRuleAttribute rr = new RollbackRuleAttribute(MyException.class); - assertThat(rr.getDepth(new MyException2())).isEqualTo(0); + @Nested + class ExceptionTypeTests { + + @Test + void constructorPreconditions() { + assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute(Object.class)); + assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((Class) null)); + } + + @Test + void notFound() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(IOException.class); + assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(-1); + } + + @Test + void alwaysFoundForThrowable() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class); + assertThat(rr.getDepth(new MyRuntimeException())).isGreaterThan(0); + assertThat(rr.getDepth(new IOException())).isGreaterThan(0); + assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0); + assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0); + } + + @Test + void foundImmediatelyWhenDirectMatch() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class); + assertThat(rr.getDepth(new Exception())).isEqualTo(0); + } + + @Test + void foundImmediatelyWhenExceptionThrownIsNestedTypeOfRegisteredException() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class); + assertThat(rr.getDepth(new EnclosingException.NestedException())).isEqualTo(0); + } + + @Test + void foundImmediatelyWhenNameOfExceptionThrownStartsWithNameOfRegisteredException() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(MyException.class); + assertThat(rr.getDepth(new MyException2())).isEqualTo(0); + } + + @Test + void foundInSuperclassHierarchy() { + RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class); + // Exception -> RuntimeException -> NestedRuntimeException -> MyRuntimeException + assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(3); + } + } diff --git a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttributeTests.java b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttributeTests.java index 8aa9815e7c94..3f4cffe2b824 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttributeTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttributeTests.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. @@ -35,13 +35,13 @@ * @author Chris Beams * @since 09.04.2003 */ -public class RuleBasedTransactionAttributeTests { +class RuleBasedTransactionAttributeTests { @Test - public void testDefaultRule() { + void defaultRule() { RuleBasedTransactionAttribute rta = new RuleBasedTransactionAttribute(); assertThat(rta.rollbackOn(new RuntimeException())).isTrue(); - assertThat(rta.rollbackOn(new MyRuntimeException(""))).isTrue(); + assertThat(rta.rollbackOn(new MyRuntimeException())).isTrue(); assertThat(rta.rollbackOn(new Exception())).isFalse(); assertThat(rta.rollbackOn(new IOException())).isFalse(); } @@ -50,20 +50,20 @@ public void testDefaultRule() { * Test one checked exception that should roll back. */ @Test - public void testRuleForRollbackOnChecked() { + void ruleForRollbackOnChecked() { List list = new ArrayList<>(); list.add(new RollbackRuleAttribute(IOException.class.getName())); RuleBasedTransactionAttribute rta = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED, list); assertThat(rta.rollbackOn(new RuntimeException())).isTrue(); - assertThat(rta.rollbackOn(new MyRuntimeException(""))).isTrue(); + assertThat(rta.rollbackOn(new MyRuntimeException())).isTrue(); assertThat(rta.rollbackOn(new Exception())).isFalse(); // Check that default behaviour is overridden assertThat(rta.rollbackOn(new IOException())).isTrue(); } @Test - public void testRuleForCommitOnUnchecked() { + void ruleForCommitOnUnchecked() { List list = new ArrayList<>(); list.add(new NoRollbackRuleAttribute(MyRuntimeException.class.getName())); list.add(new RollbackRuleAttribute(IOException.class.getName())); @@ -71,14 +71,14 @@ public void testRuleForCommitOnUnchecked() { assertThat(rta.rollbackOn(new RuntimeException())).isTrue(); // Check default behaviour is overridden - assertThat(rta.rollbackOn(new MyRuntimeException(""))).isFalse(); + assertThat(rta.rollbackOn(new MyRuntimeException())).isFalse(); assertThat(rta.rollbackOn(new Exception())).isFalse(); // Check that default behaviour is overridden assertThat(rta.rollbackOn(new IOException())).isTrue(); } @Test - public void testRuleForSelectiveRollbackOnCheckedWithString() { + void ruleForSelectiveRollbackOnCheckedWithString() { List l = new ArrayList<>(); l.add(new RollbackRuleAttribute(java.rmi.RemoteException.class.getName())); RuleBasedTransactionAttribute rta = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED, l); @@ -86,7 +86,7 @@ public void testRuleForSelectiveRollbackOnCheckedWithString() { } @Test - public void testRuleForSelectiveRollbackOnCheckedWithClass() { + void ruleForSelectiveRollbackOnCheckedWithClass() { List l = Collections.singletonList(new RollbackRuleAttribute(RemoteException.class)); RuleBasedTransactionAttribute rta = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED, l); doTestRuleForSelectiveRollbackOnChecked(rta); @@ -105,7 +105,7 @@ private void doTestRuleForSelectiveRollbackOnChecked(RuleBasedTransactionAttribu * when Exception prompts a rollback. */ @Test - public void testRuleForCommitOnSubclassOfChecked() { + void ruleForCommitOnSubclassOfChecked() { List list = new ArrayList<>(); // Note that it's important to ensure that we have this as // a FQN: otherwise it will match everything! @@ -120,20 +120,20 @@ public void testRuleForCommitOnSubclassOfChecked() { } @Test - public void testRollbackNever() { + void rollbackNever() { List list = new ArrayList<>(); list.add(new NoRollbackRuleAttribute("Throwable")); RuleBasedTransactionAttribute rta = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED, list); assertThat(rta.rollbackOn(new Throwable())).isFalse(); assertThat(rta.rollbackOn(new RuntimeException())).isFalse(); - assertThat(rta.rollbackOn(new MyRuntimeException(""))).isFalse(); + assertThat(rta.rollbackOn(new MyRuntimeException())).isFalse(); assertThat(rta.rollbackOn(new Exception())).isFalse(); assertThat(rta.rollbackOn(new IOException())).isFalse(); } @Test - public void testToStringMatchesEditor() { + void toStringMatchesEditor() { List list = new ArrayList<>(); list.add(new NoRollbackRuleAttribute("Throwable")); RuleBasedTransactionAttribute rta = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED, list); @@ -144,7 +144,7 @@ public void testToStringMatchesEditor() { assertThat(rta.rollbackOn(new Throwable())).isFalse(); assertThat(rta.rollbackOn(new RuntimeException())).isFalse(); - assertThat(rta.rollbackOn(new MyRuntimeException(""))).isFalse(); + assertThat(rta.rollbackOn(new MyRuntimeException())).isFalse(); assertThat(rta.rollbackOn(new Exception())).isFalse(); assertThat(rta.rollbackOn(new IOException())).isFalse(); } @@ -153,7 +153,7 @@ public void testToStringMatchesEditor() { * See this forum post. */ @Test - public void testConflictingRulesToDetermineExactContract() { + void conflictingRulesToDetermineExactContract() { List list = new ArrayList<>(); list.add(new NoRollbackRuleAttribute(MyBusinessWarningException.class)); list.add(new RollbackRuleAttribute(MyBusinessException.class)); diff --git a/spring-tx/src/test/java/org/springframework/transaction/interceptor/TransactionAttributeEditorTests.java b/spring-tx/src/test/java/org/springframework/transaction/interceptor/TransactionAttributeEditorTests.java index 4614f2648a3c..3bfd82bb679b 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/interceptor/TransactionAttributeEditorTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/interceptor/TransactionAttributeEditorTests.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,6 @@ package org.springframework.transaction.interceptor; - import java.io.IOException; import org.junit.jupiter.api.Test; @@ -27,72 +26,65 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** - * Tests to check conversion from String to TransactionAttribute. + * Tests to check conversion from String to TransactionAttribute using + * a {@link TransactionAttributeEditor}. * * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams * @since 26.04.2003 */ -public class TransactionAttributeEditorTests { +class TransactionAttributeEditorTests { + + private final TransactionAttributeEditor pe = new TransactionAttributeEditor(); + @Test - public void testNull() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void nullText() { pe.setAsText(null); - TransactionAttribute ta = (TransactionAttribute) pe.getValue(); - assertThat(ta == null).isTrue(); + assertThat(pe.getValue()).isNull(); } @Test - public void testEmptyString() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void emptyString() { pe.setAsText(""); - TransactionAttribute ta = (TransactionAttribute) pe.getValue(); - assertThat(ta == null).isTrue(); + assertThat(pe.getValue()).isNull(); } @Test - public void testValidPropagationCodeOnly() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void validPropagationCodeOnly() { pe.setAsText("PROPAGATION_REQUIRED"); TransactionAttribute ta = (TransactionAttribute) pe.getValue(); - assertThat(ta != null).isTrue(); - assertThat(ta.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED).isTrue(); - assertThat(ta.getIsolationLevel() == TransactionDefinition.ISOLATION_DEFAULT).isTrue(); - boolean condition = !ta.isReadOnly(); - assertThat(condition).isTrue(); + assertThat(ta).isNotNull(); + assertThat(ta.getPropagationBehavior()).isEqualTo(TransactionDefinition.PROPAGATION_REQUIRED); + assertThat(ta.getIsolationLevel()).isEqualTo(TransactionDefinition.ISOLATION_DEFAULT); + assertThat(ta.isReadOnly()).isFalse(); } @Test - public void testInvalidPropagationCodeOnly() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void invalidPropagationCodeOnly() { // should have failed with bogus propagation code - assertThatIllegalArgumentException().isThrownBy(() -> - pe.setAsText("XXPROPAGATION_REQUIRED")); + assertThatIllegalArgumentException().isThrownBy(() -> pe.setAsText("XXPROPAGATION_REQUIRED")); } @Test - public void testValidPropagationCodeAndIsolationCode() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void validPropagationCodeAndIsolationCode() { pe.setAsText("PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED"); TransactionAttribute ta = (TransactionAttribute) pe.getValue(); - assertThat(ta != null).isTrue(); - assertThat(ta.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED).isTrue(); - assertThat(ta.getIsolationLevel() == TransactionDefinition.ISOLATION_READ_UNCOMMITTED).isTrue(); + assertThat(ta).isNotNull(); + assertThat(ta.getPropagationBehavior()).isEqualTo(TransactionDefinition.PROPAGATION_REQUIRED); + assertThat(ta.getIsolationLevel()).isEqualTo(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); } @Test - public void testValidPropagationAndIsolationCodesAndInvalidRollbackRule() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void validPropagationAndIsolationCodesAndInvalidRollbackRule() { // should fail with bogus rollback rule - assertThatIllegalArgumentException().isThrownBy(() -> - pe.setAsText("PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED,XXX")); + assertThatIllegalArgumentException() + .isThrownBy(() -> pe.setAsText("PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED,XXX")); } @Test - public void testValidPropagationCodeAndIsolationCodeAndRollbackRules1() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void validPropagationCodeAndIsolationCodeAndRollbackRules1() { pe.setAsText("PROPAGATION_MANDATORY,ISOLATION_REPEATABLE_READ,timeout_10,-IOException,+MyRuntimeException"); TransactionAttribute ta = (TransactionAttribute) pe.getValue(); assertThat(ta).isNotNull(); @@ -104,13 +96,11 @@ public void testValidPropagationCodeAndIsolationCodeAndRollbackRules1() { assertThat(ta.rollbackOn(new Exception())).isFalse(); // Check for our bizarre customized rollback rules assertThat(ta.rollbackOn(new IOException())).isTrue(); - boolean condition = !ta.rollbackOn(new MyRuntimeException("")); - assertThat(condition).isTrue(); + assertThat(ta.rollbackOn(new MyRuntimeException())).isFalse(); } @Test - public void testValidPropagationCodeAndIsolationCodeAndRollbackRules2() { - TransactionAttributeEditor pe = new TransactionAttributeEditor(); + void validPropagationCodeAndIsolationCodeAndRollbackRules2() { pe.setAsText("+IOException,readOnly,ISOLATION_READ_COMMITTED,-MyRuntimeException,PROPAGATION_SUPPORTS"); TransactionAttribute ta = (TransactionAttribute) pe.getValue(); assertThat(ta).isNotNull(); @@ -122,18 +112,17 @@ public void testValidPropagationCodeAndIsolationCodeAndRollbackRules2() { assertThat(ta.rollbackOn(new Exception())).isFalse(); // Check for our bizarre customized rollback rules assertThat(ta.rollbackOn(new IOException())).isFalse(); - assertThat(ta.rollbackOn(new MyRuntimeException(""))).isTrue(); + assertThat(ta.rollbackOn(new MyRuntimeException())).isTrue(); } @Test - public void testDefaultTransactionAttributeToString() { + void defaultTransactionAttributeToString() { DefaultTransactionAttribute source = new DefaultTransactionAttribute(); source.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); source.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); source.setTimeout(10); source.setReadOnly(true); - TransactionAttributeEditor pe = new TransactionAttributeEditor(); pe.setAsText(source.toString()); TransactionAttribute ta = (TransactionAttribute) pe.getValue(); assertThat(source).isEqualTo(ta); @@ -151,7 +140,7 @@ public void testDefaultTransactionAttributeToString() { } @Test - public void testRuleBasedTransactionAttributeToString() { + void ruleBasedTransactionAttributeToString() { RuleBasedTransactionAttribute source = new RuleBasedTransactionAttribute(); source.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); source.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); @@ -160,7 +149,6 @@ public void testRuleBasedTransactionAttributeToString() { source.getRollbackRules().add(new RollbackRuleAttribute("IllegalArgumentException")); source.getRollbackRules().add(new NoRollbackRuleAttribute("IllegalStateException")); - TransactionAttributeEditor pe = new TransactionAttributeEditor(); pe.setAsText(source.toString()); TransactionAttribute ta = (TransactionAttribute) pe.getValue(); assertThat(source).isEqualTo(ta); From fa3130d71631f6de36a34d0a5cdf1a37bece3102 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 4 Mar 2022 16:39:11 +0100 Subject: [PATCH 107/998] Document that TX rollback rules may result in unintentional matches Closes gh-28125 --- .../transaction/annotation/Transactional.java | 105 +++++++++++----- .../interceptor/NoRollbackRuleAttribute.java | 25 ++-- .../interceptor/RollbackRuleAttribute.java | 47 ++++--- src/docs/asciidoc/data-access.adoc | 119 +++++++++++++----- 4 files changed, 209 insertions(+), 87 deletions(-) diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java index 42537572223f..935933af4228 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.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. @@ -37,17 +37,57 @@ * Transaction Management * section of the reference manual. * - *

    This annotation type is generally directly comparable to Spring's + *

    This annotation is generally directly comparable to Spring's * {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute} * class, and in fact {@link AnnotationTransactionAttributeSource} will directly - * convert the data to the latter class, so that Spring's transaction support code - * does not have to know about annotations. If no custom rollback rules apply, - * the transaction will roll back on {@link RuntimeException} and {@link Error} - * but not on checked exceptions. + * convert this annotation's attributes to properties in {@code RuleBasedTransactionAttribute}, + * so that Spring's transaction support code does not have to know about annotations. * - *

    For specific information about the semantics of this annotation's attributes, - * consult the {@link org.springframework.transaction.TransactionDefinition} and - * {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs. + *

    Attribute Semantics

    + * + *

    If no custom rollback rules are configured in this annotation, the transaction + * will roll back on {@link RuntimeException} and {@link Error} but not on checked + * exceptions. + * + *

    Rollback rules determine if a transaction should be rolled back when a given + * exception is thrown, and the rules are based on patterns. A pattern can be a + * fully qualified class name or a substring of a fully qualified class name for + * an exception type (which must be a subclass of {@code Throwable}), with no + * wildcard support at present. For example, a value of + * {@code "javax.servlet.ServletException"} or {@code "ServletException"} will + * match {@code javax.servlet.ServletException} and its subclasses. + * + *

    Rollback rules may be configured via {@link #rollbackFor}/{@link #noRollbackFor} + * and {@link #rollbackForClassName}/{@link #noRollbackForClassName}, which allow + * patterns to be specified as {@link Class} references or {@linkplain String + * strings}, respectively. When an exception type is specified as a class reference + * its fully qualified name will be used as the pattern. Consequently, + * {@code @Transactional(rollbackFor = example.CustomException.class)} is equivalent + * to {@code @Transactional(rollbackForClassName = "example.CustomException")}. + * + *

    WARNING: You must carefully consider how specific the pattern + * is and whether to include package information (which isn't mandatory). For example, + * {@code "Exception"} will match nearly anything and will probably hide other + * rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"} + * were meant to define a rule for all checked exceptions. With more unique + * exception names such as {@code "BaseBusinessException"} there is likely no + * need to use the fully qualified class name for the exception pattern. Furthermore, + * rollback rules may result in unintentional matches for similarly named exceptions + * and nested classes. This is due to the fact that a thrown exception is considered + * to be a match for a given rollback rule if the name of thrown exception contains + * the exception pattern configured for the rollback rule. For example, given a + * rule configured to match on {@code com.example.CustomException}, that rule + * would match against an exception named + * {@code com.example.CustomExceptionV2} (an exception in the same package as + * {@code CustomException} but with an additional suffix) or an exception named + * {@code com.example.CustomException$AnotherException} + * (an exception declared as a nested class in {@code CustomException}). + * + *

    For specific information about the semantics of other attributes in this + * annotation, consult the {@link org.springframework.transaction.TransactionDefinition} + * and {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs. + * + *

    Transaction Management

    * *

    This annotation commonly works with thread-bound transactions managed by a * {@link org.springframework.transaction.PlatformTransactionManager}, exposing a @@ -167,37 +207,33 @@ boolean readOnly() default false; /** - * Defines zero (0) or more exception {@link Class classes}, which must be + * Defines zero (0) or more exception {@linkplain Class classes}, which must be * subclasses of {@link Throwable}, indicating which exception types must cause * a transaction rollback. - *

    By default, a transaction will be rolling back on {@link RuntimeException} + *

    By default, a transaction will be rolled back on {@link RuntimeException} * and {@link Error} but not on checked exceptions (business exceptions). See * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)} * for a detailed explanation. *

    This is the preferred way to construct a rollback rule (in contrast to - * {@link #rollbackForClassName}), matching the exception class and its subclasses. - *

    Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}. + * {@link #rollbackForClassName}), matching the exception type, its subclasses, + * and its nested classes. See the {@linkplain Transactional class-level javadocs} + * for further details on rollback rule semantics and warnings regarding possible + * unintentional matches. * @see #rollbackForClassName + * @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class) * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable) */ Class[] rollbackFor() default {}; /** - * Defines zero (0) or more exception names (for exceptions which must be a + * Defines zero (0) or more exception name patterns (for exceptions which must be a * subclass of {@link Throwable}), indicating which exception types must cause * a transaction rollback. - *

    This can be a substring of a fully qualified class name, with no wildcard - * support at present. For example, a value of {@code "ServletException"} would - * match {@code javax.servlet.ServletException} and its subclasses. - *

    NB: Consider carefully how specific the pattern is and whether - * to include package information (which isn't mandatory). For example, - * {@code "Exception"} will match nearly anything and will probably hide other - * rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"} - * were meant to define a rule for all checked exceptions. With more unusual - * {@link Exception} names such as {@code "BaseBusinessException"} there is no - * need to use a FQN. - *

    Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}. + *

    See the {@linkplain Transactional class-level javadocs} for further details + * on rollback rule semantics, patterns, and warnings regarding possible + * unintentional matches. * @see #rollbackFor + * @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String) * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable) */ String[] rollbackForClassName() default {}; @@ -206,23 +242,26 @@ * Defines zero (0) or more exception {@link Class Classes}, which must be * subclasses of {@link Throwable}, indicating which exception types must * not cause a transaction rollback. - *

    This is the preferred way to construct a rollback rule (in contrast - * to {@link #noRollbackForClassName}), matching the exception class and - * its subclasses. - *

    Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}. + *

    This is the preferred way to construct a rollback rule (in contrast to + * {@link #noRollbackForClassName}), matching the exception type, its subclasses, + * and its nested classes. See the {@linkplain Transactional class-level javadocs} + * for further details on rollback rule semantics and warnings regarding possible + * unintentional matches. * @see #noRollbackForClassName + * @see org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class) * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable) */ Class[] noRollbackFor() default {}; /** - * Defines zero (0) or more exception names (for exceptions which must be a + * Defines zero (0) or more exception name patterns (for exceptions which must be a * subclass of {@link Throwable}) indicating which exception types must not * cause a transaction rollback. - *

    See the description of {@link #rollbackForClassName} for further - * information on how the specified names are treated. - *

    Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}. + *

    See the {@linkplain Transactional class-level javadocs} for further details + * on rollback rule semantics, patterns, and warnings regarding possible + * unintentional matches. * @see #noRollbackFor + * @see org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String) * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable) */ String[] noRollbackForClassName() default {}; diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.java index a92e14b9ffb4..2282274a94c8 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.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. @@ -21,6 +21,7 @@ * to the {@code RollbackRuleAttribute} superclass. * * @author Rod Johnson + * @author Sam Brannen * @since 09.04.2003 */ @SuppressWarnings("serial") @@ -28,22 +29,28 @@ public class NoRollbackRuleAttribute extends RollbackRuleAttribute { /** * Create a new instance of the {@code NoRollbackRuleAttribute} class - * for the supplied {@link Throwable} class. - * @param clazz the {@code Throwable} class + * for the given {@code exceptionType}. + * @param exceptionType exception type; must be {@link Throwable} or a subclass + * of {@code Throwable} + * @throws IllegalArgumentException if the supplied {@code exceptionType} is + * not a {@code Throwable} type or is {@code null} * @see RollbackRuleAttribute#RollbackRuleAttribute(Class) */ - public NoRollbackRuleAttribute(Class clazz) { - super(clazz); + public NoRollbackRuleAttribute(Class exceptionType) { + super(exceptionType); } /** * Create a new instance of the {@code NoRollbackRuleAttribute} class - * for the supplied {@code exceptionName}. - * @param exceptionName the exception name pattern + * for the supplied {@code exceptionPattern}. + * @param exceptionPattern the exception name pattern; can also be a fully + * package-qualified class name + * @throws IllegalArgumentException if the supplied {@code exceptionPattern} + * is {@code null} or empty * @see RollbackRuleAttribute#RollbackRuleAttribute(String) */ - public NoRollbackRuleAttribute(String exceptionName) { - super(exceptionName); + public NoRollbackRuleAttribute(String exceptionPattern) { + super(exceptionPattern); } @Override diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java index 4c3d4ec53c23..a643c4c9b168 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java @@ -27,6 +27,22 @@ *

    Multiple such rules can be applied to determine whether a transaction * should commit or rollback after an exception has been thrown. * + *

    Each rule is based on an exception pattern which can be a fully qualified + * class name or a substring of a fully qualified class name for an exception + * type (which must be a subclass of {@code Throwable}), with no wildcard support + * at present. For example, a value of {@code "javax.servlet.ServletException"} + * or {@code "ServletException"} would match {@code javax.servlet.ServletException} + * and its subclasses. + * + *

    An exception pattern can be specified as a {@link Class} reference or a + * {@link String} in {@link #RollbackRuleAttribute(Class)} and + * {@link #RollbackRuleAttribute(String)}, respectively. When an exception type + * is specified as a class reference its fully qualified name will be used as the + * pattern. See the javadocs for + * {@link org.springframework.transaction.annotation.Transactional @Transactional} + * for further details on rollback rule semantics, patterns, and warnings regarding + * possible unintentional matches. + * * @author Rod Johnson * @author Sam Brannen * @since 09.04.2003 @@ -56,6 +72,10 @@ public class RollbackRuleAttribute implements Serializable{ * for the given {@code exceptionType}. *

    This is the preferred way to construct a rollback rule that matches * the supplied exception type, its subclasses, and its nested classes. + *

    See the javadocs for + * {@link org.springframework.transaction.annotation.Transactional @Transactional} + * for further details on rollback rule semantics, patterns, and warnings regarding + * possible unintentional matches. * @param exceptionType exception type; must be {@link Throwable} or a subclass * of {@code Throwable} * @throws IllegalArgumentException if the supplied {@code exceptionType} is @@ -73,16 +93,10 @@ public RollbackRuleAttribute(Class exceptionType) { /** * Create a new instance of the {@code RollbackRuleAttribute} class * for the given {@code exceptionPattern}. - *

    This can be a substring, with no wildcard support at present. A value - * of "ServletException" would match - * {@code javax.servlet.ServletException} and subclasses, for example. - *

    NB: Consider carefully how specific the pattern is, and - * whether to include package information (which is not mandatory). For - * example, "Exception" will match nearly anything, and will probably hide - * other rules. "java.lang.Exception" would be correct if "Exception" was - * meant to define a rule for all checked exceptions. With more unique - * exception names such as "BaseBusinessException" there's no need to use a - * fully package-qualified name. + *

    See the javadocs for + * {@link org.springframework.transaction.annotation.Transactional @Transactional} + * for further details on rollback rule semantics, patterns, and warnings regarding + * possible unintentional matches. * @param exceptionPattern the exception name pattern; can also be a fully * package-qualified class name * @throws IllegalArgumentException if the supplied {@code exceptionPattern} @@ -106,7 +120,7 @@ public String getExceptionName() { * Return the depth of the superclass matching, with the following semantics. *