Compare commits

..

1 Commits

Author SHA1 Message Date
Stéphane Nicoll 34764252dc Release v6.1.12 2024-08-14 12:38:39 +02:00
79 changed files with 291 additions and 931 deletions
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.18.1");
checkstyle.setToolVersion("10.17.0");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+1 -1
View File
@@ -13,7 +13,7 @@ content:
- url: https://github.com/spring-projects/spring-framework
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
branches: ['main', '{6..9}.+({1..9}).x']
branches: ['main', '{6..9}.+({0..9}).x']
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
start_path: framework-docs
asciidoc:
@@ -1,7 +1,7 @@
[[context-introduction]]
= Additional Capabilities of the `ApplicationContext`
As discussed in the xref:core/beans/introduction.adoc[chapter introduction], the `org.springframework.beans.factory`
As discussed in the xref:web/webmvc-view/mvc-xslt.adoc#mvc-view-xslt-beandefs[chapter introduction], the `org.springframework.beans.factory`
package provides basic functionality for managing and manipulating beans, including in a
programmatic way. The `org.springframework.context` package adds the
{spring-framework-api}/context/ApplicationContext.html[`ApplicationContext`]
@@ -644,7 +644,7 @@ Each `SpEL` expression evaluates against a dedicated context. The following tabl
items made available to the context so that you can use them for conditional event processing:
[[context-functionality-events-annotation-tbl]]
.Event metadata available in SpEL expressions
.Event SpEL available metadata
|===
| Name| Location| Description| Example
@@ -660,8 +660,8 @@ items made available to the context so that you can use them for conditional eve
| __Argument name__
| evaluation context
| The name of a particular method argument. If the names are not available
(for example, because the code was compiled without the `-parameters` flag), individual
| The name of any of the method arguments. If, for some reason, the names are not available
(for example, because there is no debug information in the compiled byte code), individual
arguments are also available using the `#a<#arg>` syntax where `<#arg>` stands for the
argument index (starting from 0).
| `#blEvent` or `#a0` (you can also use `#p0` or `#p<#arg>` parameter notation as an alias)
@@ -159,12 +159,10 @@ Kotlin::
----
======
[discrete]
[[beans-factory-ctor-arguments-type]]
==== Constructor argument type matching
.[[beans-factory-ctor-arguments-type]]Constructor argument type matching
--
In the preceding scenario, the container can use type matching with simple types if
you explicitly specify the type of the constructor argument via the `type` attribute,
you explicitly specify the type of the constructor argument by using the `type` attribute,
as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -174,11 +172,10 @@ as the following example shows:
<constructor-arg type="java.lang.String" value="42"/>
</bean>
----
--
[discrete]
[[beans-factory-ctor-arguments-index]]
==== Constructor argument index
.[[beans-factory-ctor-arguments-index]]Constructor argument index
--
You can use the `index` attribute to specify explicitly the index of constructor arguments,
as the following example shows:
@@ -194,11 +191,10 @@ In addition to resolving the ambiguity of multiple simple values, specifying an
resolves ambiguity where a constructor has two arguments of the same type.
NOTE: The index is 0-based.
--
[discrete]
[[beans-factory-ctor-arguments-name]]
==== Constructor argument name
.[[beans-factory-ctor-arguments-name]]Constructor argument name
--
You can also use the constructor parameter name for value disambiguation, as the following
example shows:
@@ -211,8 +207,8 @@ example shows:
----
Keep in mind that, to make this work out of the box, your code must be compiled with the
`-parameters` flag enabled so that Spring can look up the parameter name from the constructor.
If you cannot or do not want to compile your code with the `-parameters` flag, you can use the
debug flag enabled so that Spring can look up the parameter name from the constructor.
If you cannot or do not want to compile your code with the debug flag, you can use the
https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.html[@ConstructorProperties]
JDK annotation to explicitly name your constructor arguments. The sample class would
then have to look as follows:
@@ -248,6 +244,7 @@ Kotlin::
constructor(val years: Int, val ultimateAnswer: String)
----
======
--
[[beans-setter-injection]]
@@ -582,7 +582,7 @@ it needs to be declared in the XML file even though it is not defined in an XSD
(it exists inside the Spring core).
For the rare cases where the constructor argument names are not available (usually if
the bytecode was compiled without the `-parameters` flag), you can fall back to the
the bytecode was compiled without debugging information), you can use fallback to the
argument indexes, as follows:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -116,7 +116,7 @@ the configuration model, in that references to other beans must be valid Java sy
Fortunately, solving this problem is simple. As
xref:core/beans/java/bean-annotation.adoc#beans-java-dependencies[we already discussed],
a `@Bean` method can have an arbitrary number of parameters that describe the bean
dependencies. Consider the following more realistic scenario with several `@Configuration`
dependencies. Consider the following more real-world scenario with several `@Configuration`
classes, each depending on beans declared in the others:
[tabs]
@@ -331,10 +331,8 @@ TIP: Constructor injection in `@Configuration` classes is only supported as of S
Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
bean defines only one constructor.
[discrete]
[[beans-java-injecting-imported-beans-fq]]
==== Fully-qualifying imported beans for ease of navigation
.[[beans-java-injecting-imported-beans-fq]]Fully-qualifying imported beans for ease of navigation
--
In the preceding scenario, using `@Autowired` works well and provides the desired
modularity, but determining exactly where the autowired bean definitions are declared is
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
@@ -503,6 +501,7 @@ Now `ServiceConfig` is loosely coupled with respect to the concrete
get a type hierarchy of `RepositoryConfig` implementations. In this
way, navigating `@Configuration` classes and their dependencies becomes no different
than the usual process of navigating interface-based code.
--
TIP: If you want to influence the startup creation order of certain beans, consider
declaring some of them as `@Lazy` (for creation on first access instead of on startup)
@@ -541,7 +540,7 @@ Java::
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().matchesProfiles((String[]) value)) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
@@ -560,7 +559,7 @@ Kotlin::
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.matchesProfiles(*value as Array<String>)) {
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
return true
}
}
@@ -595,18 +594,16 @@ that uses Spring XML, it is easier to create `@Configuration` classes on an
as-needed basis and include them from the existing XML files. Later in this section, we cover the
options for using `@Configuration` classes in this kind of "`XML-centric`" situation.
[discrete]
[[beans-java-combining-xml-centric-declare-as-bean]]
==== Declaring `@Configuration` classes as plain Spring `<bean/>` elements
Remember that `@Configuration` classes are ultimately bean definitions in the container.
In this series of examples, we create a `@Configuration` class named `AppConfig` and
.[[beans-java-combining-xml-centric-declare-as-bean]]Declaring `@Configuration` classes as plain Spring `<bean/>` elements
--
Remember that `@Configuration` classes are ultimately bean definitions in the
container. In this series examples, we create a `@Configuration` class named `AppConfig` and
include it within `system-test-config.xml` as a `<bean/>` definition. Because
`<context:annotation-config/>` is switched on, the container recognizes the
`@Configuration` annotation and processes the `@Bean` methods declared in `AppConfig`
properly.
The following example shows the `AppConfig` configuration class in Java and Kotlin:
The following example shows an ordinary configuration class in Java:
[tabs]
======
@@ -627,7 +624,7 @@ Java::
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository());
return new TransferService(accountRepository());
}
}
----
@@ -660,7 +657,6 @@ The following example shows part of a sample `system-test-config.xml` file:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
@@ -707,20 +703,20 @@ Kotlin::
----
======
NOTE: In the `system-test-config.xml` file, the `AppConfig` `<bean/>` does not declare an `id`
attribute. While it would be acceptable to do so, it is unnecessary, given that no other bean
NOTE: In `system-test-config.xml` file, the `AppConfig` `<bean/>` does not declare an `id`
element. While it would be acceptable to do so, it is unnecessary, given that no other bean
ever refers to it, and it is unlikely to be explicitly fetched from the container by name.
Similarly, the `DataSource` bean is only ever autowired by type, so an explicit bean `id`
is not strictly required.
--
[discrete]
[[beans-java-combining-xml-centric-component-scan]]
==== Using <context:component-scan/> to pick up `@Configuration` classes
.[[beans-java-combining-xml-centric-component-scan]] Using <context:component-scan/> to pick up `@Configuration` classes
--
Because `@Configuration` is meta-annotated with `@Component`, `@Configuration`-annotated
classes are automatically candidates for component scanning. Using the same scenario as
described in the previous example, we can redefine `system-test-config.xml` to take
advantage of component-scanning. Note that, in this case, we need not explicitly declare
described in the previous example, we can redefine `system-test-config.xml` to take advantage of component-scanning.
Note that, in this case, we need not explicitly declare
`<context:annotation-config/>`, because `<context:component-scan/>` enables the same
functionality.
@@ -731,7 +727,6 @@ The following example shows the modified `system-test-config.xml` file:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
@@ -741,17 +736,19 @@ The following example shows the modified `system-test-config.xml` file:
</bean>
</beans>
----
--
[[beans-java-combining-java-centric]]
=== `@Configuration` Class-centric Use of XML with `@ImportResource`
In applications where `@Configuration` classes are the primary mechanism for configuring
the container, it may still be necessary to use at least some XML. In such scenarios, you
can use `@ImportResource` and define only as much XML as you need. Doing so achieves a
"`Java-centric`" approach to configuring the container and keeps XML to a bare minimum.
The following example (which includes a configuration class, an XML file that defines a
bean, a properties file, and the `main()` method) shows how to use the `@ImportResource`
annotation to achieve "`Java-centric`" configuration that uses XML as needed:
the container, it is still likely necessary to use at least some XML. In these
scenarios, you can use `@ImportResource` and define only as much XML as you need. Doing
so achieves a "`Java-centric`" approach to configuring the container and keeps XML to a
bare minimum. The following example (which includes a configuration class, an XML file
that defines a bean, a properties file, and the `main` class) shows how to use
the `@ImportResource` annotation to achieve "`Java-centric`" configuration that uses XML
as needed:
[tabs]
======
@@ -776,17 +773,6 @@ Java::
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
----
@@ -811,32 +797,21 @@ Kotlin::
fun dataSource(): DataSource {
return DriverManagerDataSource(url, username, password)
}
@Bean
fun accountRepository(dataSource: DataSource): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
----
======
.properties-config.xml
[source,xml,indent=0,subs="verbatim,quotes"]
----
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
----
.jdbc.properties
[literal,subs="verbatim,quotes"]
----
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
@@ -869,3 +844,5 @@ Kotlin::
----
======
@@ -222,38 +222,29 @@ Kotlin::
[[expressions-evaluation-context]]
== Understanding `EvaluationContext`
The `EvaluationContext` API is used when evaluating an expression to resolve properties,
methods, or fields and to help perform type conversion. Spring provides two
The `EvaluationContext` interface is used when evaluating an expression to resolve
properties, methods, or fields and to help perform type conversion. Spring provides two
implementations.
`SimpleEvaluationContext`::
Exposes a subset of essential SpEL language features and configuration options, for
categories of expressions that do not require the full extent of the SpEL language
syntax and should be meaningfully restricted. Examples include but are not limited to
data binding expressions and property-based filters.
* `SimpleEvaluationContext`: Exposes a subset of essential SpEL language features and
configuration options, for categories of expressions that do not require the full extent
of the SpEL language syntax and should be meaningfully restricted. Examples include but
are not limited to data binding expressions and property-based filters.
`StandardEvaluationContext`::
Exposes the full set of SpEL language features and configuration options. You can use
it to specify a default root object and to configure every available evaluation-related
strategy.
* `StandardEvaluationContext`: Exposes the full set of SpEL language features and
configuration options. You can use it to specify a default root object and to configure
every available evaluation-related strategy.
`SimpleEvaluationContext` is designed to support only a subset of the SpEL language
syntax. For example, it excludes Java type references, constructors, and bean references.
It also requires you to explicitly choose the level of support for properties and methods
in expressions. When creating a `SimpleEvaluationContext` you need to choose the level of
support that you need for data binding in SpEL expressions:
`SimpleEvaluationContext` is designed to support only a subset of the SpEL language syntax.
It excludes Java type references, constructors, and bean references. It also requires
you to explicitly choose the level of support for properties and methods in expressions.
By default, the `create()` static factory method enables only read access to properties.
You can also obtain a builder to configure the exact level of support needed, targeting
one or some combination of the following.
* Data binding for read-only access
* Data binding for read and write access
* A custom `PropertyAccessor` (typically not reflection-based), potentially combined with
a `DataBindingPropertyAccessor`
Conveniently, `SimpleEvaluationContext.forReadOnlyDataBinding()` enables read-only access
to properties via `DataBindingPropertyAccessor`. Similarly,
`SimpleEvaluationContext.forReadWriteDataBinding()` enables read and write access to
properties. Alternatively, configure custom accessors via
`SimpleEvaluationContext.forPropertyAccessors(...)`, potentially disable assignment, and
optionally activate method resolution and/or a type converter through the builder.
* Custom `PropertyAccessor` only (no reflection)
* Data binding properties for read-only access
* Data binding properties for read and write
[[expressions-type-conversion]]
@@ -323,17 +314,17 @@ Kotlin::
It is possible to configure the SpEL expression parser by using a parser configuration
object (`org.springframework.expression.spel.SpelParserConfiguration`). The configuration
object controls the behavior of some of the expression components. For example, if you
index into a collection and the element at the specified index is `null`, SpEL can
automatically create the element. This is useful when using expressions made up of a
chain of property references. Similarly, if you index into a collection and specify an
index that is greater than the current size of the collection, SpEL can automatically
grow the collection to accommodate that index. In order to add an element at the
index into an array or collection and the element at the specified index is `null`, SpEL
can automatically create the element. This is useful when using expressions made up of a
chain of property references. If you index into an array or list and specify an index
that is beyond the end of the current size of the array or list, SpEL can automatically
grow the array or list to accommodate that index. In order to add an element at the
specified index, SpEL will try to create the element using the element type's default
constructor before setting the specified value. If the element type does not have a
default constructor, `null` will be added to the collection. If there is no built-in
converter or custom converter that knows how to set the value, `null` will remain in the
collection at the specified index. The following example demonstrates how to
automatically grow a `List`.
default constructor, `null` will be added to the array or list. If there is no built-in
or custom converter that knows how to set the value, `null` will remain in the array or
list at the specified index. The following example demonstrates how to automatically grow
the list.
[tabs]
======
@@ -332,7 +332,7 @@ metadata, such as the argument names. The following table describes the items ma
available to the context so that you can use them for key and conditional computations:
[[cache-spel-context-tbl]]
.Cache metadata available in SpEL expressions
.Cache SpEL available metadata
|===
| Name| Location| Description| Example
@@ -358,7 +358,7 @@ available to the context so that you can use them for key and conditional comput
| `args`
| Root object
| The arguments (as an object array) used for invoking the target
| The arguments (as array) used for invoking the target
| `#root.args[0]`
| `caches`
@@ -368,10 +368,9 @@ available to the context so that you can use them for key and conditional comput
| Argument name
| Evaluation context
| The name of a particular method argument. If the names are not available
(for example, because the code was compiled without the `-parameters` flag), individual
arguments are also available using the `#a<#arg>` syntax where `<#arg>` stands for the
argument index (starting from 0).
| Name of any of the method arguments. If the names are not available
(perhaps due to having no debug information), the argument names are also available under the `#a<#arg>`
where `#arg` stands for the argument index (starting from `0`).
| `#iban` or `#a0` (you can also use `#p0` or `#p<#arg>` notation as an alias).
| `result`
@@ -65,7 +65,7 @@ a "shared objects file" source, as shown in the following example:
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
the following conditions are fulfilled when creating and using the archive:
- The very same JVM must be used.
- The very same JVM must used.
- The classpath must be specified as a list of JARs, and avoid the usage of directories and `*` wildcard characters.
- The timestamps of the JARs must be preserved.
- When using the archive, the classpath must be the same than the one used to create the archive, in the same order.
@@ -19,8 +19,6 @@ A checkpoint can be created on demand, for example using a command like `jcmd ap
WARNING: Leveraging checkpoint/restore of a running application typically requires additional lifecycle management to gracefully stop and start using resources like files or sockets and stop active threads.
WARNING: Be aware that when defining scheduling tasks at a fixed rate, for example with an annotation like `@Scheduled(fixedRate = 5000)`, all missed executions between checkpoint and restore will be performed when the JVM is restored with on-demand checkpoint/restore. If this is not the behavior you want, it is recommended to schedule tasks at a fixed delay (for example with `@Scheduled(fixedDelay = 5000)`) or with a cron expression as those are calculated after every task execution.
NOTE: If the checkpoint is created on a warmed-up JVM, the restored JVM will be equally warmed-up, allowing potentially peak performance immediately. This method typically requires access to remote services, and thus requires some level of platform integration.
== Automatic checkpoint/restore at startup
@@ -34,10 +34,6 @@ Kotlin::
You can use the xref:web/webmvc/mvc-config/message-converters.adoc[Message Converters] option of the xref:web/webmvc/mvc-config.adoc[MVC Config] to
configure or customize message conversion.
NOTE: Form data should be read using xref:web/webmvc/mvc-controller/ann-methods/requestparam.adoc[`@RequestParam`],
not with `@RequestBody` which can't always be used reliably since in the Servlet API, request parameter
access causes the request body to be parsed, and it can't be read again.
You can use `@RequestBody` in combination with `jakarta.validation.Valid` or Spring's
`@Validated` annotation, both of which cause Standard Bean Validation to be applied.
By default, validation errors cause a `MethodArgumentNotValidException`, which is turned
@@ -61,7 +61,7 @@ Kotlin::
By default, method parameters that use this annotation are required, but you can specify that
a method parameter is optional by setting the `@RequestParam` annotation's `required` flag to
`false` or by declaring the argument with a `java.util.Optional` wrapper.
`false` or by declaring the argument with an `java.util.Optional` wrapper.
Type conversion is automatically applied if the target method parameter type is not
`String`. See xref:web/webmvc/mvc-controller/ann-methods/typeconversion.adoc[Type Conversion].
@@ -72,48 +72,6 @@ values for the same parameter name.
When an `@RequestParam` annotation is declared as a `Map<String, String>` or
`MultiValueMap<String, String>`, without a parameter name specified in the annotation,
then the map is populated with the request parameter values for each given parameter name.
The following example shows how to do so with form data processing:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Controller
@RequestMapping("/pets")
class EditPetForm {
// ...
@PostMapping(path = "/process", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String processForm(@RequestParam MultiValueMap<String, String> params) {
// ...
}
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Controller
@RequestMapping("/pets")
class EditPetForm {
// ...
@PostMapping("/process", consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun processForm(@RequestParam params: MultiValueMap<String, String>): String {
// ...
}
// ...
}
----
======
Note that use of `@RequestParam` is optional (for example, to set its attributes).
By default, any argument that is a simple value type (as determined by
+2 -2
View File
@@ -263,10 +263,10 @@ JavaPoet 1.13.0 is licensed under the Apache License, version 2.0, the text of
which is included above.
>>> Objenesis 3.4 (org.objenesis:objenesis:3.4):
>>> Objenesis 3.2 (org.objenesis:objenesis:3.2):
Per the LICENSE file in the Objenesis ZIP distribution downloaded from
http://objenesis.org/download.html, Objenesis 3.4 is licensed under the
http://objenesis.org/download.html, Objenesis 3.2 is licensed under the
Apache License, version 2.0, the text of which is included above.
Per the NOTICE file in the Objenesis ZIP distribution downloaded from
+11 -11
View File
@@ -8,16 +8,16 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
api(platform("io.micrometer:micrometer-bom:1.12.10"))
api(platform("io.netty:netty-bom:4.1.113.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.9"))
api(platform("io.netty:netty-bom:4.1.112.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.10"))
api(platform("io.projectreactor:reactor-bom:2023.0.9"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.22"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.26.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.13"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.13"))
api(platform("org.assertj:assertj-bom:3.26.0"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.12"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.12"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.10.3"))
@@ -56,9 +56,9 @@ dependencies {
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.8")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.17.Final")
api("io.undertow:undertow-servlet:2.3.17.Final")
api("io.undertow:undertow-websockets-jsr:2.3.17.Final")
api("io.undertow:undertow-core:2.3.15.Final")
api("io.undertow:undertow-servlet:2.3.15.Final")
api("io.undertow:undertow-websockets-jsr:2.3.15.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -116,7 +116,7 @@ dependencies {
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
api("org.dom4j:dom4j:2.1.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.7")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.5")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
@@ -140,7 +140,7 @@ dependencies {
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
api("org.seleniumhq.selenium:selenium-java:3.141.59")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.16")
api("org.slf4j:slf4j-api:2.0.13")
api("org.testng:testng:7.9.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.55")
+2 -2
View File
@@ -1,10 +1,10 @@
version=6.1.13
version=6.1.12
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
org.gradle.parallel=true
kotlinVersion=1.9.25
kotlinVersion=1.9.24
kotlin.jvm.target.validation.mode=ignore
kotlin.stdlib.default.dependency=false
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author 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,10 +19,10 @@ package org.springframework.beans.factory.aot;
/**
* Record class holding key information for beans registered in a bean factory.
*
* @author Brian Clozel
* @since 6.0.8
* @param beanName the name of the registered bean
* @param beanClass the type of the registered bean
* @author Brian Clozel
* @since 6.0.8
*/
record BeanRegistrationKey(String beanName, Class<?> beanClass) {
}
@@ -83,7 +83,6 @@ public class CodeWarnings {
* specified {@link ResolvableType}.
* @param resolvableType a type signature
* @return {@code this} instance
* @since 6.1.8
*/
public CodeWarnings detectDeprecation(ResolvableType resolvableType) {
if (ResolvableType.NONE.equals(resolvableType)) {
@@ -25,11 +25,11 @@ import org.springframework.util.ClassUtils;
* reference to the method's {@linkplain #declaringClass declaring class},
* {@linkplain #methodName name}, and {@linkplain #parameterTypes parameter types}.
*
* @author Sam Brannen
* @since 6.0.11
* @param declaringClass the method's declaring class
* @param methodName the name of the method
* @param parameterTypes the types of parameters accepted by the method
* @author Sam Brannen
* @since 6.0.11
*/
record MethodDescriptor(Class<?> declaringClass, String methodName, Class<?>... parameterTypes) {
@@ -267,10 +267,10 @@ public final class RegisteredBean {
* is usually the declaring class of the {@code executable} (in case of a constructor
* or a locally declared factory method), there are cases where retaining the actual
* concrete class is necessary (e.g. for an inherited factory method).
* @since 6.1.7
* @param executable the {@link Executable} ({@link java.lang.reflect.Constructor}
* or {@link java.lang.reflect.Method}) to invoke
* @param targetClass the target {@link Class} of the executable
* @since 6.1.7
*/
public record InstantiationDescriptor(Executable executable, Class<?> targetClass) {
@@ -371,9 +371,6 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
if (logger.isWarnEnabled()) {
logger.warn("Failed to stop bean '" + beanName + "'", ex);
}
if (bean instanceof SmartLifecycle) {
latch.countDown();
}
}
}
}
@@ -126,27 +126,6 @@ public @interface Scheduled {
*/
String zone() default "";
/**
* Execute the annotated method with a fixed period between invocations.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* @return the period
*/
long fixedRate() default -1;
/**
* Execute the annotated method with a fixed period between invocations.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* <p>This attribute variant supports Spring-style "${...}" placeholders
* as well as SpEL expressions.
* @return the period as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2
* @see #fixedRate()
*/
String fixedRateString() default "";
/**
* Execute the annotated method with a fixed period between the end of the
* last invocation and the start of the next.
@@ -176,6 +155,27 @@ public @interface Scheduled {
*/
String fixedDelayString() default "";
/**
* Execute the annotated method with a fixed period between invocations.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* @return the period
*/
long fixedRate() default -1;
/**
* Execute the annotated method with a fixed period between invocations.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* <p>This attribute variant supports Spring-style "${...}" placeholders
* as well as SpEL expressions.
* @return the period as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2
* @see #fixedRate()
*/
String fixedRateString() default "";
/**
* Number of units of time to delay before the first execution of a
* {@link #fixedRate} or {@link #fixedDelay} task.
@@ -609,7 +609,7 @@ abstract class AbstractProxyTargetClassConfig {
@Aspect
static class SupplierAdvice {
@Around("execution(* java.util.function.Supplier+.get())")
@Around("execution(public * org.springframework.aop.aspectj.autoproxy..*.*(..))")
Object aroundSupplier(ProceedingJoinPoint joinPoint) throws Throwable {
return "advised: " + joinPoint.proceed();
}
+1 -1
View File
@@ -15,7 +15,7 @@ multiRelease {
}
def javapoetVersion = "1.13.0"
def objenesisVersion = "3.4"
def objenesisVersion = "3.3"
configurations {
java21Api.extendsFrom(api)
@@ -107,8 +107,7 @@ public class BindingReflectionHintsRegistrar {
registerPropertyHints(hints, seen, method, 0);
}
else if ((methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != void.class) ||
(methodName.startsWith("is") && method.getParameterCount() == 0
&& ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType()) == Boolean.class)) {
(methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class)) {
registerPropertyHints(hints, seen, method, -1);
}
}
@@ -34,7 +34,6 @@ import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
@@ -872,7 +871,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
.formatted(rootPath.toAbsolutePath(), subPattern));
}
try (Stream<Path> files = Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS)) {
try (Stream<Path> files = Files.walk(rootPath)) {
files.filter(isMatchingFile).sorted().map(FileSystemResource::new).forEach(result::add);
}
catch (Exception ex) {
@@ -1,6 +1,6 @@
/**
* Spring's repackaging of
* <a href="http://objenesis.org">Objenesis 3.4</a>
* <a href="http://objenesis.org">Objenesis 3.2</a>
* (with SpringObjenesis entry point; for internal use only).
*
* <p>This repackaging technique avoids any potential conflicts with
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,10 +68,8 @@ class BindingReflectionHintsRegistrarKotlinTests {
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "component1")).accepts(hints)
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "copy")).accepts(hints)
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "getName")).accepts(hints)
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "isNonNullable")).accepts(hints)
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "isNullable")).accepts(hints)
val copyDefault: Method = SampleDataClass::class.java.getMethod("copy\$default", SampleDataClass::class.java,
String::class.java, Boolean::class.javaPrimitiveType, Boolean::class.javaObjectType, Int::class.java, Object::class.java)
String::class.java , Int::class.java, Object::class.java)
assertThat(RuntimeHintsPredicates.reflection().onMethod(copyDefault)).accepts(hints)
}
@@ -86,6 +84,6 @@ class BindingReflectionHintsRegistrarKotlinTests {
@kotlinx.serialization.Serializable
class SampleSerializableClass(val name: String)
data class SampleDataClass(val name: String, val isNonNullable: Boolean, val isNullable: Boolean?)
data class SampleDataClass(val name: String)
class SampleClass(val name: String)
@@ -44,8 +44,6 @@ import java.util.TimeZone;
import java.util.stream.Collectors;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncEvent;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletConnection;
@@ -922,19 +920,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
public AsyncContext startAsync(ServletRequest request, @Nullable ServletResponse response) {
Assert.state(this.asyncSupported, "Async not supported");
this.asyncStarted = true;
MockAsyncContext newAsyncContext = new MockAsyncContext(request, response);
if (this.asyncContext != null) {
try {
AsyncEvent startEvent = new AsyncEvent(newAsyncContext);
for (AsyncListener asyncListener : this.asyncContext.getListeners()) {
asyncListener.onStartAsync(startEvent);
}
}
catch (IOException ex) {
// ignore failures
}
}
this.asyncContext = newAsyncContext;
this.asyncContext = new MockAsyncContext(request, response);
return this.asyncContext;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,7 +66,6 @@ import org.springframework.web.util.UriBuilderFactory;
* @author Rossen Stoyanchev
* @author Sam Brannen
* @author Michał Rowicki
* @author Sebastien Deleuze
* @since 5.0
*/
class DefaultWebTestClient implements WebTestClient {
@@ -491,14 +490,7 @@ class DefaultWebTestClient implements WebTestClient {
@Override
public <T> FluxExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef) {
Flux<T> body;
if (elementTypeRef.getType().equals(Void.class)) {
this.response.releaseBody().block();
body = Flux.empty();
}
else {
body = this.response.bodyToFlux(elementTypeRef);
}
Flux<T> body = this.response.bodyToFlux(elementTypeRef);
return new FluxExchangeResult<>(this.exchangeResult, body);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -176,16 +176,6 @@ public class ExchangeResult {
return this.response.getStatusCode();
}
/**
* Return the HTTP status code as an integer.
* @since 5.1.10
* @deprecated in favor of {@link #getStatus()}, for removal in 7.0
*/
@Deprecated(since = "6.0", forRemoval = true)
public int getRawStatusCode() {
return getStatus().value();
}
/**
* Return the response headers received from the server.
*/
@@ -30,9 +30,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncEvent;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.http.Cookie;
import org.junit.jupiter.api.Test;
@@ -666,44 +663,6 @@ class MockHttpServletRequestTests {
request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE));
}
@Test
void shouldRejectAsyncStartsIfUnsupported() {
assertThat(request.isAsyncStarted()).isFalse();
assertThatIllegalStateException().isThrownBy(request::startAsync);
}
@Test
void startAsyncShouldUpdateRequestState() {
assertThat(request.isAsyncStarted()).isFalse();
request.setAsyncSupported(true);
AsyncContext asyncContext = request.startAsync();
assertThat(request.isAsyncStarted()).isTrue();
}
@Test
void shouldNotifyAsyncListeners() {
request.setAsyncSupported(true);
AsyncContext asyncContext = request.startAsync();
TestAsyncListener testAsyncListener = new TestAsyncListener();
asyncContext.addListener(testAsyncListener);
asyncContext.complete();
assertThat(testAsyncListener.events).hasSize(1);
assertThat(testAsyncListener.events.get(0)).extracting("name").isEqualTo("onComplete");
}
@Test
void shouldNotifyAsyncListenersWhenNewAsyncStarted() {
request.setAsyncSupported(true);
AsyncContext asyncContext = request.startAsync();
TestAsyncListener testAsyncListener = new TestAsyncListener();
asyncContext.addListener(testAsyncListener);
AsyncContext newAsyncContext = request.startAsync();
assertThat(testAsyncListener.events).hasSize(1);
ListenerEvent listenerEvent = testAsyncListener.events.get(0);
assertThat(listenerEvent).extracting("name").isEqualTo("onStartAsync");
assertThat(listenerEvent.event.getAsyncContext()).isEqualTo(newAsyncContext);
}
private void assertEqualEnumerations(Enumeration<?> enum1, Enumeration<?> enum2) {
int count = 0;
while (enum1.hasMoreElements()) {
@@ -713,31 +672,4 @@ class MockHttpServletRequestTests {
}
}
static class TestAsyncListener implements AsyncListener {
List<ListenerEvent> events = new ArrayList<>();
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
this.events.add(new ListenerEvent("onComplete", asyncEvent));
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
this.events.add(new ListenerEvent("onTimeout", asyncEvent));
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
this.events.add(new ListenerEvent("onError", asyncEvent));
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
this.events.add(new ListenerEvent("onStartAsync", asyncEvent));
}
}
record ListenerEvent(String name, AsyncEvent event) {}
}
@@ -27,10 +27,10 @@ import org.springframework.util.StringUtils;
/**
* Represents an ETag for HTTP conditional requests.
*
* @author Rossen Stoyanchev
* @since 5.3.38
* @param tag the unquoted tag value
* @param weak whether the entity tag is for weak or strong validation
* @author Rossen Stoyanchev
* @since 5.3.38
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7232">RFC 7232</a>
*/
public record ETag(String tag, boolean weak) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import org.springframework.http.HttpStatusCode;
*
* @author Arjen Poutsma
* @since 3.1.1
* @deprecated with no direct replacement; for removal in 6.2
* @deprecated as of 6.0, with no direct replacement; scheduled for removal in 6.2
*/
@Deprecated(since = "6.0", forRemoval = true)
public abstract class AbstractClientHttpResponse implements ClientHttpResponse {
@@ -48,7 +48,8 @@ public interface ClientHttpResponse extends HttpInputMessage, Closeable {
* @throws IOException in case of I/O errors
* @since 3.1.1
* @see #getStatusCode()
* @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #getStatusCode()}; scheduled for
* removal in 6.2
*/
@Deprecated(since = "6.0", forRemoval = true)
default int getRawStatusCode() throws IOException {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,18 +46,6 @@ public interface ClientHttpResponse extends ReactiveHttpInputMessage {
*/
HttpStatusCode getStatusCode();
/**
* Return the HTTP status code as an integer.
* @return the HTTP status as an integer value
* @since 5.0.6
* @see #getStatusCode()
* @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0
*/
@Deprecated(since = "6.0", forRemoval = true)
default int getRawStatusCode() {
return getStatusCode().value();
}
/**
* Return a read-only map of response cookies received from the server.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -115,6 +115,13 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
return setStatusCode(statusCode != null ? HttpStatusCode.valueOf(statusCode) : null);
}
@Deprecated
@Override
@Nullable
public Integer getRawStatusCode() {
return (this.statusCode != null ? this.statusCode.value() : null);
}
@Override
public HttpHeaders getHeaders() {
if (this.readOnlyHeaders != null) {
@@ -78,7 +78,6 @@ class ReactorNetty2ServerHttpResponse extends AbstractServerHttpResponse impleme
@Override
@Deprecated
@SuppressWarnings("removal")
public Integer getRawStatusCode() {
Integer status = super.getRawStatusCode();
return (status != null ? status : this.response.status().code());
@@ -77,7 +77,6 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze
@Override
@Deprecated
@SuppressWarnings("removal")
public Integer getRawStatusCode() {
Integer status = super.getRawStatusCode();
return (status != null ? status : this.response.status().code());
@@ -65,9 +65,9 @@ public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
* status of the response from the underlying server. The return value may
* be {@code null} if there is no default value from the underlying server.
* @since 5.2.4
* @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #getStatusCode()}
*/
@Deprecated(since = "6.0", forRemoval = true)
@Deprecated(since = "6.0")
@Nullable
default Integer getRawStatusCode() {
HttpStatusCode httpStatus = getStatusCode();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,7 +74,6 @@ public class ServerHttpResponseDecorator implements ServerHttpResponse {
@Override
@Nullable
@Deprecated
@SuppressWarnings("removal")
public Integer getRawStatusCode() {
return getDelegate().getRawStatusCode();
}
@@ -109,7 +109,6 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
@Override
@Deprecated
@SuppressWarnings("removal")
public Integer getRawStatusCode() {
Integer status = super.getRawStatusCode();
return (status != null ? status : this.response.getStatus());
@@ -89,7 +89,6 @@ class UndertowServerHttpResponse extends AbstractListenerServerHttpResponse impl
@Override
@Deprecated
@SuppressWarnings("removal")
public Integer getRawStatusCode() {
Integer status = super.getRawStatusCode();
return (status != null ? status : this.exchange.getStatusCode());
@@ -25,13 +25,11 @@ import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpEntity;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
/**
* {@link ReflectiveProcessor} implementation for {@link Controller} and
@@ -73,11 +71,6 @@ class ControllerMappingReflectiveProcessor implements ReflectiveProcessor {
protected void registerMethodHints(ReflectionHints hints, Method method) {
hints.registerMethod(method, ExecutableMode.INVOKE);
Class<?> declaringClass = method.getDeclaringClass();
if (KotlinDetector.isKotlinType(declaringClass)) {
ReflectionUtils.doWithMethods(declaringClass, m -> hints.registerMethod(m, ExecutableMode.INVOKE),
m -> m.getName().equals(method.getName() + "$default"));
}
for (Parameter parameter : method.getParameters()) {
registerParameterTypeHints(hints, MethodParameter.forParameter(parameter));
}
@@ -468,7 +468,6 @@ final class DefaultRestClient implements RestClient {
ClientHttpResponse clientResponse = null;
Observation observation = null;
Observation.Scope observationScope = null;
URI uri = null;
try {
if (DefaultRestClient.this.defaultRequest != null) {
@@ -482,7 +481,6 @@ final class DefaultRestClient implements RestClient {
observationContext.setUriTemplate(this.uriTemplate);
observation = ClientHttpObservationDocumentation.HTTP_CLIENT_EXCHANGES.observation(observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry).start();
observationScope = observation.openScope();
if (this.body != null) {
this.body.writeTo(clientRequest);
}
@@ -491,14 +489,11 @@ final class DefaultRestClient implements RestClient {
}
clientResponse = clientRequest.execute();
observationContext.setResponse(clientResponse);
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse, observation, observationScope);
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse, observation);
return exchangeFunction.exchange(clientRequest, convertibleWrapper);
}
catch (IOException ex) {
ResourceAccessException resourceAccessException = createResourceAccessException(uri, this.httpMethod, ex);
if (observationScope != null) {
observationScope.close();
}
if (observation != null) {
observation.error(resourceAccessException);
observation.stop();
@@ -506,9 +501,6 @@ final class DefaultRestClient implements RestClient {
throw resourceAccessException;
}
catch (Throwable error) {
if (observationScope != null) {
observationScope.close();
}
if (observation != null) {
observation.error(error);
observation.stop();
@@ -518,9 +510,6 @@ final class DefaultRestClient implements RestClient {
finally {
if (close && clientResponse != null) {
clientResponse.close();
if (observationScope != null) {
observationScope.close();
}
if (observation != null) {
observation.stop();
}
@@ -731,12 +720,10 @@ final class DefaultRestClient implements RestClient {
private final Observation observation;
private final Observation.Scope observationScope;
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate, Observation observation, Observation.Scope observationScope) {
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate, Observation observation) {
this.delegate = delegate;
this.observation = observation;
this.observationScope = observationScope;
}
@@ -777,7 +764,6 @@ final class DefaultRestClient implements RestClient {
@Override
public void close() {
this.delegate.close();
this.observationScope.close();
this.observation.stop();
}
@@ -125,7 +125,7 @@ public class RestClientResponseException extends RestClientException {
/**
* Return the raw HTTP status code value.
* @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #getStatusCode()}
*/
@Deprecated(since = "6.0")
public int getRawStatusCode() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -113,7 +113,7 @@ public class UnknownContentTypeException extends RestClientException {
/**
* Return the raw HTTP status code value.
* @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #getStatusCode()}
*/
@Deprecated(since = "6.0")
public int getRawStatusCode() {
@@ -21,7 +21,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@@ -212,38 +211,6 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
}
/**
* Return 0 when there is no need to obtain a lock (no async handling in
* progress), 1 if lock was acquired, and -1 if lock is not acquired because
* request is no longer usable.
*/
private int tryObtainLock() {
if (this.state == State.NEW) {
return 0;
}
// Do not wait indefinitely, stop if we moved on from ASYNC state (e.g. to ERROR),
// helps to avoid ABBA deadlock with onError callback
while (this.state == State.ASYNC) {
try {
if (this.stateLock.tryLock(500, TimeUnit.MILLISECONDS)) {
if (this.state == State.ASYNC) {
return 1;
}
this.stateLock.unlock();
break;
}
}
catch (InterruptedException ex) {
// ignore
}
}
return -1;
}
/**
* Package private access for testing only.
*/
@@ -277,7 +244,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public ServletOutputStream getOutputStream() throws IOException {
int level = obtainLockOrRaiseException();
int level = obtainLockAndCheckState();
try {
if (this.outputStream == null) {
Assert.notNull(this.asyncWebRequest, "Not initialized");
@@ -296,7 +263,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public PrintWriter getWriter() throws IOException {
int level = obtainLockOrRaiseException();
int level = obtainLockAndCheckState();
try {
if (this.writer == null) {
Assert.notNull(this.asyncWebRequest, "Not initialized");
@@ -314,7 +281,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void flushBuffer() throws IOException {
int level = obtainLockOrRaiseException();
int level = obtainLockAndCheckState();
try {
getResponse().flushBuffer();
}
@@ -326,15 +293,25 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
}
}
private int obtainLockOrRaiseException() throws AsyncRequestNotUsableException {
/**
* Return 0 if checks passed and lock is not needed, 1 if checks passed
* and lock is held, or raise AsyncRequestNotUsableException.
*/
private int obtainLockAndCheckState() throws AsyncRequestNotUsableException {
Assert.notNull(this.asyncWebRequest, "Not initialized");
int result = this.asyncWebRequest.tryObtainLock();
if (result == -1) {
throw new AsyncRequestNotUsableException("Response not usable after " +
(this.asyncWebRequest.state == State.COMPLETED ?
"async request completion" : "response errors") + ".");
if (this.asyncWebRequest.state == State.NEW) {
return 0;
}
return result;
this.asyncWebRequest.stateLock.lock();
if (this.asyncWebRequest.state == State.ASYNC) {
return 1;
}
this.asyncWebRequest.stateLock.unlock();
throw new AsyncRequestNotUsableException("Response not usable after " +
(this.asyncWebRequest.state == State.COMPLETED ?
"async request completion" : "response errors") + ".");
}
void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
@@ -380,7 +357,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void write(int b) throws IOException {
int level = this.response.obtainLockOrRaiseException();
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.write(b);
}
@@ -393,7 +370,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
}
public void write(byte[] buf, int offset, int len) throws IOException {
int level = this.response.obtainLockOrRaiseException();
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.write(buf, offset, len);
}
@@ -407,7 +384,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void flush() throws IOException {
int level = this.response.obtainLockOrRaiseException();
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.flush();
}
@@ -421,7 +398,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void close() throws IOException {
int level = this.response.obtainLockOrRaiseException();
int level = this.response.obtainLockAndCheckState();
try {
this.delegate.close();
}
@@ -455,7 +432,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void flush() {
int level = this.asyncWebRequest.tryObtainLock();
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.flush();
@@ -468,7 +445,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void close() {
int level = this.asyncWebRequest.tryObtainLock();
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.close();
@@ -486,7 +463,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void write(int c) {
int level = this.asyncWebRequest.tryObtainLock();
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.write(c);
@@ -499,7 +476,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void write(char[] buf, int off, int len) {
int level = this.asyncWebRequest.tryObtainLock();
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.write(buf, off, len);
@@ -517,7 +494,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void write(String s, int off, int len) {
int level = this.asyncWebRequest.tryObtainLock();
int level = tryObtainLockAndCheckState();
if (level > -1) {
try {
this.delegate.write(s, off, len);
@@ -533,6 +510,22 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
this.delegate.write(s);
}
/**
* Return 0 if checks passed and lock is not needed, 1 if checks passed
* and lock is held, and -1 if checks did not pass.
*/
private int tryObtainLockAndCheckState() {
if (this.asyncWebRequest.state == State.NEW) {
return 0;
}
this.asyncWebRequest.stateLock.lock();
if (this.asyncWebRequest.state == State.ASYNC) {
return 1;
}
this.asyncWebRequest.stateLock.unlock();
return -1;
}
private void releaseLock(int level) {
if (level > 0) {
this.asyncWebRequest.stateLock.unlock();
@@ -387,15 +387,16 @@ public final class WebAsyncManager {
synchronized (WebAsyncManager.this) {
if (!this.state.compareAndSet(State.ASYNC_PROCESSING, State.RESULT_SET)) {
if (logger.isDebugEnabled()) {
logger.debug("Async result already set: [" + this.state.get() +
"], ignored result for " + formatUri(this.asyncWebRequest));
logger.debug("Async result already set: " +
"[" + this.state.get() + "], ignored result: " + result +
" for " + formatUri(this.asyncWebRequest));
}
return;
}
this.concurrentResult = result;
if (logger.isDebugEnabled()) {
logger.debug("Async result set for " + formatUri(this.asyncWebRequest));
logger.debug("Async result set to: " + result + " for " + formatUri(this.asyncWebRequest));
}
if (this.asyncWebRequest.isAsyncComplete()) {
@@ -118,13 +118,13 @@ public class ServerHttpObservationFilter extends OncePerRequestFilter {
throw ex;
}
finally {
// If async is started during the first dispatch, register a listener for completion notification.
if (request.isAsyncStarted() && request.getDispatcherType() == DispatcherType.REQUEST) {
// If async is started, register a listener for completion notification.
if (request.isAsyncStarted()) {
request.getAsyncContext().addListener(new ObservationAsyncListener(observation));
}
// scope is opened for ASYNC dispatches, but the observation will be closed
// by the async listener.
else if (!isAsyncDispatch(request)) {
else if (request.getDispatcherType() != DispatcherType.ASYNC){
Throwable error = fetchException(request);
if (error != null) {
observation.error(error);
@@ -168,7 +168,6 @@ public class ServerHttpObservationFilter extends OncePerRequestFilter {
@Override
public void onStartAsync(AsyncEvent event) {
event.getAsyncContext().addListener(this);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,7 +53,6 @@ import org.springframework.web.util.UriComponents;
* in which case it removes but does not use the headers.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 5.1
* @see <a href="https://tools.ietf.org/html/rfc7239">https://tools.ietf.org/html/rfc7239</a>
* @see <a href="https://docs.spring.io/spring-framework/reference/web/webflux/reactive-spring.html#webflux-forwarded-headers">Forwarded Headers</a>
@@ -166,7 +165,7 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
String[] rawPrefixes = StringUtils.tokenizeToStringArray(header, ",");
for (String rawPrefix : rawPrefixes) {
int endIndex = rawPrefix.length();
while (endIndex > 0 && rawPrefix.charAt(endIndex - 1) == '/') {
while (endIndex > 1 && rawPrefix.charAt(endIndex - 1) == '/') {
endIndex--;
}
prefix.append((endIndex != rawPrefix.length() ? rawPrefix.substring(0, endIndex) : rawPrefix));
@@ -132,9 +132,9 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
* @param ex the exception to check
* @return the associated HTTP status code, or -1 if it can't be derived.
* @since 5.3
* @deprecated in favor of {@link #determineStatus(Throwable)}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #determineStatus(Throwable)}
*/
@Deprecated(since = "6.0", forRemoval = true)
@Deprecated(since = "6.0")
protected int determineRawStatusCode(Throwable ex) {
if (ex instanceof ResponseStatusException responseStatusException) {
return responseStatusException.getStatusCode().value();
@@ -27,19 +27,15 @@ import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.tck.TestObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistryAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.observation.ClientRequestObservationContext;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
@@ -77,15 +73,12 @@ class RestClientObservationTests {
@BeforeEach
void setupEach() {
this.client = createBuilder().build();
this.observationRegistry.observationConfig().observationHandler(new ContextAssertionObservationHandler());
}
RestClient.Builder createBuilder() {
return RestClient.builder()
this.client = RestClient.builder()
.messageConverters(converters -> converters.add(0, this.converter))
.requestFactory(this.requestFactory)
.observationRegistry(this.observationRegistry);
.observationRegistry(this.observationRegistry)
.build();
this.observationRegistry.observationConfig().observationHandler(new ContextAssertionObservationHandler());
}
@Test
@@ -245,22 +238,6 @@ class RestClientObservationTests {
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS");
}
@Test
void openScopeWithObservation() throws Exception {
this.client = createBuilder().requestInterceptor(new ObservationContextInterceptor(this.observationRegistry))
.defaultStatusHandler(new ObservationErrorHandler(this.observationRegistry)).build();
mockSentRequest(GET, "https://example.org");
mockResponseStatus(HttpStatus.OK);
mockResponseBody("Hello World", MediaType.TEXT_PLAIN);
client.get().uri("https://example.org").retrieve().toBodilessEntity();
}
@AfterEach
void checkAfter() {
assertThat(this.observationRegistry.getCurrentObservationScope()).isNull();
}
private void mockSentRequest(HttpMethod method, String uri) throws Exception {
mockSentRequest(method, uri, new HttpHeaders());
@@ -311,38 +288,4 @@ class RestClientObservationTests {
}
static class ObservationContextInterceptor implements ClientHttpRequestInterceptor {
private final TestObservationRegistry observationRegistry;
public ObservationContextInterceptor(TestObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
assertThat(this.observationRegistry.getCurrentObservationScope()).isNotNull();
return execution.execute(request, body);
}
}
static class ObservationErrorHandler implements ResponseErrorHandler {
final TestObservationRegistry observationRegistry;
ObservationErrorHandler(TestObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return true;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
assertThat(this.observationRegistry.getCurrentObservationScope()).isNotNull();
}
}
}
@@ -48,7 +48,6 @@ import static org.mockito.Mockito.mock;
* @author Eddú Meléndez
* @author Rob Winch
* @author Brian Clozel
* @author Sebastien Deleuze
*/
class ForwardedHeaderFilterTests {
@@ -443,15 +442,6 @@ class ForwardedHeaderFilterTests {
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/first/second/mvc-showcase");
}
@Test
void shouldRemoveSingleTrailingSlash() throws Exception {
request.addHeader(X_FORWARDED_PREFIX, "/prefix,/");
request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/prefix/mvc-showcase");
}
@Test
void requestURLNewStringBuffer() throws Exception {
request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
@@ -21,16 +21,11 @@ import java.io.IOException;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistryAssert;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncEvent;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -144,21 +139,6 @@ class ServerHttpObservationFilterTests {
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped();
}
@Test
void shouldRegisterListenerForAsyncStarts() throws Exception {
CustomAsyncFilter customAsyncFilter = new CustomAsyncFilter();
this.mockFilterChain = new MockFilterChain(new NoOpServlet(), customAsyncFilter);
this.request.setAsyncSupported(true);
this.request.setDispatcherType(DispatcherType.REQUEST);
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
customAsyncFilter.asyncContext.dispatch();
this.request.setDispatcherType(DispatcherType.ASYNC);
AsyncContext newAsyncContext = this.request.startAsync();
newAsyncContext.complete();
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped();
}
@Test
void shouldCloseObservationAfterAsyncError() throws Exception {
this.request.setAsyncSupported(true);
@@ -207,26 +187,4 @@ class ServerHttpObservationFilterTests {
}
}
@SuppressWarnings("serial")
static class NoOpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
static class CustomAsyncFilter implements Filter {
AsyncContext asyncContext;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
this.asyncContext = servletRequest.startAsync();
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
@@ -32,7 +32,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link ForwardedHeaderTransformer}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
class ForwardedHeaderTransformerTests {
@@ -171,17 +170,6 @@ class ForwardedHeaderTransformerTests {
assertForwardedHeadersRemoved(request);
}
@Test // gh-33465
void shouldRemoveSingleTrailingSlash() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-Prefix", "/prefix,/");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/path"));
assertThat(request.getPath().value()).isEqualTo("/prefix/path");
assertForwardedHeadersRemoved(request);
}
@Test
void forwardedForNotPresent() {
HttpHeaders headers = new HttpHeaders();
@@ -1,53 +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.web.bind.annotation
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.aot.hint.*
/**
* Kotlin tests for {@link ControllerMappingReflectiveProcessor}.
*
* @author Sebastien Deleuze
*/
class ControllerMappingReflectiveProcessorKotlinTests {
private val processor = ControllerMappingReflectiveProcessor()
private val hints = ReflectionHints()
@Test
fun registerReflectiveHintsForFunctionWithDefaultArgumentValue() {
val method = SampleController::class.java.getDeclaredMethod("defaultValue", Boolean::class.javaObjectType)
processor.registerReflectionHints(hints, method)
Assertions.assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
{
Assertions.assertThat(it.type).isEqualTo(TypeReference.of(SampleController::class.java))
Assertions.assertThat(it.methods()).extracting<String> { executableHint: ExecutableHint -> executableHint.name }
.containsExactlyInAnyOrder("defaultValue", "defaultValue\$default")
}
)
}
class SampleController {
@GetMapping("/defaultValue")
fun defaultValue(@RequestParam(required = false) argument: Boolean? = false) = argument
}
}
@@ -44,8 +44,6 @@ import java.util.TimeZone;
import java.util.stream.Collectors;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncEvent;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletConnection;
@@ -923,19 +921,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
public AsyncContext startAsync(ServletRequest request, @Nullable ServletResponse response) {
Assert.state(this.asyncSupported, "Async not supported");
this.asyncStarted = true;
MockAsyncContext newAsyncContext = new MockAsyncContext(request, response);
if (this.asyncContext != null) {
try {
AsyncEvent startEvent = new AsyncEvent(newAsyncContext);
for (AsyncListener asyncListener : this.asyncContext.getListeners()) {
asyncListener.onStartAsync(startEvent);
}
}
catch (IOException ex) {
// ignore failures
}
}
this.asyncContext = newAsyncContext;
this.asyncContext = new MockAsyncContext(request, response);
return this.asyncContext;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,17 +57,6 @@ public interface ClientResponse {
*/
HttpStatusCode statusCode();
/**
* Return the raw status code of this response.
* @return the HTTP status as an integer value
* @since 5.1
* @deprecated in favor of {@link #statusCode()}, for removal in 7.0
*/
@Deprecated(since = "6.0", forRemoval = true)
default int rawStatusCode() {
return statusCode().value();
}
/**
* Return the headers of this response.
*/
@@ -795,7 +795,7 @@ public interface WebClient {
* .retrieve()
* .bodyToMono(Account.class)
* .onErrorResume(WebClientResponseException.class,
* ex -&gt; ex.getStatusCode().value() == 404 ? Mono.empty() : Mono.error(ex));
* ex -&gt; ex.getRawStatusCode() == 404 ? Mono.empty() : Mono.error(ex));
* </pre>
* @param statusPredicate to match responses with
* @param exceptionFunction to map the response to an error signal
@@ -172,7 +172,7 @@ public class WebClientResponseException extends WebClientException {
/**
* Return the raw HTTP status code value.
* @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #getStatusCode()}
*/
@Deprecated(since = "6.0")
public int getRawStatusCode() {
@@ -323,7 +323,6 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
@Override
@Deprecated
@SuppressWarnings("removal")
public int rawStatusCode() {
return this.statusCode.value();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author 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 @@ package org.springframework.web.reactive.function.server;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
@@ -31,7 +30,6 @@ import org.springframework.http.server.PathContainer;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
@@ -65,17 +63,13 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
pathContainer = this.pattern.extractPathWithinPattern(pathContainer);
String path = processPath(pathContainer.value());
if (!StringUtils.hasText(path) || isInvalidPath(path)) {
return Mono.empty();
if (path.contains("%")) {
path = StringUtils.uriDecode(path, StandardCharsets.UTF_8);
}
if (isInvalidEncodedInputPath(path)) {
if (!StringUtils.hasLength(path) || isInvalidPath(path)) {
return Mono.empty();
}
if (!(this.location instanceof UrlResource)) {
path = UriUtils.decode(path, StandardCharsets.UTF_8);
}
try {
Resource resource = this.location.createRelative(path);
if (resource.isReadable() && isResourceUnderLocation(resource)) {
@@ -90,47 +84,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
}
}
/**
* Process the given resource path.
* <p>The default implementation replaces:
* <ul>
* <li>Backslash with forward slash.
* <li>Duplicate occurrences of slash with a single slash.
* <li>Any combination of leading slash and control characters (00-1F and 7F)
* with a single "/" or "". For example {@code " / // foo/bar"}
* becomes {@code "/foo/bar"}.
* </ul>
*/
protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path);
}
private String cleanDuplicateSlashes(String path) {
StringBuilder sb = null;
char prev = 0;
for (int i = 0; i < path.length(); i++) {
char curr = path.charAt(i);
try {
if (curr == '/' && prev == '/') {
if (sb == null) {
sb = new StringBuilder(path.substring(0, i));
}
continue;
}
if (sb != null) {
sb.append(path.charAt(i));
}
}
finally {
prev = curr;
}
}
return (sb != null ? sb.toString() : path);
}
private String cleanLeadingSlash(String path) {
private String processPath(String path) {
boolean slash = false;
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) == '/') {
@@ -140,7 +94,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
if (i == 0 || (i == 1 && slash)) {
return path;
}
return (slash ? "/" + path.substring(i) : path.substring(i));
path = slash ? "/" + path.substring(i) : path.substring(i);
return path;
}
}
return (slash ? "/" : "");
@@ -162,31 +117,6 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
return false;
}
/**
* Check whether the given path contains invalid escape sequences.
* @param path the path to validate
* @return {@code true} if the path is invalid, {@code false} otherwise
*/
private boolean isInvalidEncodedInputPath(String path) {
if (path.contains("%")) {
try {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);
if (isInvalidPath(decodedPath)) {
return true;
}
decodedPath = processPath(decodedPath);
if (isInvalidPath(decodedPath)) {
return true;
}
}
catch (IllegalArgumentException ex) {
// May not be possible to decode...
}
}
return false;
}
private boolean isResourceUnderLocation(Resource resource) throws IOException {
if (resource.getClass() != this.location.getClass()) {
return false;
@@ -212,24 +142,15 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
return true;
}
locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() ? locationPath : locationPath + "/");
return (resourcePath.startsWith(locationPath) && !isInvalidEncodedInputPath(resourcePath));
if (!resourcePath.startsWith(locationPath)) {
return false;
}
if (resourcePath.contains("%") && StringUtils.uriDecode(resourcePath, StandardCharsets.UTF_8).contains("../")) {
return false;
}
return true;
}
private boolean isInvalidEncodedResourcePath(String resourcePath) {
if (resourcePath.contains("%")) {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars...
try {
String decodedPath = URLDecoder.decode(resourcePath, StandardCharsets.UTF_8);
if (decodedPath.contains("../") || decodedPath.contains("..\\")) {
return true;
}
}
catch (IllegalArgumentException ex) {
// May not be possible to decode...
}
}
return false;
}
@Override
public String toString() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,9 +70,9 @@ public interface ServerResponse {
* Return the status code of this response as integer.
* @return the status as an integer
* @since 5.2
* @deprecated in favor of {@link #statusCode()}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #statusCode()}
*/
@Deprecated(since = "6.0", forRemoval = true)
@Deprecated(since = "6.0")
int rawStatusCode();
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -84,12 +84,7 @@ class DefaultRenderingBuilder implements Rendering.RedirectBuilder {
@Override
public DefaultRenderingBuilder status(HttpStatusCode status) {
if (this.view instanceof RedirectView redirectView) {
redirectView.setStatusCode(status);
}
else {
this.status = status;
}
this.status = status;
return this;
}
@@ -84,7 +84,7 @@ class DefaultEntityResponseBuilderTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings("deprecation")
void status() {
String body = "foo";
Mono<EntityResponse<String>> result = EntityResponse.fromObject(body).status(HttpStatus.CREATED).build();
@@ -80,7 +80,7 @@ class DefaultServerResponseBuilderTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings("deprecation")
void status() {
Mono<ServerResponse> result = ServerResponse.status(HttpStatus.CREATED).build();
StepVerifier.create(result)
@@ -223,7 +223,7 @@ class RouterFunctionsTests {
public HttpStatus statusCode() {
return HttpStatus.OK;
}
@SuppressWarnings("removal")
@SuppressWarnings("deprecation")
@Override
public int rawStatusCode() {
return 200;
@@ -262,7 +262,7 @@ class RouterFunctionsTests {
public HttpStatus statusCode() {
return HttpStatus.OK;
}
@SuppressWarnings("removal")
@SuppressWarnings("deprecation")
@Override
public int rawStatusCode() {
return 200;
@@ -23,7 +23,6 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import static org.assertj.core.api.Assertions.assertThat;
@@ -49,17 +48,16 @@ class DefaultRenderingBuilderTests {
Rendering rendering = Rendering.redirectTo("abc").build();
Object view = rendering.view();
assertThat(view).isExactlyInstanceOf(RedirectView.class);
RedirectView redirectView = (RedirectView) view;
assertThat(redirectView.getUrl()).isEqualTo("abc");
assertThat(redirectView.isContextRelative()).isTrue();
assertThat(redirectView.isPropagateQuery()).isFalse();
assertThat(view.getClass()).isEqualTo(RedirectView.class);
assertThat(((RedirectView) view).getUrl()).isEqualTo("abc");
assertThat(((RedirectView) view).isContextRelative()).isTrue();
assertThat(((RedirectView) view).isPropagateQuery()).isFalse();
}
@Test
void viewName() {
Rendering rendering = Rendering.view("foo").build();
assertThat(rendering.view()).isEqualTo("foo");
}
@@ -115,7 +113,7 @@ class DefaultRenderingBuilderTests {
Rendering rendering = Rendering.redirectTo("foo").contextRelative(false).build();
Object view = rendering.view();
assertThat(view).isExactlyInstanceOf(RedirectView.class);
assertThat(view.getClass()).isEqualTo(RedirectView.class);
assertThat(((RedirectView) view).isContextRelative()).isFalse();
}
@@ -124,20 +122,10 @@ class DefaultRenderingBuilderTests {
Rendering rendering = Rendering.redirectTo("foo").propagateQuery(true).build();
Object view = rendering.view();
assertThat(view).isExactlyInstanceOf(RedirectView.class);
assertThat(view.getClass()).isEqualTo(RedirectView.class);
assertThat(((RedirectView) view).isPropagateQuery()).isTrue();
}
@Test // gh-33498
void redirectWithCustomStatus() {
HttpStatus status = HttpStatus.MOVED_PERMANENTLY;
Rendering rendering = Rendering.redirectTo("foo").status(status).build();
Object view = rendering.view();
assertThat(view).isExactlyInstanceOf(RedirectView.class);
assertThat(((RedirectView) view).getStatusCode()).isEqualTo(status);
}
private static class Foo {}
@@ -16,7 +16,6 @@
package org.springframework.web.reactive.result.view;
import java.net.URI;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Arrays;
@@ -197,20 +196,6 @@ class ViewResolutionResultHandlerTests {
assertThat(exchange.getResponse().getHeaders().getFirst("h")).isEqualTo("h1");
}
@Test // gh-33498
void handleRedirect() {
HttpStatus status = HttpStatus.MOVED_PERMANENTLY;
Rendering returnValue = Rendering.redirectTo("foo").status(status).build();
MethodParameter returnType = on(Handler.class).resolveReturnType(Rendering.class);
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext);
MockServerWebExchange exchange = MockServerWebExchange.from(get("/path"));
resultHandler(new UrlBasedViewResolver()).handleResult(exchange, result).block(Duration.ofSeconds(5));
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(status);
assertThat(exchange.getResponse().getHeaders().getLocation()).isEqualTo(URI.create("foo"));
}
@Test
void handleWithMultipleResolvers() {
testHandle("/account",
@@ -67,7 +67,6 @@ abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
@Override
@Deprecated
@SuppressWarnings("removal")
public int rawStatusCode() {
return this.statusCode.value();
}
@@ -89,7 +89,6 @@ final class DefaultAsyncServerResponse extends ErrorHandlingServerResponse imple
@Override
@Deprecated
@SuppressWarnings("removal")
public int rawStatusCode() {
return delegate(ServerResponse::rawStatusCode);
}
@@ -18,7 +18,6 @@ package org.springframework.web.servlet.function;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.function.Function;
@@ -30,8 +29,6 @@ import org.springframework.http.server.PathContainer;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
@@ -39,7 +36,6 @@ import org.springframework.web.util.pattern.PathPatternParser;
* Lookup function used by {@link RouterFunctions#resources(String, Resource)}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 5.2
*/
class PathResourceLookupFunction implements Function<ServerRequest, Optional<Resource>> {
@@ -66,17 +62,13 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
pathContainer = this.pattern.extractPathWithinPattern(pathContainer);
String path = processPath(pathContainer.value());
if (!StringUtils.hasText(path) || isInvalidPath(path)) {
return Optional.empty();
if (path.contains("%")) {
path = StringUtils.uriDecode(path, StandardCharsets.UTF_8);
}
if (isInvalidEncodedInputPath(path)) {
if (!StringUtils.hasLength(path) || isInvalidPath(path)) {
return Optional.empty();
}
if (!(this.location instanceof UrlResource)) {
path = UriUtils.decode(path, StandardCharsets.UTF_8);
}
try {
Resource resource = this.location.createRelative(path);
if (resource.isReadable() && isResourceUnderLocation(resource)) {
@@ -91,47 +83,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
}
}
/**
* Process the given resource path.
* <p>The default implementation replaces:
* <ul>
* <li>Backslash with forward slash.
* <li>Duplicate occurrences of slash with a single slash.
* <li>Any combination of leading slash and control characters (00-1F and 7F)
* with a single "/" or "". For example {@code " / // foo/bar"}
* becomes {@code "/foo/bar"}.
* </ul>
*/
protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path);
}
private String cleanDuplicateSlashes(String path) {
StringBuilder sb = null;
char prev = 0;
for (int i = 0; i < path.length(); i++) {
char curr = path.charAt(i);
try {
if ((curr == '/') && (prev == '/')) {
if (sb == null) {
sb = new StringBuilder(path.substring(0, i));
}
continue;
}
if (sb != null) {
sb.append(path.charAt(i));
}
}
finally {
prev = curr;
}
}
return sb != null ? sb.toString() : path;
}
private String cleanLeadingSlash(String path) {
private String processPath(String path) {
boolean slash = false;
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) == '/') {
@@ -141,7 +93,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
if (i == 0 || (i == 1 && slash)) {
return path;
}
return (slash ? "/" + path.substring(i) : path.substring(i));
path = slash ? "/" + path.substring(i) : path.substring(i);
return path;
}
}
return (slash ? "/" : "");
@@ -160,26 +113,6 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
return path.contains("..") && StringUtils.cleanPath(path).contains("../");
}
private boolean isInvalidEncodedInputPath(String path) {
if (path.contains("%")) {
try {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);
if (isInvalidPath(decodedPath)) {
return true;
}
decodedPath = processPath(decodedPath);
if (isInvalidPath(decodedPath)) {
return true;
}
}
catch (IllegalArgumentException ex) {
// May not be possible to decode...
}
}
return false;
}
private boolean isResourceUnderLocation(Resource resource) throws IOException {
if (resource.getClass() != this.location.getClass()) {
return false;
@@ -196,10 +129,6 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
resourcePath = classPathResource.getPath();
locationPath = StringUtils.cleanPath(((ClassPathResource) this.location).getPath());
}
else if (resource instanceof ServletContextResource servletContextResource) {
resourcePath = servletContextResource.getPath();
locationPath = StringUtils.cleanPath(((ServletContextResource) this.location).getPath());
}
else {
resourcePath = resource.getURL().getPath();
locationPath = StringUtils.cleanPath(this.location.getURL().getPath());
@@ -209,24 +138,13 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
return true;
}
locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() ? locationPath : locationPath + "/");
return (resourcePath.startsWith(locationPath) && !isInvalidEncodedResourcePath(resourcePath));
if (!resourcePath.startsWith(locationPath)) {
return false;
}
return !resourcePath.contains("%") ||
!StringUtils.uriDecode(resourcePath, StandardCharsets.UTF_8).contains("../");
}
private boolean isInvalidEncodedResourcePath(String resourcePath) {
if (resourcePath.contains("%")) {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars...
try {
String decodedPath = URLDecoder.decode(resourcePath, StandardCharsets.UTF_8);
if (decodedPath.contains("../") || decodedPath.contains("..\\")) {
return true;
}
}
catch (IllegalArgumentException ex) {
// May not be possible to decode...
}
}
return false;
}
@Override
public String toString() {
@@ -160,7 +160,6 @@ public abstract class RouterFunctions {
*/
public static RouterFunction<ServerResponse> resource(RequestPredicate predicate, Resource resource,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
return resources(new PredicateResourceLookupFunction(predicate, resource), headersConsumer);
}
@@ -198,7 +197,6 @@ public abstract class RouterFunctions {
*/
public static RouterFunction<ServerResponse> resources(String pattern, Resource location,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
return resources(resourceLookupFunction(pattern, location), headersConsumer);
}
@@ -242,9 +240,7 @@ public abstract class RouterFunctions {
* @return a router function that routes to resources
* @since 6.1
*/
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Optional<Resource>> lookupFunction,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Optional<Resource>> lookupFunction, BiConsumer<Resource, HttpHeaders> headersConsumer) {
return new ResourcesRouterFunction(lookupFunction, headersConsumer);
}
@@ -254,12 +250,12 @@ public abstract class RouterFunctions {
* can be used to change the {@code PathPatternParser} properties from the defaults, for instance to change
* {@linkplain PathPatternParser#setCaseSensitive(boolean) case sensitivity}.
* @param routerFunction the router function to change the parser in
* @param parser the parser to change to
* @param parser the parser to change to.
* @param <T> the type of response returned by the handler function
* @return the change router function
*/
public static <T extends ServerResponse> RouterFunction<T> changeParser(
RouterFunction<T> routerFunction, PathPatternParser parser) {
public static <T extends ServerResponse> RouterFunction<T> changeParser(RouterFunction<T> routerFunction,
PathPatternParser parser) {
Assert.notNull(routerFunction, "RouterFunction must not be null");
Assert.notNull(parser, "Parser must not be null");
@@ -1155,6 +1151,7 @@ public abstract class RouterFunctions {
public void accept(Visitor visitor) {
visitor.route(this.predicate, this.handlerFunction);
}
}
@@ -1176,10 +1173,13 @@ public abstract class RouterFunctions {
return this.predicate.nest(serverRequest)
.map(nestedRequest -> {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Nested predicate \"%s\" matches against \"%s\"",
this.predicate, serverRequest));
logger.trace(
String.format(
"Nested predicate \"%s\" matches against \"%s\"",
this.predicate, serverRequest));
}
Optional<HandlerFunction<T>> result = this.routerFunction.route(nestedRequest);
Optional<HandlerFunction<T>> result =
this.routerFunction.route(nestedRequest);
if (result.isPresent() && nestedRequest != serverRequest) {
serverRequest.attributes().clear();
serverRequest.attributes().putAll(nestedRequest.attributes());
@@ -1197,6 +1197,7 @@ public abstract class RouterFunctions {
this.routerFunction.accept(visitor);
visitor.endNested(this.predicate);
}
}
@@ -1206,11 +1207,11 @@ public abstract class RouterFunctions {
private final BiConsumer<Resource, HttpHeaders> headersConsumer;
public ResourcesRouterFunction(Function<ServerRequest, Optional<Resource>> lookupFunction,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
Assert.notNull(lookupFunction, "Lookup function must not be null");
Assert.notNull(headersConsumer, "Headers consumer must not be null");
Assert.notNull(lookupFunction, "Function must not be null");
Assert.notNull(headersConsumer, "HeadersConsumer must not be null");
this.lookupFunction = lookupFunction;
this.headersConsumer = headersConsumer;
}
@@ -1278,4 +1279,5 @@ public abstract class RouterFunctions {
}
}
}
@@ -68,9 +68,9 @@ public interface ServerResponse {
/**
* Return the status code of this response as integer.
* @return the status as an integer
* @deprecated in favor of {@link #statusCode()}, for removal in 7.0
* @deprecated as of 6.0, in favor of {@link #statusCode()}
*/
@Deprecated(since = "6.0", forRemoval = true)
@Deprecated(since = "6.0")
int rawStatusCode();
/**
@@ -66,7 +66,7 @@ class DefaultEntityResponseBuilderTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings("deprecation")
void status() {
String body = "foo";
EntityResponse<String> result =
@@ -55,7 +55,7 @@ class DefaultServerResponseBuilderTests {
static final ServerResponse.Context EMPTY_CONTEXT = Collections::emptyList;
@Test
@SuppressWarnings("removal")
@SuppressWarnings("deprecation")
void status() {
ServerResponse response = ServerResponse.status(HttpStatus.CREATED).build();
assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED);
@@ -20,7 +20,6 @@ import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
@@ -30,12 +29,10 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import jakarta.validation.executable.ExecutableValidator;
import jakarta.validation.metadata.BeanDescriptor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@@ -95,8 +92,6 @@ class MethodValidationTests {
@BeforeEach
void setup() throws Exception {
LocaleContextHolder.setDefaultLocale(Locale.UK);
LocalValidatorFactoryBean validatorBean = new LocalValidatorFactoryBean();
validatorBean.afterPropertiesSet();
this.jakartaValidator = new InvocationCountingValidator(validatorBean);
@@ -126,11 +121,6 @@ class MethodValidationTests {
return handlerAdapter;
}
@AfterEach
void reset() {
LocaleContextHolder.setDefaultLocale(null);
}
@Test
void modelAttribute() {
@@ -22,15 +22,14 @@ import java.util.Locale;
import java.util.Map;
import jakarta.servlet.jsp.tagext.Tag;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.annotation.NumberFormat.Style;
import org.springframework.format.number.PercentStyleFormatter;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
@@ -50,8 +49,6 @@ class EvalTagTests extends AbstractTagTests {
@BeforeEach
void setup() {
LocaleContextHolder.setDefaultLocale(Locale.UK);
context = createPageContext();
FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
factory.afterPropertiesSet();
@@ -61,11 +58,6 @@ class EvalTagTests extends AbstractTagTests {
tag.setPageContext(context);
}
@AfterEach
void reset() {
LocaleContextHolder.setDefaultLocale(null);
}
@Test
void printScopedAttributeResult() throws Exception {
@@ -89,12 +81,13 @@ class EvalTagTests extends AbstractTagTests {
@Test
void printFormattedScopedAttributeResult() throws Exception {
PercentStyleFormatter formatter = new PercentStyleFormatter();
tag.setExpression("bean.formattable");
int action = tag.doStartTag();
assertThat(action).isEqualTo(Tag.EVAL_BODY_INCLUDE);
action = tag.doEndTag();
assertThat(action).isEqualTo(Tag.EVAL_PAGE);
assertThat(((MockHttpServletResponse) context.getResponse()).getContentAsString()).isEqualTo("25%");
assertThat(((MockHttpServletResponse) context.getResponse()).getContentAsString()).isEqualTo(formatter.print(new BigDecimal(".25"), Locale.getDefault()));
}
@Test
+1 -1
View File
@@ -179,7 +179,7 @@
<property name="offset" value="0"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck">
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, RECORD_DEF"/>
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF"/>
<property name="tagOrder" value="@author, @since, @param, @see, @version, @serial, @deprecated"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck">