diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc index 17783c7c066b..4f947ab57ce6 100644 --- a/CODE_OF_CONDUCT.adoc +++ b/CODE_OF_CONDUCT.adoc @@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by -contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will +contacting a project maintainer at spring-code-of-conduct@spring.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3857859a2e2b..93e430897e83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ First off, thank you for taking the time to contribute! :+1: :tada: This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating you are expected to uphold this code. -Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. +Please report unacceptable behavior to spring-code-of-conduct@spring.io. ### How to Contribute diff --git a/README.md b/README.md index b9f9671488ef..b3736166443d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre ## Code of Conduct -This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. +This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io. ## Access to Binaries diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index e3edacf40c85..eacaf4f5b871 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -1,2 +1,2 @@ org.gradle.caching=true -javaFormatVersion=0.0.41 +javaFormatVersion=0.0.42 diff --git a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java index 948219ac8032..373f74f61195 100644 --- a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java @@ -64,7 +64,7 @@ private static void configureNoHttpPlugin(Project project) { NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class); noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines")); noHttp.getSource().exclude("**/test-output/**", "**/.settings/**", - "**/.classpath", "**/.project", "**/.gradle/**"); + "**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**"); List buildFolders = List.of("bin", "build", "out"); project.allprojects(subproject -> { Path rootPath = project.getRootDir().toPath(); diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc index 77e78db6149c..eb7b1199d07c 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired-qualifiers.adoc @@ -154,30 +154,32 @@ Letting qualifier values select against target bean names, within the type-match candidates, does not require a `@Qualifier` annotation at the injection point. If there is no other resolution indicator (such as a qualifier or a primary marker), for a non-unique dependency situation, Spring matches the injection point name -(that is, the field name or parameter name) against the target bean names and chooses the -same-named candidate, if any. +(that is, the field name or parameter name) against the target bean names and chooses +the same-named candidate, if any (either by bean name or by associated alias). Since version 6.1, this requires the `-parameters` Java compiler flag to be present. +As of 6.2, the container applies fast shortcut resolution for bean name matches, +bypassing the full type matching algorithm when the parameter name matches the +bean name and no type, qualifier or primary conditions override the match. It is +therefore recommendable for your parameter names to match the target bean names. ==== -That said, if you intend to express annotation-driven injection by name, do not -primarily use `@Autowired`, even if it is capable of selecting by bean name among -type-matching candidates. Instead, use the JSR-250 `@Resource` annotation, which is -semantically defined to identify a specific target component by its unique name, with -the declared type being irrelevant for the matching process. `@Autowired` has rather -different semantics: After selecting candidate beans by type, the specified `String` +As an alternative for injection by name, consider the JSR-250 `@Resource` annotation +which is semantically defined to identify a specific target component by its unique name, +with the declared type being irrelevant for the matching process. `@Autowired` has rather +different semantics: after selecting candidate beans by type, the specified `String` qualifier value is considered within those type-selected candidates only (for example, matching an `account` qualifier against beans marked with the same qualifier label). For beans that are themselves defined as a collection, `Map`, or array type, `@Resource` is a fine solution, referring to the specific collection or array bean by unique name. -That said, as of 4.3, you can match collection, `Map`, and array types through Spring's +That said, you can match collection, `Map`, and array types through Spring's `@Autowired` type matching algorithm as well, as long as the element type information is preserved in `@Bean` return type signatures or collection inheritance hierarchies. In this case, you can use qualifier values to select among same-typed collections, as outlined in the previous paragraph. -As of 4.3, `@Autowired` also considers self references for injection (that is, references +`@Autowired` also considers self references for injection (that is, references back to the bean that is currently injected). Note that self injection is a fallback. Regular dependencies on other components always have precedence. In that sense, self references do not participate in regular candidate selection and are therefore in diff --git a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc index 9a407ea48c8f..a37dd34abac3 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc @@ -73,23 +73,37 @@ runtime (concurrently with live access to the factory) is not officially support lead to concurrent access exceptions, inconsistent state in the bean container, or both. ==== + + [[beans-definition-overriding]] == Overriding Beans -Bean overriding is happening when a bean is registered using an identifier that is -already allocated. While bean overriding is possible, it makes the configuration harder -to read and this feature will be deprecated in a future release. +Bean overriding occurs when a bean is registered using an identifier that is already +allocated. While bean overriding is possible, it makes the configuration harder to read. + +WARNING: Bean overriding will be deprecated in a future release. To disable bean overriding altogether, you can set the `allowBeanDefinitionOverriding` -to `false` on the `ApplicationContext` before it is refreshed. In such setup, an +flag to `false` on the `ApplicationContext` before it is refreshed. In such a setup, an exception is thrown if bean overriding is used. -By default, the container logs every bean overriding at `INFO` level so that you can -adapt your configuration accordingly. While not recommended, you can silence those logs -by setting the `allowBeanDefinitionOverriding` flag to `true`. +By default, the container logs every attempt to override a bean at `INFO` level so that +you can adapt your configuration accordingly. While not recommended, you can silence +those logs by setting the `allowBeanDefinitionOverriding` flag to `true`. + +.Java Configuration +**** +If you use Java Configuration, a corresponding `@Bean` method always silently overrides +a scanned bean class with the same component name as long as the return type of the +`@Bean` method matches that bean class. This simply means that the container will call +the `@Bean` factory method in favor of any pre-declared constructor on the bean class. +**** + +NOTE: We acknowledge that overriding beans in test scenarios is convenient, and there is +explicit support for this as of Spring Framework 6.2. Please refer to +xref:testing/testcontext-framework/bean-overriding.adoc[this section] for more details. + -NOTE: We acknowledge that overriding beans in a test is convenient, and there is -explicit support for this. For more details please refer to xref:testing/testcontext-framework/bean-overriding.adoc[this section]. [[beans-beanname]] == Naming Beans diff --git a/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc b/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc index f97c38c85d4c..716a0309c222 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc @@ -287,32 +287,7 @@ As the preceding example shows, a `ConstraintValidator` implementation can have You can integrate the method validation feature of Bean Validation into a Spring context through a `MethodValidationPostProcessor` bean definition: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] ----- - import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; - - @Configuration - public class AppConfig { - - @Bean - public static MethodValidationPostProcessor validationPostProcessor() { - return new MethodValidationPostProcessor(); - } - } - ----- - -XML:: -+ -[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] ----- - ----- -====== +include-code::./ApplicationConfiguration[tag=snippet,indent=0] To be eligible for Spring-driven method validation, target classes need to be annotated with Spring's `@Validated` annotation, which can optionally also declare the validation @@ -345,36 +320,7 @@ By default, `jakarta.validation.ConstraintViolationException` is raised with the you can have `MethodValidationException` raised instead with ``ConstraintViolation``s adapted to `MessageSourceResolvable` errors. To enable set the following flag: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] ----- - import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; - - @Configuration - public class AppConfig { - - @Bean - public static MethodValidationPostProcessor validationPostProcessor() { - MethodValidationPostProcessor processor = new MethodValidationPostProcessor(); - processor.setAdaptConstraintViolations(true); - return processor; - } - } - ----- - -XML:: -+ -[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] ----- - - - ----- -====== +include-code::./ApplicationConfiguration[tag=snippet,indent=0] `MethodValidationException` contains a list of ``ParameterValidationResult``s which group errors by method parameter, and each exposes a `MethodParameter`, the argument diff --git a/framework-docs/modules/ROOT/pages/index.adoc b/framework-docs/modules/ROOT/pages/index.adoc index 00f046b78886..6c0a08843f79 100644 --- a/framework-docs/modules/ROOT/pages/index.adoc +++ b/framework-docs/modules/ROOT/pages/index.adoc @@ -29,8 +29,6 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch -Copyright © 2002 - 2024 VMware, Inc. All Rights Reserved. - Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each -copy contains this Copyright Notice, whether distributed in print or electronically. +copy contains the Copyright Notice, whether distributed in print or electronically. diff --git a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc index 37d85683e684..ad47f5824dd3 100644 --- a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc +++ b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc @@ -1007,7 +1007,8 @@ method parameters: is supported for non-String values. | `@RequestAttribute` -| Provide an `Object` to add as a request attribute. Only supported by `WebClient`. +| Provide an `Object` to add as a request attribute. Only supported by `RestClient` + and `WebClient`. | `@RequestBody` | Provide the body of the request either as an Object to be serialized, or a diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc index d6de89bd344e..b70f2415bed2 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc @@ -1,32 +1,63 @@ [[spring-testing-annotation-beanoverriding-mockitobean]] = `@MockitoBean` and `@MockitoSpyBean` -`@MockitoBean` and `@MockitoSpyBean` are used on test class fields to override beans in -the test's `ApplicationContext` with a Mockito mock or spy, respectively. In the latter -case, the original bean definition is not replaced, but instead an early instance of the -bean is captured and wrapped by the spy. +`@MockitoBean` and `@MockitoSpyBean` are used on fields in test classes to override beans +in the test's `ApplicationContext` with a Mockito mock or spy, respectively. In the +latter case, the original bean definition is not replaced, but instead an early instance +of the bean is captured and wrapped by the spy. -By default, the annotated field's type is used to search for candidate definitions to -override, but note that `@Qualifier` annotations are also taken into account for the -purpose of matching. Users can also make things entirely explicit by specifying a bean -`name` in the annotation. +By default, the annotated field's type is used to search for candidate bean definitions +to override. If multiple candidates match, `@Qualifier` can be provided to narrow the +candidate to override. Alternatively, a candidate whose bean definition name matches the +name of the field will match. + +To use a by-name override rather than a by-type override, specify the `name` attribute +of the annotation. + +[WARNING] +==== +Qualifiers, including the name of the field, are used to determine if a separate +`ApplicationContext` needs to be created. If you are using this feature to mock or spy +the same bean in several tests, make sure to name the field consistently to avoid +creating unnecessary contexts. +==== Each annotation also defines Mockito-specific attributes to fine-tune the mocking details. The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE_DEFINITION` xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding]. - -It requires that at most one matching candidate definition exists if a bean name -is specified, or exactly one if no bean name is specified. +If no existing bean definition matches, a new bean definition is created on the fly. The `@MockitoSpyBean` annotation uses the `WRAP_BEAN` xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy], -and the original instance is wrapped in a Mockito spy. +and the original instance is wrapped in a Mockito spy. This strategy requires that +exactly one candidate bean definition exists. + +The following example shows how to use the default behavior of the `@MockitoBean` annotation: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + class OverrideBeanTests { + @MockitoBean // <1> + CustomService customService; -It requires that exactly one candidate definition exists. + // test case body... + } +---- +<1> Replace the bean with type `CustomService` with a Mockito `mock`. +====== + +In the example above, we are creating a mock for `CustomService`. If more than one bean +of that type exists, the bean named `customService` is considered. Otherwise, the test +will fail, and you will need to provide a qualifier of some sort to identify which of the +`CustomService` beans you want to override. If no such bean exists, a bean definition +will be created with an auto-generated bean name. -The following example shows how to configure the bean name via `@MockitoBean` and -`@MockitoSpyBean`: +The following example uses a by-name lookup, rather than a by-type lookup: [tabs] ====== @@ -35,17 +66,56 @@ Java:: [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- class OverrideBeanTests { + @MockitoBean(name = "service") // <1> + CustomService customService; + + // test case body... + + } +---- +<1> Replace the bean named `service` with a Mockito `mock`. +====== - @MockitoBean(name = "service1") // <1> - private CustomService mockService; +If no bean definition named `service` exists, one is created. - @MockitoSpyBean(name = "service2") // <2> - private CustomService spyService; // <3> +The following example shows how to use the default behavior of the `@MockitoSpyBean` annotation: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + class OverrideBeanTests { + @MockitoSpyBean // <1> + CustomService customService; // test case body... } ---- -<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class. -<2> Mark `spyService` as a Mockito spy override of bean `service2` in this test class. -<3> The fields will be injected with the Mockito mock and spy, respectively. +<1> Wrap the bean with type `CustomService` with a Mockito `spy`. +====== + +In the example above, we are wrapping the bean with type `CustomService`. If more than +one bean of that type exists, the bean named `customService` is considered. Otherwise, +the test will fail, and you will need to provide a qualifier of some sort to identify +which of the `CustomService` beans you want to spy. + +The following example uses a by-name lookup, rather than a by-type lookup: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + class OverrideBeanTests { + @MockitoSpyBean(name = "service") // <1> + CustomService customService; + + // test case body... + + } +---- +<1> Wrap the bean named `service` with a Mockito `spy`. ====== diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc index 3da3da2a4fce..8d4ea481aa2e 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc @@ -1,23 +1,60 @@ [[spring-testing-annotation-beanoverriding-testbean]] = `@TestBean` -`@TestBean` is used on a test class field to override a specific bean in the test's -`ApplicationContext` with an instance provided by a conventionally named static factory -method. +`@TestBean` is used on a field in a test class to override a specific bean in the test's +`ApplicationContext` with an instance provided by a factory method. -By default, the associated factory method name is derived from the annotated field's name, -but the annotation allows for a specific method name to be provided. +The associated factory method name is derived from the annotated field's name, or the +bean name if specified. The factory method must be `static`, accept no arguments, and +have a return type compatible with the type of the bean to override. To make things more +explicit, or if you'd rather use a different name, the annotation allows for a specific +method name to be provided. -The `@TestBean` annotation uses the `REPLACE_DEFINITION` -xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding]. +By default, the annotated field's type is used to search for candidate bean definitions +to override. If multiple candidates match, `@Qualifier` can be provided to narrow the +candidate to override. Alternatively, a candidate whose bean definition name matches the +name of the field will match. -By default, the annotated field's type is used to search for candidate definitions to override. -In that case it is required that exactly one definition matches, but note that `@Qualifier` -annotations are also taken into account for the purpose of matching. -Users can also make things entirely explicit by specifying a bean `name` in the annotation. +To use a by-name override rather than a by-type override, specify the `name` attribute +of the annotation. -The following example shows how to fully configure the `@TestBean` annotation, with -explicit values equivalent to the defaults: +[WARNING] +==== +Qualifiers, including the name of the field, are used to determine if a separate +`ApplicationContext` needs to be created. If you are using this feature to override the +same bean in several tests, make sure to name the field consistently to avoid creating +unnecessary contexts. +==== + +The following example shows how to use the default behavior of the `@TestBean` annotation: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + class OverrideBeanTests { + @TestBean // <1> + CustomService customService; + + // test case body... + + static CustomService customService() { // <2> + return new MyFakeCustomService(); + } + } +---- +<1> Mark a field for overriding the bean with type `CustomService`. +<2> The result of this static method will be used as the instance and injected into the field. +====== + +In the example above, we are overriding the bean with type `CustomService`. If more than +one bean of that type exists, the bean named `customService` is considered. Otherwise, +the test will fail, and you will need to provide a qualifier of some sort to identify +which of the `CustomService` beans you want to override. + +The following example uses a by-name lookup, rather than a by-type lookup: [tabs] ====== @@ -26,17 +63,18 @@ Java:: [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- class OverrideBeanTests { - @TestBean(name = "service", methodName = "serviceTestOverride") // <1> - private CustomService service; + @TestBean(name = "service", methodName = "createCustomService") // <1> + CustomService customService; // test case body... - private static CustomService serviceTestOverride() { // <2> + static CustomService createCustomService() { // <2> return new MyFakeCustomService(); } } ---- -<1> Mark a field for bean overriding in this test class. +<1> Mark a field for overriding the bean with name `service`, and specify that the + factory method is named `createCustomService`. <2> The result of this static method will be used as the instance and injected into the field. ====== diff --git a/framework-docs/modules/ROOT/pages/web/websocket/stomp/handle-simple-broker.adoc b/framework-docs/modules/ROOT/pages/web/websocket/stomp/handle-simple-broker.adoc index 51ae36f7713a..15efa72b1015 100644 --- a/framework-docs/modules/ROOT/pages/web/websocket/stomp/handle-simple-broker.adoc +++ b/framework-docs/modules/ROOT/pages/web/websocket/stomp/handle-simple-broker.adoc @@ -13,7 +13,7 @@ If configured with a task scheduler, the simple broker supports https://stomp.github.io/stomp-specification-1.2.html#Heart-beating[STOMP heartbeats]. To configure a scheduler, you can declare your own `TaskScheduler` bean and set it through the `MessageBrokerRegistry`. Alternatively, you can use the one that is automatically -declared in the built-in WebSocket configuration, however, you'll' need `@Lazy` to avoid +declared in the built-in WebSocket configuration, however, you'll need `@Lazy` to avoid a cycle between the built-in WebSocket configuration and your `WebSocketMessageBrokerConfigurer`. For example: diff --git a/framework-docs/src/main/java/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.java new file mode 100644 index 000000000000..c25e2ab073d3 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.core.validation.validationbeanvalidationspringmethod; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; + +// tag::snippet[] +@Configuration +public class ApplicationConfiguration { + + @Bean + public static MethodValidationPostProcessor validationPostProcessor() { + return new MethodValidationPostProcessor(); + } +} +// end::snippet[] diff --git a/framework-docs/src/main/java/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.java new file mode 100644 index 000000000000..ad18753eafe3 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.core.validation.validationbeanvalidationspringmethodexceptions; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; + +// tag::snippet[] +@Configuration +public class ApplicationConfiguration { + + @Bean + public static MethodValidationPostProcessor validationPostProcessor() { + MethodValidationPostProcessor processor = new MethodValidationPostProcessor(); + processor.setAdaptConstraintViolations(true); + return processor; + } +} +// end::snippet[] diff --git a/framework-docs/src/main/java/org/springframework/docs/web/websocket/stomp/websocketstomphandlebrokerrelayconfigure/WebSocketConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/web/websocket/stomp/websocketstomphandlebrokerrelayconfigure/WebSocketConfiguration.java index 926da7d78032..eb1b255157a7 100644 --- a/framework-docs/src/main/java/org/springframework/docs/web/websocket/stomp/websocketstomphandlebrokerrelayconfigure/WebSocketConfiguration.java +++ b/framework-docs/src/main/java/org/springframework/docs/web/websocket/stomp/websocketstomphandlebrokerrelayconfigure/WebSocketConfiguration.java @@ -23,7 +23,6 @@ import org.springframework.messaging.simp.stomp.StompReactorNettyCodec; import org.springframework.messaging.tcp.reactor.ReactorNettyTcpClient; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; // tag::snippet[] @@ -41,7 +40,7 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { private ReactorNettyTcpClient createTcpClient() { return new ReactorNettyTcpClient<>( - client -> client.addressSupplier(() -> new InetSocketAddress(0)), + client -> client.remoteAddress(() -> new InetSocketAddress(0)), new StompReactorNettyCodec()); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestBeanOverrideMetaAnnotation.java b/framework-docs/src/main/kotlin/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.kt similarity index 56% rename from spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestBeanOverrideMetaAnnotation.java rename to framework-docs/src/main/kotlin/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.kt index 49410ca315c1..be0946c8376a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestBeanOverrideMetaAnnotation.java +++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.kt @@ -14,14 +14,21 @@ * limitations under the License. */ -package org.springframework.test.context.bean.override.example; +package org.springframework.docs.core.validation.validationbeanvalidationspringmethod -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor -@Target({ElementType.FIELD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@ExampleBeanOverrideAnnotation("foo") -public @interface TestBeanOverrideMetaAnnotation { } +// tag::snippet[] +@Configuration +class ApplicationConfiguration { + + companion object { + + @Bean + @JvmStatic + fun validationPostProcessor() = MethodValidationPostProcessor() + } +} +// end::snippet[] diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.kt new file mode 100644 index 000000000000..07e383481edb --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.core.validation.validationbeanvalidationspringmethodexceptions + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor + +// tag::snippet[] +@Configuration +class ApplicationConfiguration { + + companion object { + + @Bean + @JvmStatic + fun validationPostProcessor() = MethodValidationPostProcessor().apply { + setAdaptConstraintViolations(true) + } + } +} +// end::snippet[] \ No newline at end of file diff --git a/framework-docs/src/main/resources/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.xml b/framework-docs/src/main/resources/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.xml new file mode 100644 index 000000000000..2d0fc9029411 --- /dev/null +++ b/framework-docs/src/main/resources/org/springframework/docs/core/validation/validationbeanvalidationspringmethod/ApplicationConfiguration.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/framework-docs/src/main/resources/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.xml b/framework-docs/src/main/resources/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.xml new file mode 100644 index 000000000000..c7477ce108d4 --- /dev/null +++ b/framework-docs/src/main/resources/org/springframework/docs/core/validation/validationbeanvalidationspringmethodexceptions/ApplicationConfiguration.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 534a7db1e348..839a07cbdbff 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -8,10 +8,10 @@ javaPlatform { dependencies { api(platform("com.fasterxml.jackson:jackson-bom:2.15.4")) - api(platform("io.micrometer:micrometer-bom:1.13.0")) + api(platform("io.micrometer:micrometer-bom:1.13.1")) api(platform("io.netty:netty-bom:4.1.110.Final")) api(platform("io.netty:netty5-bom:5.0.0.Alpha5")) - api(platform("io.projectreactor:reactor-bom:2024.0.0-M2")) + api(platform("io.projectreactor:reactor-bom:2024.0.0-M3")) api(platform("io.rsocket:rsocket-bom:1.1.3")) api(platform("org.apache.groovy:groovy-bom:4.0.21")) api(platform("org.apache.logging.log4j:log4j-bom:2.21.1")) @@ -31,7 +31,7 @@ dependencies { api("com.google.code.findbugs:findbugs:3.0.1") api("com.google.code.findbugs:jsr305:3.0.2") api("com.google.code.gson:gson:2.10.1") - api("com.google.protobuf:protobuf-java-util:3.25.3") + api("com.google.protobuf:protobuf-java-util:4.27.1") api("com.h2database:h2:2.2.224") api("com.jayway.jsonpath:json-path:2.9.0") api("com.rometools:rome:1.19.0") diff --git a/gradle/publications.gradle b/gradle/publications.gradle index 86e0d2221c0b..db0772caa4f0 100644 --- a/gradle/publications.gradle +++ b/gradle/publications.gradle @@ -29,7 +29,7 @@ publishing { developer { id = "jhoeller" name = "Juergen Hoeller" - email = "jhoeller@pivotal.io" + email = "juergen.hoeller@broadcom.com" } } issueManagement { diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java index 1a08bb454d87..a8c3d0990ce4 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java @@ -16,6 +16,7 @@ package org.springframework.aop.aspectj; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; @@ -82,6 +83,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware { + private static final String AJC_MAGIC = "ajc$"; + private static final Set SUPPORTED_PRIMITIVES = Set.of( PointcutPrimitive.EXECUTION, PointcutPrimitive.ARGS, @@ -99,6 +102,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut @Nullable private Class pointcutDeclarationScope; + private boolean aspectCompiledByAjc; + private String[] pointcutParameterNames = new String[0]; private Class[] pointcutParameterTypes = new Class[0]; @@ -128,7 +133,7 @@ public AspectJExpressionPointcut() { * @param paramTypes the parameter types for the pointcut */ public AspectJExpressionPointcut(Class declarationScope, String[] paramNames, Class[] paramTypes) { - this.pointcutDeclarationScope = declarationScope; + setPointcutDeclarationScope(declarationScope); if (paramNames.length != paramTypes.length) { throw new IllegalStateException( "Number of pointcut parameter names must match number of pointcut parameter types"); @@ -143,6 +148,7 @@ public AspectJExpressionPointcut(Class declarationScope, String[] paramNames, */ public void setPointcutDeclarationScope(Class pointcutDeclarationScope) { this.pointcutDeclarationScope = pointcutDeclarationScope; + this.aspectCompiledByAjc = compiledByAjc(pointcutDeclarationScope); } /** @@ -268,6 +274,11 @@ public PointcutExpression getPointcutExpression() { @Override public boolean matches(Class targetClass) { if (this.pointcutParsingFailed) { + // Pointcut parsing failed before below -> avoid trying again. + return false; + } + if (this.aspectCompiledByAjc && compiledByAjc(targetClass)) { + // ajc-compiled aspect class for ajc-compiled target class -> already weaved. return false; } @@ -523,6 +534,15 @@ private boolean containsAnnotationPointcut() { return resolveExpression().contains("@annotation"); } + private static boolean compiledByAjc(Class clazz) { + for (Field field : clazz.getDeclaredFields()) { + if (field.getName().startsWith(AJC_MAGIC)) { + return true; + } + } + return false; + } + @Override public boolean equals(@Nullable Object other) { diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java index 2ac439af1e8b..21bc248e508a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,12 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.aspectj.lang.reflect.PerClauseKind; import org.springframework.aop.Advisor; +import org.springframework.aop.framework.AopConfigException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.lang.Nullable; @@ -40,6 +43,8 @@ */ public class BeanFactoryAspectJAdvisorsBuilder { + private static final Log logger = LogFactory.getLog(BeanFactoryAspectJAdvisorsBuilder.class); + private final ListableBeanFactory beanFactory; private final AspectJAdvisorFactory advisorFactory; @@ -103,30 +108,37 @@ public List buildAspectJAdvisors() { continue; } if (this.advisorFactory.isAspect(beanType)) { - aspectNames.add(beanName); - AspectMetadata amd = new AspectMetadata(beanType, beanName); - if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { - MetadataAwareAspectInstanceFactory factory = - new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); - List classAdvisors = this.advisorFactory.getAdvisors(factory); - if (this.beanFactory.isSingleton(beanName)) { - this.advisorsCache.put(beanName, classAdvisors); + try { + AspectMetadata amd = new AspectMetadata(beanType, beanName); + if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { + MetadataAwareAspectInstanceFactory factory = + new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); + List classAdvisors = this.advisorFactory.getAdvisors(factory); + if (this.beanFactory.isSingleton(beanName)) { + this.advisorsCache.put(beanName, classAdvisors); + } + else { + this.aspectFactoryCache.put(beanName, factory); + } + advisors.addAll(classAdvisors); } else { + // Per target or per this. + if (this.beanFactory.isSingleton(beanName)) { + throw new IllegalArgumentException("Bean with name '" + beanName + + "' is a singleton, but aspect instantiation model is not singleton"); + } + MetadataAwareAspectInstanceFactory factory = + new PrototypeAspectInstanceFactory(this.beanFactory, beanName); this.aspectFactoryCache.put(beanName, factory); + advisors.addAll(this.advisorFactory.getAdvisors(factory)); } - advisors.addAll(classAdvisors); + aspectNames.add(beanName); } - else { - // Per target or per this. - if (this.beanFactory.isSingleton(beanName)) { - throw new IllegalArgumentException("Bean with name '" + beanName + - "' is a singleton, but aspect instantiation model is not singleton"); + catch (IllegalArgumentException | IllegalStateException | AopConfigException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Ignoring incompatible aspect [" + beanType.getName() + "]: " + ex); } - MetadataAwareAspectInstanceFactory factory = - new PrototypeAspectInstanceFactory(this.beanFactory, beanName); - this.aspectFactoryCache.put(beanName, factory); - advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } } diff --git a/spring-aspects/src/test/resources/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml b/spring-aspects/src/test/resources/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml index bc5e5258f825..e6c494c4f966 100644 --- a/spring-aspects/src/test/resources/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml +++ b/spring-aspects/src/test/resources/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml @@ -21,8 +21,10 @@ - + - + + + diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 475f6e47b94f..b77b8ca6f80d 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -635,7 +635,7 @@ private Object processCacheEvicts(Collection contexts, bo if (result instanceof CompletableFuture future) { return future.whenComplete((value, ex) -> { if (ex == null) { - performCacheEvicts(applicable, result); + performCacheEvicts(applicable, value); } }); } @@ -1117,7 +1117,7 @@ public Object processCacheEvicts(List contexts, @Nullable ReactiveAdapter adapter = (result != null ? this.registry.getAdapter(result.getClass()) : null); if (adapter != null) { return adapter.fromPublisher(Mono.from(adapter.toPublisher(result)) - .doOnSuccess(value -> performCacheEvicts(contexts, result))); + .doOnSuccess(value -> performCacheEvicts(contexts, value))); } return NOT_HANDLED; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index b4356d8957b6..38343c164764 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -323,7 +323,8 @@ protected final SourceClass doProcessConfigurationClass( List registerBeanConditions = collectRegisterBeanConditions(configClass); if (!registerBeanConditions.isEmpty()) { throw new ApplicationContextException( - "Component scan could not be used with conditions in REGISTER_BEAN phase: " + registerBeanConditions); + "Component scan for configuration class [%s] could not be used with conditions in REGISTER_BEAN phase: %s" + .formatted(configClass.getMetadata().getClassName(), registerBeanConditions)); } for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately @@ -360,6 +361,9 @@ protected final SourceClass doProcessConfigurationClass( // Process individual @Bean methods Set beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { + if (methodMetadata.isAnnotated("kotlin.jvm.JvmStatic") && !methodMetadata.isStatic()) { + continue; + } configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } diff --git a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java index 2bdebcc54440..ac775abbe566 100644 --- a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java @@ -98,7 +98,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor public static final String ON_REFRESH_VALUE = "onRefresh"; - private static final boolean checkpointOnRefresh = + private static boolean checkpointOnRefresh = ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(CHECKPOINT_PROPERTY_NAME)); private static final boolean exitOnRefresh = @@ -106,6 +106,8 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor private final Log logger = LogFactory.getLog(getClass()); + private final Map timeoutsForShutdownPhases = new ConcurrentHashMap<>(); + private volatile long timeoutPerShutdownPhase = 10000; private volatile boolean running; @@ -132,6 +134,37 @@ else if (checkpointOnRefresh) { } + /** + * Specify the maximum time allotted for the shutdown of each given phase + * (group of {@link SmartLifecycle} beans with the same 'phase' value). + *

In case of no specific timeout configured, the default timeout per + * shutdown phase will apply: 10000 milliseconds (10 seconds) as of 6.2. + * @param timeoutsForShutdownPhases a map of phase values (matching + * {@link SmartLifecycle#getPhase()}) and corresponding timeout values + * (in milliseconds) + * @since 6.2 + * @see SmartLifecycle#getPhase() + * @see #setTimeoutPerShutdownPhase + */ + public void setTimeoutsForShutdownPhases(Map timeoutsForShutdownPhases) { + this.timeoutsForShutdownPhases.putAll(timeoutsForShutdownPhases); + } + + /** + * Specify the maximum time allotted for the shutdown of a specific phase + * (group of {@link SmartLifecycle} beans with the same 'phase' value). + *

In case of no specific timeout configured, the default timeout per + * shutdown phase will apply: 10000 milliseconds (10 seconds) as of 6.2. + * @param phase the phase value (matching {@link SmartLifecycle#getPhase()}) + * @param timeout the corresponding timeout value (in milliseconds) + * @since 6.2 + * @see SmartLifecycle#getPhase() + * @see #setTimeoutPerShutdownPhase + */ + public void setTimeoutForShutdownPhase(int phase, long timeout) { + this.timeoutsForShutdownPhases.put(phase, timeout); + } + /** * Specify the maximum time allotted in milliseconds for the shutdown of any * phase (group of {@link SmartLifecycle} beans with the same 'phase' value). @@ -142,6 +175,11 @@ public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) { this.timeoutPerShutdownPhase = timeoutPerShutdownPhase; } + private long determineTimeout(int phase) { + Long timeout = this.timeoutsForShutdownPhases.get(phase); + return (timeout != null ? timeout : this.timeoutPerShutdownPhase); + } + @Override public void setBeanFactory(BeanFactory beanFactory) { if (!(beanFactory instanceof ConfigurableListableBeanFactory clbf)) { @@ -194,6 +232,7 @@ public void stop() { @Override public void onRefresh() { if (checkpointOnRefresh) { + checkpointOnRefresh = false; new CracDelegate().checkpointRestore(); } if (exitOnRefresh) { @@ -249,13 +288,13 @@ private void startBeans(boolean autoStartupOnly) { lifecycleBeans.forEach((beanName, bean) -> { if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) { - int phase = getPhase(bean); - phases.computeIfAbsent( - phase, - p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly) + int startupPhase = getPhase(bean); + phases.computeIfAbsent(startupPhase, + phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, autoStartupOnly) ).add(beanName, bean); } }); + if (!phases.isEmpty()) { phases.values().forEach(LifecycleGroup::start); } @@ -306,13 +345,14 @@ private boolean toBeStarted(String beanName, Lifecycle bean) { private void stopBeans() { Map lifecycleBeans = getLifecycleBeans(); Map phases = new TreeMap<>(Comparator.reverseOrder()); + lifecycleBeans.forEach((beanName, bean) -> { int shutdownPhase = getPhase(bean); - phases.computeIfAbsent( - shutdownPhase, - p -> new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false) + phases.computeIfAbsent(shutdownPhase, + phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, false) ).add(beanName, bean); }); + if (!phases.isEmpty()) { phases.values().forEach(LifecycleGroup::stop); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java index 799f87464b4a..9b6e7f7c0172 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,9 @@ package org.springframework.scheduling.config; +import java.time.Instant; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import org.springframework.lang.Nullable; @@ -25,6 +27,7 @@ * used as a return value for scheduling methods. * * @author Juergen Hoeller + * @author Brian Clozel * @since 4.3 * @see ScheduledTaskRegistrar#scheduleCronTask(CronTask) * @see ScheduledTaskRegistrar#scheduleFixedRateTask(FixedRateTask) @@ -76,6 +79,22 @@ public void cancel(boolean mayInterruptIfRunning) { } } + /** + * Return the next scheduled execution of the task, or {@code null} + * if the task has been cancelled or no new execution is scheduled. + * @since 6.2 + */ + @Nullable + public Instant nextExecution() { + if (this.future != null && !this.future.isCancelled()) { + long delay = this.future.getDelay(TimeUnit.MILLISECONDS); + if (delay > 0) { + return Instant.now().plusMillis(delay); + } + } + return null; + } + @Override public String toString() { return this.task.toString(); diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/Task.java b/spring-context/src/main/java/org/springframework/scheduling/config/Task.java index 3d9a6e79a775..d1a3373fa8d8 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/Task.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/Task.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.scheduling.config; +import java.time.Instant; + import org.springframework.util.Assert; /** @@ -24,12 +26,15 @@ * * @author Chris Beams * @author Juergen Hoeller + * @author Brian Clozel * @since 3.2 */ public class Task { private final Runnable runnable; + private TaskExecutionOutcome lastExecutionOutcome; + /** * Create a new {@code Task}. @@ -37,7 +42,8 @@ public class Task { */ public Task(Runnable runnable) { Assert.notNull(runnable, "Runnable must not be null"); - this.runnable = runnable; + this.runnable = new OutcomeTrackingRunnable(runnable); + this.lastExecutionOutcome = TaskExecutionOutcome.create(); } @@ -48,10 +54,45 @@ public Runnable getRunnable() { return this.runnable; } + /** + * Return the outcome of the last task execution. + * @since 6.2 + */ + public TaskExecutionOutcome getLastExecutionOutcome() { + return this.lastExecutionOutcome; + } @Override public String toString() { return this.runnable.toString(); } + + private class OutcomeTrackingRunnable implements Runnable { + + private final Runnable runnable; + + public OutcomeTrackingRunnable(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public void run() { + try { + Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.start(Instant.now()); + this.runnable.run(); + Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.success(); + } + catch (Throwable exc) { + Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.failure(exc); + throw exc; + } + } + + @Override + public String toString() { + return this.runnable.toString(); + } + } + } diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/TaskExecutionOutcome.java b/spring-context/src/main/java/org/springframework/scheduling/config/TaskExecutionOutcome.java new file mode 100644 index 000000000000..d4656c037cda --- /dev/null +++ b/spring-context/src/main/java/org/springframework/scheduling/config/TaskExecutionOutcome.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.scheduling.config; + +import java.time.Instant; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Outcome of a {@link Task} execution. + * @param executionTime the instant when the task execution started, {@code null} if the task has not started. + * @param status the {@link Status} of the execution outcome. + * @param throwable the exception thrown from the task execution, if any. + * @author Brian Clozel + * @since 6.2 + */ +public record TaskExecutionOutcome(@Nullable Instant executionTime, Status status, @Nullable Throwable throwable) { + + TaskExecutionOutcome start(Instant executionTime) { + return new TaskExecutionOutcome(executionTime, Status.STARTED, null); + } + + TaskExecutionOutcome success() { + Assert.state(this.executionTime != null, "Task has not been started yet"); + return new TaskExecutionOutcome(this.executionTime, Status.SUCCESS, null); + } + + TaskExecutionOutcome failure(Throwable throwable) { + Assert.state(this.executionTime != null, "Task has not been started yet"); + return new TaskExecutionOutcome(this.executionTime, Status.ERROR, throwable); + } + + static TaskExecutionOutcome create() { + return new TaskExecutionOutcome(null, Status.NONE, null); + } + + + /** + * Status of the task execution outcome. + */ + public enum Status { + /** + * The task has not been executed so far. + */ + NONE, + /** + * The task execution has been started and is ongoing. + */ + STARTED, + /** + * The task execution finished successfully. + */ + SUCCESS, + /** + * The task execution finished with an error. + */ + ERROR + } +} diff --git a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java index c67102f7da86..9d358b81cdc2 100644 --- a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java +++ b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -606,9 +606,9 @@ public CompletableFuture insertItem(TestBean item) { return CompletableFuture.completedFuture(item); } - @CacheEvict(cacheNames = "itemCache", allEntries = true) - public CompletableFuture clear() { - return CompletableFuture.completedFuture(null); + @CacheEvict(cacheNames = "itemCache", allEntries = true, condition = "#result > 0") + public CompletableFuture clear() { + return CompletableFuture.completedFuture(1); } } @@ -655,9 +655,9 @@ public Mono insertItem(TestBean item) { return Mono.just(item); } - @CacheEvict(cacheNames = "itemCache", allEntries = true) - public Mono clear() { - return Mono.empty(); + @CacheEvict(cacheNames = "itemCache", allEntries = true, condition = "#result > 0") + public Mono clear() { + return Mono.just(1); } } @@ -706,9 +706,9 @@ public Flux insertItem(String id, List item) { return Flux.fromIterable(item); } - @CacheEvict(cacheNames = "itemCache", allEntries = true) - public Flux clear() { - return Flux.empty(); + @CacheEvict(cacheNames = "itemCache", allEntries = true, condition = "#result > 0") + public Flux clear() { + return Flux.just(1); } } diff --git a/spring-context/src/test/java/org/springframework/cache/annotation/ReactiveCachingTests.java b/spring-context/src/test/java/org/springframework/cache/annotation/ReactiveCachingTests.java index 5c04f80a519e..06130951af90 100644 --- a/spring-context/src/test/java/org/springframework/cache/annotation/ReactiveCachingTests.java +++ b/spring-context/src/test/java/org/springframework/cache/annotation/ReactiveCachingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +51,9 @@ class ReactiveCachingTests { LateCacheHitDeterminationConfig.class, LateCacheHitDeterminationWithValueWrapperConfig.class}) void cacheHitDetermination(Class configClass) { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class); + + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class); ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class); Object key = new Object(); @@ -117,7 +119,9 @@ void cacheHitDetermination(Class configClass) { LateCacheHitDeterminationConfig.class, LateCacheHitDeterminationWithValueWrapperConfig.class}) void fluxCacheDoesntDependOnFirstRequest(Class configClass) { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class); + + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class); ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class); Object key = new Object(); @@ -135,6 +139,7 @@ void fluxCacheDoesntDependOnFirstRequest(Class configClass) { ctx.close(); } + @CacheConfig(cacheNames = "first") static class ReactiveCacheableService { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/Gh23206Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/Gh23206Tests.java index 08f4bbbf7641..5b4b42ed7075 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/Gh23206Tests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/Gh23206Tests.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.context.ApplicationContextException; +import org.springframework.context.annotation.Gh23206Tests.ConditionalConfiguration.NestedConfiguration; import org.springframework.context.annotation.componentscan.simple.SimpleComponent; import org.springframework.core.type.AnnotatedTypeMetadata; @@ -39,7 +40,9 @@ void componentScanShouldFailWithRegisterBeanCondition() { assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(context::refresh) .withMessageContaining(ConditionalComponentScanConfiguration.class.getName()) .havingCause().isInstanceOf(ApplicationContextException.class) - .withMessageContaining("Component scan could not be used with conditions in REGISTER_BEAN phase"); + .withMessageStartingWith("Component scan for configuration class [") + .withMessageContaining(ConditionalComponentScanConfiguration.class.getName()) + .withMessageContaining("could not be used with conditions in REGISTER_BEAN phase"); } @Test @@ -49,7 +52,9 @@ void componentScanShouldFailWithRegisterBeanConditionOnClasThatImportedIt() { assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(context::refresh) .withMessageContaining(ConditionalConfiguration.class.getName()) .havingCause().isInstanceOf(ApplicationContextException.class) - .withMessageContaining("Component scan could not be used with conditions in REGISTER_BEAN phase"); + .withMessageStartingWith("Component scan for configuration class [") + .withMessageContaining(NestedConfiguration.class.getName()) + .withMessageContaining("could not be used with conditions in REGISTER_BEAN phase"); } diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java index 07ee096636a9..b3cf04cc2698 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java @@ -21,7 +21,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; @@ -32,6 +31,7 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; +import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ParameterContext; @@ -116,11 +116,7 @@ void fixedDelayTask(@NameToClass Class beanClass, long expectedInterval) { new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks"); assertThat(fixedDelayTasks).hasSize(1); IntervalTask task = fixedDelayTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("fixedDelay"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedDelay"); assertThat(task.getInitialDelayDuration()).isZero(); assertThat(task.getIntervalDuration()).isEqualTo( Duration.ofMillis(expectedInterval < 0 ? Long.MAX_VALUE : expectedInterval)); @@ -150,11 +146,7 @@ void fixedRateTask(@NameToClass Class beanClass, long expectedInterval) { new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("fixedRate"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedRate"); assertSoftly(softly -> { softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isZero(); softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval)); @@ -185,11 +177,7 @@ void fixedRateTaskWithInitialDelay(@NameToClass Class beanClass, long expecte new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("fixedRate"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedRate"); assertSoftly(softly -> { softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isEqualTo(Duration.ofMillis(expectedInitialDelay)); softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval)); @@ -250,19 +238,11 @@ private void severalFixedRates(StaticApplicationContext context, new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); assertThat(fixedRateTasks).hasSize(2); IntervalTask task1 = fixedRateTasks.get(0); - ScheduledMethodRunnable runnable1 = (ScheduledMethodRunnable) task1.getRunnable(); - Object targetObject = runnable1.getTarget(); - Method targetMethod = runnable1.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("fixedRate"); + assertThatScheduledRunnable(task1.getRunnable()).hasTarget(target).hasMethodName("fixedRate"); assertThat(task1.getInitialDelayDuration()).isZero(); assertThat(task1.getIntervalDuration()).isEqualTo(Duration.ofMillis(4_000L)); IntervalTask task2 = fixedRateTasks.get(1); - ScheduledMethodRunnable runnable2 = (ScheduledMethodRunnable) task2.getRunnable(); - targetObject = runnable2.getTarget(); - targetMethod = runnable2.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("fixedRate"); + assertThatScheduledRunnable(task2.getRunnable()).hasTarget(target).hasMethodName("fixedRate"); assertThat(task2.getInitialDelayDuration()).isEqualTo(Duration.ofMillis(2_000L)); assertThat(task2.getIntervalDuration()).isEqualTo(Duration.ofMillis(4_000L)); } @@ -286,11 +266,7 @@ void oneTimeTask() { new DirectFieldAccessor(registrar).getPropertyValue("oneTimeTasks"); assertThat(oneTimeTasks).hasSize(1); OneTimeTask task = oneTimeTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("oneTimeTask"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("oneTimeTask"); assertThat(task.getInitialDelayDuration()).isEqualTo(Duration.ofMillis(2_000L)); } @@ -313,11 +289,7 @@ void cronTask() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("cron"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("cron"); assertThat(task.getExpression()).isEqualTo("*/7 * * * * ?"); } @@ -340,11 +312,7 @@ void cronTaskWithZone() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("cron"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("cron"); assertThat(task.getExpression()).isEqualTo("0 0 0-4,6-23 * * ?"); Trigger trigger = task.getTrigger(); assertThat(trigger).isNotNull(); @@ -404,11 +372,9 @@ void cronTaskWithScopedProxy() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(context.getBean(ScopedProxyUtils.getTargetBeanName("target"))); - assertThat(targetMethod.getName()).isEqualTo("cron"); + assertThatScheduledRunnable(task.getRunnable()) + .hasTarget(context.getBean(ScopedProxyUtils.getTargetBeanName("target"))) + .hasMethodName("cron"); assertThat(task.getExpression()).isEqualTo("*/7 * * * * ?"); } @@ -431,11 +397,7 @@ void metaAnnotationWithFixedRate() { new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("checkForUpdates"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("checkForUpdates"); assertThat(task.getIntervalDuration()).isEqualTo(Duration.ofMillis(5_000L)); } @@ -458,11 +420,7 @@ void composedAnnotationWithInitialDelayAndFixedRate() { new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("checkForUpdates"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("checkForUpdates"); assertThat(task.getIntervalDuration()).isEqualTo(Duration.ofMillis(5_000L)); assertThat(task.getInitialDelayDuration()).isEqualTo(Duration.ofMillis(1_000L)); } @@ -486,11 +444,7 @@ void metaAnnotationWithCronExpression() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("generateReport"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("generateReport"); assertThat(task.getExpression()).isEqualTo("0 0 * * * ?"); } @@ -519,11 +473,7 @@ void propertyPlaceholderWithCron() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("x"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("x"); assertThat(task.getExpression()).isEqualTo(businessHoursCronExpression); } @@ -578,11 +528,7 @@ void propertyPlaceholderWithFixedDelay(@NameToClass Class beanClass, String f new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks"); assertThat(fixedDelayTasks).hasSize(1); IntervalTask task = fixedDelayTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("fixedDelay"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedDelay"); assertSoftly(softly -> { softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isEqualTo(Duration.ofMillis(expectedInitialDelay)); softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval)); @@ -622,11 +568,7 @@ void propertyPlaceholderWithFixedRate(@NameToClass Class beanClass, String fi new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("fixedRate"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedRate"); assertSoftly(softly -> { softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isEqualTo(Duration.ofMillis(expectedInitialDelay)); softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval)); @@ -656,11 +598,7 @@ void expressionWithCron() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("x"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("x"); assertThat(task.getExpression()).isEqualTo(businessHoursCronExpression); } @@ -689,11 +627,7 @@ void propertyPlaceholderForMetaAnnotation() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("y"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("y"); assertThat(task.getExpression()).isEqualTo(businessHoursCronExpression); } @@ -716,11 +650,7 @@ void nonVoidReturnType() { new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); - ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); - Object targetObject = runnable.getTarget(); - Method targetMethod = runnable.getMethod(); - assertThat(targetObject).isEqualTo(target); - assertThat(targetMethod.getName()).isEqualTo("cron"); + assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("cron"); assertThat(task.getExpression()).isEqualTo("0 0 9-17 * * MON-FRI"); } @@ -1088,4 +1018,29 @@ public Class convert(Object beanClassName, ParameterContext context) throws A } } + static ScheduledMethodRunnableAssert assertThatScheduledRunnable(Runnable runnable) { + return new ScheduledMethodRunnableAssert(runnable); + } + + static class ScheduledMethodRunnableAssert extends AbstractAssert { + + public ScheduledMethodRunnableAssert(Runnable actual) { + super(actual, ScheduledMethodRunnableAssert.class); + assertThat(actual).extracting("runnable").isInstanceOf(ScheduledMethodRunnable.class); + } + + public ScheduledMethodRunnableAssert hasTarget(Object target) { + isNotNull(); + assertThat(actual).extracting("runnable.target").isEqualTo(target); + return this; + } + + public ScheduledMethodRunnableAssert hasMethodName(String name) { + isNotNull(); + assertThat(actual).extracting("runnable.method.name").isEqualTo(name); + return this; + } + + } + } diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTaskTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTaskTests.java new file mode 100644 index 000000000000..b7cf63cb65da --- /dev/null +++ b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTaskTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.scheduling.config; + + +import java.time.Duration; +import java.time.Instant; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ScheduledTask}. + * @author Brian Clozel + */ +class ScheduledTaskTests { + + private CountingRunnable countingRunnable = new CountingRunnable(); + + private SimpleAsyncTaskScheduler taskScheduler = new SimpleAsyncTaskScheduler(); + + private ScheduledTaskRegistrar taskRegistrar = new ScheduledTaskRegistrar(); + + @BeforeEach + void setup() { + this.taskRegistrar.setTaskScheduler(this.taskScheduler); + taskScheduler.start(); + } + + @AfterEach + void tearDown() { + taskScheduler.stop(); + } + + @Test + void shouldReturnConfiguredTask() { + Task task = new Task(countingRunnable); + ScheduledTask scheduledTask = new ScheduledTask(task); + assertThat(scheduledTask.getTask()).isEqualTo(task); + } + + @Test + void shouldUseTaskToString() { + Task task = new Task(countingRunnable); + ScheduledTask scheduledTask = new ScheduledTask(task); + assertThat(scheduledTask.toString()).isEqualTo(task.toString()); + } + + @Test + void unscheduledTaskShouldNotHaveNextExecution() { + ScheduledTask scheduledTask = new ScheduledTask(new Task(countingRunnable)); + assertThat(scheduledTask.nextExecution()).isNull(); + assertThat(countingRunnable.executionCount).isZero(); + } + + @Test + void scheduledTaskShouldHaveNextExecution() { + ScheduledTask scheduledTask = taskRegistrar.scheduleFixedDelayTask(new FixedDelayTask(countingRunnable, + Duration.ofSeconds(10), Duration.ofSeconds(10))); + assertThat(scheduledTask.nextExecution()).isBefore(Instant.now().plusSeconds(11)); + } + + @Test + void cancelledTaskShouldNotHaveNextExecution() { + ScheduledTask scheduledTask = taskRegistrar.scheduleFixedDelayTask(new FixedDelayTask(countingRunnable, + Duration.ofSeconds(10), Duration.ofSeconds(10))); + scheduledTask.cancel(true); + assertThat(scheduledTask.nextExecution()).isNull(); + } + + @Test + void singleExecutionShouldNotHaveNextExecution() { + ScheduledTask scheduledTask = taskRegistrar.scheduleOneTimeTask(new OneTimeTask(countingRunnable, Duration.ofSeconds(0))); + Awaitility.await().atMost(Duration.ofSeconds(5)).until(() -> countingRunnable.executionCount > 0); + assertThat(scheduledTask.nextExecution()).isNull(); + } + + class CountingRunnable implements Runnable { + + int executionCount; + + @Override + public void run() { + executionCount++; + } + } + +} diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java index c6ab6d340f9f..a58a0f9d82d3 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParserTests.java @@ -16,11 +16,11 @@ package org.springframework.scheduling.config; -import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; import java.util.List; +import org.assertj.core.api.ObjectAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -32,6 +32,7 @@ import org.springframework.scheduling.support.ScheduledMethodRunnable; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.type; /** * @author Mark Fisher @@ -68,11 +69,13 @@ void checkTarget() { List tasks = (List) new DirectFieldAccessor( this.registrar).getPropertyValue("fixedRateTasks"); Runnable runnable = tasks.get(0).getRunnable(); - assertThat(runnable.getClass()).isEqualTo(ScheduledMethodRunnable.class); - Object targetObject = ((ScheduledMethodRunnable) runnable).getTarget(); - Method targetMethod = ((ScheduledMethodRunnable) runnable).getMethod(); - assertThat(targetObject).isEqualTo(this.testBean); - assertThat(targetMethod.getName()).isEqualTo("test"); + + ObjectAssert runnableAssert = assertThat(runnable) + .extracting("runnable") + .isInstanceOf(ScheduledMethodRunnable.class) + .asInstanceOf(type(ScheduledMethodRunnable.class)); + runnableAssert.extracting("target").isEqualTo(testBean); + runnableAssert.extracting("method.name").isEqualTo("test"); } @Test diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/TaskExecutionOutcomeTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/TaskExecutionOutcomeTests.java new file mode 100644 index 000000000000..5dfd5004f9db --- /dev/null +++ b/spring-context/src/test/java/org/springframework/scheduling/config/TaskExecutionOutcomeTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.scheduling.config; + + +import java.time.Instant; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link TaskExecutionOutcome}. + * @author Brian Clozel + */ +class TaskExecutionOutcomeTests { + + @Test + void shouldCreateWithNoneStatus() { + TaskExecutionOutcome outcome = TaskExecutionOutcome.create(); + assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.NONE); + assertThat(outcome.executionTime()).isNull(); + assertThat(outcome.throwable()).isNull(); + } + + @Test + void startedTaskShouldBeOngoing() { + TaskExecutionOutcome outcome = TaskExecutionOutcome.create(); + Instant now = Instant.now(); + outcome = outcome.start(now); + assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.STARTED); + assertThat(outcome.executionTime()).isEqualTo(now); + assertThat(outcome.throwable()).isNull(); + } + + @Test + void shouldRejectSuccessWhenNotStarted() { + TaskExecutionOutcome outcome = TaskExecutionOutcome.create(); + assertThatIllegalStateException().isThrownBy(outcome::success); + } + + @Test + void shouldRejectErrorWhenNotStarted() { + TaskExecutionOutcome outcome = TaskExecutionOutcome.create(); + assertThatIllegalStateException().isThrownBy(() -> outcome.failure(new IllegalArgumentException("test error"))); + } + + @Test + void finishedTaskShouldBeSuccessful() { + TaskExecutionOutcome outcome = TaskExecutionOutcome.create(); + Instant now = Instant.now(); + outcome = outcome.start(now); + outcome = outcome.success(); + assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.SUCCESS); + assertThat(outcome.executionTime()).isEqualTo(now); + assertThat(outcome.throwable()).isNull(); + } + + @Test + void errorTaskShouldBeFailure() { + TaskExecutionOutcome outcome = TaskExecutionOutcome.create(); + Instant now = Instant.now(); + outcome = outcome.start(now); + outcome = outcome.failure(new IllegalArgumentException(("test error"))); + assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.ERROR); + assertThat(outcome.executionTime()).isEqualTo(now); + assertThat(outcome.throwable()).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void newTaskExecutionShouldNotFail() { + TaskExecutionOutcome outcome = TaskExecutionOutcome.create(); + Instant now = Instant.now(); + outcome = outcome.start(now); + outcome = outcome.failure(new IllegalArgumentException(("test error"))); + + outcome = outcome.start(now.plusSeconds(2)); + assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.STARTED); + assertThat(outcome.executionTime()).isAfter(now); + assertThat(outcome.throwable()).isNull(); + } + +} diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/TaskTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/TaskTests.java new file mode 100644 index 000000000000..685024f71cb9 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/scheduling/config/TaskTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.scheduling.config; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link Task}. + * @author Brian Clozel + */ +class TaskTests { + + @Test + void shouldRejectNullRunnable() { + assertThatIllegalArgumentException().isThrownBy(() -> new Task(null)); + } + + @Test + void initialStateShouldBeUnknown() { + TestRunnable testRunnable = new TestRunnable(); + Task task = new Task(testRunnable); + assertThat(testRunnable.hasRun).isFalse(); + TaskExecutionOutcome executionOutcome = task.getLastExecutionOutcome(); + assertThat(executionOutcome.executionTime()).isNull(); + assertThat(executionOutcome.status()).isEqualTo(TaskExecutionOutcome.Status.NONE); + assertThat(executionOutcome.throwable()).isNull(); + } + + @Test + void stateShouldUpdateAfterRun() { + TestRunnable testRunnable = new TestRunnable(); + Task task = new Task(testRunnable); + task.getRunnable().run(); + + assertThat(testRunnable.hasRun).isTrue(); + TaskExecutionOutcome executionOutcome = task.getLastExecutionOutcome(); + assertThat(executionOutcome.executionTime()).isInThePast(); + assertThat(executionOutcome.status()).isEqualTo(TaskExecutionOutcome.Status.SUCCESS); + assertThat(executionOutcome.throwable()).isNull(); + } + + @Test + void stateShouldUpdateAfterFailingRun() { + FailingTestRunnable testRunnable = new FailingTestRunnable(); + Task task = new Task(testRunnable); + assertThatIllegalStateException().isThrownBy(() -> task.getRunnable().run()); + + assertThat(testRunnable.hasRun).isTrue(); + TaskExecutionOutcome executionOutcome = task.getLastExecutionOutcome(); + assertThat(executionOutcome.executionTime()).isInThePast(); + assertThat(executionOutcome.status()).isEqualTo(TaskExecutionOutcome.Status.ERROR); + assertThat(executionOutcome.throwable()).isInstanceOf(IllegalStateException.class); + } + + + static class TestRunnable implements Runnable { + + boolean hasRun; + + @Override + public void run() { + this.hasRun = true; + } + } + + static class FailingTestRunnable implements Runnable { + + boolean hasRun; + + @Override + public void run() { + this.hasRun = true; + throw new IllegalStateException("test exception"); + } + } + + +} diff --git a/spring-context/src/test/kotlin/org/springframework/context/annotation/ConfigurationClassKotlinTests.kt b/spring-context/src/test/kotlin/org/springframework/context/annotation/ConfigurationClassKotlinTests.kt index a12e9d747913..43158dbd0791 100644 --- a/spring-context/src/test/kotlin/org/springframework/context/annotation/ConfigurationClassKotlinTests.kt +++ b/spring-context/src/test/kotlin/org/springframework/context/annotation/ConfigurationClassKotlinTests.kt @@ -19,8 +19,10 @@ package org.springframework.context.annotation import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test +import org.springframework.beans.factory.config.BeanPostProcessor import org.springframework.beans.factory.getBean import org.springframework.beans.factory.parsing.BeanDefinitionParsingException +import org.springframework.beans.factory.support.DefaultListableBeanFactory /** * Integration tests for Kotlin configuration classes. @@ -43,6 +45,16 @@ class ConfigurationClassKotlinTests { assertThat(context.getBean().foo).isEqualTo(foo) } + @Test + fun `Configuration with @JvmStatic registers a single bean`() { + val beanFactory = DefaultListableBeanFactory().apply { + isAllowBeanDefinitionOverriding = false + } + val context = AnnotationConfigApplicationContext(beanFactory) + context.register(ProcessorConfiguration::class.java) + context.refresh() + } + @Configuration class FinalConfigurationWithProxy { @@ -64,6 +76,19 @@ class ConfigurationClassKotlinTests { fun bar(foo: Foo) = Bar(foo) } + @Configuration + open class ProcessorConfiguration { + + companion object { + + @Bean + @JvmStatic + fun processor(): BeanPostProcessor { + return object: BeanPostProcessor{} + } + } + } + class Foo class Bar(val foo: Foo) diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 22976c9ee444..718f276794cc 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -59,6 +59,7 @@ * @author Arjen Poutsma * @author Sam Brannen * @author Brian Clozel + * @author Sebastien Deleuze * @since 16 April 2001 */ public abstract class StringUtils { @@ -71,6 +72,8 @@ public abstract class StringUtils { private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; + private static final String DOUBLE_BACKSLASHES = "\\\\"; + private static final String TOP_PATH = ".."; private static final String CURRENT_PATH = "."; @@ -695,7 +698,7 @@ public static String applyRelativePath(String path, String relativePath) { * Normalize the path by suppressing sequences like "path/.." and * inner simple dots. *

The result is convenient for path comparison. For other uses, - * notice that Windows separators ("\") are replaced by simple slashes. + * notice that Windows separators ("\" and "\\") are replaced by simple slashes. *

NOTE that {@code cleanPath} should not be depended * upon in a security context. Other mechanisms should be used to prevent * path-traversal issues. @@ -707,7 +710,15 @@ public static String cleanPath(String path) { return path; } - String normalizedPath = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); + String normalizedPath; + // Optimize when there is no backslash + if (path.indexOf('\\') != -1) { + normalizedPath = replace(path, DOUBLE_BACKSLASHES, FOLDER_SEPARATOR); + normalizedPath = replace(normalizedPath, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); + } + else { + normalizedPath = path; + } String pathToUse = normalizedPath; // Shortcut if there is no work to do 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 614f0dfad33a..e9d055fbd48d 100644 --- a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java @@ -419,6 +419,7 @@ void cleanPath() { assertThat(StringUtils.cleanPath("file:///c:/some/../path/the%20file.txt")).isEqualTo("file:///c:/path/the%20file.txt"); assertThat(StringUtils.cleanPath("jar:file:///c:\\some\\..\\path\\.\\the%20file.txt")).isEqualTo("jar:file:///c:/path/the%20file.txt"); assertThat(StringUtils.cleanPath("jar:file:///c:/some/../path/./the%20file.txt")).isEqualTo("jar:file:///c:/path/the%20file.txt"); + assertThat(StringUtils.cleanPath("jar:file:///c:\\\\some\\\\..\\\\path\\\\.\\\\the%20file.txt")).isEqualTo("jar:file:///c:/path/the%20file.txt"); } @Test diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java index 61ff2c57232c..1a266e2fc0c0 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,6 @@ import java.sql.SQLException; import java.sql.Savepoint; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.springframework.lang.Nullable; import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.NestedTransactionNotSupportedException; @@ -48,9 +45,6 @@ */ public abstract class JdbcTransactionObjectSupport implements SavepointManager, SmartTransactionObject { - private static final Log logger = LogFactory.getLog(JdbcTransactionObjectSupport.class); - - @Nullable private ConnectionHolder connectionHolder; @@ -186,7 +180,7 @@ public void releaseSavepoint(Object savepoint) throws TransactionException { conHolder.getConnection().releaseSavepoint((Savepoint) savepoint); } catch (Throwable ex) { - logger.debug("Could not explicitly release JDBC savepoint", ex); + throw new TransactionSystemException("Could not explicitly release JDBC savepoint", ex); } } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java index e953f32ce3be..a7e43397b437 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java @@ -1,153 +1,71 @@ -/* - * Copyright 2002-2022 the original author 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. - */ - // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.messaging.protobuf; /** * Protobuf type {@code Msg} */ -@SuppressWarnings("serial") -public final class Msg extends - com.google.protobuf.GeneratedMessage - implements MsgOrBuilder { +public final class Msg extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:Msg) + MsgOrBuilder { +private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + Msg.class.getName()); + } // Use Msg.newBuilder() to construct. private Msg(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private Msg(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Msg defaultInstance; - public static Msg getDefaultInstance() { - return defaultInstance; - } - - public Msg getDefaultInstanceForType() { - return defaultInstance; + private Msg() { + foo_ = ""; } - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private Msg( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - @SuppressWarnings("unused") - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - bitField0_ |= 0x00000001; - foo_ = input.readBytes(); - break; - } - case 18: { - SecondMsg.Builder subBuilder = null; - if (((bitField0_ & 0x00000002) == 0x00000002)) { - subBuilder = blah_.toBuilder(); - } - blah_ = input.readMessage(SecondMsg.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(blah_); - blah_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000002; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.messaging.protobuf.OuterSample.internal_static_Msg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_Msg_fieldAccessorTable + return org.springframework.messaging.protobuf.OuterSample.internal_static_Msg_fieldAccessorTable .ensureFieldAccessorsInitialized( - Msg.class, Msg.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Msg parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Msg(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + org.springframework.messaging.protobuf.Msg.class, org.springframework.messaging.protobuf.Msg.Builder.class); } private int bitField0_; - // optional string foo = 1; public static final int FOO_FIELD_NUMBER = 1; - private java.lang.Object foo_; + @SuppressWarnings("serial") + private volatile java.lang.Object foo_ = ""; /** * optional string foo = 1; + * @return Whether the foo field is set. */ + @java.lang.Override public boolean hasFoo() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional string foo = 1; + * @return The foo. */ + @java.lang.Override public java.lang.String getFoo() { java.lang.Object ref = foo_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { @@ -158,12 +76,14 @@ public java.lang.String getFoo() { } /** * optional string foo = 1; + * @return The bytes for foo. */ + @java.lang.Override public com.google.protobuf.ByteString getFooBytes() { java.lang.Object ref = foo_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); foo_ = b; @@ -173,138 +93,202 @@ public java.lang.String getFoo() { } } - // optional .SecondMsg blah = 2; public static final int BLAH_FIELD_NUMBER = 2; - private SecondMsg blah_; + private org.springframework.messaging.protobuf.SecondMsg blah_; /** * optional .SecondMsg blah = 2; + * @return Whether the blah field is set. */ + @java.lang.Override public boolean hasBlah() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000002) != 0); } /** * optional .SecondMsg blah = 2; + * @return The blah. */ - public SecondMsg getBlah() { - return blah_; + @java.lang.Override + public org.springframework.messaging.protobuf.SecondMsg getBlah() { + return blah_ == null ? org.springframework.messaging.protobuf.SecondMsg.getDefaultInstance() : blah_; } /** * optional .SecondMsg blah = 2; */ - public SecondMsgOrBuilder getBlahOrBuilder() { - return blah_; + @java.lang.Override + public org.springframework.messaging.protobuf.SecondMsgOrBuilder getBlahOrBuilder() { + return blah_ == null ? org.springframework.messaging.protobuf.SecondMsg.getDefaultInstance() : blah_; } - private void initFields() { - foo_ = ""; - blah_ = SecondMsg.getDefaultInstance(); - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; memoizedIsInitialized = 1; return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getFooBytes()); + if (((bitField0_ & 0x00000001) != 0)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, foo_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeMessage(2, blah_); + if (((bitField0_ & 0x00000002) != 0)) { + output.writeMessage(2, getBlah()); } getUnknownFields().writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, getFooBytes()); + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, foo_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000002) != 0)) { size += com.google.protobuf.CodedOutputStream - .computeMessageSize(2, blah_); + .computeMessageSize(2, getBlah()); } size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.springframework.messaging.protobuf.Msg)) { + return super.equals(obj); + } + org.springframework.messaging.protobuf.Msg other = (org.springframework.messaging.protobuf.Msg) obj; + + if (hasFoo() != other.hasFoo()) return false; + if (hasFoo()) { + if (!getFoo() + .equals(other.getFoo())) return false; + } + if (hasBlah() != other.hasBlah()) return false; + if (hasBlah()) { + if (!getBlah() + .equals(other.getBlah())) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; } - public static Msg parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasFoo()) { + hash = (37 * hash) + FOO_FIELD_NUMBER; + hash = (53 * hash) + getFoo().hashCode(); + } + if (hasBlah()) { + hash = (37 * hash) + BLAH_FIELD_NUMBER; + hash = (53 * hash) + getBlah().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.springframework.messaging.protobuf.Msg parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.springframework.messaging.protobuf.Msg parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.springframework.messaging.protobuf.Msg parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Msg parseFrom( + public static org.springframework.messaging.protobuf.Msg parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Msg parseFrom(byte[] data) + public static org.springframework.messaging.protobuf.Msg parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Msg parseFrom( + public static org.springframework.messaging.protobuf.Msg parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Msg parseFrom(java.io.InputStream input) + public static org.springframework.messaging.protobuf.Msg parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static Msg parseFrom( + public static org.springframework.messaging.protobuf.Msg parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Msg parseDelimitedFrom(java.io.InputStream input) + + public static org.springframework.messaging.protobuf.Msg parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); } - public static Msg parseDelimitedFrom( + + public static org.springframework.messaging.protobuf.Msg parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static Msg parseFrom( + public static org.springframework.messaging.protobuf.Msg parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static Msg parseFrom( + public static org.springframework.messaging.protobuf.Msg parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(Msg prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.springframework.messaging.protobuf.Msg prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } @java.lang.Override protected Builder newBuilderForType( @@ -316,18 +300,20 @@ protected Builder newBuilderForType( * Protobuf type {@code Msg} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements MsgOrBuilder { + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:Msg) + org.springframework.messaging.protobuf.MsgOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.messaging.protobuf.OuterSample.internal_static_Msg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_Msg_fieldAccessorTable + return org.springframework.messaging.protobuf.OuterSample.internal_static_Msg_fieldAccessorTable .ensureFieldAccessorsInitialized( - Msg.class, Msg.Builder.class); + org.springframework.messaging.protobuf.Msg.class, org.springframework.messaging.protobuf.Msg.Builder.class); } // Construct using org.springframework.messaging.protobuf.Msg.newBuilder() @@ -341,132 +327,164 @@ private Builder( maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessage + .alwaysUseFieldBuilders) { getBlahFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); + bitField0_ = 0; foo_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - if (blahBuilder_ == null) { - blah_ = SecondMsg.getDefaultInstance(); - } else { - blahBuilder_.clear(); + blah_ = null; + if (blahBuilder_ != null) { + blahBuilder_.dispose(); + blahBuilder_ = null; } - bitField0_ = (bitField0_ & ~0x00000002); return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.messaging.protobuf.OuterSample.internal_static_Msg_descriptor; } - public Msg getDefaultInstanceForType() { - return Msg.getDefaultInstance(); + @java.lang.Override + public org.springframework.messaging.protobuf.Msg getDefaultInstanceForType() { + return org.springframework.messaging.protobuf.Msg.getDefaultInstance(); } - public Msg build() { - Msg result = buildPartial(); + @java.lang.Override + public org.springframework.messaging.protobuf.Msg build() { + org.springframework.messaging.protobuf.Msg result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public Msg buildPartial() { - Msg result = new Msg(this); + @java.lang.Override + public org.springframework.messaging.protobuf.Msg buildPartial() { + org.springframework.messaging.protobuf.Msg result = new org.springframework.messaging.protobuf.Msg(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.springframework.messaging.protobuf.Msg result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + if (((from_bitField0_ & 0x00000001) != 0)) { + result.foo_ = foo_; to_bitField0_ |= 0x00000001; } - result.foo_ = foo_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + if (((from_bitField0_ & 0x00000002) != 0)) { + result.blah_ = blahBuilder_ == null + ? blah_ + : blahBuilder_.build(); to_bitField0_ |= 0x00000002; } - if (blahBuilder_ == null) { - result.blah_ = blah_; - } else { - result.blah_ = blahBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; + result.bitField0_ |= to_bitField0_; } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Msg) { - return mergeFrom((Msg)other); + if (other instanceof org.springframework.messaging.protobuf.Msg) { + return mergeFrom((org.springframework.messaging.protobuf.Msg)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(Msg other) { - if (other == Msg.getDefaultInstance()) return this; + public Builder mergeFrom(org.springframework.messaging.protobuf.Msg other) { + if (other == org.springframework.messaging.protobuf.Msg.getDefaultInstance()) return this; if (other.hasFoo()) { - bitField0_ |= 0x00000001; foo_ = other.foo_; + bitField0_ |= 0x00000001; onChanged(); } if (other.hasBlah()) { mergeBlah(other.getBlah()); } this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - Msg parsedMessage = null; + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + foo_ = input.readBytes(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + input.readMessage( + getBlahFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000002; + break; + } // case 18 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Msg) e.getUnfinishedMessage(); - throw e; + throw e.unwrapIOException(); } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } + onChanged(); + } // finally return this; } private int bitField0_; - // optional string foo = 1; private java.lang.Object foo_ = ""; /** * optional string foo = 1; + * @return Whether the foo field is set. */ public boolean hasFoo() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional string foo = 1; + * @return The foo. */ public java.lang.String getFoo() { java.lang.Object ref = foo_; if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - foo_ = s; + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + foo_ = s; + } return s; } else { return (java.lang.String) ref; @@ -474,12 +492,13 @@ public java.lang.String getFoo() { } /** * optional string foo = 1; + * @return The bytes for foo. */ public com.google.protobuf.ByteString getFooBytes() { java.lang.Object ref = foo_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); foo_ = b; @@ -490,57 +509,58 @@ public java.lang.String getFoo() { } /** * optional string foo = 1; + * @param value The foo to set. + * @return This builder for chaining. */ public Builder setFoo( java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; + if (value == null) { throw new NullPointerException(); } foo_ = value; + bitField0_ |= 0x00000001; onChanged(); return this; } /** * optional string foo = 1; + * @return This builder for chaining. */ public Builder clearFoo() { - bitField0_ = (bitField0_ & ~0x00000001); foo_ = getDefaultInstance().getFoo(); + bitField0_ = (bitField0_ & ~0x00000001); onChanged(); return this; } /** * optional string foo = 1; + * @param value The bytes for foo to set. + * @return This builder for chaining. */ public Builder setFooBytes( com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; + if (value == null) { throw new NullPointerException(); } foo_ = value; + bitField0_ |= 0x00000001; onChanged(); return this; } - // optional .SecondMsg blah = 2; - private SecondMsg blah_ = SecondMsg.getDefaultInstance(); + private org.springframework.messaging.protobuf.SecondMsg blah_; private com.google.protobuf.SingleFieldBuilder< - SecondMsg, SecondMsg.Builder, - SecondMsgOrBuilder> blahBuilder_; + org.springframework.messaging.protobuf.SecondMsg, org.springframework.messaging.protobuf.SecondMsg.Builder, org.springframework.messaging.protobuf.SecondMsgOrBuilder> blahBuilder_; /** * optional .SecondMsg blah = 2; + * @return Whether the blah field is set. */ public boolean hasBlah() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000002) != 0); } /** * optional .SecondMsg blah = 2; + * @return The blah. */ - public SecondMsg getBlah() { + public org.springframework.messaging.protobuf.SecondMsg getBlah() { if (blahBuilder_ == null) { - return blah_; + return blah_ == null ? org.springframework.messaging.protobuf.SecondMsg.getDefaultInstance() : blah_; } else { return blahBuilder_.getMessage(); } @@ -548,69 +568,71 @@ public SecondMsg getBlah() { /** * optional .SecondMsg blah = 2; */ - public Builder setBlah(SecondMsg value) { + public Builder setBlah(org.springframework.messaging.protobuf.SecondMsg value) { if (blahBuilder_ == null) { if (value == null) { throw new NullPointerException(); } blah_ = value; - onChanged(); } else { blahBuilder_.setMessage(value); } bitField0_ |= 0x00000002; + onChanged(); return this; } /** * optional .SecondMsg blah = 2; */ public Builder setBlah( - SecondMsg.Builder builderForValue) { + org.springframework.messaging.protobuf.SecondMsg.Builder builderForValue) { if (blahBuilder_ == null) { blah_ = builderForValue.build(); - onChanged(); } else { blahBuilder_.setMessage(builderForValue.build()); } bitField0_ |= 0x00000002; + onChanged(); return this; } /** * optional .SecondMsg blah = 2; */ - public Builder mergeBlah(SecondMsg value) { + public Builder mergeBlah(org.springframework.messaging.protobuf.SecondMsg value) { if (blahBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002) && - blah_ != SecondMsg.getDefaultInstance()) { - blah_ = - SecondMsg.newBuilder(blah_).mergeFrom(value).buildPartial(); + if (((bitField0_ & 0x00000002) != 0) && + blah_ != null && + blah_ != org.springframework.messaging.protobuf.SecondMsg.getDefaultInstance()) { + getBlahBuilder().mergeFrom(value); } else { blah_ = value; } - onChanged(); } else { blahBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000002; + if (blah_ != null) { + bitField0_ |= 0x00000002; + onChanged(); + } return this; } /** * optional .SecondMsg blah = 2; */ public Builder clearBlah() { - if (blahBuilder_ == null) { - blah_ = SecondMsg.getDefaultInstance(); - onChanged(); - } else { - blahBuilder_.clear(); - } bitField0_ = (bitField0_ & ~0x00000002); + blah_ = null; + if (blahBuilder_ != null) { + blahBuilder_.dispose(); + blahBuilder_ = null; + } + onChanged(); return this; } /** * optional .SecondMsg blah = 2; */ - public SecondMsg.Builder getBlahBuilder() { + public org.springframework.messaging.protobuf.SecondMsg.Builder getBlahBuilder() { bitField0_ |= 0x00000002; onChanged(); return getBlahFieldBuilder().getBuilder(); @@ -618,25 +640,26 @@ public SecondMsg.Builder getBlahBuilder() { /** * optional .SecondMsg blah = 2; */ - public SecondMsgOrBuilder getBlahOrBuilder() { + public org.springframework.messaging.protobuf.SecondMsgOrBuilder getBlahOrBuilder() { if (blahBuilder_ != null) { return blahBuilder_.getMessageOrBuilder(); } else { - return blah_; + return blah_ == null ? + org.springframework.messaging.protobuf.SecondMsg.getDefaultInstance() : blah_; } } /** * optional .SecondMsg blah = 2; */ private com.google.protobuf.SingleFieldBuilder< - SecondMsg, SecondMsg.Builder, - SecondMsgOrBuilder> + org.springframework.messaging.protobuf.SecondMsg, org.springframework.messaging.protobuf.SecondMsg.Builder, org.springframework.messaging.protobuf.SecondMsgOrBuilder> getBlahFieldBuilder() { if (blahBuilder_ == null) { - blahBuilder_ = new com.google.protobuf.SingleFieldBuilder<>( - blah_, - getParentForChildren(), - isClean()); + blahBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.springframework.messaging.protobuf.SecondMsg, org.springframework.messaging.protobuf.SecondMsg.Builder, org.springframework.messaging.protobuf.SecondMsgOrBuilder>( + getBlah(), + getParentForChildren(), + isClean()); blah_ = null; } return blahBuilder_; @@ -645,11 +668,51 @@ public SecondMsgOrBuilder getBlahOrBuilder() { // @@protoc_insertion_point(builder_scope:Msg) } + // @@protoc_insertion_point(class_scope:Msg) + private static final org.springframework.messaging.protobuf.Msg DEFAULT_INSTANCE; static { - defaultInstance = new Msg(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new org.springframework.messaging.protobuf.Msg(); + } + + public static org.springframework.messaging.protobuf.Msg getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Msg parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.springframework.messaging.protobuf.Msg getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:Msg) } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/MsgOrBuilder.java b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/MsgOrBuilder.java index ca9f94be7450..278e0787a308 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/MsgOrBuilder.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/MsgOrBuilder.java @@ -1,37 +1,43 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.messaging.protobuf; -public interface MsgOrBuilder - extends com.google.protobuf.MessageOrBuilder { +public interface MsgOrBuilder extends + // @@protoc_insertion_point(interface_extends:Msg) + com.google.protobuf.MessageOrBuilder { - // optional string foo = 1; /** * optional string foo = 1; + * @return Whether the foo field is set. */ boolean hasFoo(); /** * optional string foo = 1; + * @return The foo. */ java.lang.String getFoo(); /** * optional string foo = 1; + * @return The bytes for foo. */ com.google.protobuf.ByteString getFooBytes(); - // optional .SecondMsg blah = 2; /** * optional .SecondMsg blah = 2; + * @return Whether the blah field is set. */ boolean hasBlah(); /** * optional .SecondMsg blah = 2; + * @return The blah. */ - SecondMsg getBlah(); + org.springframework.messaging.protobuf.SecondMsg getBlah(); /** * optional .SecondMsg blah = 2; */ - SecondMsgOrBuilder getBlahOrBuilder(); + org.springframework.messaging.protobuf.SecondMsgOrBuilder getBlahOrBuilder(); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/OuterSample.java b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/OuterSample.java index 138b2754f055..cbfdd4ac6b61 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/OuterSample.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/OuterSample.java @@ -1,38 +1,38 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.messaging.protobuf; -@SuppressWarnings("deprecation") -public class OuterSample { +public final class OuterSample { private OuterSample() {} + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + OuterSample.class.getName()); + } + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + public static void registerAllExtensions( com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); } - static com.google.protobuf.Descriptors.Descriptor + static final com.google.protobuf.Descriptors.Descriptor internal_static_Msg_descriptor; - static + static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_Msg_fieldAccessorTable; - static com.google.protobuf.Descriptors.Descriptor + static final com.google.protobuf.Descriptors.Descriptor internal_static_SecondMsg_descriptor; - static + static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_SecondMsg_fieldAccessorTable; @@ -40,39 +40,32 @@ public static void registerAllExtensions( getDescriptor() { return descriptor; } - private static com.google.protobuf.Descriptors.FileDescriptor + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; static { java.lang.String[] descriptorData = { "\n\014sample.proto\",\n\003Msg\022\013\n\003foo\030\001 \001(\t\022\030\n\004bl" + "ah\030\002 \001(\0132\n.SecondMsg\"\031\n\tSecondMsg\022\014\n\004bla" + - "h\030\001 \001(\005B-\n\034org.springframework.protobufB" + - "\013OuterSampleP\001" + "h\030\001 \001(\005B7\n&org.springframework.messaging" + + ".protobufB\013OuterSampleP\001" }; - com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = - new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { - public com.google.protobuf.ExtensionRegistry assignDescriptors( - com.google.protobuf.Descriptors.FileDescriptor root) { - descriptor = root; - internal_static_Msg_descriptor = - getDescriptor().getMessageTypes().get(0); - internal_static_Msg_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_Msg_descriptor, - new java.lang.String[] { "Foo", "Blah", }); - internal_static_SecondMsg_descriptor = - getDescriptor().getMessageTypes().get(1); - internal_static_SecondMsg_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_SecondMsg_descriptor, - new java.lang.String[] { "Blah", }); - return null; - } - }; - com.google.protobuf.Descriptors.FileDescriptor + descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] { - }, assigner); + }); + internal_static_Msg_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_Msg_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_Msg_descriptor, + new java.lang.String[] { "Foo", "Blah", }); + internal_static_SecondMsg_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_SecondMsg_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_SecondMsg_descriptor, + new java.lang.String[] { "Blah", }); + descriptor.resolveAllFeaturesImmutable(); } // @@protoc_insertion_point(outer_class_scope) diff --git a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsg.java b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsg.java index d2630a320d39..923927096caa 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsg.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsg.java @@ -1,224 +1,222 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.messaging.protobuf; /** * Protobuf type {@code SecondMsg} */ -@SuppressWarnings("serial") -public final class SecondMsg extends - com.google.protobuf.GeneratedMessage - implements SecondMsgOrBuilder { +public final class SecondMsg extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:SecondMsg) + SecondMsgOrBuilder { +private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + SecondMsg.class.getName()); + } // Use SecondMsg.newBuilder() to construct. private SecondMsg(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private SecondMsg(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final SecondMsg defaultInstance; - public static SecondMsg getDefaultInstance() { - return defaultInstance; + private SecondMsg() { } - public SecondMsg getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private SecondMsg( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - @SuppressWarnings("unused") - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - blah_ = input.readInt32(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.messaging.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_SecondMsg_fieldAccessorTable + return org.springframework.messaging.protobuf.OuterSample.internal_static_SecondMsg_fieldAccessorTable .ensureFieldAccessorsInitialized( - SecondMsg.class, SecondMsg.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public SecondMsg parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new SecondMsg(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + org.springframework.messaging.protobuf.SecondMsg.class, org.springframework.messaging.protobuf.SecondMsg.Builder.class); } private int bitField0_; - // optional int32 blah = 1; public static final int BLAH_FIELD_NUMBER = 1; - private int blah_; + private int blah_ = 0; /** * optional int32 blah = 1; + * @return Whether the blah field is set. */ + @java.lang.Override public boolean hasBlah() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional int32 blah = 1; + * @return The blah. */ + @java.lang.Override public int getBlah() { return blah_; } - private void initFields() { - blah_ = 0; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; memoizedIsInitialized = 1; return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (((bitField0_ & 0x00000001) != 0)) { output.writeInt32(1, blah_); } getUnknownFields().writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (((bitField0_ & 0x00000001) != 0)) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(1, blah_); } size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.springframework.messaging.protobuf.SecondMsg)) { + return super.equals(obj); + } + org.springframework.messaging.protobuf.SecondMsg other = (org.springframework.messaging.protobuf.SecondMsg) obj; + + if (hasBlah() != other.hasBlah()) return false; + if (hasBlah()) { + if (getBlah() + != other.getBlah()) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasBlah()) { + hash = (37 * hash) + BLAH_FIELD_NUMBER; + hash = (53 * hash) + getBlah(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; } - public static SecondMsg parseFrom( + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static SecondMsg parseFrom( + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static SecondMsg parseFrom(byte[] data) + public static org.springframework.messaging.protobuf.SecondMsg parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static SecondMsg parseFrom( + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static SecondMsg parseFrom(java.io.InputStream input) + public static org.springframework.messaging.protobuf.SecondMsg parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static SecondMsg parseFrom( + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static SecondMsg parseDelimitedFrom(java.io.InputStream input) + + public static org.springframework.messaging.protobuf.SecondMsg parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); } - public static SecondMsg parseDelimitedFrom( + + public static org.springframework.messaging.protobuf.SecondMsg parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static SecondMsg parseFrom( + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static SecondMsg parseFrom( + public static org.springframework.messaging.protobuf.SecondMsg parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(SecondMsg prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.springframework.messaging.protobuf.SecondMsg prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } @java.lang.Override protected Builder newBuilderForType( @@ -230,145 +228,173 @@ protected Builder newBuilderForType( * Protobuf type {@code SecondMsg} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements SecondMsgOrBuilder { + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:SecondMsg) + org.springframework.messaging.protobuf.SecondMsgOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.messaging.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_SecondMsg_fieldAccessorTable + return org.springframework.messaging.protobuf.OuterSample.internal_static_SecondMsg_fieldAccessorTable .ensureFieldAccessorsInitialized( - SecondMsg.class, SecondMsg.Builder.class); + org.springframework.messaging.protobuf.SecondMsg.class, org.springframework.messaging.protobuf.SecondMsg.Builder.class); } // Construct using org.springframework.messaging.protobuf.SecondMsg.newBuilder() private Builder() { - maybeForceBuilderInitialization(); + } private Builder( com.google.protobuf.GeneratedMessage.BuilderParent parent) { super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } + } + @java.lang.Override public Builder clear() { super.clear(); + bitField0_ = 0; blah_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.messaging.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } - public SecondMsg getDefaultInstanceForType() { - return SecondMsg.getDefaultInstance(); + @java.lang.Override + public org.springframework.messaging.protobuf.SecondMsg getDefaultInstanceForType() { + return org.springframework.messaging.protobuf.SecondMsg.getDefaultInstance(); } - public SecondMsg build() { - SecondMsg result = buildPartial(); + @java.lang.Override + public org.springframework.messaging.protobuf.SecondMsg build() { + org.springframework.messaging.protobuf.SecondMsg result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public SecondMsg buildPartial() { - SecondMsg result = new SecondMsg(this); + @java.lang.Override + public org.springframework.messaging.protobuf.SecondMsg buildPartial() { + org.springframework.messaging.protobuf.SecondMsg result = new org.springframework.messaging.protobuf.SecondMsg(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.springframework.messaging.protobuf.SecondMsg result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + if (((from_bitField0_ & 0x00000001) != 0)) { + result.blah_ = blah_; to_bitField0_ |= 0x00000001; } - result.blah_ = blah_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; + result.bitField0_ |= to_bitField0_; } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof SecondMsg) { - return mergeFrom((SecondMsg)other); + if (other instanceof org.springframework.messaging.protobuf.SecondMsg) { + return mergeFrom((org.springframework.messaging.protobuf.SecondMsg)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(SecondMsg other) { - if (other == SecondMsg.getDefaultInstance()) return this; + public Builder mergeFrom(org.springframework.messaging.protobuf.SecondMsg other) { + if (other == org.springframework.messaging.protobuf.SecondMsg.getDefaultInstance()) return this; if (other.hasBlah()) { setBlah(other.getBlah()); } this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - SecondMsg parsedMessage = null; + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + blah_ = input.readInt32(); + bitField0_ |= 0x00000001; + break; + } // case 8 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (SecondMsg) e.getUnfinishedMessage(); - throw e; + throw e.unwrapIOException(); } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } + onChanged(); + } // finally return this; } private int bitField0_; - // optional int32 blah = 1; private int blah_ ; /** * optional int32 blah = 1; + * @return Whether the blah field is set. */ + @java.lang.Override public boolean hasBlah() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional int32 blah = 1; + * @return The blah. */ + @java.lang.Override public int getBlah() { return blah_; } /** * optional int32 blah = 1; + * @param value The blah to set. + * @return This builder for chaining. */ public Builder setBlah(int value) { - bitField0_ |= 0x00000001; + blah_ = value; + bitField0_ |= 0x00000001; onChanged(); return this; } /** * optional int32 blah = 1; + * @return This builder for chaining. */ public Builder clearBlah() { bitField0_ = (bitField0_ & ~0x00000001); @@ -380,11 +406,51 @@ public Builder clearBlah() { // @@protoc_insertion_point(builder_scope:SecondMsg) } + // @@protoc_insertion_point(class_scope:SecondMsg) + private static final org.springframework.messaging.protobuf.SecondMsg DEFAULT_INSTANCE; static { - defaultInstance = new SecondMsg(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new org.springframework.messaging.protobuf.SecondMsg(); + } + + public static org.springframework.messaging.protobuf.SecondMsg getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public SecondMsg parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.springframework.messaging.protobuf.SecondMsg getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:SecondMsg) } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsgOrBuilder.java b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsgOrBuilder.java index fc4ff1576cd6..9a45eae14834 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsgOrBuilder.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/SecondMsgOrBuilder.java @@ -1,18 +1,22 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.messaging.protobuf; -public interface SecondMsgOrBuilder - extends com.google.protobuf.MessageOrBuilder { +public interface SecondMsgOrBuilder extends + // @@protoc_insertion_point(interface_extends:SecondMsg) + com.google.protobuf.MessageOrBuilder { - // optional int32 blah = 1; /** * optional int32 blah = 1; + * @return Whether the blah field is set. */ boolean hasBlah(); /** * optional int32 blah = 1; + * @return The blah. */ int getBlah(); } diff --git a/spring-messaging/src/test/proto/sample.proto b/spring-messaging/src/test/proto/sample.proto index c303ef2894cf..74517b05b66a 100644 --- a/spring-messaging/src/test/proto/sample.proto +++ b/spring-messaging/src/test/proto/sample.proto @@ -1,4 +1,4 @@ -option java_package = "org.springframework.protobuf.messaging"; +option java_package = "org.springframework.messaging.protobuf"; option java_outer_classname = "OuterSample"; option java_multiple_files = true; diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java index bac710f1bf9f..10736fe4f024 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; @@ -46,6 +48,9 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie private boolean executed = false; + @Nullable + Map attributes; + /** * Create a {@code MockClientHttpRequest} with {@link HttpMethod#GET GET} as @@ -115,6 +120,16 @@ public boolean isExecuted() { return this.executed; } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new ConcurrentHashMap<>(); + this.attributes = attributes; + } + return attributes; + } + /** * Set the {@link #isExecuted() executed} flag to {@code true} and return the * configured {@link #setResponse(ClientHttpResponse) response}. diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java b/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java index 59f1c395e03b..e3589d746305 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,6 +98,29 @@ public String getSameSite() { return getAttribute(SAME_SITE); } + /** + * Set the "Partitioned" attribute for this cookie. + * @since 6.2 + * @see The Partitioned attribute spec + */ + public void setPartitioned(boolean partitioned) { + if (partitioned) { + setAttribute("Partitioned", ""); + } + else { + setAttribute("Partitioned", null); + } + } + + /** + * Return whether the "Partitioned" attribute is set for this cookie. + * @since 6.2 + * @see The Partitioned attribute spec + */ + public boolean isPartitioned() { + return getAttribute("Partitioned") != null; + } + /** * Factory method that parses the value of the supplied "Set-Cookie" header. * @param setCookieHeader the "Set-Cookie" value; never {@code null} or empty @@ -146,6 +169,9 @@ else if (StringUtils.startsWithIgnoreCase(attribute, SAME_SITE)) { else if (StringUtils.startsWithIgnoreCase(attribute, "Comment")) { cookie.setComment(extractAttributeValue(attribute, setCookieHeader)); } + else if (!attribute.isEmpty()) { + cookie.setAttribute(attribute, extractOptionalAttributeValue(attribute, setCookieHeader)); + } } return cookie; } @@ -157,6 +183,11 @@ private static String extractAttributeValue(String attribute, String header) { return nameAndValue[1]; } + private static String extractOptionalAttributeValue(String attribute, String header) { + String[] nameAndValue = attribute.split("="); + return nameAndValue.length == 2 ? nameAndValue[1] : ""; + } + @Override public void setAttribute(String name, @Nullable String value) { if (EXPIRES.equalsIgnoreCase(name)) { @@ -176,6 +207,7 @@ public String toString() { .append("Comment", getComment()) .append("Secure", getSecure()) .append("HttpOnly", isHttpOnly()) + .append("Partitioned", isPartitioned()) .append(SAME_SITE, getSameSite()) .append("Max-Age", getMaxAge()) .append(EXPIRES, getAttribute(EXPIRES)) diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index 08a6200fba16..1f9f77b2f188 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -481,6 +481,9 @@ else if (expires != null) { if (cookie.isHttpOnly()) { buf.append("; HttpOnly"); } + if (cookie.getAttribute("Partitioned") != null) { + buf.append("; Partitioned"); + } if (cookie instanceof MockCookie mockCookie) { if (StringUtils.hasText(mockCookie.getSameSite())) { buf.append("; SameSite=").append(mockCookie.getSameSite()); diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java index 3d1b851c661f..d98fc3bb86e4 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java @@ -34,6 +34,8 @@ import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.beans.factory.support.DefaultBeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; @@ -41,45 +43,44 @@ import org.springframework.util.StringUtils; /** - * A {@link BeanFactoryPostProcessor} implementation that processes test classes - * and adapts the {@link BeanFactory} for any {@link BeanOverride} it may define. + * A {@link BeanFactoryPostProcessor} implementation that processes identified + * use of {@link BeanOverride} and adapts the {@link BeanFactory} accordingly. * - *

A set of classes from which to parse {@link OverrideMetadata} must be - * provided to this processor. Each test class is expected to use any - * annotation meta-annotated with {@link BeanOverride @BeanOverride} to mark - * beans to override. The {@link BeanOverrideParsingUtils#hasBeanOverride(Class)} - * method can be used to check if a class matches the above criteria. + *

For each override, the bean factory is prepared according to the chosen + * {@link BeanOverrideStrategy overriding strategy}. The override value is created, + * if necessary, and the necessary infrastructure is updated to allow the value + * to be injected in the corresponding {@linkplain OverrideMetadata#getField() field} + * of the test class. * - *

The provided classes are fully parsed at creation to build a metadata set. - * This processor implements several {@link BeanOverrideStrategy overriding - * strategies} and chooses the correct one according to each override metadata's - * {@link OverrideMetadata#getStrategy()} method. Additionally, it provides - * support for injecting the overridden bean instances into their corresponding - * annotated {@link Field fields}. + *

This processor does not work against a particular test class, it only prepares + * the bean factory for the identified, unique, set of bean overrides. * * @author Simon Baslé + * @author Stephane Nicoll * @since 6.2 */ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { + private final Set metadata; + private final BeanOverrideRegistrar overrideRegistrar; + private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator(); + /** * Create a new {@code BeanOverrideBeanFactoryPostProcessor} instance with - * the given {@link BeanOverrideRegistrar}, which contains a set of parsed - * {@link OverrideMetadata}. + * the set of {@link OverrideMetadata} to process, using the given + * {@link BeanOverrideRegistrar}. + * @param metadata the {@link OverrideMetadata} instances to process * @param overrideRegistrar the {@link BeanOverrideRegistrar} used to track * metadata */ - public BeanOverrideBeanFactoryPostProcessor(BeanOverrideRegistrar overrideRegistrar) { - this.overrideRegistrar = overrideRegistrar; - } + public BeanOverrideBeanFactoryPostProcessor(Set metadata, + BeanOverrideRegistrar overrideRegistrar) { - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE - 10; + this.metadata = metadata; + this.overrideRegistrar = overrideRegistrar; } @@ -92,8 +93,13 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) postProcessWithRegistry(beanFactory, registry); } + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 10; + } + private void postProcessWithRegistry(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) { - for (OverrideMetadata metadata : this.overrideRegistrar.getOverrideMetadata()) { + for (OverrideMetadata metadata : this.metadata) { registerBeanOverride(beanFactory, registry, metadata); } } @@ -128,19 +134,14 @@ private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFacto RootBeanDefinition beanDefinition = createBeanDefinition(overrideMetadata); String beanName = overrideMetadata.getBeanName(); + String beanNameIncludingFactory; BeanDefinition existingBeanDefinition = null; if (beanName == null) { - Set candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true); - int candidateCount = candidateNames.size(); - if (candidateCount != 1) { - Field field = overrideMetadata.getField(); - throw new IllegalStateException("Unable to select a bean definition to override: found " + - candidateCount + " bean definitions of type " + overrideMetadata.getBeanType() + - " (as required by annotated field '" + field.getDeclaringClass().getSimpleName() + - "." + field.getName() + "')" + (candidateCount > 0 ? ": " + candidateNames : "")); + beanNameIncludingFactory = getBeanNameForType(beanFactory, registry, overrideMetadata, beanDefinition, enforceExistingDefinition); + beanName = BeanFactoryUtils.transformedBeanName(beanNameIncludingFactory); + if (registry.containsBeanDefinition(beanName)) { + existingBeanDefinition = beanFactory.getBeanDefinition(beanName); } - beanName = candidateNames.iterator().next(); - existingBeanDefinition = beanFactory.getBeanDefinition(beanName); } else { Set candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, false); @@ -151,6 +152,7 @@ else if (enforceExistingDefinition) { throw new IllegalStateException("Unable to override bean '" + beanName + "': there is no " + "bean definition to replace with that name of type " + overrideMetadata.getBeanType()); } + beanNameIncludingFactory = beanName; } if (existingBeanDefinition != null) { @@ -160,7 +162,7 @@ else if (enforceExistingDefinition) { registry.registerBeanDefinition(beanName, beanDefinition); Object override = overrideMetadata.createOverride(beanName, existingBeanDefinition, null); - if (beanFactory.isSingleton(beanName)) { + if (beanFactory.isSingleton(beanNameIncludingFactory)) { // Now we have an instance (the override) that we can register. // At this stage we don't expect a singleton instance to be present, // and this call will throw if there is such an instance already. @@ -168,7 +170,31 @@ else if (enforceExistingDefinition) { } overrideMetadata.track(override, beanFactory); - this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanName); + this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanNameIncludingFactory); + } + + private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry, + OverrideMetadata overrideMetadata, RootBeanDefinition beanDefinition, boolean enforceExistingDefinition) { + Set candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true); + int candidateCount = candidateNames.size(); + if (candidateCount == 1) { + return candidateNames.iterator().next(); + } + else if (candidateCount == 0) { + if (enforceExistingDefinition) { + Field field = overrideMetadata.getField(); + throw new IllegalStateException( + "Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')" + .formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName())); + } + return this.beanNameGenerator.generateBeanName(beanDefinition, registry); + } + Field field = overrideMetadata.getField(); + throw new IllegalStateException(String.format( + "Unable to select a bean definition to override: found %s bean definitions of type %s " + + "(as required by annotated field '%s.%s'): %s", + candidateCount, overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), + field.getName(), candidateNames)); } /** @@ -190,7 +216,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr " (as required by annotated field '" + field.getDeclaringClass().getSimpleName() + "." + field.getName() + "')" + (candidateCount > 0 ? ": " + candidateNames : "")); } - beanName = candidateNames.iterator().next(); + beanName = BeanFactoryUtils.transformedBeanName(candidateNames.iterator().next()); } else { Set candidates = getExistingBeanNamesByType(beanFactory, metadata, false); @@ -204,7 +230,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr } RootBeanDefinition createBeanDefinition(OverrideMetadata metadata) { - RootBeanDefinition definition = new RootBeanDefinition(); + RootBeanDefinition definition = new RootBeanDefinition(metadata.getBeanType().resolve()); definition.setTargetType(metadata.getBeanType()); definition.setQualifiedElement(metadata.getField()); return definition; @@ -233,11 +259,18 @@ private Set getExistingBeanNamesByType(ConfigurableListableBeanFactory b else { beans.removeIf(ScopedProxyUtils::isScopedTarget); } + // In case of multiple matches, last resort fallback on the field's name + if (beans.size() > 1) { + String fieldName = metadata.getField().getName(); + if (beans.contains(fieldName)) { + return Set.of(fieldName); + } + } return beans; } - static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, + static class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, PriorityOrdered { private final Map earlyReferences = new ConcurrentHashMap<>(16); @@ -245,7 +278,7 @@ static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAware private final BeanOverrideRegistrar overrideRegistrar; - private WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) { + WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) { this.overrideRegistrar = registrar; } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java index dbc22897f79c..d74740aa8b88 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java @@ -49,25 +49,40 @@ class BeanOverrideContextCustomizer implements ContextCustomizer { "org.springframework.test.context.bean.override.internalWrapEarlyBeanPostProcessor"; - private final Set> detectedClasses; + private final Set metadata; - BeanOverrideContextCustomizer(Set> detectedClasses) { - this.detectedClasses = detectedClasses; + BeanOverrideContextCustomizer(Set metadata) { + this.metadata = metadata; } - static void registerInfrastructure(BeanDefinitionRegistry registry, Set> detectedClasses) { + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + if (!(context instanceof BeanDefinitionRegistry registry)) { + throw new IllegalStateException("Cannot process bean overrides with an ApplicationContext " + + "that doesn't implement BeanDefinitionRegistry: " + context.getClass()); + } + registerInfrastructure(registry); + } + + Set getMetadata() { + return this.metadata; + } + + private void registerInfrastructure(BeanDefinitionRegistry registry) { addInfrastructureBeanDefinition(registry, BeanOverrideRegistrar.class, REGISTRAR_BEAN_NAME, - constructorArgs -> constructorArgs.addIndexedArgumentValue(0, detectedClasses)); + constructorArgs -> {}); + RuntimeBeanReference registrarReference = new RuntimeBeanReference(REGISTRAR_BEAN_NAME); - addInfrastructureBeanDefinition( - registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME, - constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference)); - addInfrastructureBeanDefinition( - registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME, + addInfrastructureBeanDefinition(registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME, constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference)); + addInfrastructureBeanDefinition(registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME, + constructorArgs -> { + constructorArgs.addIndexedArgumentValue(0, this.metadata); + constructorArgs.addIndexedArgumentValue(1, registrarReference); + }); } - private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry, + private void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry, Class clazz, String beanName, Consumer constructorArgumentsConsumer) { if (!registry.containsBeanDefinition(beanName)) { @@ -80,29 +95,20 @@ private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry regis } @Override - public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { - if (!(context instanceof BeanDefinitionRegistry registry)) { - throw new IllegalStateException("Cannot process bean overrides with an ApplicationContext " + - "that doesn't implement BeanDefinitionRegistry: " + context.getClass()); - } - registerInfrastructure(registry, this.detectedClasses); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { + public boolean equals(Object other) { + if (other == this) { return true; } - if (obj == null || obj.getClass() != getClass()) { + if (other == null || other.getClass() != getClass()) { return false; } - BeanOverrideContextCustomizer other = (BeanOverrideContextCustomizer) obj; - return this.detectedClasses.equals(other.detectedClasses); + BeanOverrideContextCustomizer that = (BeanOverrideContextCustomizer) other; + return this.metadata.equals(that.metadata); } @Override public int hashCode() { - return this.detectedClasses.hashCode(); + return this.metadata.hashCode(); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java index 73b072a62be3..4b79f97d4564 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java @@ -16,13 +16,12 @@ package org.springframework.test.context.bean.override; -import java.util.LinkedHashSet; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfigurationAttributes; -import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.TestContextAnnotationUtils; @@ -31,6 +30,7 @@ * Bean Overriding. * * @author Simon Baslé + * @author Stephane Nicoll * @since 6.2 * @see BeanOverride */ @@ -38,24 +38,22 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory { @Override @Nullable - public ContextCustomizer createContextCustomizer(Class testClass, + public BeanOverrideContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { - Set> detectedClasses = new LinkedHashSet<>(); - findClassesWithBeanOverride(testClass, detectedClasses); - if (detectedClasses.isEmpty()) { + Set metadata = new HashSet<>(); + findOverrideMetadata(testClass, metadata); + if (metadata.isEmpty()) { return null; } - - return new BeanOverrideContextCustomizer(detectedClasses); + return new BeanOverrideContextCustomizer(metadata); } - private void findClassesWithBeanOverride(Class testClass, Set> detectedClasses) { - if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) { - detectedClasses.add(testClass); - } + private void findOverrideMetadata(Class testClass, Set metadata) { + List overrideMetadata = OverrideMetadata.forTestClass(testClass); + metadata.addAll(overrideMetadata); if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) { - findClassesWithBeanOverride(testClass.getEnclosingClass(), detectedClasses); + findOverrideMetadata(testClass.getEnclosingClass(), metadata); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java deleted file mode 100644 index 3c0984ec8c82..000000000000 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.springframework.beans.BeanUtils; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.DIRECT; - -/** - * Internal parsing utilities to discover the presence of - * {@link BeanOverride @BeanOverride} and create the relevant - * {@link OverrideMetadata} if necessary. - * - * @author Simon Baslé - * @author Sam Brannen - * @since 6.2 - */ -abstract class BeanOverrideParsingUtils { - - /** - * Check if at least one field of the given {@code clazz} is meta-annotated - * with {@link BeanOverride @BeanOverride}. - * @param clazz the class which fields to inspect - * @return {@code true} if there is a bean override annotation present, - * {@code false} otherwise - */ - static boolean hasBeanOverride(Class clazz) { - AtomicBoolean hasBeanOverride = new AtomicBoolean(); - ReflectionUtils.doWithFields(clazz, field -> { - if (hasBeanOverride.get()) { - return; - } - boolean present = MergedAnnotations.from(field, DIRECT).isPresent(BeanOverride.class); - hasBeanOverride.compareAndSet(false, present); - }); - return hasBeanOverride.get(); - } - - /** - * Parse the specified classes for the presence of fields annotated with - * {@link BeanOverride @BeanOverride}, and create an {@link OverrideMetadata} - * for each identified field. - * @param classes the classes to parse - */ - static Set parse(Iterable> classes) { - Set result = new LinkedHashSet<>(); - classes.forEach(c -> ReflectionUtils.doWithFields(c, field -> parseField(field, c, result))); - return result; - } - - /** - * Convenience method to parse a single test class. - * @see #parse(Iterable) - */ - static Set parse(Class clazz) { - return parse(List.of(clazz)); - } - - private static void parseField(Field field, Class testClass, Set metadataSet) { - AtomicBoolean overrideAnnotationFound = new AtomicBoolean(); - MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> { - MergedAnnotation metaSource = mergedAnnotation.getMetaSource(); - Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present"); - - BeanOverride beanOverride = mergedAnnotation.synthesize(); - BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value()); - Annotation composedAnnotation = metaSource.synthesize(); - - Assert.state(overrideAnnotationFound.compareAndSet(false, true), - () -> "Multiple @BeanOverride annotations found on field: " + field); - OverrideMetadata metadata = processor.createMetadata(composedAnnotation, testClass, field); - metadataSet.add(metadata); - }); - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java index e8a8bfd30d15..2dafdcf23cf2 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java @@ -19,7 +19,6 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; -import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; @@ -45,21 +44,10 @@ class BeanOverrideRegistrar implements BeanFactoryAware { private final Map earlyOverrideMetadata = new HashMap<>(); - private final Set overrideMetadata; - @Nullable private ConfigurableBeanFactory beanFactory; - /** - * Create a new registrar and immediately parse the provided classes. - * @param classesToParse the initial set of classes that have been - * detected to contain bean overriding annotations - */ - BeanOverrideRegistrar(Set> classesToParse) { - this.overrideMetadata = BeanOverrideParsingUtils.parse(classesToParse); - } - @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) { @@ -69,13 +57,6 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = cbf; } - /** - * Get the detected {@link OverrideMetadata} instances. - */ - Set getOverrideMetadata() { - return this.overrideMetadata; - } - /** * Check {@link #markWrapEarly(OverrideMetadata, String) early override} * records and use the {@link OverrideMetadata} to create an override diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java index e84b65cf422d..10a59a5d3015 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java @@ -17,6 +17,7 @@ package org.springframework.test.context.bean.override; import java.lang.reflect.Field; +import java.util.List; import java.util.function.BiConsumer; import org.springframework.test.context.TestContext; @@ -74,12 +75,12 @@ protected void reinjectFieldsIfConfigured(TestContext testContext) throws Except if (Boolean.TRUE.equals( testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) { - postProcessFields(testContext, (testMetadata, postProcessor) -> { + postProcessFields(testContext, (testMetadata, registrar) -> { Object testInstance = testMetadata.testInstance; Field field = testMetadata.overrideMetadata.getField(); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, testInstance, null); - postProcessor.inject(testInstance, testMetadata.overrideMetadata); + registrar.inject(testInstance, testMetadata.overrideMetadata); }); } } @@ -90,13 +91,11 @@ private void postProcessFields(TestContext testContext, BiConsumer testClass = testContext.getTestClass(); Object testInstance = testContext.getTestInstance(); - if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) { + List metadataForFields = OverrideMetadata.forTestClass(testClass); + if (!metadataForFields.isEmpty()) { BeanOverrideRegistrar registrar = testContext.getApplicationContext().getBean(BeanOverrideRegistrar.class); - for (OverrideMetadata metadata : registrar.getOverrideMetadata()) { - if (!metadata.getField().getDeclaringClass().isAssignableFrom(testClass)) { - continue; - } + for (OverrideMetadata metadata : metadataForFields) { consumer.accept(new TestContextOverrideMetadata(testInstance, metadata), registrar); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java index c139c18af4ca..666eba9e7993 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java @@ -16,14 +16,26 @@ package org.springframework.test.context.bean.override; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.style.ToStringCreator; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.DIRECT; /** * Metadata for Bean Override injection points, also responsible for creation of @@ -38,6 +50,7 @@ * annotation or the annotated field. * * @author Simon Baslé + * @author Stephane Nicoll * @since 6.2 */ public abstract class OverrideMetadata { @@ -46,25 +59,55 @@ public abstract class OverrideMetadata { private final ResolvableType beanType; + @Nullable + private final String beanName; + private final BeanOverrideStrategy strategy; - protected OverrideMetadata(Field field, ResolvableType beanType, + protected OverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName, BeanOverrideStrategy strategy) { - this.field = field; this.beanType = beanType; + this.beanName = beanName; this.strategy = strategy; } /** - * Get the bean name to override, or {@code null} to look for a single - * matching bean of type {@link #getBeanType()}. - *

Defaults to {@code null}. + * Parse the given {@code testClass} and build the corresponding list of + * bean {@code OverrideMetadata}. + * @param testClass the class to parse + * @return a list of {@code OverrideMetadata} */ - @Nullable - protected String getBeanName() { - return null; + public static List forTestClass(Class testClass) { + List metadataList = new LinkedList<>(); + ReflectionUtils.doWithFields(testClass, field -> parseField(field, testClass, metadataList)); + return metadataList; + } + + private static void parseField(Field field, Class testClass, List metadataList) { + AtomicBoolean overrideAnnotationFound = new AtomicBoolean(); + MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> { + MergedAnnotation metaSource = mergedAnnotation.getMetaSource(); + Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present"); + + BeanOverride beanOverride = mergedAnnotation.synthesize(); + BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value()); + Annotation composedAnnotation = metaSource.synthesize(); + + Assert.state(overrideAnnotationFound.compareAndSet(false, true), + () -> "Multiple @BeanOverride annotations found on field: " + field); + OverrideMetadata metadata = processor.createMetadata(composedAnnotation, testClass, field); + metadataList.add(metadata); + }); + } + + + /** + * Get the annotated {@link Field}. + */ + public final Field getField() { + return this.field; } /** @@ -75,10 +118,12 @@ public final ResolvableType getBeanType() { } /** - * Get the annotated {@link Field}. + * Get the bean name to override, or {@code null} to look for a single + * matching bean of type {@link #getBeanType()}. */ - public final Field getField() { - return this.field; + @Nullable + public String getBeanName() { + return this.beanName; } /** @@ -115,30 +160,41 @@ protected void track(Object override, SingletonBeanRegistry trackingBeanRegistry } @Override - public boolean equals(Object obj) { - if (obj == this) { + public boolean equals(Object other) { + if (other == this) { return true; } - if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { + if (other == null || other.getClass() != getClass()) { + return false; + } + OverrideMetadata that = (OverrideMetadata) other; + if (!Objects.equals(this.beanType.getType(), that.beanType.getType()) || + !Objects.equals(this.beanName, that.beanName) || + !Objects.equals(this.strategy, that.strategy)) { return false; } - OverrideMetadata that = (OverrideMetadata) obj; - return Objects.equals(this.strategy, that.strategy) && - Objects.equals(this.field, that.field) && - Objects.equals(this.beanType, that.beanType); + if (this.beanName != null) { + return true; + } + // by type lookup + return Objects.equals(this.field.getName(), that.field.getName()) && + Arrays.equals(this.field.getAnnotations(), that.field.getAnnotations()); } @Override public int hashCode() { - return Objects.hash(this.strategy, this.field, this.beanType); + int hash = Objects.hash(getClass(), this.beanType.getType(), this.beanName, this.strategy); + return (this.beanName != null ? hash : hash + + 31 * Objects.hash(this.field.getName(), Arrays.hashCode(this.field.getAnnotations()))); } @Override public String toString() { return new ToStringCreator(this) - .append("strategy", this.strategy) .append("field", this.field) .append("beanType", this.beanType) + .append("beanName", this.beanName) + .append("strategy", this.strategy) .toString(); } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java index d4cc67bb7ad0..ec5f92679af3 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java @@ -43,9 +43,9 @@ *

    *
  • If the {@link #methodName()} is specified, look for a static method with * that name.
  • - *
  • If a method name is not specified, look for exactly one static method named - * with a suffix equal to {@value #CONVENTION_SUFFIX} and starting with either the - * name of the annotated field or the name of the bean (if specified).
  • + *
  • If a method name is not specified, look for exactly one static method + * named with either the name of the annotated field or the name of the bean + * (if specified).
  • *
* *

Consider the following example. @@ -58,16 +58,16 @@ * * // Tests * - * private static CustomerRepository repositoryTestOverride() { + * private static CustomerRepository repository() { * return new TestCustomerRepository(); * } * } * *

In the example above, the {@code repository} bean is replaced by the - * instance generated by the {@code repositoryTestOverride()} method. Not only - * is the overridden instance injected into the {@code repository} field, but it - * is also replaced in the {@code BeanFactory} so that other injection points - * for that bean use the overridden bean instance. + * instance generated by the {@code repository()} method. Not only is the + * overridden instance injected into the {@code repository} field, but it is + * also replaced in the {@code BeanFactory} so that other injection points for + * that bean use the overridden bean instance. * *

To make things more explicit, the bean and method names can be set, * as shown in the following example. @@ -75,7 +75,7 @@ *


  * class CustomerServiceTests {
  *
- *     @TestBean(name = "repository", methodName = "createTestCustomerRepository")
+ *     @TestBean(name = "customerRepository", methodName = "createTestCustomerRepository")
  *     private CustomerRepository repository;
  *
  *     // Tests
@@ -97,14 +97,6 @@
 @BeanOverride(TestBeanOverrideProcessor.class)
 public @interface TestBean {
 
-	/**
-	 * Required suffix for the name of a factory method that overrides a bean
-	 * instance when the factory method is detected by convention.
-	 * @see #methodName()
-	 */
-	String CONVENTION_SUFFIX = "TestOverride";
-
-
 	/**
 	 * Alias for {@link #name()}.
 	 * 

Intended to be used when no other attributes are needed — for @@ -130,8 +122,7 @@ * also considered. Similarly, in case the test class inherits from a base * class the whole class hierarchy is considered. *

If left unspecified, the name of the factory method will be detected - * based on convention. - * @see #CONVENTION_SUFFIX + * based either on the name of the annotated field or the name of the bean. */ String methodName() default ""; diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java new file mode 100644 index 000000000000..675094baff29 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override.convention; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; +import org.springframework.test.context.bean.override.BeanOverrideStrategy; +import org.springframework.test.context.bean.override.OverrideMetadata; +import org.springframework.util.ReflectionUtils; + +/** + * {@link OverrideMetadata} implementation for {@link TestBean}. + * + * @author Simon Baslé + * @author Stephane Nicoll + * @since 6.2 + */ +final class TestBeanOverrideMetadata extends OverrideMetadata { + + private final Method overrideMethod; + + + TestBeanOverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName, + Method overrideMethod) { + + super(field, beanType, beanName, BeanOverrideStrategy.REPLACE_DEFINITION); + this.overrideMethod = overrideMethod; + } + + + @Override + protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, + @Nullable Object existingBeanInstance) { + + try { + ReflectionUtils.makeAccessible(this.overrideMethod); + return this.overrideMethod.invoke(null); + } + catch (IllegalAccessException | InvocationTargetException ex) { + throw new IllegalStateException("Failed to invoke bean overriding method " + this.overrideMethod.getName() + + "; a static method with no formal parameters is expected", ex); + } + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + if (!super.equals(other)) { + return false; + } + TestBeanOverrideMetadata that = (TestBeanOverrideMetadata) other; + return Objects.equals(this.overrideMethod, that.overrideMethod); + } + + @Override + public int hashCode() { + return this.overrideMethod.hashCode() * 29 + super.hashCode(); + } +} diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessor.java index 5e28e57c7a0e..2769001a48dc 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessor.java @@ -18,25 +18,20 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; -import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.MethodIntrospector; import org.springframework.core.ResolvableType; -import org.springframework.lang.Nullable; import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.bean.override.BeanOverrideProcessor; -import org.springframework.test.context.bean.override.BeanOverrideStrategy; -import org.springframework.test.context.bean.override.OverrideMetadata; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodFilter; import org.springframework.util.StringUtils; @@ -48,15 +43,44 @@ * * @author Simon Baslé * @author Sam Brannen + * @author Stephane Nicoll * @since 6.2 */ class TestBeanOverrideProcessor implements BeanOverrideProcessor { + @Override + public TestBeanOverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { + if (!(overrideAnnotation instanceof TestBean testBeanAnnotation)) { + throw new IllegalStateException("Invalid annotation passed to %s: expected @TestBean on field %s.%s" + .formatted(getClass().getSimpleName(), field.getDeclaringClass().getName(), field.getName())); + } + Method overrideMethod; + String methodName = testBeanAnnotation.methodName(); + if (!methodName.isBlank()) { + // If the user specified an explicit method name, search for that. + overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), methodName); + } + else { + // Otherwise, search for candidate factory methods the field name + // or explicit bean name (if any). + List candidateMethodNames = new ArrayList<>(); + candidateMethodNames.add(field.getName()); + + String beanName = testBeanAnnotation.name(); + if (StringUtils.hasText(beanName)) { + candidateMethodNames.add(beanName); + } + overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames); + } + String beanName = (StringUtils.hasText(testBeanAnnotation.name()) ? testBeanAnnotation.name() : null); + return new TestBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), beanName, overrideMethod); + } + /** * Find a test bean factory {@link Method} for the given {@link Class}. - *

Delegates to {@link #findTestBeanFactoryMethod(Class, Class, List)}. + *

Delegates to {@link #findTestBeanFactoryMethod(Class, Class, Collection)}. */ - static Method findTestBeanFactoryMethod(Class clazz, Class methodReturnType, String... methodNames) { + Method findTestBeanFactoryMethod(Class clazz, Class methodReturnType, String... methodNames) { return findTestBeanFactoryMethod(clazz, methodReturnType, List.of(methodNames)); } @@ -83,7 +107,7 @@ static Method findTestBeanFactoryMethod(Class clazz, Class methodReturnTyp * @throws IllegalStateException if a matching factory method cannot * be found or multiple methods match */ - static Method findTestBeanFactoryMethod(Class clazz, Class methodReturnType, List methodNames) { + Method findTestBeanFactoryMethod(Class clazz, Class methodReturnType, Collection methodNames) { Assert.notEmpty(methodNames, "At least one candidate method name is required"); Set supportedNames = new LinkedHashSet<>(methodNames); MethodFilter methodFilter = method -> (Modifier.isStatic(method.getModifiers()) && @@ -92,49 +116,20 @@ static Method findTestBeanFactoryMethod(Class clazz, Class methodReturnTyp Set methods = findMethods(clazz, methodFilter); - Assert.state(!methods.isEmpty(), () -> """ - Failed to find a static test bean factory method in %s with return type %s \ - whose name matches one of the supported candidates %s""".formatted( - clazz.getName(), methodReturnType.getName(), supportedNames)); + String methodNamesDescription = supportedNames.stream() + .map(name -> name + "()").collect(Collectors.joining(" or ")); + Assert.state(!methods.isEmpty(), () -> + "No static method found named %s in %s with return type %s".formatted( + methodNamesDescription, clazz.getName(), methodReturnType.getName())); long uniqueMethodNameCount = methods.stream().map(Method::getName).distinct().count(); - Assert.state(uniqueMethodNameCount == 1, () -> """ - Found %d competing static test bean factory methods in %s with return type %s \ - whose name matches one of the supported candidates %s""".formatted( - uniqueMethodNameCount, clazz.getName(), methodReturnType.getName(), supportedNames)); + Assert.state(uniqueMethodNameCount == 1, () -> + "Found %d competing static methods named %s in %s with return type %s".formatted( + uniqueMethodNameCount, methodNamesDescription, clazz.getName(), methodReturnType.getName())); return methods.iterator().next(); } - @Override - public TestBeanOverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { - if (!(overrideAnnotation instanceof TestBean testBeanAnnotation)) { - throw new IllegalStateException("Invalid annotation passed to %s: expected @TestBean on field %s.%s" - .formatted(getClass().getSimpleName(), field.getDeclaringClass().getName(), field.getName())); - } - Method overrideMethod; - String methodName = testBeanAnnotation.methodName(); - if (!methodName.isBlank()) { - // If the user specified an explicit method name, search for that. - overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), methodName); - } - else { - // Otherwise, search for candidate factory methods using the convention - // suffix and the field name or explicit bean name (if any). - List candidateMethodNames = new ArrayList<>(); - candidateMethodNames.add(field.getName() + TestBean.CONVENTION_SUFFIX); - - String beanName = testBeanAnnotation.name(); - if (StringUtils.hasText(beanName)) { - candidateMethodNames.add(beanName + TestBean.CONVENTION_SUFFIX); - } - overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames); - } - - return new TestBeanOverrideMetadata(field, overrideMethod, testBeanAnnotation, ResolvableType.forField(field, testClass)); - } - - private static Set findMethods(Class clazz, MethodFilter methodFilter) { Set methods = MethodIntrospector.selectMethods(clazz, methodFilter); if (methods.isEmpty() && TestContextAnnotationUtils.searchEnclosingClass(clazz)) { @@ -143,61 +138,4 @@ private static Set findMethods(Class clazz, MethodFilter methodFilter return methods; } - - static final class TestBeanOverrideMetadata extends OverrideMetadata { - - private final Method overrideMethod; - - private final String beanName; - - public TestBeanOverrideMetadata(Field field, Method overrideMethod, TestBean overrideAnnotation, - ResolvableType typeToOverride) { - - super(field, typeToOverride, BeanOverrideStrategy.REPLACE_DEFINITION); - this.beanName = overrideAnnotation.name(); - this.overrideMethod = overrideMethod; - } - - @Override - @Nullable - protected String getBeanName() { - return StringUtils.hasText(this.beanName) ? this.beanName : super.getBeanName(); - } - - @Override - protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, - @Nullable Object existingBeanInstance) { - - try { - ReflectionUtils.makeAccessible(this.overrideMethod); - return this.overrideMethod.invoke(null); - } - catch (IllegalAccessException | InvocationTargetException ex) { - throw new IllegalStateException("Failed to invoke bean overriding method " + this.overrideMethod.getName() + - "; a static method with no formal parameters is expected", ex); - } - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - TestBeanOverrideMetadata that = (TestBeanOverrideMetadata) o; - return Objects.equals(this.overrideMethod, that.overrideMethod) - && Objects.equals(this.beanName, that.beanName); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), this.overrideMethod, this.beanName); - } - } - } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java similarity index 70% rename from spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanMetadata.java rename to spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java index b1f99958002b..a4e7e5a14c19 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.springframework.core.style.ToStringCreator; import org.springframework.lang.Nullable; import org.springframework.test.context.bean.override.BeanOverrideStrategy; +import org.springframework.test.context.bean.override.OverrideMetadata; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -39,12 +40,13 @@ import static org.mockito.Mockito.mock; /** - * A complete definition that can be used to create a Mockito mock. + * {@link OverrideMetadata} implementation for Mockito {@code mock} support. * * @author Phillip Webb + * @author Stephane Nicoll * @since 6.2 */ -class MockitoBeanMetadata extends MockitoMetadata { +class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata { private final Set> extraInterfaces; @@ -53,27 +55,22 @@ class MockitoBeanMetadata extends MockitoMetadata { private final boolean serializable; - MockitoBeanMetadata(MockitoBean annotation, Field field, ResolvableType typeToMock) { - this(annotation.name(), annotation.reset(), field, typeToMock, - annotation.extraInterfaces(), annotation.answers(), annotation.serializable()); + MockitoBeanOverrideMetadata(Field field, ResolvableType typeToMock, MockitoBean annotation) { + this(field, typeToMock, (StringUtils.hasText(annotation.name()) ? annotation.name() : null), + annotation.reset(), annotation.extraInterfaces(), annotation.answers(), annotation.serializable()); } - MockitoBeanMetadata(String name, MockReset reset, Field field, ResolvableType typeToMock, + MockitoBeanOverrideMetadata(Field field, ResolvableType typeToMock, @Nullable String beanName, MockReset reset, Class[] extraInterfaces, @Nullable Answers answer, boolean serializable) { - super(name, reset, false, field, typeToMock, BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION); - Assert.notNull(typeToMock, "TypeToMock must not be null"); + super(field, typeToMock, beanName, BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION, reset, false); + Assert.notNull(typeToMock, "'typeToMock' must not be null"); this.extraInterfaces = asClassSet(extraInterfaces); this.answer = (answer != null) ? answer : Answers.RETURNS_DEFAULTS; this.serializable = serializable; } - @Override - protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) { - return createMock(beanName); - } - - private Set> asClassSet(@Nullable Class[] classes) { + private static Set> asClassSet(@Nullable Class[] classes) { Set> classSet = new LinkedHashSet<>(); if (classes != null) { classSet.addAll(Arrays.asList(classes)); @@ -81,6 +78,7 @@ private Set> asClassSet(@Nullable Class[] classes) { return Collections.unmodifiableSet(classSet); } + /** * Return the extra interfaces. * @return the extra interfaces or an empty set @@ -106,53 +104,58 @@ boolean isSerializable() { } @Override - public boolean equals(@Nullable Object obj) { - if (obj == this) { + protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) { + return createMock(beanName); + } + + @SuppressWarnings("unchecked") + T createMock(String name) { + MockSettings settings = MockReset.withSettings(getReset()); + if (StringUtils.hasLength(name)) { + settings.name(name); + } + if (!this.extraInterfaces.isEmpty()) { + settings.extraInterfaces(ClassUtils.toClassArray(this.extraInterfaces)); + } + settings.defaultAnswer(this.answer); + if (this.serializable) { + settings.serializable(); + } + Class targetType = getBeanType().resolve(); + return (T) mock(targetType, settings); + } + + @Override + public boolean equals(@Nullable Object other) { + if (other == this) { return true; } - if (obj == null || obj.getClass() != getClass()) { + if (other == null || other.getClass() != getClass()) { return false; } - MockitoBeanMetadata other = (MockitoBeanMetadata) obj; - boolean result = super.equals(obj); - result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces, other.extraInterfaces); - result = result && ObjectUtils.nullSafeEquals(this.answer, other.answer); - result = result && this.serializable == other.serializable; + MockitoBeanOverrideMetadata that = (MockitoBeanOverrideMetadata) other; + boolean result = super.equals(that); + result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces, that.extraInterfaces); + result = result && ObjectUtils.nullSafeEquals(this.answer, that.answer); + result = result && this.serializable == that.serializable; return result; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), this.extraInterfaces, this.answer, this.serializable); + return Objects.hash(this.extraInterfaces, this.answer, this.serializable) + super.hashCode(); } @Override public String toString() { return new ToStringCreator(this) + .append("beanType", getBeanType()) .append("beanName", getBeanName()) - .append("fieldType", getBeanType()) + .append("reset", getReset()) .append("extraInterfaces", getExtraInterfaces()) .append("answer", getAnswer()) .append("serializable", isSerializable()) - .append("reset", getReset()) .toString(); } - @SuppressWarnings("unchecked") - T createMock(String name) { - MockSettings settings = MockReset.withSettings(getReset()); - if (StringUtils.hasLength(name)) { - settings.name(name); - } - if (!this.extraInterfaces.isEmpty()) { - settings.extraInterfaces(ClassUtils.toClassArray(this.extraInterfaces)); - } - settings.defaultAnswer(this.answer); - if (this.serializable) { - settings.serializable(); - } - Class targetType = getBeanType().resolve(); - return (T) mock(targetType, settings); - } - } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java index 12dc919b51e5..febb7538b3bc 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java @@ -34,12 +34,12 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor { @Override - public MockitoMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { + public MockitoOverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { if (overrideAnnotation instanceof MockitoBean mockBean) { - return new MockitoBeanMetadata(mockBean, field, ResolvableType.forField(field, testClass)); + return new MockitoBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), mockBean); } else if (overrideAnnotation instanceof MockitoSpyBean spyBean) { - return new MockitoSpyBeanMetadata(spyBean, field, ResolvableType.forField(field, testClass)); + return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), spyBean); } throw new IllegalStateException(String.format("Invalid annotation passed to MockitoBeanOverrideProcessor: " + "expected @MockitoBean/@MockitoSpyBean on field %s.%s", diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java similarity index 67% rename from spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoMetadata.java rename to spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java index aa69db785569..974966f00637 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,37 +26,44 @@ import org.springframework.test.context.bean.override.BeanOverrideStrategy; import org.springframework.test.context.bean.override.OverrideMetadata; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; /** - * Base class for Mockito override metadata. + * Base {@link OverrideMetadata} implementation for Mockito. * * @author Phillip Webb + * @author Stephane Nicoll * @since 6.2 */ -abstract class MockitoMetadata extends OverrideMetadata { - - protected final String name; +abstract class MockitoOverrideMetadata extends OverrideMetadata { private final MockReset reset; private final boolean proxyTargetAware; - MockitoMetadata(String name, @Nullable MockReset reset, boolean proxyTargetAware, Field field, - ResolvableType typeToOverride, BeanOverrideStrategy strategy) { + protected MockitoOverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName, + BeanOverrideStrategy strategy, @Nullable MockReset reset, boolean proxyTargetAware) { - super(field, typeToOverride, strategy); - this.name = name; + super(field, beanType, beanName, strategy); this.reset = (reset != null) ? reset : MockReset.AFTER; this.proxyTargetAware = proxyTargetAware; } - @Override - @Nullable - protected String getBeanName() { - return StringUtils.hasText(this.name) ? this.name : super.getBeanName(); + /** + * Return the mock reset mode. + * @return the reset mode + */ + MockReset getReset() { + return this.reset; + } + + /** + * Return if AOP advised beans should be proxy target aware. + * @return if proxy target aware + */ + boolean isProxyTargetAware() { + return this.proxyTargetAware; } @Override @@ -75,41 +82,24 @@ protected void track(Object mock, SingletonBeanRegistry trackingBeanRegistry) { tracker.add(mock); } - /** - * Return the mock reset mode. - * @return the reset mode - */ - MockReset getReset() { - return this.reset; - } - - /** - * Return if AOP advised beans should be proxy target aware. - * @return if proxy target aware - */ - boolean isProxyTargetAware() { - return this.proxyTargetAware; - } - @Override - public boolean equals(@Nullable Object obj) { - if (obj == this) { + public boolean equals(@Nullable Object other) { + if (other == this) { return true; } - if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { + if (other == null || !getClass().isAssignableFrom(other.getClass())) { return false; } - MockitoMetadata other = (MockitoMetadata) obj; - boolean result = super.equals(obj); - result = result && ObjectUtils.nullSafeEquals(this.name, other.name); - result = result && ObjectUtils.nullSafeEquals(this.reset, other.reset); - result = result && ObjectUtils.nullSafeEquals(this.proxyTargetAware, other.proxyTargetAware); + MockitoOverrideMetadata that = (MockitoOverrideMetadata) other; + boolean result = super.equals(that); + result = result && ObjectUtils.nullSafeEquals(this.reset, that.reset); + result = result && ObjectUtils.nullSafeEquals(this.proxyTargetAware, that.proxyTargetAware); return result; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), this.name, this.reset, this.proxyTargetAware); + return Objects.hash(this.reset, this.proxyTargetAware) + super.hashCode(); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java similarity index 75% rename from spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanMetadata.java rename to spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java index 1a8b27e7f5c6..931b92c4b323 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Proxy; -import java.util.Objects; import org.mockito.AdditionalAnswers; import org.mockito.MockSettings; @@ -34,27 +33,29 @@ import org.springframework.test.context.bean.override.OverrideMetadata; import org.springframework.test.util.AopTestUtils; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static org.mockito.Mockito.mock; /** - * {@link OverrideMetadata} for Mockito {@code spy} support. + * {@link OverrideMetadata} implementation for Mockito {@code spy} support. * * @author Phillip Webb * @author Simon Baslé + * @author Stephane Nicoll * @since 6.2 */ -class MockitoSpyBeanMetadata extends MockitoMetadata { +class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata { - MockitoSpyBeanMetadata(MockitoSpyBean spyAnnotation, Field field, ResolvableType typeToSpy) { - this(spyAnnotation.name(), spyAnnotation.reset(), spyAnnotation.proxyTargetAware(), - field, typeToSpy); + MockitoSpyBeanOverrideMetadata(Field field, ResolvableType typeToSpy, MockitoSpyBean spyAnnotation) { + this(field, typeToSpy, (StringUtils.hasText(spyAnnotation.name()) ? spyAnnotation.name() : null), + spyAnnotation.reset(), spyAnnotation.proxyTargetAware()); } - MockitoSpyBeanMetadata(String name, MockReset reset, boolean proxyTargetAware, Field field, ResolvableType typeToSpy) { - super(name, reset, proxyTargetAware, field, typeToSpy, BeanOverrideStrategy.WRAP_BEAN); + MockitoSpyBeanOverrideMetadata(Field field, ResolvableType typeToSpy, @Nullable String beanName, + MockReset reset, boolean proxyTargetAware) { + + super(field, typeToSpy, beanName, BeanOverrideStrategy.WRAP_BEAN, reset, proxyTargetAware); Assert.notNull(typeToSpy, "typeToSpy must not be null"); } @@ -68,33 +69,6 @@ protected Object createOverride(String beanName, @Nullable BeanDefinition existi return createSpy(beanName, existingBeanInstance); } - @Override - public boolean equals(@Nullable Object obj) { - if (obj == this) { - return true; - } - // For SpyBean we want the class to be exactly the same. - if (obj == null || obj.getClass() != getClass()) { - return false; - } - MockitoSpyBeanMetadata that = (MockitoSpyBeanMetadata) obj; - return (super.equals(obj) && ObjectUtils.nullSafeEquals(getBeanType(), that.getBeanType())); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), getBeanType()); - } - - @Override - public String toString() { - return new ToStringCreator(this) - .append("beanName", getBeanName()) - .append("beanType", getBeanType()) - .append("reset", getReset()) - .toString(); - } - @SuppressWarnings("unchecked") T createSpy(String name, Object instance) { Assert.notNull(instance, "Instance must not be null"); @@ -124,6 +98,16 @@ T createSpy(String name, Object instance) { return (T) mock(toSpy, settings); } + @Override + public String toString() { + return new ToStringCreator(this) + .append("beanName", getBeanName()) + .append("beanType", getBeanType()) + .append("reset", getReset()) + .toString(); + } + + /** * A {@link VerificationStartedListener} that bypasses any proxy created by * Spring AOP when the verification of a spy starts. @@ -134,7 +118,6 @@ private static final class SpringAopBypassingVerificationStartedListener impleme public void onVerificationStarted(VerificationStartedEvent event) { event.setMock(AopTestUtils.getUltimateTargetObject(event.getMock())); } - } } diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java index 36678088637a..de937e6051bd 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java @@ -174,13 +174,12 @@ public RequestMatcher formDataContains(Map expected) { return formData(multiValueMap, false); } - @SuppressWarnings("unchecked") private RequestMatcher formData(MultiValueMap expectedMap, boolean containsExactly) { return request -> { MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; MockHttpInputMessage message = new MockHttpInputMessage(mockRequest.getBodyAsBytes()); message.getHeaders().putAll(mockRequest.getHeaders()); - MultiValueMap actualMap = (MultiValueMap) new FormHttpMessageConverter().read(null, message); + MultiValueMap actualMap = new FormHttpMessageConverter().read(null, message); if (containsExactly) { assertEquals("Form data", expectedMap, actualMap); } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java index e29e4e529543..df5bdbd46ef5 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java @@ -197,6 +197,19 @@ public WebTestClient.ResponseSpec httpOnly(String name, boolean expected) { return this.responseSpec; } + /** + * Assert a cookie's "Partitioned" attribute. + * @since 6.2 + */ + public WebTestClient.ResponseSpec partitioned(String name, boolean expected) { + boolean isPartitioned = getCookie(name).isPartitioned(); + this.exchangeResult.assertWithDiagnostics(() -> { + String message = getMessage(name) + " isPartitioned"; + assertEquals(message, expected, isPartitioned); + }); + return this.responseSpec; + } + /** * Assert a cookie's "SameSite" attribute. */ 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 9b94b74f66f1..a4de02424ec1 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 @@ -1060,7 +1060,7 @@ default BodyContentSpec json(String expectedJson) { *

Use of this method requires the * XMLUnit library on * the classpath. - * @param expectedXml the expected JSON content. + * @param expectedXml the expected XML content. * @since 5.1 * @see org.springframework.test.util.XmlExpectationsHelper#assertXmlEqual(String, String) */ diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java index d54278330a85..e387a3b65e89 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java @@ -209,6 +209,7 @@ private MockClientHttpResponse adaptResponse(MvcResult mvcResult) { .path(cookie.getPath()) .secure(cookie.getSecure()) .httpOnly(cookie.isHttpOnly()) + .partitioned(cookie.getAttribute("Partitioned") != null) .sameSite(cookie.getAttribute("samesite")) .build(); clientResponse.getCookies().add(httpCookie.getName(), httpCookie); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java index f8b0a54f3dc4..3adb4fa10b1b 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java @@ -902,7 +902,7 @@ public HttpHeaders getHeaders() { }; try { - return (MultiValueMap) new FormHttpMessageConverter().read(null, message); + return new FormHttpMessageConverter().read(null, message); } catch (IOException ex) { throw new IllegalStateException("Failed to parse form data in request body", ex); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/CookieResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/CookieResultMatchers.java index 88ed2f04ed15..01a346d36e42 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/CookieResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/CookieResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -229,6 +229,17 @@ public ResultMatcher httpOnly(String name, boolean httpOnly) { }; } + /** + * Assert whether the cookie is partitioned. + * @since 6.2 + */ + public ResultMatcher partitioned(String name, boolean partitioned) { + return result -> { + Cookie cookie = getCookie(result, name); + assertEquals("Response cookie '" + name + "' partitioned", partitioned, cookie.getAttribute("Partitioned") != null); + }; + } + /** * Assert a cookie's specified attribute with a Hamcrest {@link Matcher}. * @param cookieAttribute the name of the Cookie attribute (case-insensitive) diff --git a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/CookieResultMatchersDsl.kt b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/CookieResultMatchersDsl.kt index 1c18645e53a8..6a9ee8733542 100644 --- a/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/CookieResultMatchersDsl.kt +++ b/spring-test/src/main/kotlin/org/springframework/test/web/servlet/result/CookieResultMatchersDsl.kt @@ -157,6 +157,14 @@ class CookieResultMatchersDsl internal constructor (private val actions: ResultA actions.andExpect(matchers.httpOnly(name, httpOnly)) } + /** + * @see CookieResultMatchers.partitioned + * @since 6.2 + */ + fun partitioned(name: String, partitioned: Boolean) { + actions.andExpect(matchers.partitioned(name, partitioned)) + } + /** * @see CookieResultMatchers.attribute * @since 6.0.8 diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java index 6a55669caf7c..e078b3809405 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java @@ -46,6 +46,7 @@ void constructCookie() { assertThat(cookie.getMaxAge()).isEqualTo(-1); assertThat(cookie.getPath()).isNull(); assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.isPartitioned()).isFalse(); assertThat(cookie.getSecure()).isFalse(); assertThat(cookie.getSameSite()).isNull(); } @@ -71,7 +72,7 @@ void parseHeaderWithoutAttributes() { @Test void parseHeaderWithAttributes() { MockCookie cookie = MockCookie.parse("SESSION=123; Domain=example.com; Max-Age=60; " + - "Expires=Tue, 8 Oct 2019 19:50:00 GMT; Path=/; Secure; HttpOnly; SameSite=Lax"); + "Expires=Tue, 8 Oct 2019 19:50:00 GMT; Path=/; Secure; HttpOnly; Partitioned; SameSite=Lax"); assertCookie(cookie, "SESSION", "123"); assertThat(cookie.getDomain()).isEqualTo("example.com"); @@ -79,6 +80,7 @@ void parseHeaderWithAttributes() { assertThat(cookie.getPath()).isEqualTo("/"); assertThat(cookie.getSecure()).isTrue(); assertThat(cookie.isHttpOnly()).isTrue(); + assertThat(cookie.isPartitioned()).isTrue(); assertThat(cookie.getExpires()).isEqualTo(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", DateTimeFormatter.RFC_1123_DATE_TIME)); assertThat(cookie.getSameSite()).isEqualTo("Lax"); @@ -203,4 +205,14 @@ void setInvalidAttributeExpiresShouldThrow() { assertThatThrownBy(() -> cookie.setAttribute("expires", "12345")).isInstanceOf(DateTimeParseException.class); } + @Test + void setPartitioned() { + MockCookie cookie = new MockCookie("SESSION", "123"); + assertThat(cookie.isPartitioned()).isFalse(); + cookie.setPartitioned(true); + assertThat(cookie.isPartitioned()).isTrue(); + cookie.setPartitioned(false); + assertThat(cookie.isPartitioned()).isFalse(); + } + } diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java index a6f2964ddc2e..0bc9975a7c0b 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java @@ -274,12 +274,13 @@ void cookies() { cookie.setMaxAge(0); cookie.setSecure(true); cookie.setHttpOnly(true); + cookie.setAttribute("Partitioned", ""); response.addCookie(cookie); assertThat(response.getHeader(SET_COOKIE)).isEqualTo(("foo=bar; Path=/path; Domain=example.com; " + "Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; " + - "Secure; HttpOnly")); + "Secure; HttpOnly; Partitioned")); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java index 2e53e436750a..dd1dc22c480b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java @@ -17,6 +17,7 @@ package org.springframework.test.context.bean.override; import java.lang.reflect.Field; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.function.Predicate; @@ -32,144 +33,194 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.support.SimpleThreadScope; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; -import org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation; -import org.springframework.test.context.bean.override.example.ExampleService; -import org.springframework.test.context.bean.override.example.FailingExampleService; -import org.springframework.test.context.bean.override.example.RealExampleService; +import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.Assert; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.Mockito.mock; /** * Tests for {@link BeanOverrideBeanFactoryPostProcessor} combined with a * {@link BeanOverrideRegistrar}. * * @author Simon Baslé + * @author Stephane Nicoll */ class BeanOverrideBeanFactoryPostProcessorTests { @Test - void canReplaceExistingBeanDefinitions() { - AnnotationConfigApplicationContext context = createContext(ReplaceBeans.class); - context.register(ReplaceBeans.class); - context.registerBean("explicit", ExampleService.class, () -> new RealExampleService("unexpected")); - context.registerBean("implicitName", ExampleService.class, () -> new RealExampleService("unexpected")); - + void replaceBeanByNameWithMatchingBeanDefinition() { + AnnotationConfigApplicationContext context = createContext(CaseByName.class); + context.registerBean("descriptionBean", String.class, () -> "Original"); context.refresh(); - assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE); - assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE); + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); } @Test - void cannotReplaceIfNoBeanMatching() { - AnnotationConfigApplicationContext context = createContext(ReplaceBeans.class); - context.register(ReplaceBeans.class); - //note we don't register any original bean here + void replaceBeanByNameWithoutMatchingBeanDefinitionFails() { + AnnotationConfigApplicationContext context = createContext(CaseByName.class); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage("Unable to override bean 'explicit': there is no bean definition " + - "to replace with that name of type org.springframework.test.context.bean.override.example.ExampleService"); + .withMessage("Unable to override bean 'descriptionBean': there is no bean definition " + + "to replace with that name of type java.lang.String"); } @Test - void canReplaceExistingBeanDefinitionsWithCreateReplaceStrategy() { - AnnotationConfigApplicationContext context = createContext(CreateIfOriginalIsMissingBean.class); - context.register(CreateIfOriginalIsMissingBean.class); - context.registerBean("explicit", ExampleService.class, () -> new RealExampleService("unexpected")); - context.registerBean("implicitName", ExampleService.class, () -> new RealExampleService("unexpected")); + void replaceBeanByNameWithMatchingBeanDefinitionAndWrongTypeFails() { + AnnotationConfigApplicationContext context = createContext(CaseByName.class); + context.registerBean("descriptionBean", Integer.class, () -> -1); + + assertThatIllegalStateException() + .isThrownBy(context::refresh) + .withMessage("Unable to override bean 'descriptionBean': there is no bean definition " + + "to replace with that name of type java.lang.String"); + } + @Test + void replaceBeanByNameCanOverrideBeanProducedByFactoryBeanWithClassObjectTypeAttribute() { + AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(CharSequence.class); context.refresh(); - assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE); - assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE); + assertThat(context.getBean("beanToBeOverridden")).isEqualTo("overridden"); } @Test - void canCreateIfOriginalMissingWithCreateReplaceStrategy() { - AnnotationConfigApplicationContext context = createContext(CreateIfOriginalIsMissingBean.class); - context.register(CreateIfOriginalIsMissingBean.class); - //note we don't register original beans here - + void replaceBeanByNameCanOverrideBeanProducedByFactoryBeanWithResolvableTypeObjectTypeAttribute() { + AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(ResolvableType.forClass(CharSequence.class)); context.refresh(); - assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE); - assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE); + assertThat(context.getBean("beanToBeOverridden")).isEqualTo("overridden"); } - @Test - void canOverrideBeanProducedByFactoryBeanWithClassObjectTypeAttribute() { - AnnotationConfigApplicationContext context = createContext(OverriddenFactoryBean.class); + private AnnotationConfigApplicationContext prepareContextWithFactoryBean(Object objectTypeAttribute) { + AnnotationConfigApplicationContext context = createContext(CaseOverrideBeanProducedByFactoryBean.class); + context.registerBean("testFactoryBean", TestFactoryBean.class, TestFactoryBean::new); RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class); - factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, SomeInterface.class); + factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, objectTypeAttribute); context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition); - context.register(OverriddenFactoryBean.class); + return context; + } + @Test + void replaceBeanByTypeWithSingleMatchingBean() { + AnnotationConfigApplicationContext context = createContext(CaseByType.class); + context.registerBean("someInteger", Integer.class, () -> 1); context.refresh(); - assertThat(context.getBean("beanToBeOverridden")).isSameAs(OVERRIDE); + assertThat(context.getBean("someInteger")).isEqualTo(42); } @Test - void canOverrideBeanProducedByFactoryBeanWithResolvableTypeObjectTypeAttribute() { - AnnotationConfigApplicationContext context = createContext(OverriddenFactoryBean.class); - RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class); - ResolvableType objectType = ResolvableType.forClass(SomeInterface.class); - factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, objectType); - context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition); - context.register(OverriddenFactoryBean.class); + void replaceBeanByTypeWithoutMatchingBeanFails() { + AnnotationConfigApplicationContext context = createContext(CaseByType.class); + assertThatIllegalStateException() + .isThrownBy(context::refresh) + .withMessage("Unable to override bean: no bean definitions of type java.lang.Integer " + + "(as required by annotated field 'CaseByType.counter')"); + } + + @Test + void replaceBeanByTypeWithMultipleMatchesAndNoQualifierFails() { + AnnotationConfigApplicationContext context = createContext(CaseByType.class); + context.registerBean("someInteger", Integer.class, () -> 1); + context.registerBean("anotherInteger", Integer.class, () -> 2); + + assertThatIllegalStateException() + .isThrownBy(context::refresh) + .withMessage("Unable to select a bean definition to override: found 2 bean definitions " + + "of type java.lang.Integer (as required by annotated field 'CaseByType.counter'): " + + "[someInteger, anotherInteger]"); + } + + @Test + void replaceBeanByTypeWithMultipleMatchesAndFieldNameQualifierMatches() { + AnnotationConfigApplicationContext context = createContext(CaseByType.class); + context.registerBean("counter", Integer.class, () -> 1); + context.registerBean("someInteger", Integer.class, () -> 2); context.refresh(); - assertThat(context.getBean("beanToBeOverridden")).isSameAs(OVERRIDE); + assertThat(context.getBean("counter")).isSameAs(42); + } + + @Test + void createOrReplaceBeanByNameWithMatchingBeanDefinition() { + AnnotationConfigApplicationContext context = createContext(CaseByNameWithReplaceOrCreateStrategy.class); + context.registerBean("descriptionBean", String.class, () -> "Original"); + context.refresh(); + + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); + } + + @Test + void createOrReplaceBeanByNameWithoutMatchingDefinitionCreatesBeanDefinition() { + AnnotationConfigApplicationContext context = createContext(CaseByNameWithReplaceOrCreateStrategy.class); + context.refresh(); + + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); + } + + @Test + void createOrReplaceBeanByTypeWithMatchingBean() { + AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class); + context.registerBean("someBean", String.class, () -> "Original"); + context.refresh(); + + assertThat(context.getBean("someBean")).isEqualTo("overridden"); + } + + @Test + void createOrReplaceBeanByTypeWithoutMatchingDefinitionCreatesBeanDefinition() { + AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class); + context.refresh(); + + String generatedBeanName = "java.lang.String#0"; + assertThat(context.getBeanDefinitionNames()).contains(generatedBeanName); + assertThat(context.getBean(generatedBeanName)).isEqualTo("overridden"); } @Test void postProcessorShouldNotTriggerEarlyInitialization() { - AnnotationConfigApplicationContext context = createContext(EagerInitBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class); context.register(FactoryBeanRegisteringPostProcessor.class); context.register(EarlyBeanInitializationDetector.class); - context.register(EagerInitBean.class); assertThatNoException().isThrownBy(context::refresh); } @Test void allowReplaceDefinitionWhenSingletonDefinitionPresent() { - AnnotationConfigApplicationContext context = createContext(SingletonBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByName.class); RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL"); definition.setScope(BeanDefinition.SCOPE_SINGLETON); - context.registerBeanDefinition("singleton", definition); - context.register(SingletonBean.class); + context.registerBeanDefinition("descriptionBean", definition); assertThatNoException().isThrownBy(context::refresh); - assertThat(context.isSingleton("singleton")).as("isSingleton").isTrue(); - assertThat(context.getBean("singleton")).as("overridden").isEqualTo("USED THIS"); + assertThat(context.isSingleton("descriptionBean")).as("isSingleton").isTrue(); + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); } @Test void copyDefinitionPrimaryFallbackAndScope() { - AnnotationConfigApplicationContext context = createContext(SingletonBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByName.class); context.getBeanFactory().registerScope("customScope", new SimpleThreadScope()); RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL"); definition.setScope("customScope"); definition.setPrimary(true); definition.setFallback(true); - context.registerBeanDefinition("singleton", definition); - context.register(SingletonBean.class); + context.registerBeanDefinition("descriptionBean", definition); assertThatNoException().isThrownBy(context::refresh); - assertThat(context.getBeanDefinition("singleton")) + assertThat(context.getBeanDefinition("descriptionBean")) .isNotSameAs(definition) .matches(BeanDefinition::isPrimary, "isPrimary") .matches(BeanDefinition::isFallback, "isFallback") @@ -180,125 +231,89 @@ void copyDefinitionPrimaryFallbackAndScope() { @Test void createDefinitionShouldSetQualifierElement() { - AnnotationConfigApplicationContext context = createContext(QualifiedBean.class); - context.registerBeanDefinition("singleton", new RootBeanDefinition(String.class, () -> "ORIGINAL")); - context.register(QualifiedBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByNameWithQualifier.class); + context.registerBeanDefinition("descriptionBean", new RootBeanDefinition(String.class, () -> "ORIGINAL")); assertThatNoException().isThrownBy(context::refresh); - - assertThat(context.getBeanDefinition("singleton")) + assertThat(context.getBeanDefinition("descriptionBean")) .isInstanceOfSatisfying(RootBeanDefinition.class, this::isTheValueField); } private void isTheValueField(RootBeanDefinition def) { assertThat(def.getQualifiedElement()).isInstanceOfSatisfying(Field.class, field -> { - assertThat(field.getDeclaringClass()).isEqualTo(QualifiedBean.class); + assertThat(field.getDeclaringClass()).isEqualTo(CaseByNameWithQualifier.class); assertThat(field.getName()).as("annotated field name") - .isEqualTo("value"); + .isEqualTo("description"); }); } - private AnnotationConfigApplicationContext createContext(Class... classes) { + private AnnotationConfigApplicationContext createContext(Class testClass) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - BeanOverrideContextCustomizer.registerInfrastructure(context, Set.of(classes)); + Set metadata = new LinkedHashSet<>(OverrideMetadata.forTestClass(testClass)); + new BeanOverrideContextCustomizer(metadata).customizeContext(context, mock(MergedContextConfiguration.class)); return context; } - /* - Classes to parse and register with the bean post processor - ----- - Note that some of these are both a @Configuration class and bean override field holder. - This is for this test convenience, as typically the bean override annotated fields - should not be in configuration classes but rather in test case classes - (where a TestExecutionListener automatically discovers and parses them). - */ - - static final SomeInterface OVERRIDE = new SomeImplementation(); + static class CaseByName { - static final ExampleService OVERRIDE_SERVICE = new FailingExampleService(); + @DummyBean(beanName = "descriptionBean") + private String description; - static class ReplaceBeans { + } - @ExampleBeanOverrideAnnotation(value = "useThis", beanName = "explicit") - private ExampleService explicitName; + static class CaseByType { - @ExampleBeanOverrideAnnotation(value = "useThis") - private ExampleService implicitName; + @DummyBean + private Integer counter; - static ExampleService useThis() { - return OVERRIDE_SERVICE; - } } - static class CreateIfOriginalIsMissingBean { + static class CaseByNameWithReplaceOrCreateStrategy { - @ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true, beanName = "explicit") - private ExampleService explicitName; + @DummyBean(beanName = "descriptionBean", strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION) + private String description; - @ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true) - private ExampleService implicitName; - - static ExampleService useThis() { - return OVERRIDE_SERVICE; - } } - @Configuration(proxyBeanMethods = false) - static class OverriddenFactoryBean { + static class CaseByTypeWithReplaceOrCreateStrategy { - @ExampleBeanOverrideAnnotation(value = "fOverride", beanName = "beanToBeOverridden") - SomeInterface f; + @DummyBean(strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION) + private String description; - static SomeInterface fOverride() { - return OVERRIDE; - } - - @Bean - TestFactoryBean testFactoryBean() { - return new TestFactoryBean(); - } } - static class EagerInitBean { + static class CaseByNameAndByTypeWithReplaceOrCreateStrategy { - @ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true) - private ExampleService service; + @DummyBean(beanName = "descriptionBean", strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION) + private String description; + + @DummyBean(strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION) + private Integer counter; - static ExampleService useThis() { - return OVERRIDE_SERVICE; - } } - static class SingletonBean { + static class CaseOverrideBeanProducedByFactoryBean { - @ExampleBeanOverrideAnnotation(beanName = "singleton", - value = "useThis", createIfMissing = false) - private String value; + @DummyBean(beanName = "beanToBeOverridden") + CharSequence description; - static String useThis() { - return "USED THIS"; - } } - static class QualifiedBean { + static class CaseByNameWithQualifier { @Qualifier("preferThis") - @ExampleBeanOverrideAnnotation(beanName = "singleton", - value = "useThis", createIfMissing = false) - private String value; + @DummyBean(beanName = "descriptionBean") + private String description; - static String useThis() { - return "USED THIS"; - } } static class TestFactoryBean implements FactoryBean { @Override public Object getObject() { - return new SomeImplementation(); + return "test"; } @Override @@ -337,10 +352,4 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) } } - interface SomeInterface { - } - - static class SomeImplementation implements SomeInterface { - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java new file mode 100644 index 000000000000..bf7034502459 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override; + +import java.util.Collections; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.lang.Nullable; +import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Green; +import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Orange; +import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyOverrideMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BeanOverrideContextCustomizerFactory}. + * + * @author Stephane Nicoll + */ +class BeanOverrideContextCustomizerFactoryTests { + + private final BeanOverrideContextCustomizerFactory factory = new BeanOverrideContextCustomizerFactory(); + + @Test + void createContextCustomizerWhenTestHasNoBeanOverride() { + assertThat(createContextCustomizer(String.class)).isNull(); + } + + @Test + void createContextCustomizerWhenTestHasSingleBeanOverride() { + BeanOverrideContextCustomizer customizer = createContextCustomizer(Test1.class); + assertThat(customizer).isNotNull(); + assertThat(customizer.getMetadata()).singleElement().satisfies(dummyMetadata(null, String.class)); + } + + @Test + void createContextCustomizerWhenNestedTestHasSingleBeanOverrideInParent() { + BeanOverrideContextCustomizer customizer = createContextCustomizer(Orange.class); + assertThat(customizer).isNotNull(); + assertThat(customizer.getMetadata()).singleElement().satisfies(dummyMetadata(null, String.class)); + } + + @Test + void createContextCustomizerWhenNestedTestHasBeanOverrideAsWellAsTheParent() { + BeanOverrideContextCustomizer customizer = createContextCustomizer(Green.class); + assertThat(customizer).isNotNull(); + assertThat(customizer.getMetadata()) + .anySatisfy(dummyMetadata(null, String.class)) + .anySatisfy(dummyMetadata("counterBean", Integer.class)) + .hasSize(2); + } + + + private Consumer dummyMetadata(@Nullable String beanName, Class beanType) { + return dummyMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION); + } + + private Consumer dummyMetadata(@Nullable String beanName, Class beanType, BeanOverrideStrategy strategy) { + return metadata -> { + assertThat(metadata).isExactlyInstanceOf(DummyOverrideMetadata.class); + assertThat(metadata.getBeanName()).isEqualTo(beanName); + assertThat(metadata.getBeanType().toClass()).isEqualTo(beanType); + assertThat(metadata.getStrategy()).isEqualTo(strategy); + }; + } + + @Nullable + BeanOverrideContextCustomizer createContextCustomizer(Class testClass) { + return this.factory.createContextCustomizer(testClass, Collections.emptyList()); + } + + static class Test1 { + + @DummyBean + private String descriptor; + + } + + static class Test2 { + + @DummyBean + private String name; + + @Nested + class Orange { + + } + + @Nested + class Green { + + @DummyBean(beanName = "counterBean") + private Integer counter; + + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTestUtils.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTestUtils.java new file mode 100644 index 000000000000..e3aada2b8a8c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTestUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override; + +import java.util.Collections; + +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextCustomizer; + +/** + * Test utilities for {@link BeanOverrideContextCustomizer} that are public so + * that specific bean override implementations can use them. + * + * @author Stephane Nicoll + */ +public abstract class BeanOverrideContextCustomizerTestUtils { + + + private static final BeanOverrideContextCustomizerFactory factory = new BeanOverrideContextCustomizerFactory(); + + /** + * Create a {@link ContextCustomizer} for the given {@code testClass}. Return + * a customizer to handle any use of {@link BeanOverride} or {@code null} if + * the test class does not use them. + * @param testClass a test class to introspect + * @return a context customizer for bean override support, or null + */ + @Nullable + public static ContextCustomizer createContextCustomizer(Class testClass) { + return factory.createContextCustomizer(testClass, Collections.emptyList()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java new file mode 100644 index 000000000000..f518d08ea583 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Objects; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link BeanOverrideContextCustomizer}. + * + * @author Stephane Nicoll + */ +class BeanOverrideContextCustomizerTests { + + @Test + void customizerIsEqualWithIdenticalMetadata() { + BeanOverrideContextCustomizer customizer = createCustomizer(new DummyOverrideMetadata("key")); + BeanOverrideContextCustomizer customizer2 = createCustomizer(new DummyOverrideMetadata("key")); + assertThat(customizer).isEqualTo(customizer2); + assertThat(customizer).hasSameHashCodeAs(customizer2); + } + + @Test + void customizerIsEqualWithIdenticalMetadataInDifferentOrder() { + BeanOverrideContextCustomizer customizer = createCustomizer( + new DummyOverrideMetadata("key1"), new DummyOverrideMetadata("key2")); + BeanOverrideContextCustomizer customizer2 = createCustomizer( + new DummyOverrideMetadata("key2"), new DummyOverrideMetadata("key1")); + assertThat(customizer).isEqualTo(customizer2); + assertThat(customizer).hasSameHashCodeAs(customizer2); + } + + @Test + void customizerIsNotEqualWithDifferentMetadata() { + BeanOverrideContextCustomizer customizer = createCustomizer(new DummyOverrideMetadata("key")); + BeanOverrideContextCustomizer customizer2 = createCustomizer( + new DummyOverrideMetadata("key"), new DummyOverrideMetadata("another")); + assertThat(customizer).isNotEqualTo(customizer2); + } + + private BeanOverrideContextCustomizer createCustomizer(OverrideMetadata... metadata) { + return new BeanOverrideContextCustomizer(new LinkedHashSet<>(Arrays.asList(metadata))); + } + + private static class DummyOverrideMetadata extends OverrideMetadata { + + private final String key; + + public DummyOverrideMetadata(String key) { + super(mock(Field.class), ResolvableType.forClass(Object.class), null, BeanOverrideStrategy.REPLACE_DEFINITION); + this.key = key; + } + + @Override + protected Object createOverride(String beanName, BeanDefinition existingBeanDefinition, + Object existingBeanInstance) { + return existingBeanInstance; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DummyOverrideMetadata that = (DummyOverrideMetadata) o; + return Objects.equals(this.key, that.key); + } + + @Override + public int hashCode() { + return this.key.hashCode(); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java deleted file mode 100644 index ef134d292ffd..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override; - -import java.lang.reflect.Field; - -import org.junit.jupiter.api.Test; - -import org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation; -import org.springframework.test.context.bean.override.example.TestBeanOverrideMetaAnnotation; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatRuntimeException; - -/** - * Unit tests for {@link BeanOverrideParsingUtils}. - * - * @since 6.2 - */ -class BeanOverrideParsingUtilsTests { - - // Metadata built from a String that starts with DUPLICATE_TRIGGER are considered equal - private static final String DUPLICATE_TRIGGER1 = ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER + "-v1"; - private static final String DUPLICATE_TRIGGER2 = ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER + "-v2"; - - @Test - void findsOnField() { - assertThat(BeanOverrideParsingUtils.parse(SingleAnnotationOnField.class)) - .map(Object::toString) - .containsExactly("onField"); - } - - @Test - void allowsMultipleProcessorsOnDifferentElements() { - assertThat(BeanOverrideParsingUtils.parse(AnnotationsOnMultipleFields.class)) - .map(Object::toString) - .containsExactlyInAnyOrder("onField1", "onField2"); - } - - @Test - void rejectsMultipleAnnotationsOnSameElement() { - Field field = ReflectionUtils.findField(MultipleAnnotationsOnField.class, "message"); - assertThatRuntimeException() - .isThrownBy(() -> BeanOverrideParsingUtils.parse(MultipleAnnotationsOnField.class)) - .withMessage("Multiple @BeanOverride annotations found on field: " + field); - } - - @Test - void keepsFirstOccurrenceOfEqualMetadata() { - assertThat(BeanOverrideParsingUtils.parse(DuplicateConf.class)) - .map(Object::toString) - .containsExactly("{DUPLICATE-v1}"); - } - - - static class SingleAnnotationOnField { - - @ExampleBeanOverrideAnnotation("onField") - String message; - - static String onField() { - return "OK"; - } - } - - static class MultipleAnnotationsOnField { - - @ExampleBeanOverrideAnnotation("foo") - @TestBeanOverrideMetaAnnotation - String message; - - static String foo() { - return "foo"; - } - } - - static class AnnotationsOnMultipleFields { - - @ExampleBeanOverrideAnnotation("onField1") - String message; - - @ExampleBeanOverrideAnnotation("onField2") - String messageOther; - - static String onField1() { - return "OK1"; - } - - static String onField2() { - return "OK2"; - } - } - - static class DuplicateConf { - - @ExampleBeanOverrideAnnotation(DUPLICATE_TRIGGER1) - String message1; - - @ExampleBeanOverrideAnnotation(DUPLICATE_TRIGGER2) - String message2; - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/DummyBean.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/DummyBean.java new file mode 100644 index 000000000000..55f01fd035dc --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/DummyBean.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; +import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor; +import org.springframework.util.StringUtils; + +/** + * A dummy {@link BeanOverride} implementation that only handles {@link CharSequence} + * and {@link Integer} and replace them with {@code "overridden"} and {@code 42}, + * respectively. + * + * @author Stephane Nicoll + */ +@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@BeanOverride(DummyBeanOverrideProcessor.class) +@interface DummyBean { + + String beanName() default ""; + + BeanOverrideStrategy strategy() default BeanOverrideStrategy.REPLACE_DEFINITION; + + class DummyBeanOverrideProcessor implements BeanOverrideProcessor { + + @Override + public OverrideMetadata createMetadata(Annotation annotation, Class testClass, Field field) { + DummyBean dummyBean = (DummyBean) annotation; + String beanName = (StringUtils.hasText(dummyBean.beanName()) ? dummyBean.beanName() : null); + return new DummyBeanOverrideProcessor.DummyOverrideMetadata(field, field.getType(), beanName, + dummyBean.strategy()); + } + + // Bare bone, "dummy", implementation that should not override anything + // else than createOverride. + static class DummyOverrideMetadata extends OverrideMetadata { + + DummyOverrideMetadata(Field field, Class typeToOverride, @Nullable String beanName, + BeanOverrideStrategy strategy) { + + super(field, ResolvableType.forClass(typeToOverride), beanName, strategy); + } + + @Override + protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, + @Nullable Object existingBeanInstance) { + + Class beanType = getField().getType(); + if (CharSequence.class.isAssignableFrom(beanType)) { + return "overridden"; + } + else if (Integer.class.isAssignableFrom(beanType)) { + return 42; + } + throw new IllegalStateException("Could not handle bean type " + beanType); + } + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java index c2d3e5107493..54836b7592b2 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java @@ -16,55 +16,250 @@ package org.springframework.test.context.bean.override; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.List; +import java.util.function.Consumer; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.core.ResolvableType; -import org.springframework.lang.NonNull; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.lang.Nullable; +import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyOverrideMetadata; +import org.springframework.test.context.bean.override.example.CustomQualifier; +import org.springframework.test.context.bean.override.example.ExampleService; +import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link OverrideMetadata}. * * @author Simon Baslé + * @author Stephane Nicoll * @since 6.2 */ -class OverrideMetadataTests { +public class OverrideMetadataTests { @Test - void implicitConfigurations() throws Exception { - OverrideMetadata metadata = exampleOverride(); - assertThat(metadata.getBeanName()).as("expectedBeanName").isNull(); + void forTestClassWithSingleField() { + List overrideMetadata = OverrideMetadata.forTestClass(SingleAnnotation.class); + assertThat(overrideMetadata).singleElement().satisfies(hasTestBeanMetadata( + field(SingleAnnotation.class, "message"), String.class, null)); } + @Test + void forTestClassWithMultipleFields() { + List overrideMetadata = OverrideMetadata.forTestClass(MultipleAnnotations.class); + assertThat(overrideMetadata).hasSize(2) + .anySatisfy(hasTestBeanMetadata( + field(MultipleAnnotations.class, "message"), String.class, null)) + .anySatisfy(hasTestBeanMetadata( + field(MultipleAnnotations.class, "counter"), Integer.class, null)); + } - @NonNull - String annotated = "exampleField"; + @Test + void forTestClassWithMultipleFieldsSameMetadata() { + List overrideMetadata = OverrideMetadata.forTestClass(MultipleAnnotationsDuplicate.class); + assertThat(overrideMetadata).hasSize(2) + .anySatisfy(hasTestBeanMetadata( + field(MultipleAnnotationsDuplicate.class, "message1"), String.class, "messageBean")) + .anySatisfy(hasTestBeanMetadata( + field(MultipleAnnotationsDuplicate.class, "message2"), String.class, "messageBean")); + assertThat(new HashSet<>(overrideMetadata)).hasSize(1); + } - private static OverrideMetadata exampleOverride() throws Exception { - Field field = OverrideMetadataTests.class.getDeclaredField("annotated"); - return new ConcreteOverrideMetadata(field, ResolvableType.forClass(String.class), - BeanOverrideStrategy.REPLACE_DEFINITION); + @Test + void forTestClassWithDifferentOverrideMetadataOnSameField() { + Field faultyField = field(MultipleAnnotationsOnSameField.class, "message"); + assertThatIllegalStateException() + .isThrownBy(() -> OverrideMetadata.forTestClass(MultipleAnnotationsOnSameField.class)) + .withMessageStartingWith("Multiple @BeanOverride annotations found") + .withMessageContaining(faultyField.toString()); } - static class ConcreteOverrideMetadata extends OverrideMetadata { + @Test + void getBeanNameIsNullByDefault() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier")); + assertThat(metadata.getBeanName()).isNull(); + } - ConcreteOverrideMetadata(Field field, ResolvableType typeToOverride, - BeanOverrideStrategy strategy) { + @Test + void isEqualToWithSameInstance() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier")); + assertThat(metadata).isEqualTo(metadata); + assertThat(metadata).hasSameHashCodeAs(metadata); + } - super(field, typeToOverride, strategy); - } + @Test + void isEqualToWithSameMetadata() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier")); + OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "noQualifier")); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isEqualToWithSameMetadataAndBeanNames() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"), "testBean"); + OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "noQualifier"), "testBean"); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataAndDifferentBeaName() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"), "testBean"); + OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "noQualifier"), "testBean2"); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isEqualToWithSameMetadataButDifferentFields() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier")); + OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "noQualifier")); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isEqualToWithByNameLookupAndDifferentFieldNames() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"), "beanToOverride"); + OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "example"), "beanToOverride"); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isEqualToWithSameMetadataAndSameQualifierValues() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier")); + OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "directQualifier")); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataAndDifferentQualifierValues() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier")); + OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "differentDirectQualifier")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataAndDifferentQualifiers() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier")); + OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "customQualifier")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithByTypeLookupAndDifferentFieldNames() { + OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier")); + OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "example")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + private OverrideMetadata createMetadata(Field field) { + return createMetadata(field, null); + } - @Override - protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, - @Nullable Object existingBeanInstance) { + private OverrideMetadata createMetadata(Field field, @Nullable String name) { + return new DummyOverrideMetadata(field, field.getType(), name, BeanOverrideStrategy.REPLACE_DEFINITION); + } + + private Field field(Class target, String fieldName) { + Field field = ReflectionUtils.findField(target, fieldName); + assertThat(field).isNotNull(); + return field; + } + + private Consumer hasTestBeanMetadata(Field field, Class beanType, @Nullable String beanName) { + return hasOverrideMetadata(field, beanType, BeanOverrideStrategy.REPLACE_DEFINITION, beanName); + } + + private Consumer hasOverrideMetadata(Field field, Class beanType, BeanOverrideStrategy strategy, @Nullable String beanName) { + return metadata -> { + assertThat(metadata.getField()).isEqualTo(field); + assertThat(metadata.getBeanType().toClass()).isEqualTo(beanType); + assertThat(metadata.getStrategy()).isEqualTo(strategy); + assertThat(metadata.getBeanName()).isEqualTo(beanName); + }; + } + + + static class SingleAnnotation { + + @DummyBean + String message; + + } + + static class MultipleAnnotations { + + @DummyBean + String message; + + @DummyBean + Integer counter; + } + + static class MultipleAnnotationsDuplicate { + + @DummyBean(beanName = "messageBean") + String message1; + + @DummyBean(beanName = "messageBean") + String message2; - return BeanOverrideStrategy.REPLACE_DEFINITION; + } + + static class MultipleAnnotationsOnSameField { + + @MetaDummyBean() + @DummyBean + String message; + + static String foo() { + return "foo"; } } + public static class ConfigA { + + ExampleService noQualifier; + + @Qualifier("test") + ExampleService directQualifier; + + @Qualifier("different") + ExampleService differentDirectQualifier; + + @CustomQualifier + ExampleService customQualifier; + + } + + public static class ConfigB { + + ExampleService noQualifier; + + ExampleService example; + + @Qualifier("test") + ExampleService directQualifier; + + } + + // Simple OverrideMetadata implementation + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @DummyBean + public @interface MetaDummyBean {} + } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java index c4fa549b9b0a..7a5407363a93 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java @@ -32,11 +32,11 @@ abstract class AbstractTestBeanIntegrationTestCase { @TestBean(name = "thirdBean") Pojo anotherBean; - static Pojo otherBeanTestOverride() { + static Pojo otherBean() { return new FakePojo("otherBean in superclass"); } - static Pojo thirdBeanTestOverride() { + static Pojo thirdBean() { return new FakePojo("third in superclass"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java index c3418ea9a200..c8627752388f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java @@ -51,8 +51,8 @@ void zeroCandidates() { cause( instanceOf(IllegalStateException.class), message(""" - Unable to select a bean definition to override: found 0 bean definitions \ - of type %s (as required by annotated field '%s.example')""" + Unable to override bean: no bean definitions of type \ + %s (as required by annotated field '%s.example')""" .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); } @@ -80,7 +80,7 @@ static class NoMatchingBeansTestCase { void test() { } - static ExampleService exampleTestOverride() { + static ExampleService example() { return fail("unexpected override"); } @@ -100,7 +100,7 @@ static class TooManyBeansTestCase { void test() { } - static ExampleService exampleTestOverride() { + static ExampleService example() { return fail("unexpected override"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java index b02ced0a63d2..4c32cbe5501d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java @@ -18,12 +18,12 @@ import org.junit.jupiter.api.Test; +import org.springframework.test.context.bean.override.convention.AbstractTestBeanIntegrationTestCase.Pojo; import org.springframework.test.context.junit.EngineTestKitUtils; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; -import static org.springframework.test.context.junit.EngineTestKitUtils.rootCause; /** * {@link TestBean @TestBean} inheritance integration tests for failure scenarios. @@ -40,12 +40,9 @@ void failsIfFieldInSupertypeButNoMethod() { Class testClass = FieldInSupertypeButNoMethodTestCase.class; EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, finishedWithFailure( - rootCause( instanceOf(IllegalStateException.class), - message(""" - Failed to find a static test bean factory method in %s with return type %s \ - whose name matches one of the supported candidates [someBeanTestOverride]""" - .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))))); + message("No static method found named someBean() in %s with return type %s" + .formatted(FieldInSupertypeButNoMethodTestCase.class.getName(), Pojo.class.getName())))); } @Test @@ -53,13 +50,9 @@ void failsIfMethod1InSupertypeAndMethod2InType() { Class testClass = Method1InSupertypeAndMethod2InTypeTestCase.class; EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, finishedWithFailure( - rootCause( instanceOf(IllegalStateException.class), - message(""" - Found 2 competing static test bean factory methods in %s with return type %s \ - whose name matches one of the supported candidates \ - [anotherBeanTestOverride, thirdBeanTestOverride]""" - .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))))); + message("Found 2 competing static methods named anotherBean() or thirdBean() in %s with return type %s" + .formatted(Method1InSupertypeAndMethod2InTypeTestCase.class.getName(), Pojo.class.getName())))); } @@ -72,11 +65,11 @@ void test() { static class Method1InSupertypeAndMethod2InTypeTestCase extends AbstractTestBeanIntegrationTestCase { - static Pojo someBeanTestOverride() { + static Pojo someBean() { return new FakePojo("ignored"); } - static Pojo anotherBeanTestOverride() { + static Pojo anotherBean() { return new FakePojo("sub2"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java index 9c04f8a34a5a..1c96fe5da37a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java @@ -66,28 +66,21 @@ void testBeanFailingNoExplicitNameBean() { @Test void testBeanFailingNoImplicitMethod() { - Class testClass = ExplicitTestOverrideMethodNotPresentTestCase.class; + Class testClass = ExplicitOverrideMethodNotPresentTestCase.class; EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, finishedWithFailure( - rootCause( instanceOf(IllegalStateException.class), - message(""" - Failed to find a static test bean factory method in %s with return type \ - java.lang.String whose name matches one of the supported candidates \ - [notPresent]""".formatted(testClass.getName()))))); + message("No static method found named notPresent() in %s with return type %s" + .formatted(testClass.getName(), String.class.getName())))); } @Test void testBeanFailingNoExplicitMethod() { - Class testClass = ImplicitTestOverrideMethodNotPresentTestCase.class; + Class testClass = ImplicitOverrideMethodNotPresentTestCase.class; EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, - finishedWithFailure( - rootCause( - instanceOf(IllegalStateException.class), - message(""" - Failed to find a static test bean factory method in %s with return type \ - java.lang.String whose name matches one of the supported candidates \ - [fieldTestOverride]""".formatted(testClass.getName()))))); + finishedWithFailure(instanceOf(IllegalStateException.class), + message("No static method found named field() in %s with return type %s" + .formatted(testClass.getName(), String.class.getName())))); } @Test @@ -114,7 +107,7 @@ void test() { fail("should fail earlier"); } - static String noOriginalBeanTestOverride() { + static String noOriginalBean() { return "should be ignored"; } } @@ -130,13 +123,13 @@ void test() { fail("should fail earlier"); } - static String notPresentTestOverride() { + static String notPresent() { return "should be ignored"; } } @SpringJUnitConfig - static class ExplicitTestOverrideMethodNotPresentTestCase { + static class ExplicitOverrideMethodNotPresentTestCase { @TestBean(methodName = "notPresent") String field; @@ -148,9 +141,9 @@ void test() { } @SpringJUnitConfig - static class ImplicitTestOverrideMethodNotPresentTestCase { + static class ImplicitOverrideMethodNotPresentTestCase { - @TestBean // expects fieldTestOverride method + @TestBean // expects field method String field; @Test @@ -170,7 +163,7 @@ void test() { fail("should fail earlier"); } - static String fieldTestOverride() { + static String field() { return "should be ignored"; } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java index 70003428a325..8646ab76ffe7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java @@ -51,7 +51,7 @@ public class TestBeanByTypeIntegrationTests { @CustomQualifier StringBuilder anyNameForStringBuilder2; - static ExampleService anyNameForServiceTestOverride() { + static ExampleService anyNameForService() { return new RealExampleService("Mocked greeting"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanContextCustomizerEqualityTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanContextCustomizerEqualityTests.java new file mode 100644 index 000000000000..22ddb34c8067 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanContextCustomizerEqualityTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override.convention; + +import org.junit.jupiter.api.Test; + +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that validate the behavior of {@link TestBean} with the TCF context cache. + * + * @author Stephane Nicoll + */ +class TestBeanContextCustomizerEqualityTests { + + @Test + void contextCustomizerWithSameOverrideInDifferentTestClassesIsEqual() { + assertThat(createContextCustomizer(Case1.class)).isEqualTo(createContextCustomizer(Case2.class)); + } + + @Test + void contextCustomizerWithDifferentMethodsIsNotEqual() { + assertThat(createContextCustomizer(Case1.class)).isNotEqualTo(createContextCustomizer(Case3.class)); + } + + @Test + void contextCustomizerWithByNameVsByTypeLookupIsNotEqual() { + assertThat(createContextCustomizer(Case4.class)).isNotEqualTo(createContextCustomizer(Case5.class)); + } + + + private ContextCustomizer createContextCustomizer(Class testClass) { + ContextCustomizer customizer = BeanOverrideContextCustomizerTestUtils.createContextCustomizer(testClass); + assertThat(customizer).isNotNull(); + return customizer; + } + + interface DescriptionProvider { + + static String createDescription() { + return "override"; + } + + } + + static class Case1 implements DescriptionProvider { + + @TestBean(methodName = "createDescription") + private String description; + + } + + static class Case2 implements DescriptionProvider { + + @TestBean(methodName = "createDescription") + private String description; + + } + + static class Case3 implements DescriptionProvider { + + @TestBean(methodName = "createDescription") + private String description; + + static String createDescription() { + return "another value"; + } + } + + static class Case4 { + + @TestBean + private String description; + + static String description() { + return "overridden"; + } + } + + static class Case5 { + + @TestBean(name = "descriptionBean") + private String description; + + static String description() { + return "overridden"; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanFactory.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanFactory.java index eeeb05d07c7a..b1ca21b4ffe6 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanFactory.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanFactory.java @@ -24,7 +24,7 @@ */ interface TestBeanFactory { - public static String createTestMessage() { + static String createTestMessage() { return "test"; } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java index 22c3a5c39a4f..dc7db7f94111 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java @@ -57,12 +57,12 @@ public class NestedConcreteTestBeanIntegrationTests extends AbstractTestBeanInte @TestBean(name = "pojo2", methodName = "enclosingClassBeanOverride") Pojo pojo2; - static Pojo someBeanTestOverride() { + static Pojo someBean() { return new FakePojo("someBeanOverride"); } - // Hides otherBeanTestOverride() defined in AbstractTestBeanIntegrationTestCase. - static Pojo otherBeanTestOverride() { + // Hides otherBean() defined in AbstractTestBeanIntegrationTestCase. + static Pojo otherBean() { return new FakePojo("otherBean in subclass"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java index a9cd9286ba57..e2665aea31fb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java @@ -50,17 +50,17 @@ public class TestBeanIntegrationTests { @TestBean(name = "nestedField") String renamed2; - @TestBean(name = "methodRenamed1", methodName = "fieldTestOverride") + @TestBean(name = "methodRenamed1", methodName = "field") String methodRenamed1; - @TestBean(name = "methodRenamed2", methodName = "nestedFieldTestOverride") + @TestBean(name = "methodRenamed2", methodName = "nestedField") String methodRenamed2; - static String fieldTestOverride() { + static String field() { return "fieldOverride"; } - static String nestedFieldTestOverride() { + static String nestedField() { return "nestedFieldOverride"; } @@ -133,7 +133,7 @@ void fieldWithMethodNameHasOverride(ApplicationContext ctx) { @DisplayName("With factory method in enclosing class") public class TestBeanFactoryMethodInEnclosingClassTests { - @TestBean(methodName = "nestedFieldTestOverride", name = "nestedField") + @TestBean(methodName = "nestedField", name = "nestedField") String nestedField2; @Test @@ -146,7 +146,7 @@ void fieldHasOverride(ApplicationContext ctx) { @DisplayName("With factory method in the enclosing class of the enclosing class") public class TestBeanFactoryMethodInEnclosingClassLevel2Tests { - @TestBean(methodName = "nestedFieldTestOverride", name = "nestedField") + @TestBean(methodName = "nestedField", name = "nestedField") String nestedField2; @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java new file mode 100644 index 000000000000..e7df2a12db5a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java @@ -0,0 +1,188 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override.convention; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.ResolvableType; +import org.springframework.test.context.bean.override.OverrideMetadata; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link TestBeanOverrideMetadata}. + * + * @author Stephane Nicoll + */ +class TestBeanOverrideMetadataTests { + + @Test + void forTestClassSetsNameToNullIfAnnotationNameIsNull() { + List list = OverrideMetadata.forTestClass(SampleOneOverride.class); + assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull()); + } + + @Test + void forTestClassSetsNameToAnnotationName() { + List list = OverrideMetadata.forTestClass(SampleOneOverrideWithName.class); + assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherBean")); + } + + @Test + void forTestClassWithMissingMethod() { + assertThatIllegalStateException() + .isThrownBy(() ->OverrideMetadata.forTestClass(SampleMissingMethod.class)) + .withMessage("No static method found named message() in %s with return type %s", + SampleMissingMethod.class.getName(), String.class.getName()); + } + + @Test + void isEqualToWithSameInstance() { + TestBeanOverrideMetadata metadata = createMetadata(sampleField("message"), sampleMethod("message")); + assertThat(metadata).isEqualTo(metadata); + assertThat(metadata).hasSameHashCodeAs(metadata); + } + + @Test + void isEqualToWithSameMetadata() { + TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); + TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message"), sampleMethod("message")); + assertThat(metadata1).isEqualTo(metadata2); + assertThat(metadata1).hasSameHashCodeAs(metadata2); + } + + @Test + void isEqualToWithSameMetadataByNameLookupAndDifferentField() { + TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message3"), sampleMethod("message")); + TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message4"), sampleMethod("message")); + assertThat(metadata1).isEqualTo(metadata2); + assertThat(metadata1).hasSameHashCodeAs(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataByTypeLookupAndDifferentField() { + TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); + TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message2"), sampleMethod("message")); + assertThat(metadata1).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentBeanName() { + TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); + TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message3"), sampleMethod("message")); + assertThat(metadata1).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentMethod() { + TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); + TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message"), sampleMethod("description")); + assertThat(metadata1).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentAnnotations() { + TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); + TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message5"), sampleMethod("message")); + assertThat(metadata1).isNotEqualTo(metadata2); + } + + private Field sampleField(String fieldName) { + Field field = ReflectionUtils.findField(Sample.class, fieldName); + assertThat(field).isNotNull(); + return field; + } + + private Method sampleMethod(String noArgMethodName) { + Method method = ReflectionUtils.findMethod(Sample.class, noArgMethodName); + assertThat(method).isNotNull(); + return method; + } + + private TestBeanOverrideMetadata createMetadata(Field field, Method overrideMethod) { + TestBean annotation = field.getAnnotation(TestBean.class); + String beanName = (StringUtils.hasText(annotation.name()) ? annotation.name() : null); + return new TestBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), beanName, overrideMethod); + } + + static class SampleOneOverride { + + @TestBean + String message; + + static String message() { + return "OK"; + } + + } + + static class SampleOneOverrideWithName { + + @TestBean(name = "anotherBean") + String message; + + static String message() { + return "OK"; + } + + } + + static class SampleMissingMethod { + + @TestBean + String message; + + } + + + @SuppressWarnings("unused") + static class Sample { + + @TestBean + private String message; + + @TestBean + private String message2; + + @TestBean(name = "anotherBean") + private String message3; + + @TestBean(name = "anotherBean") + private String message4; + + @Qualifier("anotherBean") + @TestBean + private String message5; + + static String message() { + return "OK"; + } + + static String description() { + return message(); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessorTests.java index 19e8ff5a677a..0ebbaddf44d4 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessorTests.java @@ -18,20 +18,17 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.NonNull; -import org.springframework.test.context.bean.override.convention.TestBeanOverrideProcessor.TestBeanOverrideMetadata; import org.springframework.test.context.bean.override.example.ExampleService; import org.springframework.util.ReflectionUtils; 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.test.context.bean.override.convention.TestBeanOverrideProcessor.findTestBeanFactoryMethod; /** * Tests for {@link TestBeanOverrideProcessor}. @@ -42,12 +39,15 @@ */ class TestBeanOverrideProcessorTests { + private final TestBeanOverrideProcessor processor = new TestBeanOverrideProcessor(); + @Test void findTestBeanFactoryMethodFindsFromCandidateNames() { Class clazz = MethodConventionTestCase.class; Class returnType = ExampleService.class; - Method method = findTestBeanFactoryMethod(clazz, returnType, "example1", "example2", "example3"); + Method method = this.processor.findTestBeanFactoryMethod( + clazz, returnType, "example1", "example2", "example3"); assertThat(method.getName()).isEqualTo("example2"); } @@ -57,7 +57,7 @@ void findTestBeanFactoryMethodFindsLocalMethodWhenSubclassMethodHidesSuperclassM Class clazz = SubTestCase.class; Class returnType = String.class; - Method method = findTestBeanFactoryMethod(clazz, returnType, "factory"); + Method method = this.processor.findTestBeanFactoryMethod(clazz, returnType, "factory"); assertThat(method).isEqualTo(ReflectionUtils.findMethod(clazz, "factory")); } @@ -68,11 +68,9 @@ void findTestBeanFactoryMethodNotFound() { Class returnType = ExampleService.class; assertThatIllegalStateException() - .isThrownBy(() -> findTestBeanFactoryMethod(clazz, returnType, "example1", "example3")) - .withMessage(""" - Failed to find a static test bean factory method in %s with return type %s \ - whose name matches one of the supported candidates %s""", - clazz.getName(), returnType.getName(), List.of("example1", "example3")); + .isThrownBy(() -> this.processor.findTestBeanFactoryMethod(clazz, returnType, "example1", "example3")) + .withMessage("No static method found named example1() or example3() in %s with return type %s", + MethodConventionTestCase.class.getName(), ExampleService.class.getName()); } @Test @@ -81,17 +79,15 @@ void findTestBeanFactoryMethodTwoFound() { Class returnType = ExampleService.class; assertThatIllegalStateException() - .isThrownBy(() -> findTestBeanFactoryMethod(clazz, returnType, "example2", "example4")) - .withMessage(""" - Found %d competing static test bean factory methods in %s with return type %s \ - whose name matches one of the supported candidates %s""".formatted( - 2, clazz.getName(), returnType.getName(), List.of("example2", "example4"))); + .isThrownBy(() -> this.processor.findTestBeanFactoryMethod(clazz, returnType, "example2", "example4")) + .withMessage("Found 2 competing static methods named example2() or example4() in %s with return type %s", + clazz.getName(), returnType.getName()); } @Test void findTestBeanFactoryMethodNoNameProvided() { assertThatIllegalArgumentException() - .isThrownBy(() -> findTestBeanFactoryMethod(MethodConventionTestCase.class, ExampleService.class)) + .isThrownBy(() -> this.processor.findTestBeanFactoryMethod(MethodConventionTestCase.class, ExampleService.class)) .withMessage("At least one candidate method name is required"); } @@ -103,13 +99,10 @@ void createMetaDataForUnknownExplicitMethod() throws Exception { TestBean overrideAnnotation = field.getAnnotation(TestBean.class); assertThat(overrideAnnotation).isNotNull(); - TestBeanOverrideProcessor processor = new TestBeanOverrideProcessor(); assertThatIllegalStateException() - .isThrownBy(() -> processor.createMetadata(overrideAnnotation, clazz, field)) - .withMessage(""" - Failed to find a static test bean factory method in %s with return type %s \ - whose name matches one of the supported candidates %s""", - clazz.getName(), returnType.getName(), List.of("explicit1")); + .isThrownBy(() -> this.processor.createMetadata(overrideAnnotation, clazz, field)) + .withMessage("No static method found named explicit1() in %s with return type %s", + clazz.getName(), returnType.getName()); } @Test @@ -119,8 +112,7 @@ void createMetaDataForKnownExplicitMethod() throws Exception { TestBean overrideAnnotation = field.getAnnotation(TestBean.class); assertThat(overrideAnnotation).isNotNull(); - TestBeanOverrideProcessor processor = new TestBeanOverrideProcessor(); - assertThat(processor.createMetadata(overrideAnnotation, clazz, field)) + assertThat(this.processor.createMetadata(overrideAnnotation, clazz, field)) .isInstanceOf(TestBeanOverrideMetadata.class); } @@ -132,13 +124,10 @@ void createMetaDataForConventionBasedFactoryMethod() throws Exception { TestBean overrideAnnotation = field.getAnnotation(TestBean.class); assertThat(overrideAnnotation).isNotNull(); - TestBeanOverrideProcessor processor = new TestBeanOverrideProcessor(); - assertThatIllegalStateException().isThrownBy(() -> processor.createMetadata( + assertThatIllegalStateException().isThrownBy(() -> this.processor.createMetadata( overrideAnnotation, clazz, field)) - .withMessage(""" - Failed to find a static test bean factory method in %s with return type %s \ - whose name matches one of the supported candidates %s""", - clazz.getName(), returnType.getName(), List.of("fieldTestOverride", "someFieldTestOverride")); + .withMessage("No static method found named field() or someField() in %s with return type %s", + clazz.getName(), returnType.getName()); } @Test @@ -147,8 +136,7 @@ void failToCreateMetadataForOtherAnnotation() throws NoSuchFieldException { Field field = clazz.getField("field"); NonNull badAnnotation = AnnotationUtils.synthesizeAnnotation(NonNull.class); - TestBeanOverrideProcessor processor = new TestBeanOverrideProcessor(); - assertThatIllegalStateException().isThrownBy(() -> processor.createMetadata(badAnnotation, clazz, field)) + assertThatIllegalStateException().isThrownBy(() -> this.processor.createMetadata(badAnnotation, clazz, field)) .withMessage("Invalid annotation passed to TestBeanOverrideProcessor: expected @TestBean" + " on field %s.%s", field.getDeclaringClass().getName(), field.getName()); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideAnnotation.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideAnnotation.java deleted file mode 100644 index 81e61b8870bf..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideAnnotation.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override.example; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.test.context.bean.override.BeanOverride; - -@BeanOverride(ExampleBeanOverrideProcessor.class) -@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ExampleBeanOverrideAnnotation { - - String DEFAULT_VALUE = "TEST OVERRIDE"; - // Any metadata using this as the prefix for the bean name will be considered equal - String DUPLICATE_TRIGGER = "DUPLICATE"; - - String value() default DEFAULT_VALUE; - - boolean createIfMissing() default false; - - String beanName() default ""; -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideProcessor.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideProcessor.java deleted file mode 100644 index 6ecbe2f1bc40..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideProcessor.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override.example; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; - -import org.springframework.core.ResolvableType; -import org.springframework.test.context.bean.override.BeanOverrideProcessor; -import org.springframework.test.context.bean.override.OverrideMetadata; - -// Intentionally NOT public -class ExampleBeanOverrideProcessor implements BeanOverrideProcessor { - - @Override - public OverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { - if (!(overrideAnnotation instanceof ExampleBeanOverrideAnnotation annotation)) { - throw new IllegalStateException("unexpected annotation"); - } - if (annotation.value().startsWith(ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER)) { - return new TestOverrideMetadata(annotation.value()); - } - return new TestOverrideMetadata(field, annotation, ResolvableType.forField(field, testClass)); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestOverrideMetadata.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestOverrideMetadata.java deleted file mode 100644 index 0532561ff101..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestOverrideMetadata.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override.example; - - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.core.ResolvableType; -import org.springframework.lang.Nullable; -import org.springframework.test.context.bean.override.BeanOverrideStrategy; -import org.springframework.test.context.bean.override.OverrideMetadata; -import org.springframework.util.StringUtils; - -import static org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation.DEFAULT_VALUE; - -class TestOverrideMetadata extends OverrideMetadata { - - @Nullable - private final Method method; - - @Nullable - private final String beanName; - - private final String methodName; - - @Nullable - private static Method findMethod(AnnotatedElement element, String methodName) { - if (DEFAULT_VALUE.equals(methodName)) { - return null; - } - if (element instanceof Field f) { - for (Method m : f.getDeclaringClass().getDeclaredMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - continue; - } - if (m.getName().equals(methodName)) { - return m; - } - } - throw new IllegalStateException("Expected a static method named <" + methodName + "> alongside annotated field <" + f.getName() + ">"); - } - if (element instanceof Method m) { - if (m.getName().equals(methodName) && Modifier.isStatic(m.getModifiers())) { - return m; - } - throw new IllegalStateException("Expected the annotated method to be static and named <" + methodName + ">"); - } - if (element instanceof Class c) { - for (Method m : c.getDeclaredMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - continue; - } - if (m.getName().equals(methodName)) { - return m; - } - } - throw new IllegalStateException("Expected a static method named <" + methodName + "> on annotated class <" + c.getSimpleName() + ">"); - } - throw new IllegalStateException("Expected the annotated element to be a Field, Method or Class"); - } - - public TestOverrideMetadata(Field field, ExampleBeanOverrideAnnotation overrideAnnotation, ResolvableType typeToOverride) { - super(field, typeToOverride, overrideAnnotation.createIfMissing() ? - BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION: BeanOverrideStrategy.REPLACE_DEFINITION); - this.method = findMethod(field, overrideAnnotation.value()); - this.methodName = overrideAnnotation.value(); - this.beanName = overrideAnnotation.beanName(); - } - - //Used to trigger duplicate detection in parser test - TestOverrideMetadata(String duplicateTrigger) { - super(null, null, null); - this.method = null; - this.methodName = duplicateTrigger; - this.beanName = duplicateTrigger; - } - - @Override - protected String getBeanName() { - if (StringUtils.hasText(this.beanName)) { - return this.beanName; - } - return getField().getName(); - } - - String getAnnotationMethodName() { - return this.methodName; - } - - @Override - protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) { - if (this.method == null) { - return DEFAULT_VALUE; - } - try { - this.method.setAccessible(true); - return this.method.invoke(null); - } - catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean equals(Object obj) { - if (this.method == null) { - return obj instanceof TestOverrideMetadata tem && - tem.beanName != null && - tem.beanName.startsWith(ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER); - } - return super.equals(obj); - } - - @Override - public int hashCode() { - if (this.method == null) { - return ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER.hashCode(); - } - return super.hashCode(); - } - - @Override - public String toString() { - if (this.method == null) { - return "{" + this.beanName + "}"; - } - return this.methodName; - } -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java index 62a490609ef2..bc877302dc88 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java @@ -43,19 +43,6 @@ */ class FailingMockitoBeanByTypeIntegrationTests { - @Test - void zeroCandidates() { - Class testClass = ZeroCandidatesTestCase.class; - EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, - finishedWithFailure( - cause( - instanceOf(IllegalStateException.class), - message(""" - Unable to select a bean definition to override: found 0 bean definitions \ - of type %s (as required by annotated field '%s.example')""" - .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); - } - @Test void tooManyCandidates() { Class testClass = TooManyCandidatesTestCase.class; @@ -70,22 +57,6 @@ void tooManyCandidates() { } - @SpringJUnitConfig - static class ZeroCandidatesTestCase { - - @MockitoBean - ExampleService example; - - @Test - void test() { - assertThat(example).isNotNull(); - } - - @Configuration - static class Config { - } - } - @SpringJUnitConfig static class TooManyCandidatesTestCase { diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java index 2aa512b24f91..92be96e444ce 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -30,7 +31,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatException; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.BDDMockito.when; import static org.mockito.Mockito.times; @@ -48,6 +49,9 @@ @SpringJUnitConfig public class MockitoBeanByTypeIntegrationTests { + @MockitoBean + AnotherService serviceIsNotABean; + @MockitoBean ExampleService anyNameForService; @@ -59,6 +63,17 @@ public class MockitoBeanByTypeIntegrationTests { @CustomQualifier StringBuilder ambiguousMeta; + @Test + void mockIsCreatedWhenNoCandidateIsFound() { + assertThat(this.serviceIsNotABean) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()); + + when(this.serviceIsNotABean.hello()).thenReturn("Mocked hello"); + + assertThat(this.serviceIsNotABean.hello()).isEqualTo("Mocked hello"); + verify(this.serviceIsNotABean, times(1)).hello(); + verifyNoMoreInteractions(this.serviceIsNotABean); + } @Test void overrideIsFoundByType(ApplicationContext ctx) { @@ -80,9 +95,9 @@ void overrideIsFoundByTypeAndDisambiguatedByQualifier(ApplicationContext ctx) { .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(ctx.getBean("ambiguous2")); - assertThatException() + assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) .isThrownBy(() -> ctx.getBean(StringBuilder.class)) - .withMessageEndingWith("but found 2: ambiguous2,ambiguous1"); + .satisfies(ex -> assertThat(ex.getBeanNamesFound()).containsOnly("ambiguous1", "ambiguous2")); assertThat(this.ambiguous).isEmpty(); assertThat(this.ambiguous.substring(0)).isNull(); @@ -97,9 +112,9 @@ void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(ctx.getBean("ambiguous1")); - assertThatException() + assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) .isThrownBy(() -> ctx.getBean(StringBuilder.class)) - .withMessageEndingWith("but found 2: ambiguous2,ambiguous1"); + .satisfies(ex -> assertThat(ex.getBeanNamesFound()).containsOnly("ambiguous1", "ambiguous2")); assertThat(this.ambiguousMeta).isEmpty(); assertThat(this.ambiguousMeta.substring(0)).isNull(); @@ -108,6 +123,11 @@ void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx verifyNoMoreInteractions(this.ambiguousMeta); } + interface AnotherService { + + String hello(); + + } @Configuration static class Config { diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanContextCustomizerEqualityTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanContextCustomizerEqualityTests.java new file mode 100644 index 000000000000..8ea5de3c786a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanContextCustomizerEqualityTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override.mockito; + +import org.junit.jupiter.api.Test; +import org.mockito.Answers; + +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that validate the behavior of {@link MockitoBean} and + * {@link MockitoSpyBean} with the TCF context cache. + * + * @author Stephane Nicoll + */ +class MockitoBeanContextCustomizerEqualityTests { + + + @Test + void contextCustomizerWithSameMockByNameInDifferentClassIsEqual() { + assertThat(createContextCustomizer(Case1ByName.class)).isEqualTo(createContextCustomizer(Case2ByName.class)); + } + + @Test + void contextCustomizerWithSameMockByTypeInDifferentClassIsEqual() { + assertThat(createContextCustomizer(Case1ByType.class)).isEqualTo(createContextCustomizer(Case2ByTypeSameFieldName.class)); + } + + @Test + void contextCustomizerWithSameMockByTypeAndDifferentFieldNamesAreNotEqual() { + assertThat(createContextCustomizer(Case1ByType.class)).isNotEqualTo(createContextCustomizer(Case2ByType.class)); + } + + @Test + void contextCustomizerWithSameSpyByNameInDifferentClassIsEqual() { + assertThat(createContextCustomizer(Case4ByName.class)).isEqualTo(createContextCustomizer(Case5ByName.class)); + } + + @Test + void contextCustomizerWithSameSpyByTypeInDifferentClassIsEqual() { + assertThat(createContextCustomizer(Case4ByType.class)).isEqualTo(createContextCustomizer(Case5ByTypeSameFieldName.class)); + } + + @Test + void contextCustomizerWithSameSpyByTypeAndDifferentFieldNamesAreNotEqual() { + assertThat(createContextCustomizer(Case4ByType.class)).isNotEqualTo(createContextCustomizer(Case5ByType.class)); + } + + @Test + void contextCustomizerWithSimilarMockButDifferentAnswersIsNotEqual() { + assertThat(createContextCustomizer(Case1ByType.class)).isNotEqualTo(createContextCustomizer(Case3.class)); + } + + @Test + void contextCustomizerWithSimilarSpyButDifferentProxyTargetClassFlagIsNotEqual() { + assertThat(createContextCustomizer(Case5ByType.class)).isNotEqualTo(createContextCustomizer(Case6.class)); + } + + @Test + void contextCustomizerWithMockAndSpyAreNotEqual() { + assertThat(createContextCustomizer(Case1ByType.class)).isNotEqualTo(createContextCustomizer(Case4ByType.class)); + } + + private ContextCustomizer createContextCustomizer(Class testClass) { + ContextCustomizer customizer = BeanOverrideContextCustomizerTestUtils.createContextCustomizer(testClass); + assertThat(customizer).isNotNull(); + return customizer; + } + + static class Case1ByName { + + @MockitoBean(name = "serviceBean") + private String exampleService; + + } + + static class Case1ByType { + + @MockitoBean + private String exampleService; + + } + + static class Case2ByName { + + @MockitoBean(name = "serviceBean") + private String serviceToMock; + + } + + static class Case2ByType { + + @MockitoBean + private String serviceToMock; + + } + + static class Case2ByTypeSameFieldName { + + @MockitoBean + private String exampleService; + + } + + static class Case3 { + + @MockitoBean(answers = Answers.RETURNS_MOCKS) + private String exampleService; + + } + + static class Case4ByName { + + @MockitoSpyBean(name = "serviceBean") + private String exampleService; + + } + + static class Case4ByType { + + @MockitoSpyBean + private String exampleService; + + } + + static class Case5ByName { + + @MockitoSpyBean(name = "serviceBean") + private String serviceToMock; + + } + + static class Case5ByType { + + @MockitoSpyBean + private String serviceToMock; + + } + + static class Case5ByTypeSameFieldName { + + @MockitoSpyBean + private String exampleService; + + } + + static class Case6 { + + @MockitoSpyBean(proxyTargetAware = false) + private String serviceToMock; + + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForBeanFactoryIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForBeanFactoryIntegrationTests.java new file mode 100644 index 000000000000..962df47e9dbb --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForBeanFactoryIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override.mockito; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test {@link MockitoBean @MockitoBean} for a factory bean. + * + * @author Phillip Webb + */ +@ExtendWith(SpringExtension.class) +class MockitoBeanForBeanFactoryIntegrationTests { + + // spring-boot/gh-7439 + + @MockitoBean + private TestFactoryBean testFactoryBean; + + @Autowired + private ApplicationContext applicationContext; + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + void testName() { + TestBean testBean = mock(TestBean.class); + given(testBean.hello()).willReturn("amock"); + given(this.testFactoryBean.getObjectType()).willReturn((Class) TestBean.class); + given(this.testFactoryBean.getObject()).willReturn(testBean); + TestBean bean = this.applicationContext.getBean(TestBean.class); + assertThat(bean.hello()).isEqualTo("amock"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + TestFactoryBean testFactoryBean() { + return new TestFactoryBean(); + } + + } + + static class TestFactoryBean implements FactoryBean { + + @Override + public TestBean getObject() { + return () -> "normal"; + } + + @Override + public Class getObjectType() { + return TestBean.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + } + + interface TestBean { + + String hello(); + + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java new file mode 100644 index 000000000000..99aad2960db5 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override.mockito; + +import java.io.Externalizable; +import java.lang.reflect.Field; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.mockito.Answers; + +import org.springframework.core.ResolvableType; +import org.springframework.test.context.bean.override.OverrideMetadata; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MockitoBeanOverrideMetadata}. + * + * @author Stephane Nicoll + */ +class MockitoBeanOverrideMetadataTests { + + @Test + void forTestClassSetsNameToNullIfAnnotationNameIsNull() { + List list = OverrideMetadata.forTestClass(SampleOneMock.class); + assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull()); + } + + @Test + void forTestClassSetsNameToAnnotationName() { + List list = OverrideMetadata.forTestClass(SampleOneMockWithName.class); + assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherService")); + } + + @Test + void isEqualToWithSameInstance() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + assertThat(metadata).isEqualTo(metadata); + assertThat(metadata).hasSameHashCodeAs(metadata); + } + + @Test + void isEqualToWithSameMetadata() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service")); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isNotEqualEqualToByTypeLookupWithSameMetadataButDifferentField() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isEqualEqualToByNameLookupWithSameMetadataButDifferentField() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service3")); + MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4")); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentBeanName() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service3")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentExtraInterfaces() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentAnswers() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service6")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentSerializableFlag() { + MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service7")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + + private Field sampleField(String fieldName) { + Field field = ReflectionUtils.findField(Sample.class, fieldName); + assertThat(field).isNotNull(); + return field; + } + + private MockitoBeanOverrideMetadata createMetadata(Field field) { + MockitoBean annotation = field.getAnnotation(MockitoBean.class); + return new MockitoBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation); + } + + + static class SampleOneMock { + + @MockitoBean + String service; + + } + + static class SampleOneMockWithName { + + @MockitoBean(name = "anotherService") + String service; + + } + + static class Sample { + + @MockitoBean + private String service; + + @MockitoBean + private String service2; + + @MockitoBean(name = "beanToMock") + private String service3; + + @MockitoBean(name = "beanToMock") + private String service4; + + @MockitoBean(extraInterfaces = Externalizable.class) + private String service5; + + @MockitoBean(answers = Answers.RETURNS_MOCKS) + private String service6; + + @MockitoBean(serializable = true) + private String service7; + + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java index 6fe90daaf49c..bd4c0fc49f5d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java @@ -40,7 +40,7 @@ void mockAnnotationCreatesMockMetadata() throws NoSuchFieldException { OverrideMetadata object = processor.createMetadata(annotation, clazz, field); - assertThat(object).isExactlyInstanceOf(MockitoBeanMetadata.class); + assertThat(object).isExactlyInstanceOf(MockitoBeanOverrideMetadata.class); } @Test @@ -52,7 +52,7 @@ void spyAnnotationCreatesSpyMetadata() throws NoSuchFieldException { OverrideMetadata object = processor.createMetadata(annotation, clazz, field); - assertThat(object).isExactlyInstanceOf(MockitoSpyBeanMetadata.class); + assertThat(object).isExactlyInstanceOf(MockitoSpyBeanOverrideMetadata.class); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java new file mode 100644 index 000000000000..1872aede3396 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override.mockito; + +import java.lang.reflect.Field; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.ResolvableType; +import org.springframework.test.context.bean.override.OverrideMetadata; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MockitoSpyBeanOverrideMetadata}. + * + * @author Stephane Nicoll + */ +class MockitoSpyBeanOverrideMetadataTests { + + @Test + void forTestClassSetsNameToNullIfAnnotationNameIsNull() { + List list = OverrideMetadata.forTestClass(SampleOneSpy.class); + assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull()); + } + + @Test + void forTestClassSetsNameToAnnotationName() { + List list = OverrideMetadata.forTestClass(SampleOneSpyWithName.class); + assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherService")); + } + + @Test + void isEqualToWithSameInstance() { + MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + assertThat(metadata).isEqualTo(metadata); + assertThat(metadata).hasSameHashCodeAs(metadata); + } + + @Test + void isEqualToWithSameMetadata() { + MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service")); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isNotEqualToByTypeLookupWithSameMetadataButDifferentField() { + MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isEqualToByNameLookupWithSameMetadataButDifferentField() { + MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service3")); + MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4")); + assertThat(metadata).isEqualTo(metadata2); + assertThat(metadata).hasSameHashCodeAs(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentBeanName() { + MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service3")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentReset() { + MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + @Test + void isNotEqualToWithSameMetadataButDifferentProxyTargetAwareFlag() { + MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); + MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service6")); + assertThat(metadata).isNotEqualTo(metadata2); + } + + + private Field sampleField(String fieldName) { + Field field = ReflectionUtils.findField(Sample.class, fieldName); + assertThat(field).isNotNull(); + return field; + } + + private MockitoSpyBeanOverrideMetadata createMetadata(Field field) { + MockitoSpyBean annotation = field.getAnnotation(MockitoSpyBean.class); + return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation); + } + + + static class SampleOneSpy { + + @MockitoSpyBean + String service; + + } + + static class SampleOneSpyWithName { + + @MockitoSpyBean(name = "anotherService") + String service; + + } + + static class Sample { + + @MockitoSpyBean + private String service; + + @MockitoSpyBean + private String service2; + + @MockitoSpyBean(name = "beanToMock") + private String service3; + + @MockitoSpyBean(name = "beanToMock") + private String service4; + + @MockitoSpyBean(reset = MockReset.BEFORE) + private String service5; + + @MockitoSpyBean(proxyTargetAware = false) + private String service6; + + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionsTests.java similarity index 94% rename from spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionTests.java rename to spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionsTests.java index c3eedbe6456a..a8c6ca455bfe 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionsTests.java @@ -37,7 +37,7 @@ * * @author Rossen Stoyanchev */ -public class CookieAssertionTests { +public class CookieAssertionsTests { private final ResponseCookie cookie = ResponseCookie.from("foo", "bar") .maxAge(Duration.ofMinutes(30)) @@ -45,6 +45,7 @@ public class CookieAssertionTests { .path("/foo") .secure(true) .httpOnly(true) + .partitioned(true) .sameSite("Lax") .build(); @@ -117,6 +118,12 @@ void httpOnly() { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.httpOnly("foo", false)); } + @Test + void partitioned() { + assertions.partitioned("foo", true); + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.partitioned("foo", false)); + } + @Test void sameSite() { assertions.sameSite("foo", "Lax"); diff --git a/spring-web/src/main/java/org/springframework/http/HttpRequest.java b/spring-web/src/main/java/org/springframework/http/HttpRequest.java index 62ea73fad5d0..d9a592512894 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/HttpRequest.java @@ -17,6 +17,7 @@ package org.springframework.http; import java.net.URI; +import java.util.Map; /** * Represents an HTTP request message, consisting of a @@ -41,4 +42,10 @@ public interface HttpRequest extends HttpMessage { */ URI getURI(); + /** + * Return a mutable map of request attributes for this request. + * @since 6.2 + */ + Map getAttributes(); + } diff --git a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java index 485550614fc2..b65df60254c6 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java @@ -47,6 +47,8 @@ public final class ResponseCookie extends HttpCookie { private final boolean httpOnly; + private final boolean partitioned; + @Nullable private final String sameSite; @@ -55,7 +57,7 @@ public final class ResponseCookie extends HttpCookie { * Private constructor. See {@link #from(String, String)}. */ private ResponseCookie(String name, @Nullable String value, Duration maxAge, @Nullable String domain, - @Nullable String path, boolean secure, boolean httpOnly, @Nullable String sameSite) { + @Nullable String path, boolean secure, boolean httpOnly, boolean partitioned, @Nullable String sameSite) { super(name, value); Assert.notNull(maxAge, "Max age must not be null"); @@ -65,6 +67,7 @@ private ResponseCookie(String name, @Nullable String value, Duration maxAge, @Nu this.path = path; this.secure = secure; this.httpOnly = httpOnly; + this.partitioned = partitioned; this.sameSite = sameSite; Rfc6265Utils.validateCookieName(name); @@ -116,6 +119,15 @@ public boolean isHttpOnly() { return this.httpOnly; } + /** + * Return {@code true} if the cookie has the "Partitioned" attribute. + * @since 6.2 + * @see The Partitioned attribute spec + */ + public boolean isPartitioned() { + return this.partitioned; + } + /** * Return the cookie "SameSite" attribute, or {@code null} if not set. *

This limits the scope of the cookie such that it will only be attached to @@ -139,6 +151,7 @@ public ResponseCookieBuilder mutate() { .path(this.path) .secure(this.secure) .httpOnly(this.httpOnly) + .partitioned(this.partitioned) .sameSite(this.sameSite); } @@ -180,6 +193,9 @@ public String toString() { if (this.httpOnly) { sb.append("; HttpOnly"); } + if (this.partitioned) { + sb.append("; Partitioned"); + } if (StringUtils.hasText(this.sameSite)) { sb.append("; SameSite=").append(this.sameSite); } @@ -272,6 +288,13 @@ public interface ResponseCookieBuilder { */ ResponseCookieBuilder httpOnly(boolean httpOnly); + /** + * Add the "Partitioned" attribute to the cookie. + * @since 6.2 + * @see The Partitioned attribute spec + */ + ResponseCookieBuilder partitioned(boolean partitioned); + /** * Add the "SameSite" attribute to the cookie. *

This limits the scope of the cookie such that it will only be @@ -397,6 +420,8 @@ private static class DefaultResponseCookieBuilder implements ResponseCookieBuild private boolean httpOnly; + private boolean partitioned; + @Nullable private String sameSite; @@ -461,6 +486,12 @@ public ResponseCookieBuilder httpOnly(boolean httpOnly) { return this; } + @Override + public ResponseCookieBuilder partitioned(boolean partitioned) { + this.partitioned = partitioned; + return this; + } + @Override public ResponseCookieBuilder sameSite(@Nullable String sameSite) { this.sameSite = sameSite; @@ -470,7 +501,7 @@ public ResponseCookieBuilder sameSite(@Nullable String sameSite) { @Override public ResponseCookie build() { return new ResponseCookie(this.name, this.value, this.maxAge, - this.domain, this.path, this.secure, this.httpOnly, this.sameSite); + this.domain, this.path, this.secure, this.httpOnly, this.partitioned, this.sameSite); } } diff --git a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java index f964e66e80d1..5404493a343f 100644 --- a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.LinkedHashMap; +import java.util.Map; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; @@ -39,6 +41,9 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { @Nullable private HttpHeaders readOnlyHeaders; + @Nullable + private Map attributes; + @Override public final HttpHeaders getHeaders() { @@ -60,6 +65,16 @@ public final OutputStream getBody() throws IOException { return getBodyInternal(this.headers); } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new LinkedHashMap<>(); + this.attributes = attributes; + } + return attributes; + } + @Override public final ClientHttpResponse execute() throws IOException { assertNotExecuted(); diff --git a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java index 5149618c7b1c..07203df37102 100644 --- a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java @@ -91,6 +91,7 @@ public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOExc HttpMethod method = request.getMethod(); ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method); request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value)); + request.getAttributes().forEach((key, value) -> delegate.getAttributes().put(key, value)); if (body.length > 0) { if (delegate instanceof StreamingHttpOutputMessage streamingOutputMessage) { streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() { diff --git a/spring-web/src/main/java/org/springframework/http/client/JdkClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/JdkClientHttpRequestFactory.java index 3d0d0c08194d..6597048cb1ea 100644 --- a/spring-web/src/main/java/org/springframework/http/client/JdkClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/JdkClientHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2023 the original author or authors. + * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,8 +28,7 @@ import org.springframework.util.Assert; /** - * {@link ClientHttpRequestFactory} implementation based on the Java - * {@link HttpClient}. + * {@link ClientHttpRequestFactory} implementation based on the Java {@link HttpClient}. * * @author Marten Deinum * @author Arjen Poutsma @@ -89,13 +88,11 @@ public void setReadTimeout(int readTimeout) { } /** - * Set the underlying {@code HttpClient}'s read timeout as a - * {@code Duration}. + * Set the underlying {@code HttpClient}'s read timeout as a {@code Duration}. *

Default is the system's default timeout. * @see java.net.http.HttpRequest.Builder#timeout */ public void setReadTimeout(Duration readTimeout) { - Assert.notNull(readTimeout, "ReadTimeout must not be null"); this.readTimeout = readTimeout; } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpConnector.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpConnector.java index 4313c658076d..7424104a17c9 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpConnector.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpConnector.java @@ -21,6 +21,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -49,6 +50,9 @@ public class JdkClientHttpConnector implements ClientHttpConnector { private DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance; + @Nullable + private Duration readTimeout = null; + /** * Default constructor that uses {@link HttpClient#newHttpClient()}. @@ -91,12 +95,24 @@ public void setBufferFactory(DataBufferFactory bufferFactory) { this.bufferFactory = bufferFactory; } + /** + * Set the underlying {@code HttpClient}'s read timeout as a {@code Duration}. + *

Default is the system's default timeout. + * @since 6.2 + * @see java.net.http.HttpRequest.Builder#timeout + */ + public void setReadTimeout(Duration readTimeout) { + Assert.notNull(readTimeout, "readTimeout is required"); + this.readTimeout = readTimeout; + } + @Override public Mono connect( HttpMethod method, URI uri, Function> requestCallback) { - JdkClientHttpRequest jdkClientHttpRequest = new JdkClientHttpRequest(method, uri, this.bufferFactory); + JdkClientHttpRequest jdkClientHttpRequest = new JdkClientHttpRequest(method, uri, this.bufferFactory, + this.readTimeout); return requestCallback.apply(jdkClientHttpRequest).then(Mono.defer(() -> { HttpRequest httpRequest = jdkClientHttpRequest.getNativeRequest(); diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java index 2295d4ba2cb9..9eeebb6b2755 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java @@ -20,6 +20,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.concurrent.Flow; @@ -36,6 +37,7 @@ import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; @@ -57,7 +59,8 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest { private final HttpRequest.Builder builder; - public JdkClientHttpRequest(HttpMethod httpMethod, URI uri, DataBufferFactory bufferFactory) { + public JdkClientHttpRequest(HttpMethod httpMethod, URI uri, DataBufferFactory bufferFactory, + @Nullable Duration readTimeout) { Assert.notNull(httpMethod, "HttpMethod is required"); Assert.notNull(uri, "URI is required"); Assert.notNull(bufferFactory, "DataBufferFactory is required"); @@ -66,6 +69,9 @@ public JdkClientHttpRequest(HttpMethod httpMethod, URI uri, DataBufferFactory bu this.uri = uri; this.bufferFactory = bufferFactory; this.builder = HttpRequest.newBuilder(uri); + if (readTimeout != null) { + this.builder.timeout(readTimeout); + } } diff --git a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java index dad91a6b1b0c..57120d15c509 100644 --- a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java @@ -17,6 +17,7 @@ package org.springframework.http.client.support; import java.net.URI; +import java.util.Map; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -70,6 +71,14 @@ public URI getURI() { return this.request.getURI(); } + /** + * Return the attributes of the wrapped request. + */ + @Override + public Map getAttributes() { + return this.request.getAttributes(); + } + /** * Return the headers of the wrapped request. */ diff --git a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java index 482909528c71..cc15b8796390 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,7 +92,8 @@ public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType if (super.canEncode(elementType, mimeType)) { Class outputClass = elementType.toClass(); return (outputClass.isAnnotationPresent(XmlRootElement.class) || - outputClass.isAnnotationPresent(XmlType.class)); + outputClass.isAnnotationPresent(XmlType.class) || + elementType.isAssignableFrom(JAXBElement.class)); } else { return false; diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index 93440fe9422c..4ebbd80b9950 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -50,24 +50,12 @@ * Implementation of {@link HttpMessageConverter} to read and write 'normal' HTML * forms and also to write (but not read) multipart data (e.g. file uploads). * - *

- * The following table shows an overview of the supported media and class types. - * - * - * - * - * - * - * - * - * - * - * - * - *
Media typeReadWrite
{@code "application/x-www-form-urlencoded"}{@link MultiValueMap MultiValueMap<String, String>}{@link Map Map<String, String>}
- * {@link MultiValueMap MultiValueMap<String, String>}
{@code "multipart/form-data"}
- * {@code "multipart/mixed"}
Unsupported{@link Map Map<String, Object>}
- * {@link MultiValueMap MultiValueMap<String, Object>}
+ *

In other words, this converter can read and write the + * {@code "application/x-www-form-urlencoded"} media type as + * {@link MultiValueMap MultiValueMap<String, String>}, and it can also + * write (but not read) the {@code "multipart/form-data"} and + * {@code "multipart/mixed"} media types as + * {@link MultiValueMap MultiValueMap<String, Object>}. * *

Multipart Data

* @@ -167,10 +155,6 @@ *

Some methods in this class were inspired by * {@code org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. * - *

As of 6.2, the {@code FormHttpMessageConverter} is parameterized over - * {@code Map} in order to support writing single-value maps. - * Before 6.2, this class was parameterized over {@code MultiValueMap}. - * * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Juergen Hoeller @@ -179,7 +163,7 @@ * @see org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter * @see org.springframework.util.MultiValueMap */ -public class FormHttpMessageConverter implements HttpMessageConverter> { +public class FormHttpMessageConverter implements HttpMessageConverter> { /** The default charset used by the converter. */ public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; @@ -342,7 +326,7 @@ public boolean canRead(Class clazz, @Nullable MediaType mediaType) { @Override public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { - if (!Map.class.isAssignableFrom(clazz)) { + if (!MultiValueMap.class.isAssignableFrom(clazz)) { return false; } if (mediaType == null || MediaType.ALL.equals(mediaType)) { @@ -357,7 +341,7 @@ public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { } @Override - public Map read(@Nullable Class> clazz, + public MultiValueMap read(@Nullable Class> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { MediaType contentType = inputMessage.getHeaders().getContentType(); @@ -383,38 +367,33 @@ public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { @Override @SuppressWarnings("unchecked") - public void write(Map map, @Nullable MediaType contentType, HttpOutputMessage outputMessage) + public void write(MultiValueMap map, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { if (isMultipart(map, contentType)) { - writeMultipart((Map) map, contentType, outputMessage); + writeMultipart((MultiValueMap) map, contentType, outputMessage); } else { - writeForm((Map) map, contentType, outputMessage); + writeForm((MultiValueMap) map, contentType, outputMessage); } } - private boolean isMultipart(Map map, @Nullable MediaType contentType) { + private boolean isMultipart(MultiValueMap map, @Nullable MediaType contentType) { if (contentType != null) { return contentType.getType().equalsIgnoreCase("multipart"); } - for (Object value : map.values()) { - if (value instanceof List values) { - for (Object v : values) { - if (v != null && !(v instanceof String)) { - return true; - } + for (List values : map.values()) { + for (Object value : values) { + if (value != null && !(value instanceof String)) { + return true; } } - else if (value != null && !(value instanceof String)) { - return true; - } } return false; } - private void writeForm(Map formData, @Nullable MediaType mediaType, + private void writeForm(MultiValueMap formData, @Nullable MediaType mediaType, HttpOutputMessage outputMessage) throws IOException { mediaType = getFormContentType(mediaType); @@ -462,36 +441,30 @@ protected MediaType getFormContentType(@Nullable MediaType contentType) { return contentType; } - protected String serializeForm(Map formData, Charset charset) { + protected String serializeForm(MultiValueMap formData, Charset charset) { StringBuilder builder = new StringBuilder(); - formData.forEach((name, value) -> { - if (value instanceof List values) { + formData.forEach((name, values) -> { if (name == null) { Assert.isTrue(CollectionUtils.isEmpty(values), () -> "Null name in form data: " + formData); return; } - values.forEach(v -> appendFormValue(builder, name, v, charset)); - } - else { - appendFormValue(builder, name, value, charset); - } + values.forEach(value -> { + if (builder.length() != 0) { + builder.append('&'); + } + builder.append(URLEncoder.encode(name, charset)); + if (value != null) { + builder.append('='); + builder.append(URLEncoder.encode(String.valueOf(value), charset)); + } + }); }); - return builder.toString(); - } - private static void appendFormValue(StringBuilder builder, String name, @Nullable Object value, Charset charset) { - if (!builder.isEmpty()) { - builder.append('&'); - } - builder.append(URLEncoder.encode(name, charset)); - if (value != null) { - builder.append('='); - builder.append(URLEncoder.encode(String.valueOf(value), charset)); - } + return builder.toString(); } private void writeMultipart( - Map parts, @Nullable MediaType contentType, HttpOutputMessage outputMessage) + MultiValueMap parts, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException { // If the supplied content type is null, fall back to multipart/form-data. @@ -538,24 +511,16 @@ private boolean isFilenameCharsetSet() { return (this.multipartCharset != null); } - private void writeParts(OutputStream os, Map parts, byte[] boundary) throws IOException { - for (Map.Entry entry : parts.entrySet()) { + private void writeParts(OutputStream os, MultiValueMap parts, byte[] boundary) throws IOException { + for (Map.Entry> entry : parts.entrySet()) { String name = entry.getKey(); - Object value = entry.getValue(); - if (value instanceof List values) { - for (Object part : values) { - if (part != null) { - writeBoundary(os, boundary); - writePart(name, getHttpEntity(part), os); - writeNewLine(os); - } + for (Object part : entry.getValue()) { + if (part != null) { + writeBoundary(os, boundary); + writePart(name, getHttpEntity(part), os); + writeNewLine(os); } } - else if (value != null) { - writeBoundary(os, boundary); - writePart(name, getHttpEntity(value), os); - writeNewLine(os); - } } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java index a87143836deb..72029653dbb9 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java @@ -203,7 +203,6 @@ protected boolean canWrite(@Nullable MediaType mediaType) { (this.protobufFormatSupport != null && this.protobufFormatSupport.supportsWriteOnly(mediaType))); } - @SuppressWarnings("deprecation") @Override protected void writeInternal(Message message, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { @@ -226,7 +225,7 @@ protected void writeInternal(Message message, HttpOutputMessage outputMessage) } else if (TEXT_PLAIN.isCompatibleWith(contentType)) { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputMessage.getBody(), charset); - TextFormat.print(message, outputStreamWriter); // deprecated on Protobuf 3.9 + TextFormat.printer().print(message, outputStreamWriter); outputStreamWriter.flush(); outputMessage.getBody().flush(); } 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 85850d138ffc..62023d264380 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 @@ -29,11 +29,16 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.Principal; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; import java.util.Arrays; +import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import jakarta.servlet.http.HttpServletRequest; @@ -67,6 +72,10 @@ public class ServletServerHttpRequest implements ServerHttpRequest { @Nullable private HttpHeaders headers; + @Nullable + private Map attributes; + + @Nullable private ServerHttpAsyncRequestControl asyncRequestControl; @@ -207,6 +216,16 @@ public InetSocketAddress getRemoteAddress() { return new InetSocketAddress(this.servletRequest.getRemoteHost(), this.servletRequest.getRemotePort()); } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new AttributesMap(); + this.attributes = attributes; + } + return attributes; + } + @Override public InputStream getBody() throws IOException { if (isFormPost(this.servletRequest) && this.servletRequest.getQueryString() == null) { @@ -276,4 +295,151 @@ private InputStream getBodyFromServletRequestParameters(HttpServletRequest reque return new ByteArrayInputStream(bytes); } + + private final class AttributesMap extends AbstractMap { + + @Nullable + private transient Set keySet; + + @Nullable + private transient Collection values; + + @Nullable + private transient Set> entrySet; + + + @Override + public int size() { + int size = 0; + for (Enumeration names = servletRequest.getAttributeNames(); names.hasMoreElements(); names.nextElement()) { + size++; + } + return size; + } + + @Override + @Nullable + public Object get(Object key) { + if (key instanceof String name) { + return servletRequest.getAttribute(name); + } + else { + return null; + } + } + + @Override + @Nullable + public Object put(String key, Object value) { + Object old = get(key); + servletRequest.setAttribute(key, value); + return old; + } + + @Override + @Nullable + public Object remove(Object key) { + if (key instanceof String name) { + Object old = get(key); + servletRequest.removeAttribute(name); + return old; + } + else { + return null; + } + } + + @Override + public void clear() { + for (Enumeration names = servletRequest.getAttributeNames(); names.hasMoreElements(); ) { + String name = names.nextElement(); + servletRequest.removeAttribute(name); + } + } + + @Override + public Set keySet() { + Set keySet = this.keySet; + if (keySet == null) { + keySet = new AbstractSet<>() { + @Override + public Iterator iterator() { + return servletRequest.getAttributeNames().asIterator(); + } + + @Override + public int size() { + return AttributesMap.this.size(); + } + }; + this.keySet = keySet; + } + return keySet; + } + + @Override + public Collection values() { + Collection values = this.values; + if (values == null) { + values = new AbstractCollection<>() { + @Override + public Iterator iterator() { + Enumeration e = servletRequest.getAttributeNames(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return e.hasMoreElements(); + } + + @Override + public Object next() { + String name = e.nextElement(); + return servletRequest.getAttribute(name); + } + }; + } + + @Override + public int size() { + return AttributesMap.this.size(); + } + }; + this.values = values; + } + return values; + } + + @Override + public Set> entrySet() { + Set> entrySet = this.entrySet; + if (entrySet == null) { + entrySet = new AbstractSet<>() { + @Override + public Iterator> iterator() { + Enumeration e = servletRequest.getAttributeNames(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return e.hasMoreElements(); + } + + @Override + public Entry next() { + String name = e.nextElement(); + Object value = servletRequest.getAttribute(name); + return new SimpleImmutableEntry<>(name, value); + } + }; + } + + @Override + public int size() { + return AttributesMap.this.size(); + } + }; + this.entrySet = entrySet; + } + return entrySet; + } + } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java index 829a2202a814..c14399a6be2a 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java @@ -19,6 +19,9 @@ import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -68,6 +71,9 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { @Nullable private String logPrefix; + @Nullable + private Supplier> attributesSupplier; + /** * Constructor with the method, URI and headers for the request. @@ -122,6 +128,16 @@ public URI getURI() { return this.uri; } + @Override + public Map getAttributes() { + if (this.attributesSupplier != null) { + return this.attributesSupplier.get(); + } + else { + return Collections.emptyMap(); + } + } + @Override public RequestPath getPath() { return this.path; @@ -230,4 +246,12 @@ protected String initLogPrefix() { return getId(); } + /** + * Set the attribute supplier. + *

Note: This is exposed mainly for internal framework + * use. + */ + public void setAttributesSupplier(Supplier> attributesSupplier) { + this.attributesSupplier = attributesSupplier; + } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java index bafd8733b8d0..1788c72ce7ea 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java @@ -111,6 +111,7 @@ protected void applyCookies() { for (ResponseCookie httpCookie : getCookies().get(name)) { Long maxAge = (!httpCookie.getMaxAge().isNegative()) ? httpCookie.getMaxAge().getSeconds() : null; HttpSetCookie.SameSite sameSite = (httpCookie.getSameSite() != null) ? HttpSetCookie.SameSite.valueOf(httpCookie.getSameSite()) : null; + // TODO: support Partitioned attribute when available in Netty 5 API DefaultHttpSetCookie cookie = new DefaultHttpSetCookie(name, httpCookie.getValue(), httpCookie.getPath(), httpCookie.getDomain(), null, maxAge, sameSite, false, httpCookie.isSecure(), httpCookie.isHttpOnly()); this.response.addCookie(cookie); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java index c73ea0dda91a..b75ae051ed46 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java @@ -120,6 +120,7 @@ protected void applyCookies() { } cookie.setSecure(httpCookie.isSecure()); cookie.setHttpOnly(httpCookie.isHttpOnly()); + cookie.setPartitioned(httpCookie.isPartitioned()); if (httpCookie.getSameSite() != null) { cookie.setSameSite(CookieHeaderNames.SameSite.valueOf(httpCookie.getSameSite())); } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java index fc6143bfdaf0..8b651de18b8b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java @@ -18,6 +18,7 @@ import java.net.InetSocketAddress; import java.net.URI; +import java.util.Map; import reactor.core.publisher.Flux; @@ -70,6 +71,11 @@ public URI getURI() { return getDelegate().getURI(); } + @Override + public Map getAttributes() { + return getDelegate().getAttributes(); + } + @Override public RequestPath getPath() { return getDelegate().getPath(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index 2e52b6ed1e92..ee96c9143a64 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -39,6 +39,7 @@ import org.springframework.http.ResponseCookie; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * Adapt {@link ServerHttpResponse} to the Servlet {@link HttpServletResponse}. @@ -49,6 +50,8 @@ */ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { + private static final boolean IS_SERVLET61 = ReflectionUtils.findField(HttpServletResponse.class, "SC_PERMANENT_REDIRECT") != null; + private final HttpServletResponse response; private final ServletOutputStream outputStream; @@ -181,6 +184,14 @@ protected void applyCookies() { } cookie.setSecure(httpCookie.isSecure()); cookie.setHttpOnly(httpCookie.isHttpOnly()); + if (httpCookie.isPartitioned()) { + if (IS_SERVLET61) { + cookie.setAttribute("Partitioned", ""); + } + else { + cookie.setAttribute("Partitioned", "true"); + } + } this.response.addCookie(cookie); } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java index 78272ab3f89f..e4382da2a0ed 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java @@ -122,6 +122,7 @@ protected void applyCookies() { } cookie.setSecure(httpCookie.isSecure()); cookie.setHttpOnly(httpCookie.isHttpOnly()); + // TODO: add "Partitioned" attribute when Undertow supports it cookie.setSameSiteMode(httpCookie.getSameSite()); this.exchange.setResponseCookie(cookie); } diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index 471f5ced20e0..8ef97588cef2 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -82,6 +83,8 @@ final class DefaultRestClient implements RestClient { private static final ClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention(); + private static final String URI_TEMPLATE_ATTRIBUTE = RestClient.class.getName() + ".uriTemplate"; + private final ClientHttpRequestFactory clientRequestFactory; @@ -297,7 +300,7 @@ private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec { private InternalBody body; @Nullable - private String uriTemplate; + private Map attributes; @Nullable private Consumer httpRequestConsumer; @@ -308,19 +311,19 @@ public DefaultRequestBodyUriSpec(HttpMethod httpMethod) { @Override public RequestBodySpec uri(String uriTemplate, Object... uriVariables) { - this.uriTemplate = uriTemplate; + attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate); return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables)); } @Override public RequestBodySpec uri(String uriTemplate, Map uriVariables) { - this.uriTemplate = uriTemplate; + attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate); return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables)); } @Override public RequestBodySpec uri(String uriTemplate, Function uriFunction) { - this.uriTemplate = uriTemplate; + attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate); return uri(uriFunction.apply(DefaultRestClient.this.uriBuilderFactory.uriString(uriTemplate))); } @@ -392,6 +395,27 @@ public DefaultRequestBodyUriSpec ifNoneMatch(String... ifNoneMatches) { return this; } + @Override + public RequestBodySpec attribute(String name, Object value) { + getAttributes().put(name, value); + return this; + } + + @Override + public RequestBodySpec attributes(Consumer> attributesConsumer) { + attributesConsumer.accept(getAttributes()); + return this; + } + + private Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new ConcurrentHashMap<>(4); + this.attributes = attributes; + } + return attributes; + } + @Override public RequestBodySpec httpRequest(Consumer requestConsumer) { this.httpRequestConsumer = (this.httpRequestConsumer != null ? @@ -483,8 +507,10 @@ private T exchangeInternal(ExchangeFunction exchangeFunction, boolean clo HttpHeaders headers = initHeaders(); ClientHttpRequest clientRequest = createRequest(uri); clientRequest.getHeaders().addAll(headers); + Map attributes = getAttributes(); + clientRequest.getAttributes().putAll(attributes); ClientRequestObservationContext observationContext = new ClientRequestObservationContext(clientRequest); - observationContext.setUriTemplate(this.uriTemplate); + observationContext.setUriTemplate((String) attributes.get(URI_TEMPLATE_ATTRIBUTE)); observation = ClientHttpObservationDocumentation.HTTP_CLIENT_EXCHANGES.observation(observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry).start(); if (this.body != null) { diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClient.java b/spring-web/src/main/java/org/springframework/web/client/RestClient.java index e1a560062b0c..ef995daff8e3 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestClient.java @@ -498,6 +498,24 @@ interface RequestHeadersSpec> { */ S headers(Consumer headersConsumer); + /** + * Set the attribute with the given name to the given value. + * @param name the name of the attribute to add + * @param value the value of the attribute to add + * @return this builder + * @since 6.2 + */ + S attribute(String name, Object value); + + /** + * Provides access to every attribute declared so far with the + * possibility to add, replace, or remove values. + * @param attributesConsumer the consumer to provide access to + * @return this builder + * @since 6.2 + */ + S attributes(Consumer> attributesConsumer); + /** * Callback for access to the {@link ClientHttpRequest} that in turn * provides access to the native request of the underlying HTTP library. diff --git a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java index 403d7f71562e..67c4ecca9061 100644 --- a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java @@ -56,7 +56,7 @@ private RestClientAdapter(RestClient restClient) { @Override public boolean supportsRequestAttributes() { - return false; + return true; } @Override @@ -121,6 +121,8 @@ else if (values.getUriTemplate() != null) { bodySpec.header(HttpHeaders.COOKIE, String.join("; ", cookies)); } + bodySpec.attributes(attributes -> attributes.putAll(values.getAttributes())); + if (values.getBodyValue() != null) { bodySpec.body(values.getBodyValue()); } diff --git a/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java b/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java index 5d028416bca3..bad214fb5d59 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,7 +95,6 @@ protected void doFilterInternal( } @Nullable - @SuppressWarnings("unchecked") private MultiValueMap parseIfNecessary(HttpServletRequest request) throws IOException { if (!shouldParse(request)) { return null; @@ -107,7 +106,7 @@ public InputStream getBody() throws IOException { return request.getInputStream(); } }; - return (MultiValueMap) this.formConverter.read(null, inputMessage); + return this.formConverter.read(null, inputMessage); } private boolean shouldParse(HttpServletRequest request) { diff --git a/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java index 179b4ffcdd66..1c56741a4ba2 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java @@ -21,9 +21,12 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; import jakarta.servlet.FilterChain; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -94,11 +97,6 @@ public static Optional findObservationContext(H return Optional.ofNullable((ServerRequestObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE)); } - @Override - protected boolean shouldNotFilterAsyncDispatch() { - return false; - } - @Override @SuppressWarnings("try") protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) @@ -115,8 +113,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse throw ex; } finally { - // Only stop Observation if async processing is done or has never been started. - if (!request.isAsyncStarted()) { + // If async is started, register a listener for completion notification. + if (request.isAsyncStarted()) { + request.getAsyncContext().addListener(new ObservationAsyncListener(observation)); + } + // Stop Observation right now if async processing has not been started. + else { Throwable error = fetchException(request); if (error != null) { observation.error(error); @@ -152,13 +154,43 @@ private Observation createOrFetchObservation(HttpServletRequest request, HttpSer } @Nullable - private Throwable unwrapServletException(Throwable ex) { + static Throwable unwrapServletException(Throwable ex) { return (ex instanceof ServletException) ? ex.getCause() : ex; } @Nullable - private Throwable fetchException(HttpServletRequest request) { + static Throwable fetchException(ServletRequest request) { return (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); } + private static class ObservationAsyncListener implements AsyncListener { + + private final Observation currentObservation; + + public ObservationAsyncListener(Observation currentObservation) { + this.currentObservation = currentObservation; + } + + @Override + public void onStartAsync(AsyncEvent event) { + } + + @Override + public void onTimeout(AsyncEvent event) { + this.currentObservation.stop(); + } + + @Override + public void onComplete(AsyncEvent event) { + this.currentObservation.stop(); + } + + @Override + public void onError(AsyncEvent event) { + this.currentObservation.error(unwrapServletException(event.getThrowable())); + this.currentObservation.stop(); + } + + } + } diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java index 2c6f73bfbfcb..c17beb484b8b 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java @@ -42,6 +42,7 @@ import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.AbstractServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; @@ -137,6 +138,10 @@ public DefaultServerWebExchange(ServerHttpRequest request, ServerHttpResponse re this.formDataMono = initFormData(request, codecConfigurer, getLogPrefix()); this.multipartDataMono = initMultipartData(codecConfigurer, getLogPrefix()); this.applicationContext = applicationContext; + + if (request instanceof AbstractServerHttpRequest abstractServerHttpRequest) { + abstractServerHttpRequest.setAttributesSupplier(() -> this.attributes); + } } private static Mono> initFormData(ServerHttpRequest request, diff --git a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java index 6d0e1135071d..3821051fa7b7 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java @@ -89,7 +89,12 @@ public ContentCachingRequestWrapper(HttpServletRequest request) { public ContentCachingRequestWrapper(HttpServletRequest request, int contentCacheLimit) { super(request); int contentLength = request.getContentLength(); - this.cachedContent = (contentLength > 0) ? new FastByteArrayOutputStream(contentLength) : new FastByteArrayOutputStream(); + if (contentLength > 0) { + this.cachedContent = new FastByteArrayOutputStream(Math.min(contentLength, contentCacheLimit)); + } + else { + this.cachedContent = new FastByteArrayOutputStream(); + } this.contentCacheLimit = contentCacheLimit; } diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 171441ba13ac..1a64481252dc 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -236,7 +236,8 @@ public static UriComponentsBuilder fromUriString(String uri) throws InvalidUrlEx builder.port(urlRecord.port().toString()); } if (urlRecord.path().isOpaque()) { - builder.schemeSpecificPart(urlRecord.path().toString()); + String ssp = urlRecord.path() + urlRecord.search(); + builder.schemeSpecificPart(ssp); } else { builder.path(urlRecord.path().toString()); diff --git a/spring-web/src/main/kotlin/org/springframework/web/server/CoWebExceptionHandler.kt b/spring-web/src/main/kotlin/org/springframework/web/server/CoWebExceptionHandler.kt new file mode 100644 index 000000000000..12f1987fb7c7 --- /dev/null +++ b/spring-web/src/main/kotlin/org/springframework/web/server/CoWebExceptionHandler.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.reactor.mono +import reactor.core.publisher.Mono +import kotlin.coroutines.CoroutineContext + +/** + * Kotlin-specific implementation of the [WebExceptionHandler] interface that allows for + * using coroutines, including [kotlin.coroutines.CoroutineContext] propagation. + * + * @author Sangyoon Jeong + * @since 6.2 + */ +abstract class CoWebExceptionHandler : WebExceptionHandler { + + final override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono { + val context = exchange.attributes[CoWebFilter.COROUTINE_CONTEXT_ATTRIBUTE] as CoroutineContext? + return mono(context ?: Dispatchers.Unconfined) { coHandle(exchange, ex) }.then() + } + + protected abstract suspend fun coHandle(exchange: ServerWebExchange, ex: Throwable) +} diff --git a/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java index 444b0dd26e2b..e1e19dc6b4bd 100644 --- a/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java +++ b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java @@ -37,12 +37,12 @@ void basic() { assertThat(ResponseCookie.from("id", "1fWa").build().toString()).isEqualTo("id=1fWa"); ResponseCookie cookie = ResponseCookie.from("id", "1fWa") - .domain("abc").path("/path").maxAge(0).httpOnly(true).secure(true).sameSite("None") + .domain("abc").path("/path").maxAge(0).httpOnly(true).partitioned(true).secure(true).sameSite("None") .build(); assertThat(cookie.toString()).isEqualTo("id=1fWa; Path=/path; Domain=abc; " + "Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; " + - "Secure; HttpOnly; SameSite=None"); + "Secure; HttpOnly; Partitioned; SameSite=None"); } @Test 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 cd08464d2076..2735c00e315f 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 @@ -115,6 +115,32 @@ protected ClientHttpResponse executeInternal() { request.execute(); } + @Test + void changeAttribute() throws Exception { + final String attrName = "Foo"; + final String attrValue = "Bar"; + + ClientHttpRequestInterceptor interceptor = (request, body, execution) -> { + System.out.println("interceptor"); + request.getAttributes().put(attrName, attrValue); + return execution.execute(request, body); + }; + + requestMock = new MockClientHttpRequest() { + @Override + protected ClientHttpResponse executeInternal() { + System.out.println("execute"); + assertThat(getAttributes()).containsEntry(attrName, attrValue); + return responseMock; + } + }; + + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); + + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); + request.execute(); + } + @Test void changeURI() throws Exception { final URI changedUri = URI.create("https://example.com/2"); diff --git a/spring-web/src/test/java/org/springframework/http/client/JdkClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/JdkClientHttpRequestFactoryTests.java index 0f2d407348ee..443cb069526a 100644 --- a/spring-web/src/test/java/org/springframework/http/client/JdkClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/JdkClientHttpRequestFactoryTests.java @@ -31,6 +31,8 @@ import static org.assertj.core.api.Assertions.assertThat; /** + * Tests for {@link JdkClientHttpRequestFactory}. + * * @author Marten Deinum */ class JdkClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests { @@ -68,12 +70,12 @@ void httpMethods() throws Exception { @Test void customizeDisallowedHeaders() throws IOException { - ClientHttpRequest request = this.factory.createRequest(URI.create(this.baseUrl + "/status/299"), HttpMethod.PUT); - request.getHeaders().set("Expect", "299"); + ClientHttpRequest request = this.factory.createRequest(URI.create(this.baseUrl + "/status/299"), HttpMethod.PUT); + request.getHeaders().set("Expect", "299"); - try (ClientHttpResponse response = request.execute()) { - assertThat(response.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatusCode.valueOf(299)); - } + try (ClientHttpResponse response = request.execute()) { + assertThat(response.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatusCode.valueOf(299)); + } } @Test // gh-31451 diff --git a/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlEncoderTests.java index 6cde0d5bca80..f04b9aafb745 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlEncoderTests.java @@ -63,6 +63,8 @@ protected void canEncode() { assertThat(this.encoder.canEncode(forClass(TypePojo.class), MediaType.APPLICATION_XML)).isTrue(); assertThat(this.encoder.canEncode(forClass(getClass()), MediaType.APPLICATION_XML)).isFalse(); + assertThat(this.encoder.canEncode(forClass(JAXBElement.class), MediaType.APPLICATION_XML)).isTrue(); + // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isFalse(); } 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 299ca939bf80..98c937bc7339 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 @@ -118,13 +118,12 @@ void addSupportedMediaTypes() { } @Test - @SuppressWarnings("unchecked") void readForm() throws Exception { String body = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.ISO_8859_1)); inputMessage.getHeaders().setContentType( new MediaType("application", "x-www-form-urlencoded", StandardCharsets.ISO_8859_1)); - MultiValueMap result = (MultiValueMap) this.converter.read(null, inputMessage); + MultiValueMap result = this.converter.read(null, inputMessage); assertThat(result).as("Invalid result").hasSize(3); assertThat(result.getFirst("name 1")).as("Invalid result").isEqualTo("value 1"); @@ -152,24 +151,7 @@ void writeForm() throws IOException { } @Test - void writeFormSingleValue() throws IOException { - Map body = new LinkedHashMap<>(); - body.put("name 1", "value 1"); - body.put("name 2", "value 2"); - body.put("name 3", null); - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - this.converter.write(body, APPLICATION_FORM_URLENCODED, outputMessage); - - assertThat(outputMessage.getBodyAsString(UTF_8)) - .as("Invalid result").isEqualTo("name+1=value+1&name+2=value+2&name+3"); - assertThat(outputMessage.getHeaders().getContentType()) - .as("Invalid content-type").isEqualTo(APPLICATION_FORM_URLENCODED); - assertThat(outputMessage.getHeaders().getContentLength()) - .as("Invalid content-length").isEqualTo(outputMessage.getBodyAsBytes().length); - } - - @Test - void writeMultipartMultiValue() throws Exception { + void writeMultipart() throws Exception { MultiValueMap parts = new LinkedMultiValueMap<>(); parts.add("name 1", "value 1"); @@ -246,78 +228,6 @@ public String getFilename() { assertThat(item.getContentType()).isEqualTo("application/json"); } - @Test - void writeMultipartSingleValue() throws Exception { - - Map parts = new LinkedHashMap<>(); - parts.put("name 1", "value 1"); - parts.put("name 2", "value 2"); - parts.put("name 3", null); - - Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); - parts.put("logo", logo); - - // SPR-12108 - Resource utf8 = new ClassPathResource("/org/springframework/http/converter/logo.jpg") { - @Override - public String getFilename() { - return "Hall\u00F6le.jpg"; - } - }; - parts.put("utf8", utf8); - - MyBean myBean = new MyBean(); - myBean.setString("foo"); - HttpHeaders entityHeaders = new HttpHeaders(); - entityHeaders.setContentType(APPLICATION_JSON); - HttpEntity entity = new HttpEntity<>(myBean, entityHeaders); - parts.put("json", entity); - - Map parameters = new LinkedHashMap<>(2); - parameters.put("charset", UTF_8.name()); - parameters.put("foo", "bar"); - - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - this.converter.write(parts, new MediaType("multipart", "form-data", parameters), outputMessage); - - final MediaType contentType = outputMessage.getHeaders().getContentType(); - assertThat(contentType.getParameters()).containsKeys("charset", "boundary", "foo"); // gh-21568, gh-25839 - - // see if Commons FileUpload can read what we wrote - FileUpload fileUpload = new FileUpload(); - fileUpload.setFileItemFactory(new DiskFileItemFactory()); - RequestContext requestContext = new MockHttpOutputMessageRequestContext(outputMessage); - List items = fileUpload.parseRequest(requestContext); - assertThat(items).hasSize(5); - FileItem item = items.get(0); - assertThat(item.isFormField()).isTrue(); - assertThat(item.getFieldName()).isEqualTo("name 1"); - assertThat(item.getString()).isEqualTo("value 1"); - - item = items.get(1); - assertThat(item.isFormField()).isTrue(); - assertThat(item.getFieldName()).isEqualTo("name 2"); - assertThat(item.getString()).isEqualTo("value 2"); - - item = items.get(2); - assertThat(item.isFormField()).isFalse(); - assertThat(item.getFieldName()).isEqualTo("logo"); - assertThat(item.getName()).isEqualTo("logo.jpg"); - assertThat(item.getContentType()).isEqualTo("image/jpeg"); - assertThat(item.getSize()).isEqualTo(logo.getFile().length()); - - item = items.get(3); - assertThat(item.isFormField()).isFalse(); - assertThat(item.getFieldName()).isEqualTo("utf8"); - assertThat(item.getName()).isEqualTo("Hall\u00F6le.jpg"); - assertThat(item.getContentType()).isEqualTo("image/jpeg"); - assertThat(item.getSize()).isEqualTo(logo.getFile().length()); - - item = items.get(4); - assertThat(item.getFieldName()).isEqualTo("json"); - assertThat(item.getContentType()).isEqualTo("application/json"); - } - @Test void writeMultipartWithSourceHttpMessageConverter() throws Exception { @@ -491,8 +401,8 @@ private void assertCannotRead(Class clazz, MediaType mediaType) { } private void assertCanWrite(MediaType mediaType) { - assertThat(this.converter.canWrite(MultiValueMap.class, mediaType)).as("MultiValueMap : " + mediaType).isTrue(); - assertThat(this.converter.canWrite(Map.class, mediaType)).as("Map : " + mediaType).isTrue(); + Class clazz = MultiValueMap.class; + assertThat(this.converter.canWrite(clazz, mediaType)).as(clazz.getSimpleName() + " : " + mediaType).isTrue(); } private void assertCannotWrite(MediaType mediaType) { 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 dcb98b145151..a021b4c9b9b9 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 @@ -217,4 +217,10 @@ void getFormBodyWhenNotEncodedCharactersPresent() throws IOException { assertThat(request.getHeaders().getContentLength()).isEqualTo(result.length); } + @Test + void attributes() { + request.getAttributes().put("foo", "bar"); + assertThat(mockRequest.getAttribute("foo")).isEqualTo("bar"); + } + } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java index 8d2daafa972e..4ceee53b45c9 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java @@ -70,13 +70,32 @@ public void basicTest(HttpServer httpServer) throws Exception { List cookie0 = splitCookie(headerValues.get(0)); assertThat(cookie0.remove("SID=31d4d96e407aad42")).as("SID").isTrue(); assertThat(cookie0.stream().map(String::toLowerCase)) - .containsExactlyInAnyOrder("path=/", "secure", "httponly"); + .contains("path=/", "secure", "httponly"); List cookie1 = splitCookie(headerValues.get(1)); assertThat(cookie1.remove("lang=en-US")).as("lang").isTrue(); assertThat(cookie1.stream().map(String::toLowerCase)) .containsExactlyInAnyOrder("path=/", "domain=example.com"); } + @ParameterizedHttpServerTest + public void partitionedAttributeTest(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof UndertowHttpServer, "Undertow does not support Partitioned cookies"); + startServer(httpServer); + + URI url = URI.create("http://localhost:" + port); + String header = "SID=31d4d96e407aad42; lang=en-US"; + ResponseEntity response = new RestTemplate().exchange( + RequestEntity.get(url).header("Cookie", header).build(), Void.class); + + List headerValues = response.getHeaders().get("Set-Cookie"); + assertThat(headerValues).hasSize(2); + + List cookie0 = splitCookie(headerValues.get(0)); + assertThat(cookie0.remove("SID=31d4d96e407aad42")).as("SID").isTrue(); + assertThat(cookie0.stream().map(String::toLowerCase)) + .contains("partitioned"); + } + @ParameterizedHttpServerTest public void cookiesWithSameNameTest(HttpServer httpServer) throws Exception { assumeFalse(httpServer instanceof UndertowHttpServer, "Bug in Undertow in Cookies with same name handling"); @@ -116,7 +135,7 @@ public Mono handle(ServerHttpRequest request, ServerHttpResponse response) this.requestCookies.size(); // Cause lazy loading response.getCookies().add("SID", ResponseCookie.from("SID", "31d4d96e407aad42") - .path("/").secure(true).httpOnly(true).build()); + .path("/").secure(true).httpOnly(true).partitioned(true).build()); response.getCookies().add("lang", ResponseCookie.from("lang", "en-US") .domain("example.com").path("/").build()); diff --git a/spring-web/src/test/java/org/springframework/protobuf/Msg.java b/spring-web/src/test/java/org/springframework/protobuf/Msg.java index 9a278ee9019f..e47b4738e7e7 100644 --- a/spring-web/src/test/java/org/springframework/protobuf/Msg.java +++ b/spring-web/src/test/java/org/springframework/protobuf/Msg.java @@ -1,5 +1,7 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.protobuf; @@ -7,47 +9,49 @@ * Protobuf type {@code Msg} */ public final class Msg extends - com.google.protobuf.GeneratedMessageV3 implements + com.google.protobuf.GeneratedMessage implements // @@protoc_insertion_point(message_implements:Msg) MsgOrBuilder { private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + Msg.class.getName()); + } // Use Msg.newBuilder() to construct. - private Msg(com.google.protobuf.GeneratedMessageV3.Builder builder) { + private Msg(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); } private Msg() { foo_ = ""; } - @Override - @SuppressWarnings({"unused"}) - protected Object newInstance( - UnusedPrivateParameter unused) { - return new Msg(); - } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.protobuf.OuterSample.internal_static_Msg_descriptor; } - @Override - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_Msg_fieldAccessorTable + return org.springframework.protobuf.OuterSample.internal_static_Msg_fieldAccessorTable .ensureFieldAccessorsInitialized( - Msg.class, Builder.class); + org.springframework.protobuf.Msg.class, org.springframework.protobuf.Msg.Builder.class); } private int bitField0_; public static final int FOO_FIELD_NUMBER = 1; @SuppressWarnings("serial") - private volatile Object foo_ = ""; + private volatile java.lang.Object foo_ = ""; /** * optional string foo = 1; * @return Whether the foo field is set. */ - @Override + @java.lang.Override public boolean hasFoo() { return ((bitField0_ & 0x00000001) != 0); } @@ -55,15 +59,15 @@ public boolean hasFoo() { * optional string foo = 1; * @return The foo. */ - @Override - public String getFoo() { - Object ref = foo_; - if (ref instanceof String) { - return (String) ref; + @java.lang.Override + public java.lang.String getFoo() { + java.lang.Object ref = foo_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { foo_ = s; } @@ -74,14 +78,14 @@ public String getFoo() { * optional string foo = 1; * @return The bytes for foo. */ - @Override + @java.lang.Override public com.google.protobuf.ByteString getFooBytes() { - Object ref = foo_; - if (ref instanceof String) { + java.lang.Object ref = foo_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); foo_ = b; return b; } else { @@ -90,12 +94,12 @@ public String getFoo() { } public static final int BLAH_FIELD_NUMBER = 2; - private SecondMsg blah_; + private org.springframework.protobuf.SecondMsg blah_; /** * optional .SecondMsg blah = 2; * @return Whether the blah field is set. */ - @Override + @java.lang.Override public boolean hasBlah() { return ((bitField0_ & 0x00000002) != 0); } @@ -103,20 +107,20 @@ public boolean hasBlah() { * optional .SecondMsg blah = 2; * @return The blah. */ - @Override - public SecondMsg getBlah() { - return blah_ == null ? SecondMsg.getDefaultInstance() : blah_; + @java.lang.Override + public org.springframework.protobuf.SecondMsg getBlah() { + return blah_ == null ? org.springframework.protobuf.SecondMsg.getDefaultInstance() : blah_; } /** * optional .SecondMsg blah = 2; */ - @Override - public SecondMsgOrBuilder getBlahOrBuilder() { - return blah_ == null ? SecondMsg.getDefaultInstance() : blah_; + @java.lang.Override + public org.springframework.protobuf.SecondMsgOrBuilder getBlahOrBuilder() { + return blah_ == null ? org.springframework.protobuf.SecondMsg.getDefaultInstance() : blah_; } private byte memoizedIsInitialized = -1; - @Override + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -126,11 +130,11 @@ public final boolean isInitialized() { return true; } - @Override + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { if (((bitField0_ & 0x00000001) != 0)) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 1, foo_); + com.google.protobuf.GeneratedMessage.writeString(output, 1, foo_); } if (((bitField0_ & 0x00000002) != 0)) { output.writeMessage(2, getBlah()); @@ -138,14 +142,14 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) getUnknownFields().writeTo(output); } - @Override + @java.lang.Override public int getSerializedSize() { int size = memoizedSize; if (size != -1) return size; size = 0; if (((bitField0_ & 0x00000001) != 0)) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, foo_); + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, foo_); } if (((bitField0_ & 0x00000002) != 0)) { size += com.google.protobuf.CodedOutputStream @@ -156,15 +160,15 @@ public int getSerializedSize() { return size; } - @Override - public boolean equals(final Object obj) { + @java.lang.Override + public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } - if (!(obj instanceof Msg)) { + if (!(obj instanceof org.springframework.protobuf.Msg)) { return super.equals(obj); } - Msg other = (Msg) obj; + org.springframework.protobuf.Msg other = (org.springframework.protobuf.Msg) obj; if (hasFoo() != other.hasFoo()) return false; if (hasFoo()) { @@ -180,7 +184,7 @@ public boolean equals(final Object obj) { return true; } - @Override + @java.lang.Override public int hashCode() { if (memoizedHashCode != 0) { return memoizedHashCode; @@ -200,95 +204,95 @@ public int hashCode() { return hash; } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Msg parseFrom(byte[] data) + public static org.springframework.protobuf.Msg parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Msg parseFrom(java.io.InputStream input) + public static org.springframework.protobuf.Msg parseFrom(java.io.InputStream input) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input, extensionRegistry); } - public static Msg parseDelimitedFrom(java.io.InputStream input) + public static org.springframework.protobuf.Msg parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input); } - public static Msg parseDelimitedFrom( + public static org.springframework.protobuf.Msg parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static Msg parseFrom( + public static org.springframework.protobuf.Msg parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input, extensionRegistry); } - @Override + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } public static Builder newBuilder() { return DEFAULT_INSTANCE.toBuilder(); } - public static Builder newBuilder(Msg prototype) { + public static Builder newBuilder(org.springframework.protobuf.Msg prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } - @Override + @java.lang.Override public Builder toBuilder() { return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessage.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } @@ -296,20 +300,20 @@ protected Builder newBuilderForType( * Protobuf type {@code Msg} */ public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:Msg) - MsgOrBuilder { + org.springframework.protobuf.MsgOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.protobuf.OuterSample.internal_static_Msg_descriptor; } - @Override - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_Msg_fieldAccessorTable + return org.springframework.protobuf.OuterSample.internal_static_Msg_fieldAccessorTable .ensureFieldAccessorsInitialized( - Msg.class, Builder.class); + org.springframework.protobuf.Msg.class, org.springframework.protobuf.Msg.Builder.class); } // Construct using org.springframework.protobuf.Msg.newBuilder() @@ -318,17 +322,17 @@ private Builder() { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessage.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 + if (com.google.protobuf.GeneratedMessage .alwaysUseFieldBuilders) { getBlahFieldBuilder(); } } - @Override + @java.lang.Override public Builder clear() { super.clear(); bitField0_ = 0; @@ -341,35 +345,35 @@ public Builder clear() { return this; } - @Override + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.protobuf.OuterSample.internal_static_Msg_descriptor; } - @Override - public Msg getDefaultInstanceForType() { - return Msg.getDefaultInstance(); + @java.lang.Override + public org.springframework.protobuf.Msg getDefaultInstanceForType() { + return org.springframework.protobuf.Msg.getDefaultInstance(); } - @Override - public Msg build() { - Msg result = buildPartial(); + @java.lang.Override + public org.springframework.protobuf.Msg build() { + org.springframework.protobuf.Msg result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - @Override - public Msg buildPartial() { - Msg result = new Msg(this); + @java.lang.Override + public org.springframework.protobuf.Msg buildPartial() { + org.springframework.protobuf.Msg result = new org.springframework.protobuf.Msg(this); if (bitField0_ != 0) { buildPartial0(result); } onBuilt(); return result; } - private void buildPartial0(Msg result) { + private void buildPartial0(org.springframework.protobuf.Msg result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) != 0)) { @@ -385,50 +389,18 @@ private void buildPartial0(Msg result) { result.bitField0_ |= to_bitField0_; } - @Override - public Builder clone() { - return super.clone(); - } - @Override - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return super.setField(field, value); - } - @Override - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return super.clearField(field); - } - @Override - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return super.clearOneof(oneof); - } - @Override - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return super.setRepeatedField(field, index, value); - } - @Override - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return super.addRepeatedField(field, value); - } - @Override + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Msg) { - return mergeFrom((Msg)other); + if (other instanceof org.springframework.protobuf.Msg) { + return mergeFrom((org.springframework.protobuf.Msg)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(Msg other) { - if (other == Msg.getDefaultInstance()) return this; + public Builder mergeFrom(org.springframework.protobuf.Msg other) { + if (other == org.springframework.protobuf.Msg.getDefaultInstance()) return this; if (other.hasFoo()) { foo_ = other.foo_; bitField0_ |= 0x00000001; @@ -442,18 +414,18 @@ public Builder mergeFrom(Msg other) { return this; } - @Override + @java.lang.Override public final boolean isInitialized() { return true; } - @Override + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { if (extensionRegistry == null) { - throw new NullPointerException(); + throw new java.lang.NullPointerException(); } try { boolean done = false; @@ -492,7 +464,7 @@ public Builder mergeFrom( } private int bitField0_; - private Object foo_ = ""; + private java.lang.Object foo_ = ""; /** * optional string foo = 1; * @return Whether the foo field is set. @@ -504,18 +476,18 @@ public boolean hasFoo() { * optional string foo = 1; * @return The foo. */ - public String getFoo() { - Object ref = foo_; - if (!(ref instanceof String)) { + public java.lang.String getFoo() { + java.lang.Object ref = foo_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { foo_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** @@ -524,11 +496,11 @@ public String getFoo() { */ public com.google.protobuf.ByteString getFooBytes() { - Object ref = foo_; + java.lang.Object ref = foo_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); foo_ = b; return b; } else { @@ -541,7 +513,7 @@ public String getFoo() { * @return This builder for chaining. */ public Builder setFoo( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } foo_ = value; bitField0_ |= 0x00000001; @@ -572,9 +544,9 @@ public Builder setFooBytes( return this; } - private SecondMsg blah_; - private com.google.protobuf.SingleFieldBuilderV3< - SecondMsg, SecondMsg.Builder, SecondMsgOrBuilder> blahBuilder_; + private org.springframework.protobuf.SecondMsg blah_; + private com.google.protobuf.SingleFieldBuilder< + org.springframework.protobuf.SecondMsg, org.springframework.protobuf.SecondMsg.Builder, org.springframework.protobuf.SecondMsgOrBuilder> blahBuilder_; /** * optional .SecondMsg blah = 2; * @return Whether the blah field is set. @@ -586,9 +558,9 @@ public boolean hasBlah() { * optional .SecondMsg blah = 2; * @return The blah. */ - public SecondMsg getBlah() { + public org.springframework.protobuf.SecondMsg getBlah() { if (blahBuilder_ == null) { - return blah_ == null ? SecondMsg.getDefaultInstance() : blah_; + return blah_ == null ? org.springframework.protobuf.SecondMsg.getDefaultInstance() : blah_; } else { return blahBuilder_.getMessage(); } @@ -596,7 +568,7 @@ public SecondMsg getBlah() { /** * optional .SecondMsg blah = 2; */ - public Builder setBlah(SecondMsg value) { + public Builder setBlah(org.springframework.protobuf.SecondMsg value) { if (blahBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -613,7 +585,7 @@ public Builder setBlah(SecondMsg value) { * optional .SecondMsg blah = 2; */ public Builder setBlah( - SecondMsg.Builder builderForValue) { + org.springframework.protobuf.SecondMsg.Builder builderForValue) { if (blahBuilder_ == null) { blah_ = builderForValue.build(); } else { @@ -626,11 +598,11 @@ public Builder setBlah( /** * optional .SecondMsg blah = 2; */ - public Builder mergeBlah(SecondMsg value) { + public Builder mergeBlah(org.springframework.protobuf.SecondMsg value) { if (blahBuilder_ == null) { if (((bitField0_ & 0x00000002) != 0) && blah_ != null && - blah_ != SecondMsg.getDefaultInstance()) { + blah_ != org.springframework.protobuf.SecondMsg.getDefaultInstance()) { getBlahBuilder().mergeFrom(value); } else { blah_ = value; @@ -660,7 +632,7 @@ public Builder clearBlah() { /** * optional .SecondMsg blah = 2; */ - public SecondMsg.Builder getBlahBuilder() { + public org.springframework.protobuf.SecondMsg.Builder getBlahBuilder() { bitField0_ |= 0x00000002; onChanged(); return getBlahFieldBuilder().getBuilder(); @@ -668,23 +640,23 @@ public SecondMsg.Builder getBlahBuilder() { /** * optional .SecondMsg blah = 2; */ - public SecondMsgOrBuilder getBlahOrBuilder() { + public org.springframework.protobuf.SecondMsgOrBuilder getBlahOrBuilder() { if (blahBuilder_ != null) { return blahBuilder_.getMessageOrBuilder(); } else { return blah_ == null ? - SecondMsg.getDefaultInstance() : blah_; + org.springframework.protobuf.SecondMsg.getDefaultInstance() : blah_; } } /** * optional .SecondMsg blah = 2; */ - private com.google.protobuf.SingleFieldBuilderV3< - SecondMsg, SecondMsg.Builder, SecondMsgOrBuilder> + private com.google.protobuf.SingleFieldBuilder< + org.springframework.protobuf.SecondMsg, org.springframework.protobuf.SecondMsg.Builder, org.springframework.protobuf.SecondMsgOrBuilder> getBlahFieldBuilder() { if (blahBuilder_ == null) { - blahBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< - SecondMsg, SecondMsg.Builder, SecondMsgOrBuilder>( + blahBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.springframework.protobuf.SecondMsg, org.springframework.protobuf.SecondMsg.Builder, org.springframework.protobuf.SecondMsgOrBuilder>( getBlah(), getParentForChildren(), isClean()); @@ -692,35 +664,23 @@ public SecondMsgOrBuilder getBlahOrBuilder() { } return blahBuilder_; } - @Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); - } - - @Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.mergeUnknownFields(unknownFields); - } - // @@protoc_insertion_point(builder_scope:Msg) } // @@protoc_insertion_point(class_scope:Msg) - private static final Msg DEFAULT_INSTANCE; + private static final org.springframework.protobuf.Msg DEFAULT_INSTANCE; static { - DEFAULT_INSTANCE = new Msg(); + DEFAULT_INSTANCE = new org.springframework.protobuf.Msg(); } - public static Msg getDefaultInstance() { + public static org.springframework.protobuf.Msg getDefaultInstance() { return DEFAULT_INSTANCE; } - @Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - @Override + @java.lang.Override public Msg parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) @@ -744,13 +704,13 @@ public static com.google.protobuf.Parser parser() { return PARSER; } - @Override + @java.lang.Override public com.google.protobuf.Parser getParserForType() { return PARSER; } - @Override - public Msg getDefaultInstanceForType() { + @java.lang.Override + public org.springframework.protobuf.Msg getDefaultInstanceForType() { return DEFAULT_INSTANCE; } diff --git a/spring-web/src/test/java/org/springframework/protobuf/MsgOrBuilder.java b/spring-web/src/test/java/org/springframework/protobuf/MsgOrBuilder.java index 866b7cfde73b..527491727758 100644 --- a/spring-web/src/test/java/org/springframework/protobuf/MsgOrBuilder.java +++ b/spring-web/src/test/java/org/springframework/protobuf/MsgOrBuilder.java @@ -1,5 +1,7 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.protobuf; @@ -16,7 +18,7 @@ public interface MsgOrBuilder extends * optional string foo = 1; * @return The foo. */ - String getFoo(); + java.lang.String getFoo(); /** * optional string foo = 1; * @return The bytes for foo. @@ -33,9 +35,9 @@ public interface MsgOrBuilder extends * optional .SecondMsg blah = 2; * @return The blah. */ - SecondMsg getBlah(); + org.springframework.protobuf.SecondMsg getBlah(); /** * optional .SecondMsg blah = 2; */ - SecondMsgOrBuilder getBlahOrBuilder(); + org.springframework.protobuf.SecondMsgOrBuilder getBlahOrBuilder(); } diff --git a/spring-web/src/test/java/org/springframework/protobuf/OuterSample.java b/spring-web/src/test/java/org/springframework/protobuf/OuterSample.java index 6e9d9fc25cac..26267b08e6c9 100644 --- a/spring-web/src/test/java/org/springframework/protobuf/OuterSample.java +++ b/spring-web/src/test/java/org/springframework/protobuf/OuterSample.java @@ -1,26 +1,21 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.protobuf; public final class OuterSample { private OuterSample() {} + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + OuterSample.class.getName()); + } public static void registerAllExtensions( com.google.protobuf.ExtensionRegistryLite registry) { } @@ -33,12 +28,12 @@ public static void registerAllExtensions( static final com.google.protobuf.Descriptors.Descriptor internal_static_Msg_descriptor; static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_Msg_fieldAccessorTable; static final com.google.protobuf.Descriptors.Descriptor internal_static_SecondMsg_descriptor; static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_SecondMsg_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor @@ -48,7 +43,7 @@ public static void registerAllExtensions( private static com.google.protobuf.Descriptors.FileDescriptor descriptor; static { - String[] descriptorData = { + java.lang.String[] descriptorData = { "\n\014sample.proto\",\n\003Msg\022\013\n\003foo\030\001 \001(\t\022\030\n\004bl" + "ah\030\002 \001(\0132\n.SecondMsg\"\031\n\tSecondMsg\022\014\n\004bla" + "h\030\001 \001(\005B-\n\034org.springframework.protobufB" + @@ -61,15 +56,16 @@ public static void registerAllExtensions( internal_static_Msg_descriptor = getDescriptor().getMessageTypes().get(0); internal_static_Msg_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_Msg_descriptor, - new String[] { "Foo", "Blah", }); + new java.lang.String[] { "Foo", "Blah", }); internal_static_SecondMsg_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_SecondMsg_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_SecondMsg_descriptor, - new String[] { "Blah", }); + new java.lang.String[] { "Blah", }); + descriptor.resolveAllFeaturesImmutable(); } // @@protoc_insertion_point(outer_class_scope) diff --git a/spring-web/src/test/java/org/springframework/protobuf/SecondMsg.java b/spring-web/src/test/java/org/springframework/protobuf/SecondMsg.java index 5b6ab1092dd8..7f75718f23b9 100644 --- a/spring-web/src/test/java/org/springframework/protobuf/SecondMsg.java +++ b/spring-web/src/test/java/org/springframework/protobuf/SecondMsg.java @@ -1,5 +1,7 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.protobuf; @@ -7,35 +9,37 @@ * Protobuf type {@code SecondMsg} */ public final class SecondMsg extends - com.google.protobuf.GeneratedMessageV3 implements + com.google.protobuf.GeneratedMessage implements // @@protoc_insertion_point(message_implements:SecondMsg) SecondMsgOrBuilder { private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + SecondMsg.class.getName()); + } // Use SecondMsg.newBuilder() to construct. - private SecondMsg(com.google.protobuf.GeneratedMessageV3.Builder builder) { + private SecondMsg(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); } private SecondMsg() { } - @Override - @SuppressWarnings({"unused"}) - protected Object newInstance( - UnusedPrivateParameter unused) { - return new SecondMsg(); - } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } - @Override - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_SecondMsg_fieldAccessorTable + return org.springframework.protobuf.OuterSample.internal_static_SecondMsg_fieldAccessorTable .ensureFieldAccessorsInitialized( - SecondMsg.class, Builder.class); + org.springframework.protobuf.SecondMsg.class, org.springframework.protobuf.SecondMsg.Builder.class); } private int bitField0_; @@ -45,7 +49,7 @@ protected Object newInstance( * optional int32 blah = 1; * @return Whether the blah field is set. */ - @Override + @java.lang.Override public boolean hasBlah() { return ((bitField0_ & 0x00000001) != 0); } @@ -53,13 +57,13 @@ public boolean hasBlah() { * optional int32 blah = 1; * @return The blah. */ - @Override + @java.lang.Override public int getBlah() { return blah_; } private byte memoizedIsInitialized = -1; - @Override + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -69,7 +73,7 @@ public final boolean isInitialized() { return true; } - @Override + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { if (((bitField0_ & 0x00000001) != 0)) { @@ -78,7 +82,7 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) getUnknownFields().writeTo(output); } - @Override + @java.lang.Override public int getSerializedSize() { int size = memoizedSize; if (size != -1) return size; @@ -93,15 +97,15 @@ public int getSerializedSize() { return size; } - @Override - public boolean equals(final Object obj) { + @java.lang.Override + public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } - if (!(obj instanceof SecondMsg)) { + if (!(obj instanceof org.springframework.protobuf.SecondMsg)) { return super.equals(obj); } - SecondMsg other = (SecondMsg) obj; + org.springframework.protobuf.SecondMsg other = (org.springframework.protobuf.SecondMsg) obj; if (hasBlah() != other.hasBlah()) return false; if (hasBlah()) { @@ -112,7 +116,7 @@ public boolean equals(final Object obj) { return true; } - @Override + @java.lang.Override public int hashCode() { if (memoizedHashCode != 0) { return memoizedHashCode; @@ -128,95 +132,95 @@ public int hashCode() { return hash; } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static SecondMsg parseFrom(byte[] data) + public static org.springframework.protobuf.SecondMsg parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static SecondMsg parseFrom(java.io.InputStream input) + public static org.springframework.protobuf.SecondMsg parseFrom(java.io.InputStream input) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input, extensionRegistry); } - public static SecondMsg parseDelimitedFrom(java.io.InputStream input) + public static org.springframework.protobuf.SecondMsg parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input); } - public static SecondMsg parseDelimitedFrom( + public static org.springframework.protobuf.SecondMsg parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input); } - public static SecondMsg parseFrom( + public static org.springframework.protobuf.SecondMsg parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 + return com.google.protobuf.GeneratedMessage .parseWithIOException(PARSER, input, extensionRegistry); } - @Override + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } public static Builder newBuilder() { return DEFAULT_INSTANCE.toBuilder(); } - public static Builder newBuilder(SecondMsg prototype) { + public static Builder newBuilder(org.springframework.protobuf.SecondMsg prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } - @Override + @java.lang.Override public Builder toBuilder() { return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessage.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } @@ -224,20 +228,20 @@ protected Builder newBuilderForType( * Protobuf type {@code SecondMsg} */ public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:SecondMsg) - SecondMsgOrBuilder { + org.springframework.protobuf.SecondMsgOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } - @Override - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_SecondMsg_fieldAccessorTable + return org.springframework.protobuf.OuterSample.internal_static_SecondMsg_fieldAccessorTable .ensureFieldAccessorsInitialized( - SecondMsg.class, Builder.class); + org.springframework.protobuf.SecondMsg.class, org.springframework.protobuf.SecondMsg.Builder.class); } // Construct using org.springframework.protobuf.SecondMsg.newBuilder() @@ -246,11 +250,11 @@ private Builder() { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessage.BuilderParent parent) { super(parent); } - @Override + @java.lang.Override public Builder clear() { super.clear(); bitField0_ = 0; @@ -258,35 +262,35 @@ public Builder clear() { return this; } - @Override + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } - @Override - public SecondMsg getDefaultInstanceForType() { - return SecondMsg.getDefaultInstance(); + @java.lang.Override + public org.springframework.protobuf.SecondMsg getDefaultInstanceForType() { + return org.springframework.protobuf.SecondMsg.getDefaultInstance(); } - @Override - public SecondMsg build() { - SecondMsg result = buildPartial(); + @java.lang.Override + public org.springframework.protobuf.SecondMsg build() { + org.springframework.protobuf.SecondMsg result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - @Override - public SecondMsg buildPartial() { - SecondMsg result = new SecondMsg(this); + @java.lang.Override + public org.springframework.protobuf.SecondMsg buildPartial() { + org.springframework.protobuf.SecondMsg result = new org.springframework.protobuf.SecondMsg(this); if (bitField0_ != 0) { buildPartial0(result); } onBuilt(); return result; } - private void buildPartial0(SecondMsg result) { + private void buildPartial0(org.springframework.protobuf.SecondMsg result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) != 0)) { @@ -296,50 +300,18 @@ private void buildPartial0(SecondMsg result) { result.bitField0_ |= to_bitField0_; } - @Override - public Builder clone() { - return super.clone(); - } - @Override - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return super.setField(field, value); - } - @Override - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return super.clearField(field); - } - @Override - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return super.clearOneof(oneof); - } - @Override - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return super.setRepeatedField(field, index, value); - } - @Override - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return super.addRepeatedField(field, value); - } - @Override + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof SecondMsg) { - return mergeFrom((SecondMsg)other); + if (other instanceof org.springframework.protobuf.SecondMsg) { + return mergeFrom((org.springframework.protobuf.SecondMsg)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(SecondMsg other) { - if (other == SecondMsg.getDefaultInstance()) return this; + public Builder mergeFrom(org.springframework.protobuf.SecondMsg other) { + if (other == org.springframework.protobuf.SecondMsg.getDefaultInstance()) return this; if (other.hasBlah()) { setBlah(other.getBlah()); } @@ -348,18 +320,18 @@ public Builder mergeFrom(SecondMsg other) { return this; } - @Override + @java.lang.Override public final boolean isInitialized() { return true; } - @Override + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { if (extensionRegistry == null) { - throw new NullPointerException(); + throw new java.lang.NullPointerException(); } try { boolean done = false; @@ -396,7 +368,7 @@ public Builder mergeFrom( * optional int32 blah = 1; * @return Whether the blah field is set. */ - @Override + @java.lang.Override public boolean hasBlah() { return ((bitField0_ & 0x00000001) != 0); } @@ -404,7 +376,7 @@ public boolean hasBlah() { * optional int32 blah = 1; * @return The blah. */ - @Override + @java.lang.Override public int getBlah() { return blah_; } @@ -430,35 +402,23 @@ public Builder clearBlah() { onChanged(); return this; } - @Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); - } - - @Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.mergeUnknownFields(unknownFields); - } - // @@protoc_insertion_point(builder_scope:SecondMsg) } // @@protoc_insertion_point(class_scope:SecondMsg) - private static final SecondMsg DEFAULT_INSTANCE; + private static final org.springframework.protobuf.SecondMsg DEFAULT_INSTANCE; static { - DEFAULT_INSTANCE = new SecondMsg(); + DEFAULT_INSTANCE = new org.springframework.protobuf.SecondMsg(); } - public static SecondMsg getDefaultInstance() { + public static org.springframework.protobuf.SecondMsg getDefaultInstance() { return DEFAULT_INSTANCE; } - @Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - @Override + @java.lang.Override public SecondMsg parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) @@ -482,13 +442,13 @@ public static com.google.protobuf.Parser parser() { return PARSER; } - @Override + @java.lang.Override public com.google.protobuf.Parser getParserForType() { return PARSER; } - @Override - public SecondMsg getDefaultInstanceForType() { + @java.lang.Override + public org.springframework.protobuf.SecondMsg getDefaultInstanceForType() { return DEFAULT_INSTANCE; } diff --git a/spring-web/src/test/java/org/springframework/protobuf/SecondMsgOrBuilder.java b/spring-web/src/test/java/org/springframework/protobuf/SecondMsgOrBuilder.java index fdfe62258e0b..6fa02af4caac 100644 --- a/spring-web/src/test/java/org/springframework/protobuf/SecondMsgOrBuilder.java +++ b/spring-web/src/test/java/org/springframework/protobuf/SecondMsgOrBuilder.java @@ -1,5 +1,7 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.protobuf; diff --git a/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java index 2fcdf9191990..7bca8a323fab 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java @@ -54,6 +54,11 @@ class ServerHttpObservationFilterTests { private ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry); + @Test + void filterShouldNotProcessAsyncDispatch() { + assertThat(this.filter.shouldNotFilterAsyncDispatch()).isTrue(); + } + @Test void filterShouldFillObservationContext() throws Exception { this.filter.doFilter(this.request, this.response, this.mockFilterChain); @@ -64,7 +69,7 @@ void filterShouldFillObservationContext() throws Exception { assertThat(context.getCarrier()).isEqualTo(this.request); assertThat(context.getResponse()).isEqualTo(this.response); assertThat(context.getPathPattern()).isNull(); - assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS"); + assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped(); } @Test @@ -121,6 +126,16 @@ void customFilterShouldCallScopeOpened() throws Exception { assertThat(this.response.getHeader("X-Trace-Id")).isEqualTo("badc0ff33"); } + @Test + void shouldCloseObservationAfterAsyncCompletion() throws Exception { + this.request.setAsyncSupported(true); + this.request.startAsync(); + this.filter.doFilter(this.request, this.response, this.mockFilterChain); + this.request.getAsyncContext().complete(); + + assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped(); + } + private TestObservationRegistryAssert.TestObservationRegistryAssertReturningObservationContextAssert assertThatHttpObservation() { return TestObservationRegistryAssert.assertThat(this.observationRegistry) .hasObservationWithNameEqualTo("http.server.requests").that(); 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 df5b778268fc..fe710e1695b1 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 @@ -17,12 +17,15 @@ package org.springframework.web.util; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; 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.FastByteArrayOutputStream; +import org.springframework.util.ReflectionUtils; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import static org.assertj.core.api.Assertions.assertThat; @@ -89,6 +92,19 @@ void cachedContentToStringWithLimit() throws Exception { assertThat(wrapper.getContentAsString()).isEqualTo(new String("Hel".getBytes(CHARSET), CHARSET)); } + @Test + void shouldNotAllocateMoreThanCacheLimit() throws Exception { + ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello World"), CONTENT_CACHE_LIMIT); + Field field = ReflectionUtils.findField(ContentCachingRequestWrapper.class, "cachedContent"); + ReflectionUtils.makeAccessible(field); + FastByteArrayOutputStream cachedContent = (FastByteArrayOutputStream) ReflectionUtils.getField(field, wrapper); + field = ReflectionUtils.findField(FastByteArrayOutputStream.class, "initialBlockSize"); + ReflectionUtils.makeAccessible(field); + int blockSize = (int) ReflectionUtils.getField(field, cachedContent); + assertThat(blockSize).isEqualTo(CONTENT_CACHE_LIMIT); + } + + @Test void cachedContentWithOverflow() throws Exception { ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper( diff --git a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java index 01199a582ecd..f5921e7ff2bf 100644 --- a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java @@ -17,6 +17,8 @@ package org.springframework.web.util; import java.net.URI; +import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -363,6 +365,11 @@ public URI getURI() { return UriComponentsBuilder.fromUriString("/").build().toUri(); } + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } + @Override public HttpHeaders getHeaders() { return new HttpHeaders(); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index 949b1b91706c..a5face72d171 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -196,6 +196,16 @@ void fromUriString() { assertThat(result.getQuery()).isNull(); assertThat(result.getFragment()).isEqualTo("baz"); + result = UriComponentsBuilder.fromUriString("mailto:user@example.com?subject=foo").build(); + assertThat(result.getScheme()).isEqualTo("mailto"); + assertThat(result.getUserInfo()).isNull(); + assertThat(result.getHost()).isNull(); + assertThat(result.getPort()).isEqualTo(-1); + assertThat(result.getSchemeSpecificPart()).isEqualTo("user@example.com?subject=foo"); + assertThat(result.getPath()).isNull(); + assertThat(result.getQuery()).isNull(); + assertThat(result.getFragment()).isNull(); + result = UriComponentsBuilder.fromUriString("docs/guide/collections/designfaq.html#28").build(); assertThat(result.getScheme()).isNull(); assertThat(result.getUserInfo()).isNull(); diff --git a/spring-web/src/test/java/org/springframework/web/util/UrlParserTests.java b/spring-web/src/test/java/org/springframework/web/util/UrlParserTests.java index ce8d884afff0..2dc1f51a259b 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UrlParserTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UrlParserTests.java @@ -61,8 +61,31 @@ private void testParse(String input, String scheme, @Nullable String host, @Null else { assertThat(result.port()).as("Port is not null").isNull(); } + assertThat(result.hasOpaquePath()).as("Result has opaque path").isFalse(); assertThat(result.path().toString()).as("Invalid path").isEqualTo(path); assertThat(result.query()).as("Invalid query").isEqualTo(query); assertThat(result.fragment()).as("Invalid fragment").isEqualTo(fragment); } + + @Test + void parseOpaque() { + testParseOpaque("mailto:user@example.com?subject=foo", "user@example.com", "subject=foo"); + + } + + void testParseOpaque(String input, String path, @Nullable String query) { + UrlParser.UrlRecord result = UrlParser.parse("mailto:user@example.com?subject=foo", EMPTY_URL_RECORD, null, null); + + + assertThat(result.scheme()).as("Invalid scheme").isEqualTo("mailto"); + assertThat(result.hasOpaquePath()).as("Result has no opaque path").isTrue(); + assertThat(result.path().toString()).as("Invalid path").isEqualTo(path); + if (query != null) { + assertThat(result.query()).as("Query is null").isNotNull(); + assertThat(result.query()).as("Invalid query").isEqualTo(query); + } + else { + assertThat(result.query()).as("Query is not null").isNull(); + } + } } diff --git a/spring-web/src/test/kotlin/org/springframework/web/server/CoWebExceptionHandlerTests.kt b/spring-web/src/test/kotlin/org/springframework/web/server/CoWebExceptionHandlerTests.kt new file mode 100644 index 000000000000..a85f56debb74 --- /dev/null +++ b/spring-web/src/test/kotlin/org/springframework/web/server/CoWebExceptionHandlerTests.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest +import org.springframework.web.testfixture.server.MockServerWebExchange +import reactor.test.StepVerifier + +class CoWebExceptionHandlerTest { + + @Test + fun handle() { + val exchange = MockServerWebExchange.from(MockServerHttpRequest.get("https://example.com")) + val ex = RuntimeException() + + val handler = MyCoWebExceptionHandler() + val result = handler.handle(exchange, ex) + + StepVerifier.create(result).verifyComplete() + + assertThat(exchange.attributes["foo"]).isEqualTo("bar") + } +} + +private class MyCoWebExceptionHandler : CoWebExceptionHandler() { + + override suspend fun coHandle(exchange: ServerWebExchange, ex: Throwable) { + exchange.attributes["foo"] = "bar" + } +} diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java index 826b193712ec..1cf6a68e5963 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; @@ -46,6 +48,9 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie private boolean executed = false; + @Nullable + Map attributes; + /** * Create a {@code MockClientHttpRequest} with {@link HttpMethod#GET GET} as @@ -115,6 +120,16 @@ public boolean isExecuted() { return this.executed; } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new ConcurrentHashMap<>(); + this.attributes = attributes; + } + return attributes; + } + /** * Set the {@link #isExecuted() executed} flag to {@code true} and return the * configured {@link #setResponse(ClientHttpResponse) response}. diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockCookie.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockCookie.java index 8183675a4b9b..ea0353fb2104 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockCookie.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockCookie.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,6 +98,29 @@ public String getSameSite() { return getAttribute(SAME_SITE); } + /** + * Set the "Partitioned" attribute for this cookie. + * @since 6.2 + * @see The Partitioned attribute spec + */ + public void setPartitioned(boolean partitioned) { + if (partitioned) { + setAttribute("Partitioned", ""); + } + else { + setAttribute("Partitioned", null); + } + } + + /** + * Return whether the "Partitioned" attribute is set for this cookie. + * @since 6.2 + * @see The Partitioned attribute spec + */ + public boolean isPartitioned() { + return getAttribute("Partitioned") != null; + } + /** * Factory method that parses the value of the supplied "Set-Cookie" header. * @param setCookieHeader the "Set-Cookie" value; never {@code null} or empty @@ -146,6 +169,9 @@ else if (StringUtils.startsWithIgnoreCase(attribute, SAME_SITE)) { else if (StringUtils.startsWithIgnoreCase(attribute, "Comment")) { cookie.setComment(extractAttributeValue(attribute, setCookieHeader)); } + else { + cookie.setAttribute(attribute, extractAttributeValue(attribute, setCookieHeader)); + } } return cookie; } @@ -176,6 +202,7 @@ public String toString() { .append("Comment", getComment()) .append("Secure", getSecure()) .append("HttpOnly", isHttpOnly()) + .append("Partitioned", isPartitioned()) .append(SAME_SITE, getSameSite()) .append("Max-Age", getMaxAge()) .append(EXPIRES, getAttribute(EXPIRES)) diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java index b48bd360229a..336121698b91 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java @@ -481,6 +481,9 @@ else if (expires != null) { if (cookie.isHttpOnly()) { buf.append("; HttpOnly"); } + if (cookie.getAttribute("Partitioned") != null) { + buf.append("; Partitioned"); + } if (cookie instanceof MockCookie mockCookie) { if (StringUtils.hasText(mockCookie.getSameSite())) { buf.append("; SameSite=").append(mockCookie.getSameSite()); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java index 24f41905d403..0a49c0897143 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java @@ -18,6 +18,8 @@ import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; @@ -65,6 +67,11 @@ public URI getURI() { public HttpHeaders getHeaders() { return HttpHeaders.EMPTY; } + + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } }; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java index cb77cac3fd2d..c11c5d51178b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java @@ -17,6 +17,7 @@ package org.springframework.web.reactive.function.client; import java.net.URI; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -149,6 +150,11 @@ public URI getURI() { return request.url(); } + @Override + public Map getAttributes() { + return request.attributes(); + } + @Override public HttpHeaders getHeaders() { return request.headers(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java index a97fbd902563..11c979525116 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java @@ -193,7 +193,7 @@ public ServerRequest.Builder attributes(Consumer> attributes @Override public ServerRequest build() { ServerHttpRequest serverHttpRequest = new BuiltServerHttpRequest(this.exchange.getRequest().getId(), - this.method, this.uri, this.contextPath, this.headers, this.cookies, this.body); + this.method, this.uri, this.contextPath, this.headers, this.cookies, this.body, this.attributes); ServerWebExchange exchange = new DelegatingServerWebExchange( serverHttpRequest, this.attributes, this.exchange, this.messageReaders); return new DefaultServerRequest(exchange, this.messageReaders); @@ -220,8 +220,10 @@ private static class BuiltServerHttpRequest implements ServerHttpRequest { private final Flux body; + private final Map attributes; + public BuiltServerHttpRequest(String id, HttpMethod method, URI uri, @Nullable String contextPath, - HttpHeaders headers, MultiValueMap cookies, Flux body) { + HttpHeaders headers, MultiValueMap cookies, Flux body, Map attributes) { this.id = id; this.method = method; @@ -231,6 +233,7 @@ public BuiltServerHttpRequest(String id, HttpMethod method, URI uri, @Nullable S this.cookies = unmodifiableCopy(cookies); this.queryParams = parseQueryParams(uri); this.body = body; + this.attributes = attributes; } private static MultiValueMap unmodifiableCopy(MultiValueMap original) { @@ -273,6 +276,11 @@ public URI getURI() { return this.uri; } + @Override + public Map getAttributes() { + return this.attributes; + } + @Override public RequestPath getPath() { return this.path; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java index 187fbcb97faa..a40bc25e6f44 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java @@ -19,6 +19,7 @@ import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.OptionalLong; @@ -324,6 +325,11 @@ public URI getURI() { public HttpHeaders getHeaders() { return HttpHeaders.EMPTY; } + + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } }; given(mockExchangeStrategies.messageReaders()).willReturn( diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java index f57c3ca4de23..5c0b1b9d1d41 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java @@ -1,155 +1,73 @@ -/* - * Copyright 2002-2022 the original author 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. - */ - // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.web.reactive.protobuf; /** * Protobuf type {@code Msg} */ -@SuppressWarnings("serial") -public final class Msg extends - com.google.protobuf.GeneratedMessage - implements MsgOrBuilder { +public final class Msg extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:Msg) + MsgOrBuilder { +private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + Msg.class.getName()); + } // Use Msg.newBuilder() to construct. private Msg(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private Msg(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Msg defaultInstance; - public static Msg getDefaultInstance() { - return defaultInstance; - } - - public Msg getDefaultInstanceForType() { - return defaultInstance; + private Msg() { + foo_ = ""; } - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private Msg( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - @SuppressWarnings("unused") - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - bitField0_ |= 0x00000001; - foo_ = input.readBytes(); - break; - } - case 18: { - SecondMsg.Builder subBuilder = null; - if (((bitField0_ & 0x00000002) == 0x00000002)) { - subBuilder = blah_.toBuilder(); - } - blah_ = input.readMessage(SecondMsg.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(blah_); - blah_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000002; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_Msg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_Msg_fieldAccessorTable + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_Msg_fieldAccessorTable .ensureFieldAccessorsInitialized( - Msg.class, Msg.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Msg parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Msg(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + org.springframework.web.reactive.protobuf.Msg.class, org.springframework.web.reactive.protobuf.Msg.Builder.class); } private int bitField0_; - // optional string foo = 1; public static final int FOO_FIELD_NUMBER = 1; - private Object foo_; + @SuppressWarnings("serial") + private volatile java.lang.Object foo_ = ""; /** * optional string foo = 1; + * @return Whether the foo field is set. */ + @java.lang.Override public boolean hasFoo() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional string foo = 1; + * @return The foo. */ - public String getFoo() { - Object ref = foo_; - if (ref instanceof String) { - return (String) ref; + @java.lang.Override + public java.lang.String getFoo() { + java.lang.Object ref = foo_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { foo_ = s; } @@ -158,14 +76,16 @@ public String getFoo() { } /** * optional string foo = 1; + * @return The bytes for foo. */ + @java.lang.Override public com.google.protobuf.ByteString getFooBytes() { - Object ref = foo_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = + java.lang.Object ref = foo_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); foo_ = b; return b; } else { @@ -173,140 +93,204 @@ public String getFoo() { } } - // optional .SecondMsg blah = 2; public static final int BLAH_FIELD_NUMBER = 2; - private SecondMsg blah_; + private org.springframework.web.reactive.protobuf.SecondMsg blah_; /** * optional .SecondMsg blah = 2; + * @return Whether the blah field is set. */ + @java.lang.Override public boolean hasBlah() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000002) != 0); } /** * optional .SecondMsg blah = 2; + * @return The blah. */ - public SecondMsg getBlah() { - return blah_; + @java.lang.Override + public org.springframework.web.reactive.protobuf.SecondMsg getBlah() { + return blah_ == null ? org.springframework.web.reactive.protobuf.SecondMsg.getDefaultInstance() : blah_; } /** * optional .SecondMsg blah = 2; */ - public SecondMsgOrBuilder getBlahOrBuilder() { - return blah_; + @java.lang.Override + public org.springframework.web.reactive.protobuf.SecondMsgOrBuilder getBlahOrBuilder() { + return blah_ == null ? org.springframework.web.reactive.protobuf.SecondMsg.getDefaultInstance() : blah_; } - private void initFields() { - foo_ = ""; - blah_ = SecondMsg.getDefaultInstance(); - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; memoizedIsInitialized = 1; return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getFooBytes()); + if (((bitField0_ & 0x00000001) != 0)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, foo_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeMessage(2, blah_); + if (((bitField0_ & 0x00000002) != 0)) { + output.writeMessage(2, getBlah()); } getUnknownFields().writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, getFooBytes()); + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, foo_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000002) != 0)) { size += com.google.protobuf.CodedOutputStream - .computeMessageSize(2, blah_); + .computeMessageSize(2, getBlah()); } size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.springframework.web.reactive.protobuf.Msg)) { + return super.equals(obj); + } + org.springframework.web.reactive.protobuf.Msg other = (org.springframework.web.reactive.protobuf.Msg) obj; + + if (hasFoo() != other.hasFoo()) return false; + if (hasFoo()) { + if (!getFoo() + .equals(other.getFoo())) return false; + } + if (hasBlah() != other.hasBlah()) return false; + if (hasBlah()) { + if (!getBlah() + .equals(other.getBlah())) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasFoo()) { + hash = (37 * hash) + FOO_FIELD_NUMBER; + hash = (53 * hash) + getFoo().hashCode(); + } + if (hasBlah()) { + hash = (37 * hash) + BLAH_FIELD_NUMBER; + hash = (53 * hash) + getBlah().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; } - public static Msg parseFrom( + public static org.springframework.web.reactive.protobuf.Msg parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.springframework.web.reactive.protobuf.Msg parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.springframework.web.reactive.protobuf.Msg parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Msg parseFrom( + public static org.springframework.web.reactive.protobuf.Msg parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Msg parseFrom(byte[] data) + public static org.springframework.web.reactive.protobuf.Msg parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Msg parseFrom( + public static org.springframework.web.reactive.protobuf.Msg parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Msg parseFrom(java.io.InputStream input) + public static org.springframework.web.reactive.protobuf.Msg parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static Msg parseFrom( + public static org.springframework.web.reactive.protobuf.Msg parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Msg parseDelimitedFrom(java.io.InputStream input) + + public static org.springframework.web.reactive.protobuf.Msg parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); } - public static Msg parseDelimitedFrom( + + public static org.springframework.web.reactive.protobuf.Msg parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static Msg parseFrom( + public static org.springframework.web.reactive.protobuf.Msg parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static Msg parseFrom( + public static org.springframework.web.reactive.protobuf.Msg parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(Msg prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.springframework.web.reactive.protobuf.Msg prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( com.google.protobuf.GeneratedMessage.BuilderParent parent) { Builder builder = new Builder(parent); @@ -316,21 +300,23 @@ protected Builder newBuilderForType( * Protobuf type {@code Msg} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements MsgOrBuilder { + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:Msg) + org.springframework.web.reactive.protobuf.MsgOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_Msg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_Msg_fieldAccessorTable + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_Msg_fieldAccessorTable .ensureFieldAccessorsInitialized( - Msg.class, Msg.Builder.class); + org.springframework.web.reactive.protobuf.Msg.class, org.springframework.web.reactive.protobuf.Msg.Builder.class); } - // Construct using org.springframework.protobuf.Msg.newBuilder() + // Construct using org.springframework.web.reactive.protobuf.Msg.newBuilder() private Builder() { maybeForceBuilderInitialization(); } @@ -341,147 +327,180 @@ private Builder( maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessage + .alwaysUseFieldBuilders) { getBlahFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); + bitField0_ = 0; foo_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - if (blahBuilder_ == null) { - blah_ = SecondMsg.getDefaultInstance(); - } else { - blahBuilder_.clear(); + blah_ = null; + if (blahBuilder_ != null) { + blahBuilder_.dispose(); + blahBuilder_ = null; } - bitField0_ = (bitField0_ & ~0x00000002); return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return OuterSample.internal_static_Msg_descriptor; + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_Msg_descriptor; } - public Msg getDefaultInstanceForType() { - return Msg.getDefaultInstance(); + @java.lang.Override + public org.springframework.web.reactive.protobuf.Msg getDefaultInstanceForType() { + return org.springframework.web.reactive.protobuf.Msg.getDefaultInstance(); } - public Msg build() { - Msg result = buildPartial(); + @java.lang.Override + public org.springframework.web.reactive.protobuf.Msg build() { + org.springframework.web.reactive.protobuf.Msg result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public Msg buildPartial() { - Msg result = new Msg(this); + @java.lang.Override + public org.springframework.web.reactive.protobuf.Msg buildPartial() { + org.springframework.web.reactive.protobuf.Msg result = new org.springframework.web.reactive.protobuf.Msg(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.springframework.web.reactive.protobuf.Msg result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + if (((from_bitField0_ & 0x00000001) != 0)) { + result.foo_ = foo_; to_bitField0_ |= 0x00000001; } - result.foo_ = foo_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + if (((from_bitField0_ & 0x00000002) != 0)) { + result.blah_ = blahBuilder_ == null + ? blah_ + : blahBuilder_.build(); to_bitField0_ |= 0x00000002; } - if (blahBuilder_ == null) { - result.blah_ = blah_; - } else { - result.blah_ = blahBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; + result.bitField0_ |= to_bitField0_; } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Msg) { - return mergeFrom((Msg)other); + if (other instanceof org.springframework.web.reactive.protobuf.Msg) { + return mergeFrom((org.springframework.web.reactive.protobuf.Msg)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(Msg other) { - if (other == Msg.getDefaultInstance()) return this; + public Builder mergeFrom(org.springframework.web.reactive.protobuf.Msg other) { + if (other == org.springframework.web.reactive.protobuf.Msg.getDefaultInstance()) return this; if (other.hasFoo()) { - bitField0_ |= 0x00000001; foo_ = other.foo_; + bitField0_ |= 0x00000001; onChanged(); } if (other.hasBlah()) { mergeBlah(other.getBlah()); } this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - Msg parsedMessage = null; + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + foo_ = input.readBytes(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + input.readMessage( + getBlahFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000002; + break; + } // case 18 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Msg) e.getUnfinishedMessage(); - throw e; + throw e.unwrapIOException(); } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } + onChanged(); + } // finally return this; } private int bitField0_; - // optional string foo = 1; - private Object foo_ = ""; + private java.lang.Object foo_ = ""; /** * optional string foo = 1; + * @return Whether the foo field is set. */ public boolean hasFoo() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional string foo = 1; + * @return The foo. */ - public String getFoo() { - Object ref = foo_; - if (!(ref instanceof String)) { - String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - foo_ = s; + public java.lang.String getFoo() { + java.lang.Object ref = foo_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + foo_ = s; + } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** * optional string foo = 1; + * @return The bytes for foo. */ public com.google.protobuf.ByteString getFooBytes() { - Object ref = foo_; + java.lang.Object ref = foo_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); foo_ = b; return b; } else { @@ -490,57 +509,58 @@ public String getFoo() { } /** * optional string foo = 1; + * @param value The foo to set. + * @return This builder for chaining. */ public Builder setFoo( - String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } foo_ = value; + bitField0_ |= 0x00000001; onChanged(); return this; } /** * optional string foo = 1; + * @return This builder for chaining. */ public Builder clearFoo() { - bitField0_ = (bitField0_ & ~0x00000001); foo_ = getDefaultInstance().getFoo(); + bitField0_ = (bitField0_ & ~0x00000001); onChanged(); return this; } /** * optional string foo = 1; + * @param value The bytes for foo to set. + * @return This builder for chaining. */ public Builder setFooBytes( com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; + if (value == null) { throw new NullPointerException(); } foo_ = value; + bitField0_ |= 0x00000001; onChanged(); return this; } - // optional .SecondMsg blah = 2; - private SecondMsg blah_ = SecondMsg.getDefaultInstance(); + private org.springframework.web.reactive.protobuf.SecondMsg blah_; private com.google.protobuf.SingleFieldBuilder< - SecondMsg, SecondMsg.Builder, - SecondMsgOrBuilder> blahBuilder_; + org.springframework.web.reactive.protobuf.SecondMsg, org.springframework.web.reactive.protobuf.SecondMsg.Builder, org.springframework.web.reactive.protobuf.SecondMsgOrBuilder> blahBuilder_; /** * optional .SecondMsg blah = 2; + * @return Whether the blah field is set. */ public boolean hasBlah() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000002) != 0); } /** * optional .SecondMsg blah = 2; + * @return The blah. */ - public SecondMsg getBlah() { + public org.springframework.web.reactive.protobuf.SecondMsg getBlah() { if (blahBuilder_ == null) { - return blah_; + return blah_ == null ? org.springframework.web.reactive.protobuf.SecondMsg.getDefaultInstance() : blah_; } else { return blahBuilder_.getMessage(); } @@ -548,69 +568,71 @@ public SecondMsg getBlah() { /** * optional .SecondMsg blah = 2; */ - public Builder setBlah(SecondMsg value) { + public Builder setBlah(org.springframework.web.reactive.protobuf.SecondMsg value) { if (blahBuilder_ == null) { if (value == null) { throw new NullPointerException(); } blah_ = value; - onChanged(); } else { blahBuilder_.setMessage(value); } bitField0_ |= 0x00000002; + onChanged(); return this; } /** * optional .SecondMsg blah = 2; */ public Builder setBlah( - SecondMsg.Builder builderForValue) { + org.springframework.web.reactive.protobuf.SecondMsg.Builder builderForValue) { if (blahBuilder_ == null) { blah_ = builderForValue.build(); - onChanged(); } else { blahBuilder_.setMessage(builderForValue.build()); } bitField0_ |= 0x00000002; + onChanged(); return this; } /** * optional .SecondMsg blah = 2; */ - public Builder mergeBlah(SecondMsg value) { + public Builder mergeBlah(org.springframework.web.reactive.protobuf.SecondMsg value) { if (blahBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002) && - blah_ != SecondMsg.getDefaultInstance()) { - blah_ = - SecondMsg.newBuilder(blah_).mergeFrom(value).buildPartial(); + if (((bitField0_ & 0x00000002) != 0) && + blah_ != null && + blah_ != org.springframework.web.reactive.protobuf.SecondMsg.getDefaultInstance()) { + getBlahBuilder().mergeFrom(value); } else { blah_ = value; } - onChanged(); } else { blahBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000002; + if (blah_ != null) { + bitField0_ |= 0x00000002; + onChanged(); + } return this; } /** * optional .SecondMsg blah = 2; */ public Builder clearBlah() { - if (blahBuilder_ == null) { - blah_ = SecondMsg.getDefaultInstance(); - onChanged(); - } else { - blahBuilder_.clear(); - } bitField0_ = (bitField0_ & ~0x00000002); + blah_ = null; + if (blahBuilder_ != null) { + blahBuilder_.dispose(); + blahBuilder_ = null; + } + onChanged(); return this; } /** * optional .SecondMsg blah = 2; */ - public SecondMsg.Builder getBlahBuilder() { + public org.springframework.web.reactive.protobuf.SecondMsg.Builder getBlahBuilder() { bitField0_ |= 0x00000002; onChanged(); return getBlahFieldBuilder().getBuilder(); @@ -618,25 +640,26 @@ public SecondMsg.Builder getBlahBuilder() { /** * optional .SecondMsg blah = 2; */ - public SecondMsgOrBuilder getBlahOrBuilder() { + public org.springframework.web.reactive.protobuf.SecondMsgOrBuilder getBlahOrBuilder() { if (blahBuilder_ != null) { return blahBuilder_.getMessageOrBuilder(); } else { - return blah_; + return blah_ == null ? + org.springframework.web.reactive.protobuf.SecondMsg.getDefaultInstance() : blah_; } } /** * optional .SecondMsg blah = 2; */ private com.google.protobuf.SingleFieldBuilder< - SecondMsg, SecondMsg.Builder, - SecondMsgOrBuilder> + org.springframework.web.reactive.protobuf.SecondMsg, org.springframework.web.reactive.protobuf.SecondMsg.Builder, org.springframework.web.reactive.protobuf.SecondMsgOrBuilder> getBlahFieldBuilder() { if (blahBuilder_ == null) { - blahBuilder_ = new com.google.protobuf.SingleFieldBuilder<>( - blah_, - getParentForChildren(), - isClean()); + blahBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.springframework.web.reactive.protobuf.SecondMsg, org.springframework.web.reactive.protobuf.SecondMsg.Builder, org.springframework.web.reactive.protobuf.SecondMsgOrBuilder>( + getBlah(), + getParentForChildren(), + isClean()); blah_ = null; } return blahBuilder_; @@ -645,11 +668,51 @@ public SecondMsgOrBuilder getBlahOrBuilder() { // @@protoc_insertion_point(builder_scope:Msg) } + // @@protoc_insertion_point(class_scope:Msg) + private static final org.springframework.web.reactive.protobuf.Msg DEFAULT_INSTANCE; static { - defaultInstance = new Msg(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new org.springframework.web.reactive.protobuf.Msg(); + } + + public static org.springframework.web.reactive.protobuf.Msg getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Msg parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.springframework.web.reactive.protobuf.Msg getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:Msg) } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/MsgOrBuilder.java b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/MsgOrBuilder.java index 83466057675f..0bb57f720ec4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/MsgOrBuilder.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/MsgOrBuilder.java @@ -1,37 +1,43 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.web.reactive.protobuf; -public interface MsgOrBuilder - extends com.google.protobuf.MessageOrBuilder { +public interface MsgOrBuilder extends + // @@protoc_insertion_point(interface_extends:Msg) + com.google.protobuf.MessageOrBuilder { - // optional string foo = 1; /** * optional string foo = 1; + * @return Whether the foo field is set. */ boolean hasFoo(); /** * optional string foo = 1; + * @return The foo. */ - String getFoo(); + java.lang.String getFoo(); /** * optional string foo = 1; + * @return The bytes for foo. */ com.google.protobuf.ByteString getFooBytes(); - // optional .SecondMsg blah = 2; /** * optional .SecondMsg blah = 2; + * @return Whether the blah field is set. */ boolean hasBlah(); /** * optional .SecondMsg blah = 2; + * @return The blah. */ - SecondMsg getBlah(); + org.springframework.web.reactive.protobuf.SecondMsg getBlah(); /** * optional .SecondMsg blah = 2; */ - SecondMsgOrBuilder getBlahOrBuilder(); + org.springframework.web.reactive.protobuf.SecondMsgOrBuilder getBlahOrBuilder(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/OuterSample.java b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/OuterSample.java index 5c6532382f80..be316c54b2e2 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/OuterSample.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/OuterSample.java @@ -1,38 +1,38 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.web.reactive.protobuf; -@SuppressWarnings("deprecation") -public class OuterSample { +public final class OuterSample { private OuterSample() {} + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + OuterSample.class.getName()); + } + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + public static void registerAllExtensions( com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); } - static com.google.protobuf.Descriptors.Descriptor + static final com.google.protobuf.Descriptors.Descriptor internal_static_Msg_descriptor; - static + static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_Msg_fieldAccessorTable; - static com.google.protobuf.Descriptors.Descriptor + static final com.google.protobuf.Descriptors.Descriptor internal_static_SecondMsg_descriptor; - static + static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_SecondMsg_fieldAccessorTable; @@ -40,39 +40,32 @@ public static void registerAllExtensions( getDescriptor() { return descriptor; } - private static com.google.protobuf.Descriptors.FileDescriptor + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; static { - String[] descriptorData = { + java.lang.String[] descriptorData = { "\n\014sample.proto\",\n\003Msg\022\013\n\003foo\030\001 \001(\t\022\030\n\004bl" + "ah\030\002 \001(\0132\n.SecondMsg\"\031\n\tSecondMsg\022\014\n\004bla" + - "h\030\001 \001(\005B-\n\034org.springframework.protobufB" + - "\013OuterSampleP\001" + "h\030\001 \001(\005B:\n)org.springframework.web.react" + + "ive.protobufB\013OuterSampleP\001" }; - com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = - new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { - public com.google.protobuf.ExtensionRegistry assignDescriptors( - com.google.protobuf.Descriptors.FileDescriptor root) { - descriptor = root; - internal_static_Msg_descriptor = - getDescriptor().getMessageTypes().get(0); - internal_static_Msg_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_Msg_descriptor, - new String[] { "Foo", "Blah", }); - internal_static_SecondMsg_descriptor = - getDescriptor().getMessageTypes().get(1); - internal_static_SecondMsg_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_SecondMsg_descriptor, - new String[] { "Blah", }); - return null; - } - }; - com.google.protobuf.Descriptors.FileDescriptor + descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] { - }, assigner); + }); + internal_static_Msg_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_Msg_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_Msg_descriptor, + new java.lang.String[] { "Foo", "Blah", }); + internal_static_SecondMsg_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_SecondMsg_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_SecondMsg_descriptor, + new java.lang.String[] { "Blah", }); + descriptor.resolveAllFeaturesImmutable(); } // @@protoc_insertion_point(outer_class_scope) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsg.java b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsg.java index 480798a0a248..a06b72b310fb 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsg.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsg.java @@ -1,226 +1,224 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.web.reactive.protobuf; /** * Protobuf type {@code SecondMsg} */ -@SuppressWarnings("serial") -public final class SecondMsg extends - com.google.protobuf.GeneratedMessage - implements SecondMsgOrBuilder { +public final class SecondMsg extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:SecondMsg) + SecondMsgOrBuilder { +private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 27, + /* patch= */ 0, + /* suffix= */ "", + SecondMsg.class.getName()); + } // Use SecondMsg.newBuilder() to construct. private SecondMsg(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private SecondMsg(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final SecondMsg defaultInstance; - public static SecondMsg getDefaultInstance() { - return defaultInstance; } - - public SecondMsg getDefaultInstanceForType() { - return defaultInstance; + private SecondMsg() { } - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private SecondMsg( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - @SuppressWarnings("unused") - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - blah_ = input.readInt32(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_SecondMsg_fieldAccessorTable + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_SecondMsg_fieldAccessorTable .ensureFieldAccessorsInitialized( - SecondMsg.class, SecondMsg.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public SecondMsg parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new SecondMsg(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + org.springframework.web.reactive.protobuf.SecondMsg.class, org.springframework.web.reactive.protobuf.SecondMsg.Builder.class); } private int bitField0_; - // optional int32 blah = 1; public static final int BLAH_FIELD_NUMBER = 1; - private int blah_; + private int blah_ = 0; /** * optional int32 blah = 1; + * @return Whether the blah field is set. */ + @java.lang.Override public boolean hasBlah() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional int32 blah = 1; + * @return The blah. */ + @java.lang.Override public int getBlah() { return blah_; } - private void initFields() { - blah_ = 0; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; memoizedIsInitialized = 1; return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (((bitField0_ & 0x00000001) != 0)) { output.writeInt32(1, blah_); } getUnknownFields().writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (((bitField0_ & 0x00000001) != 0)) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(1, blah_); } size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.springframework.web.reactive.protobuf.SecondMsg)) { + return super.equals(obj); + } + org.springframework.web.reactive.protobuf.SecondMsg other = (org.springframework.web.reactive.protobuf.SecondMsg) obj; + + if (hasBlah() != other.hasBlah()) return false; + if (hasBlah()) { + if (getBlah() + != other.getBlah()) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasBlah()) { + hash = (37 * hash) + BLAH_FIELD_NUMBER; + hash = (53 * hash) + getBlah(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; } - public static SecondMsg parseFrom( + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static SecondMsg parseFrom( + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static SecondMsg parseFrom(byte[] data) + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static SecondMsg parseFrom( + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static SecondMsg parseFrom(java.io.InputStream input) + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static SecondMsg parseFrom( + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static SecondMsg parseDelimitedFrom(java.io.InputStream input) + + public static org.springframework.web.reactive.protobuf.SecondMsg parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); } - public static SecondMsg parseDelimitedFrom( + + public static org.springframework.web.reactive.protobuf.SecondMsg parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static SecondMsg parseFrom( + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); } - public static SecondMsg parseFrom( + public static org.springframework.web.reactive.protobuf.SecondMsg parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(SecondMsg prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.springframework.web.reactive.protobuf.SecondMsg prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( com.google.protobuf.GeneratedMessage.BuilderParent parent) { Builder builder = new Builder(parent); @@ -230,145 +228,173 @@ protected Builder newBuilderForType( * Protobuf type {@code SecondMsg} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements SecondMsgOrBuilder { + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:SecondMsg) + org.springframework.web.reactive.protobuf.SecondMsgOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } + @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return OuterSample.internal_static_SecondMsg_fieldAccessorTable + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_SecondMsg_fieldAccessorTable .ensureFieldAccessorsInitialized( - SecondMsg.class, SecondMsg.Builder.class); + org.springframework.web.reactive.protobuf.SecondMsg.class, org.springframework.web.reactive.protobuf.SecondMsg.Builder.class); } - // Construct using org.springframework.protobuf.SecondMsg.newBuilder() + // Construct using org.springframework.web.reactive.protobuf.SecondMsg.newBuilder() private Builder() { - maybeForceBuilderInitialization(); + } private Builder( com.google.protobuf.GeneratedMessage.BuilderParent parent) { super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } + } + @java.lang.Override public Builder clear() { super.clear(); + bitField0_ = 0; blah_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return OuterSample.internal_static_SecondMsg_descriptor; + return org.springframework.web.reactive.protobuf.OuterSample.internal_static_SecondMsg_descriptor; } - public SecondMsg getDefaultInstanceForType() { - return SecondMsg.getDefaultInstance(); + @java.lang.Override + public org.springframework.web.reactive.protobuf.SecondMsg getDefaultInstanceForType() { + return org.springframework.web.reactive.protobuf.SecondMsg.getDefaultInstance(); } - public SecondMsg build() { - SecondMsg result = buildPartial(); + @java.lang.Override + public org.springframework.web.reactive.protobuf.SecondMsg build() { + org.springframework.web.reactive.protobuf.SecondMsg result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public SecondMsg buildPartial() { - SecondMsg result = new SecondMsg(this); + @java.lang.Override + public org.springframework.web.reactive.protobuf.SecondMsg buildPartial() { + org.springframework.web.reactive.protobuf.SecondMsg result = new org.springframework.web.reactive.protobuf.SecondMsg(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.springframework.web.reactive.protobuf.SecondMsg result) { int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + if (((from_bitField0_ & 0x00000001) != 0)) { + result.blah_ = blah_; to_bitField0_ |= 0x00000001; } - result.blah_ = blah_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; + result.bitField0_ |= to_bitField0_; } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof SecondMsg) { - return mergeFrom((SecondMsg)other); + if (other instanceof org.springframework.web.reactive.protobuf.SecondMsg) { + return mergeFrom((org.springframework.web.reactive.protobuf.SecondMsg)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(SecondMsg other) { - if (other == SecondMsg.getDefaultInstance()) return this; + public Builder mergeFrom(org.springframework.web.reactive.protobuf.SecondMsg other) { + if (other == org.springframework.web.reactive.protobuf.SecondMsg.getDefaultInstance()) return this; if (other.hasBlah()) { setBlah(other.getBlah()); } this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - SecondMsg parsedMessage = null; + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + blah_ = input.readInt32(); + bitField0_ |= 0x00000001; + break; + } // case 8 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (SecondMsg) e.getUnfinishedMessage(); - throw e; + throw e.unwrapIOException(); } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } + onChanged(); + } // finally return this; } private int bitField0_; - // optional int32 blah = 1; private int blah_ ; /** * optional int32 blah = 1; + * @return Whether the blah field is set. */ + @java.lang.Override public boolean hasBlah() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000001) != 0); } /** * optional int32 blah = 1; + * @return The blah. */ + @java.lang.Override public int getBlah() { return blah_; } /** * optional int32 blah = 1; + * @param value The blah to set. + * @return This builder for chaining. */ public Builder setBlah(int value) { - bitField0_ |= 0x00000001; + blah_ = value; + bitField0_ |= 0x00000001; onChanged(); return this; } /** * optional int32 blah = 1; + * @return This builder for chaining. */ public Builder clearBlah() { bitField0_ = (bitField0_ & ~0x00000001); @@ -380,11 +406,51 @@ public Builder clearBlah() { // @@protoc_insertion_point(builder_scope:SecondMsg) } + // @@protoc_insertion_point(class_scope:SecondMsg) + private static final org.springframework.web.reactive.protobuf.SecondMsg DEFAULT_INSTANCE; static { - defaultInstance = new SecondMsg(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new org.springframework.web.reactive.protobuf.SecondMsg(); + } + + public static org.springframework.web.reactive.protobuf.SecondMsg getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public SecondMsg parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.springframework.web.reactive.protobuf.SecondMsg getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:SecondMsg) } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsgOrBuilder.java b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsgOrBuilder.java index 29117f85b952..ff4dbb40898d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsgOrBuilder.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/SecondMsgOrBuilder.java @@ -1,18 +1,22 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE // source: sample.proto +// Protobuf Java Version: 4.27.0 package org.springframework.web.reactive.protobuf; -public interface SecondMsgOrBuilder - extends com.google.protobuf.MessageOrBuilder { +public interface SecondMsgOrBuilder extends + // @@protoc_insertion_point(interface_extends:SecondMsg) + com.google.protobuf.MessageOrBuilder { - // optional int32 blah = 1; /** * optional int32 blah = 1; + * @return Whether the blah field is set. */ boolean hasBlah(); /** * optional int32 blah = 1; + * @return The blah. */ int getBlah(); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java index a376f7b6dad3..c9ef0b85de20 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.springframework.lang.Nullable; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.function.HandlerFunction; /** * Abstract base class for @@ -34,9 +35,9 @@ public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver { /** - * Checks if the handler is a {@link HandlerMethod} and then delegates to the - * base class implementation of {@code #shouldApplyTo(HttpServletRequest, Object)} - * passing the bean of the {@code HandlerMethod}. Otherwise returns {@code false}. + * Checks if the handler is a {@link HandlerMethod} or a {@link HandlerFunction} + * and then delegates to the base class implementation of {@code #shouldApplyTo(HttpServletRequest, Object)} + * passing the bean of the {@code HandlerMethod}. Otherwise, returns {@code false}. */ @Override protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { @@ -47,6 +48,9 @@ else if (handler instanceof HandlerMethod handlerMethod) { handler = handlerMethod.getBean(); return super.shouldApplyTo(request, handler); } + else if (handler instanceof HandlerFunction handlerFunction) { + return super.shouldApplyTo(request, handlerFunction); + } else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) { return super.shouldApplyTo(request, handler); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java index 917294c997eb..d0cb25855b01 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java @@ -60,6 +60,8 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.function.HandlerFunction; +import org.springframework.web.servlet.function.ServerResponse; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; @@ -82,6 +84,8 @@ @SuppressWarnings("unused") class ExceptionHandlerExceptionResolverTests { + //TODO + private static int DEFAULT_RESOLVER_COUNT; private static int DEFAULT_HANDLER_COUNT; @@ -255,6 +259,19 @@ void resolveExceptionGlobalHandler() throws Exception { assertExceptionHandledAsBody(mav, "AnotherTestExceptionResolver: IllegalAccessException"); } + @Test + void resolveExceptionGlobalHandlerForHandlerFunction() throws Exception { + loadConfiguration(MyConfig.class); + + IllegalAccessException ex = new IllegalAccessException(); + HandlerFunction handlerFunction = req -> { + throw new IllegalAccessException(); + }; + ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerFunction, ex); + + assertExceptionHandledAsBody(mav, "AnotherTestExceptionResolver: IllegalAccessException"); + } + @Test void resolveExceptionGlobalHandlerOrdered() throws Exception { loadConfiguration(MyConfig.class); diff --git a/src/eclipse/org.eclipse.jdt.core.prefs b/src/eclipse/org.eclipse.jdt.core.prefs index 0d363b81ca8b..0393b6403381 100644 --- a/src/eclipse/org.eclipse.jdt.core.prefs +++ b/src/eclipse/org.eclipse.jdt.core.prefs @@ -87,7 +87,6 @@ org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=ignore org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore -org.eclipse.jdt.core.compiler.problem.terminalDeprecation=ignore org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning