Compare commits

...

72 Commits

Author SHA1 Message Date
Spring Builds 28bb639736 Release v6.0.14 2023-11-16 13:56:32 +00:00
rstoyanchev 770cbd2fb5 Revise exception handling in HandlerMappingIntrospector
See gh-31588
2023-11-16 11:15:29 +00:00
Juergen Hoeller e5f04e5ddf Polishing 2023-11-16 11:34:31 +01:00
Brian Clozel c18784678d Reduce allocations in server conventions
This commit optimizes the default observation conventions to reduce
`KeyValues` allocations.
2023-11-16 09:00:24 +01:00
Stéphane Nicoll 51cdff591c Merge pull request #31612 from PiotrFLEURY
* pr/31612:
  Polish "Provide invalid class name in exception message"
  Provide invalid class name in exception message

Closes gh-31612
2023-11-15 20:49:55 +01:00
Stéphane Nicoll d93114df9a Polish "Provide invalid class name in exception message"
See gh-31612
2023-11-15 20:45:24 +01:00
PiotrFLEURY 5ac4c3bd76 Provide invalid class name in exception message
See gh-31612
2023-11-15 20:40:45 +01:00
rstoyanchev 05c3ffb2fb Use InvalidMimeTypeException in MimeTypeUtils#sortBySpecificity
Closes gh-31254
2023-11-15 18:57:23 +00:00
rstoyanchev 19e8ed130c Cache Filter for HandlerMappingIntrospector and log warnings
See gh-31588
2023-11-15 18:57:23 +00:00
Stéphane Nicoll 4464251754 Add missing runtime hints for ProblemDetail mixins
Closes gh-31606
2023-11-15 18:44:17 +01:00
Arjen Poutsma 8868fe2ea5 Fix position bug in NettyDataBuffer::toByteBuffer
Closes gh-31605
2023-11-15 14:23:43 +01:00
Juergen Hoeller c373f496f3 Consistent ordering of overloaded operations 2023-11-15 13:29:35 +01:00
Juergen Hoeller eb1883bdc4 Upgrade to Reactor 2022.0.13 and Netty 4.1.101
Includes RxJava 3.1.8, Apache HttpComponents Core Reactive 5.2.3, POI 5.2.4, Commons IO 2.15, WebJars Locator 0.55, OpenPDF 1.3.33, JRuby 9.4.5, H2 2.2.224, ActiveMQ 5.17.6, Checkstyle 10.12.5

Closes gh-31585
2023-11-15 13:29:13 +01:00
Stéphane Nicoll df6f66110f Fix wrong nullability requirement
Closes gh-31610
2023-11-15 12:01:26 +01:00
rstoyanchev ac235a0c43 Fix checkstyle violation 2023-11-14 20:00:16 +00:00
rstoyanchev a4e3af5cbe Revise HandlerMappingIntrospector caching
Expose methods to set and reset cache to use from a Filter instead
of a method to create such a Filter. Also use cached results only
if they match by dispatcher type and requestURI.

See gh-31588
2023-11-14 19:24:29 +00:00
Sam Brannen e71117dcdf Polish contribution
See gh-31598
2023-11-14 14:46:34 +01:00
Jason d5874ab99e Avoid duplicate resources in PathMatchingResourcePatternResolver on Windows
This commit updates PathMatchingResourcePatternResolver to avoid
returning duplicate resources on MS Windows when searching using the
`classpath*:` prefix and a wildcard pattern that matches resources
which are directly present in a JAR as well as present via classpath
manifest entries.

Closes gh-31598
2023-11-14 14:39:48 +01:00
Juergen Hoeller 99327b7db1 Preserve nested square brackets within parameter name
Closes gh-31596
2023-11-14 12:51:19 +01:00
Juergen Hoeller 3e06441d97 Cache SpEL-loaded types in StandardTypeLocator
Closes gh-31579
2023-11-14 12:46:16 +01:00
Brian Clozel 01f2925048 Upgrade to Micrometer 1.10.13
Closes gh-31586
2023-11-13 14:28:45 +01:00
rstoyanchev 44a37000ec HandlerMappingIntrospector exposes Filter for caching
Closes gh-31588
2023-11-10 17:43:52 +00:00
rstoyanchev 53fe5fafed Minor refactoring in HandlerMappingIntrospector
See gh-31588
2023-11-10 17:27:54 +00:00
rstoyanchev b9bd98fc5b Polishing in HandlerMappingIntrospector
See gh-31588
2023-11-10 11:27:44 +00:00
rstoyanchev 7714110940 Polishing in DefaultWebClient 2023-11-09 12:53:15 +00:00
rstoyanchev 75d1278bde Include port when logging URI in DefaultWebClient
Closes gh-30519
2023-11-09 12:53:15 +00:00
Sébastien Deleuze 620f558547 Register hints for superclass in BindingReflectionHintsRegistrar
Closes gh-31552
2023-11-09 13:11:36 +01:00
Juergen Hoeller 1e78cc35e5 Log4jLog re-resolves ExtendedLogger on deserialization
This is necessary for compatibility with Log4J 2.21, analogous to the existing re-resolution in Spring's SLF4J adapter.

Closes gh-31582
2023-11-09 11:46:22 +01:00
Juergen Hoeller 11fdb5ba17 Upgrade to Log4J 2.21.1, Tomcat 10.1.15, Jetty 11.0.18, Undertow 2.3.10, EclipseLink 3.0.4, Mockito 5.7 2023-11-09 10:53:47 +01:00
Juergen Hoeller 9957bb6918 Check for procedure vs function constants in CallMetaDataContext
Closes gh-31550
2023-11-09 10:52:51 +01:00
lorenzsimon 6a7a0bddb7 Restore support for recursive annotations in Kotlin
This commit reinstates support for recursive annotations in Kotlin.

See gh-28012
See gh-28618
See gh-31400
Closes gh-31518
2023-11-08 13:28:28 +01:00
rstoyanchev 5c012bbb0c Set maxAge correctly when expiring WebSession
Closes gh-31214
2023-11-08 11:44:36 +00:00
rstoyanchev 5df6e8825d Polishing in CookieWebSessionIdResolver
See gh-31214
2023-11-06 11:31:39 +00:00
rstoyanchev 654e822676 Fix Javadoc link 2023-11-03 14:38:53 +00:00
Stéphane Nicoll e943058b18 Merge pull request #31571 from izeye
* pr/31571:
  Add Javadoc since to ProblemDetail.setProperties()

Closes gh-31571
2023-11-08 08:04:10 +01:00
Johnny Lim cafb38ad1d Add Javadoc since to ProblemDetail.setProperties()
See gh-31571
2023-11-08 08:02:57 +01:00
Sam Brannen e778d2e908 Polish duplicate key exception error code support for SAP HANA database for R2DBC
See gh-31554
2023-11-07 17:12:29 +01:00
Sam Brannen c5bcfc7682 Polish contribution
See gh-31554
2023-11-07 17:08:44 +01:00
Sam Brannen 5752e03d97 Polishing 2023-11-07 16:58:21 +01:00
Arjen Poutsma dc26d3b0ec Defer cleanup in DefaultServerWebExchange
This commit ensures that the multipartRead flag is read in a deferred
block, and is not evaluated too early.

Closes gh-31567
2023-11-07 15:00:20 +01:00
Stéphane Nicoll 7f94c64b72 Merge pull request #31554 from baratrax
* pr/31554:
  Polish "Add SAP HANA duplicate key exception error code"
  Add SAP HANA duplicate key exception error code

Closes gh-31554
2023-11-07 10:37:14 +01:00
Stéphane Nicoll 50e55d5219 Polish "Add SAP HANA duplicate key exception error code"
See gh-31554
2023-11-07 10:33:14 +01:00
Fabrizio De Felice fcd4ba2f1f Add SAP HANA duplicate key exception error code
See gh-31554
2023-11-07 10:27:51 +01:00
Stéphane Nicoll e73107341c Document that pertypewithin is supported by Spring AOT
Closes gh-25887
2023-11-02 15:39:45 +01:00
rstoyanchev f8a33cd66e Remove outdated reference to Netty in rest-clients section
Closes gh-31526
2023-11-01 13:25:16 +00:00
CroBurnt f088d4a05b Fix link text in @⁠Transactional section of ref docs
Closes gh-31519
2023-10-30 13:29:30 +01:00
Stéphane Nicoll 84c28995fb Improve documentation for the default profile
Closes gh-29071
2023-10-25 11:20:21 +02:00
Juergen Hoeller 925fa0272b Polishing 2023-10-24 22:53:44 +02:00
Juergen Hoeller 09aa59f9e7 Avoid ResolvableType for simple assignability check in copyProperties
Closes gh-27246
2023-10-24 22:53:00 +02:00
Sébastien Deleuze 7874a59771 Remove invalid domain integration tests
Closes gh-31119
2023-10-24 17:54:39 +02:00
rstoyanchev 4dab35205d Avoid super.doTrace for ERROR dispatches
Closes gh-31457
2023-10-24 12:58:11 +01:00
rstoyanchev 5c6b9be3a1 Send 400 for PathVariable that is null after conversion
This implies a value was actually sent, but is not something
that can be converted to the expected type.

Closes gh-31382
2023-10-24 12:53:25 +01:00
Stéphane Nicoll 33fba8ea0c Merge pull request #31483 from bernie-schelberg-invicara
* pr/31483:
  Polish "Return consistent collection type for matrix variables"
  Return consistent collection type for matrix variables

Closes gh-31483
2023-10-24 10:56:36 +02:00
Stéphane Nicoll 9aa707ec4b Polish "Return consistent collection type for matrix variables"
See gh-31483
2023-10-24 10:55:42 +02:00
Bernie Schelberg ea30c8fb5b Return consistent collection type for matrix variables
See gh-31483
2023-10-24 10:27:47 +02:00
Juergen Hoeller 6bdf7ad36a Polishing 2023-10-23 17:32:45 +02:00
Juergen Hoeller d2108d2db6 Test for @Resource @Lazy fallback type match
See gh-31447
2023-10-23 17:32:35 +02:00
Juergen Hoeller f9be717602 Avoid getObjectType exception for uninitialized ProxyFactoryBean
Closes gh-31473
2023-10-23 17:32:25 +02:00
Stéphane Nicoll bb446a3905 Merge pull request #31433 from martin-lukas
* pr/31433:
  Polish "Ignore @Value on record property"
  Ignore @Value on record property

Closes gh-31433
2023-10-23 11:33:57 +02:00
Stéphane Nicoll f3dce4bb9a Polish "Ignore @Value on record property"
See gh-31433
2023-10-23 11:20:49 +02:00
Martin Lukas 70cb96c1d8 Ignore @Value on record property
See gh-31433
2023-10-23 11:15:24 +02:00
Brian Clozel 6ec264252b Ensure consistent value count in ConcurrentReferenceHashMap#Segment
Prior to this commit, the `ConcurrentReferenceHashMap#Segment` would
recalculate its element count during the restructure phase by
subtracting the number of polled elements from the queue to the current
count.
Later, the newly restructured `references` array would only contain
references that 1) are not in the set of elements to purge and
2) that hold a non-null value.

The issues with this approach are multiple. The newly calculated count
is only an estimate, as some situations can make it invalid, even
temporarily.

* since we initially collected all elements to be purged from the queue,
  the GC might have collected new values. This means that we might
  filter out more references that we initially intended to

* because the restructure operation re-creates new references for all
  elements in the original array, we might later get references from the
  queue that are not in the array anymore. This could lead to
  "duplicate" removals for the same value

Because several methods in the Segment class have special no-op behavior
when `count == 0`, an invalid count can lead to keys appearing missing
when they are actually still present. In some scenarios, this can
decrease the performance of the cache since values need to be
recalculated.

This commit fixes this inconsistency count issue by first using an
estimate in order to decide whether the array needs a resize and then by
counting the actual numbers of elements inserted in the restructured
array as the new count.

Fixes gh-31373
2023-10-19 15:54:31 +02:00
Brian Clozel 187b4e5ea6 Upgrade CI image to ubuntu:jammy-20231004 2023-10-19 10:02:15 +02:00
Brian Clozel 3bc607df53 Upgrade CI to JDK 17.0.9 2023-10-19 10:01:20 +02:00
Stéphane Nicoll 69c92f9ac7 Document when to use multiple property placeholder configurers
This commit adds a warning in the reference guide to address the
use cases where users might be tempted to use several property
placeholder configurers.

Closes gh-14623
2023-10-18 10:12:26 +02:00
Sébastien Deleuze 875eeabb6f Add a properties setter to ProblemDetail
Mainly to allow Kotlin idiomatic properties assignment.

Closes gh-31430
2023-10-17 14:11:24 +02:00
Juergen Hoeller 7a60e2024b BeanCopier sets name prefix for public classes as well
Includes consistent formatting of Spring-patched files.

Closes gh-28699
2023-10-15 16:09:17 +02:00
Brian Clozel da95542d8f Prevent duplicate HTTP server observations
Prior to this commit, HTTP server observations for Spring WebFlux could
be recorded twice for a single request in some cases. The "COMPLETE" and
"CANCEL" signals would race in the reactive pipeline and would trigger
both the `doOnComplete()` and ` `doOnCancel()` operators, each calling
`observation.stop()` on the current observation.
This would in fact publish two different observations for the same
request.

This commit ensures that the instrumentation uses the `Mono#tap`
operator to guard against this case and only call `Observation#stop`
once for each request.

Fixes gh-31417
2023-10-13 14:56:40 +02:00
Stéphane Nicoll 6669ab1ae7 Merge pull request #31416 from GVictorG7
* pr/31416:
  Polish "Replace deprecated method getBuildDir()"
  Replace deprecated method getBuildDir()

Closes gh-31416
2023-10-12 16:19:41 +02:00
Stéphane Nicoll d05ac097dd Polish "Replace deprecated method getBuildDir()"
See gh-31416
2023-10-12 16:14:23 +02:00
Victor Georgescu 35103b0cd1 Replace deprecated method getBuildDir()
See gh-31416
2023-10-12 16:10:40 +02:00
Spring Builds 3b50f992fe Next development version (v6.0.14-SNAPSHOT) 2023-10-12 09:28:10 +00:00
100 changed files with 2251 additions and 1398 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.12.4"
toolVersion = "10.12.5"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -131,8 +131,9 @@ public class ApiDiffPlugin implements Plugin<Project> {
}
private File getOutputFile(String baseLineVersion, Project project) {
Path outDir = Paths.get(project.getRootProject().getBuildDir().getAbsolutePath(),
"reports", "api-diff",
String buildDirectoryPath = project.getRootProject()
.getLayout().getBuildDirectory().getAsFile().get().getAbsolutePath();
Path outDir = Paths.get(buildDirectoryPath, "reports", "api-diff",
baseLineVersion + "_to_" + project.getRootProject().getVersion());
return project.file(outDir.resolve(project.getName() + ".html").toString());
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:jammy-20230816
FROM ubuntu:jammy-20231004
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+1 -1
View File
@@ -3,7 +3,7 @@ set -e
case "$1" in
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.9+11/bellsoft-jdk17.0.9+11-linux-amd64.tar.gz"
;;
java20)
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz"
@@ -6,8 +6,8 @@ it until later.
By default, there is a single instance of each aspect within the application
context. AspectJ calls this the singleton instantiation model. It is possible to define
aspects with alternate lifecycles. Spring supports AspectJ's `perthis` and `pertarget`
instantiation models; `percflow`, `percflowbelow`, and `pertypewithin` are not currently
aspects with alternate lifecycles. Spring supports AspectJ's `perthis`, `pertarget`, and
`pertypewithin` instantiation models; `percflow` and `percflowbelow` are not currently
supported.
You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect`
@@ -565,6 +565,17 @@ is a convenience mechanism that sets up a xref:core/beans/factory-extension.adoc
for you. If you need more control over the specific
`PropertySourcesPlaceholderConfigurer` setup, you can explicitly define it as a bean yourself.
[WARNING]
=====
Only one such element should be defined for a given application with the properties
that it needs. Several property placeholders can be configured as long as they have distinct
placeholder syntax (`${...}`).
If you need to modularize the source of properties used for the replacement, you should
not create multiple properties placeholders. Rather, each module should contribute a
`PropertySource` to the `Environment`. Alternatively, you can create your own
`PropertySourcesPlaceholderConfigurer` bean that gathers the properties to use.
=====
[[xsd-schemas-context-ac]]
=== Using `<annotation-config/>`
@@ -562,8 +562,9 @@ If no profile is active, the `dataSource` is created. You can see this
as a way to provide a default definition for one or more beans. If any
profile is enabled, the default profile does not apply.
You can change the name of the default profile by using `setDefaultProfiles()` on
the `Environment` or, declaratively, by using the `spring.profiles.default` property.
The name of the default profile is `default`. You can change the name of
the default profile by using `setDefaultProfiles()` on the `Environment` or,
declaratively, by using the `spring.profiles.default` property.
@@ -372,6 +372,17 @@ The `PropertySourcesPlaceholderConfigurer` not only looks for properties in the
file you specify. By default, if it cannot find a property in the specified properties files,
it checks against Spring `Environment` properties and regular Java `System` properties.
[WARNING]
=====
Only one such element should be defined for a given application with the properties
that it needs. Several property placeholders can be configured as long as they have distinct
placeholder syntax (`${...}`).
If you need to modularize the source of properties used for the replacement, you should
not create multiple properties placeholders. Rather, you should create your own
`PropertySourcesPlaceholderConfigurer` bean that gathers the properties to use.
=====
[TIP]
=====
You can use the `PropertySourcesPlaceholderConfigurer` to substitute class names, which
@@ -76,7 +76,7 @@ Kotlin::
Used at the class level as above, the annotation indicates a default for all methods of
the declaring class (as well as its subclasses). Alternatively, each method can be
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[null] for
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
further details on which methods Spring considers transactional. Note that a class-level
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
inherited methods need to be locally redeclared in order to participate in a
@@ -108,7 +108,7 @@ By default, the following `KeyValues` are created:
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
@@ -141,7 +141,7 @@ By default, the following `KeyValues` are created:
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
@@ -174,7 +174,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
[cols="a,a"]
|===
|Name | Description
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
@@ -203,7 +203,7 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
[cols="a,a"]
|===
|Name | Description
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
@@ -97,11 +97,7 @@ preparation and response extraction through callback interfaces.
The default constructor uses `java.net.HttpURLConnection` to perform requests. You can
switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`.
There is built-in support for the following:
* Apache HttpComponents
* Netty
* OkHttp
Currently, there is also built-in support for Apache HttpComponents and OkHttp.
For example, to switch to Apache HttpComponents, you can use the following:
+27 -29
View File
@@ -8,30 +8,30 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.14.3"))
api(platform("io.micrometer:micrometer-bom:1.10.12"))
api(platform("io.netty:netty-bom:4.1.100.Final"))
api(platform("io.micrometer:micrometer-bom:1.10.13"))
api(platform("io.netty:netty-bom:4.1.101.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2022.0.12"))
api(platform("io.projectreactor:reactor-bom:2022.0.13"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.17"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.18"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
api(platform("org.junit:junit-bom:5.9.3"))
api(platform("org.mockito:mockito-bom:5.6.0"))
api(platform("org.mockito:mockito-bom:5.7.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
api("com.github.librepdf:openpdf:1.3.30")
api("com.github.librepdf:openpdf:1.3.33")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.10.1")
api("com.google.protobuf:protobuf-java-util:3.23.2")
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
api("com.h2database:h2:2.2.220")
api("com.h2database:h2:2.2.224")
api("com.jayway.jsonpath:json-path:2.8.0")
api("com.rometools:rome:1.19.0")
api("com.squareup.okhttp3:mockwebserver:3.14.9")
@@ -43,7 +43,7 @@ dependencies {
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.0.3")
api("com.thoughtworks.xstream:xstream:1.4.20")
api("commons-io:commons-io:2.11.0")
api("commons-io:commons-io:2.15.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("io.micrometer:context-propagation:1.0.6")
api("io.mockk:mockk:1.13.4")
@@ -52,11 +52,11 @@ dependencies {
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.6")
api("io.reactivex.rxjava3:rxjava:3.1.8")
api("io.smallrye.reactive:mutiny:1.9.0")
api("io.undertow:undertow-core:2.3.8.Final")
api("io.undertow:undertow-servlet:2.3.8.Final")
api("io.undertow:undertow-websockets-jsr:2.3.8.Final")
api("io.undertow:undertow-core:2.3.10.Final")
api("io.undertow:undertow-servlet:2.3.10.Final")
api("io.undertow:undertow-websockets-jsr:2.3.10.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")
@@ -89,20 +89,20 @@ dependencies {
api("net.sf.jopt-simple:jopt-simple:5.0.4")
api("net.sourceforge.htmlunit:htmlunit:2.70.0")
api("org.apache-extras.beanshell:bsh:2.0b6")
api("org.apache.activemq:activemq-broker:5.17.4")
api("org.apache.activemq:activemq-kahadb-store:5.17.4")
api("org.apache.activemq:activemq-stomp:5.17.4")
api("org.apache.activemq:activemq-broker:5.17.6")
api("org.apache.activemq:activemq-kahadb-store:5.17.6")
api("org.apache.activemq:activemq-stomp:5.17.6")
api("org.apache.commons:commons-pool2:2.9.0")
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.2")
api("org.apache.poi:poi-ooxml:5.2.3")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.14")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.14")
api("org.apache.tomcat:tomcat-util:10.1.14")
api("org.apache.tomcat:tomcat-websocket:10.1.14")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.3")
api("org.apache.poi:poi-ooxml:5.2.4")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.15")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.15")
api("org.apache.tomcat:tomcat-util:10.1.15")
api("org.apache.tomcat:tomcat-websocket:10.1.15")
api("org.aspectj:aspectjrt:1.9.20.1")
api("org.aspectj:aspectjtools:1.9.20.1")
api("org.aspectj:aspectjweaver:1.9.20.1")
@@ -111,24 +111,22 @@ dependencies {
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
api("org.dom4j:dom4j:2.1.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.8")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.3")
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.10")
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")
api("org.ehcache:jcache:1.0.1")
api("org.freemarker:freemarker:2.3.32")
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
// is not available on Maven Central
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
api("org.glassfish:jakarta.el:4.0.2")
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
api("org.graalvm.sdk:graal-sdk:22.3.1")
api("org.hamcrest:hamcrest:2.2")
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.2")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.4.3.0")
api("org.jruby:jruby:9.4.5.0")
api("org.junit.support:testng-engine:1.0.4")
api("org.mozilla:rhino:1.7.14")
api("org.ogce:xpp3:1.1.6")
@@ -140,7 +138,7 @@ dependencies {
api("org.slf4j:slf4j-api:2.0.9")
api("org.testng:testng:7.8.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.53")
api("org.webjars:webjars-locator-core:0.55")
api("org.xmlunit:xmlunit-assertj:2.9.1")
api("org.xmlunit:xmlunit-matchers:2.9.1")
api("org.yaml:snakeyaml:1.33")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.0.13-SNAPSHOT
version=6.0.14
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+2 -1
View File
@@ -47,7 +47,8 @@ rootProject.children.each {project ->
settings.gradle.projectsLoaded {
gradleEnterprise {
buildScan {
File buildDir = settings.gradle.rootProject.getBuildDir()
File buildDir = settings.gradle.rootProject
.getLayout().getBuildDirectory().getAsFile().get()
buildDir.mkdirs()
new File(buildDir, "build-scan-uri.txt").text = "(build scan not generated)"
buildScanPublished { scan ->
@@ -40,11 +40,12 @@ import org.springframework.util.ClassUtils;
*/
class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
private static final boolean aspectJPresent = ClassUtils.isPresent(
"org.aspectj.lang.annotation.Pointcut", AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
private static final boolean aspectJPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
@Nullable
@Override
@Nullable
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
if (aspectJPresent) {
return AspectDelegate.processAheadOfTime(beanFactory);
@@ -52,6 +53,7 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
return null;
}
/**
* Inner class to avoid a hard dependency on AspectJ at runtime.
*/
@@ -61,9 +63,8 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
private static AspectContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
BeanFactoryAspectJAdvisorsBuilder builder = new BeanFactoryAspectJAdvisorsBuilder(beanFactory);
List<Advisor> advisors = builder.buildAspectJAdvisors();
return advisors.isEmpty() ? null : new AspectContribution(advisors);
return (advisors.isEmpty() ? null : new AspectContribution(advisors));
}
}
@@ -84,7 +85,6 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
}
}
}
}
}
@@ -265,18 +265,32 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
* Return the type of the proxy. Will check the singleton instance if
* already created, else fall back to the proxy interface (in case of just
* a single one), the target bean type, or the TargetSource's target class.
* @see org.springframework.aop.TargetSource#getTargetClass
* @see org.springframework.aop.framework.AopProxy#getProxyClass
*/
@Override
@Nullable
public Class<?> getObjectType() {
synchronized (this) {
if (this.singletonInstance != null) {
return this.singletonInstance.getClass();
}
}
// This might be incomplete since it potentially misses introduced interfaces
// from Advisors that will be lazily retrieved via setInterceptorNames.
return createAopProxy().getProxyClass(this.proxyClassLoader);
try {
// This might be incomplete since it potentially misses introduced interfaces
// from Advisors that will be lazily retrieved via setInterceptorNames.
return createAopProxy().getProxyClass(this.proxyClassLoader);
}
catch (AopConfigException ex) {
if (getTargetClass() == null) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to determine early proxy class: " + ex.getMessage());
}
return null;
}
else {
throw ex;
}
}
}
@Override
@@ -123,7 +123,6 @@ public abstract class ClassFilters {
public String toString() {
return getClass().getName() + ": " + Arrays.toString(this.filters);
}
}
@@ -164,7 +163,6 @@ public abstract class ClassFilters {
public String toString() {
return getClass().getName() + ": " + Arrays.toString(this.filters);
}
}
}
@@ -1000,18 +1000,20 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
*/
protected abstract static class PropertyHandler {
@Nullable
private final Class<?> propertyType;
private final boolean readable;
private final boolean writable;
public PropertyHandler(Class<?> propertyType, boolean readable, boolean writable) {
public PropertyHandler(@Nullable Class<?> propertyType, boolean readable, boolean writable) {
this.propertyType = propertyType;
this.readable = readable;
this.writable = writable;
}
@Nullable
public Class<?> getPropertyType() {
return this.propertyType;
}
@@ -23,6 +23,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.time.temporal.Temporal;
@@ -615,8 +616,8 @@ public abstract class BeanUtils {
* @return a corresponding MethodParameter object
*/
public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) {
if (pd instanceof GenericTypeAwarePropertyDescriptor typeAwarePd) {
return new MethodParameter(typeAwarePd.getWriteMethodParameter());
if (pd instanceof GenericTypeAwarePropertyDescriptor gpd) {
return new MethodParameter(gpd.getWriteMethodParameter());
}
else {
Method writeMethod = pd.getWriteMethod();
@@ -787,38 +788,28 @@ public abstract class BeanUtils {
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
"] not assignable to editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
Set<String> ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null);
CachedIntrospectionResults sourceResults = (actualEditable != source.getClass() ?
CachedIntrospectionResults.forClass(source.getClass()) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
PropertyDescriptor sourcePd = (sourceResults != null ?
sourceResults.getPropertyDescriptor(targetPd.getName()) : targetPd);
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null) {
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
boolean isAssignable =
(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetResolvableType.isAssignableFrom(sourceResolvableType));
if (isAssignable) {
if (isAssignable(writeMethod, readMethod)) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
ReflectionUtils.makeAccessible(readMethod);
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
@@ -832,6 +823,24 @@ public abstract class BeanUtils {
}
}
private static boolean isAssignable(Method writeMethod, Method readMethod) {
Type paramType = writeMethod.getGenericParameterTypes()[0];
if (paramType instanceof Class<?> clazz) {
return ClassUtils.isAssignable(clazz, readMethod.getReturnType());
}
else if (paramType.equals(readMethod.getGenericReturnType())) {
return true;
}
else {
ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0);
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetType.isAssignableFrom(sourceType));
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
@@ -896,7 +905,6 @@ public abstract class BeanUtils {
}
return kotlinConstructor.callBy(argParameters);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -129,7 +129,6 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
ReflectionUtils.makeAccessible(this.field);
return this.field.get(getWrappedInstance());
}
catch (IllegalAccessException ex) {
throw new InvalidPropertyException(getWrappedClass(),
this.field.getName(), "Field is not accessible", ex);
@@ -582,6 +582,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return;
}
if (method.getParameterCount() == 0) {
if (method.getDeclaringClass().isRecord()) {
// Annotations on the compact constructor arguments made available on accessors, ignoring.
return;
}
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " +
method);
@@ -69,6 +69,7 @@ class BeanDefinitionPropertyValueCodeGenerator {
BeanDefinitionPropertyValueCodeGenerator(GeneratedMethods generatedMethods,
@Nullable BiFunction<Object, ResolvableType, CodeBlock> customValueGenerator) {
this.generatedMethods = generatedMethods;
this.delegates = new ArrayList<>();
if (customValueGenerator != null) {
@@ -143,7 +144,6 @@ class BeanDefinitionPropertyValueCodeGenerator {
@Nullable
CodeBlock generateCode(Object value, ResolvableType type);
}
@@ -163,7 +163,6 @@ class BeanDefinitionPropertyValueCodeGenerator {
'\\', "\\\\"
);
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
@@ -231,7 +230,6 @@ class BeanDefinitionPropertyValueCodeGenerator {
}
return null;
}
}
@@ -321,8 +319,8 @@ class BeanDefinitionPropertyValueCodeGenerator {
this.emptyResult = emptyResult;
}
@Override
@SuppressWarnings("unchecked")
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (this.collectionType.isInstance(value)) {
@@ -445,7 +445,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
* @see #getResource
* @see Lazy
*/
protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {
protected Object buildLazyResourceProxy(LookupElement element, @Nullable String requestingBeanName) {
TargetSource ts = new TargetSource() {
@Override
public Class<?> getTargetClass() {
@@ -525,16 +525,16 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
String name = element.name;
if (factory instanceof AutowireCapableBeanFactory autowireCapableBeanFactory) {
DependencyDescriptor descriptor = element.getDependencyDescriptor();
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<>();
resource = autowireCapableBeanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
resource = autowireCapableBeanFactory.resolveDependency(
element.getDependencyDescriptor(), requestingBeanName, autowiredBeanNames, null);
if (resource == null) {
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
}
}
else {
resource = autowireCapableBeanFactory.resolveBeanByName(name, descriptor);
resource = autowireCapableBeanFactory.resolveBeanByName(name, element.getDependencyDescriptor());
autowiredBeanNames = Collections.singleton(name);
}
}
@@ -661,8 +661,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
/**
* Class representing injection information about an annotated field
* or setter method, supporting the @Resource annotation.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -38,6 +38,14 @@ import org.springframework.core.env.Profiles;
* for web applications. Profiles may also be activated declaratively in
* integration tests via the {@code @ActiveProfiles} annotation.
*
* <p>If no profile is active using one of those options, a default profile is
* enabled as a fallback. The name of the default profile is
* {@value AbstractEnvironment#RESERVED_DEFAULT_PROFILE_NAME}. This can be changed
* via {@link ConfigurableEnvironment#setDefaultProfiles} or declaratively by
* setting the {@link AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
* spring.profiles.default} property as a JVM system property, as an environment
* variable, or as a Servlet context parameter in {@code web.xml} for web applications.
*
* <p>The {@code @Profile} annotation may be used in any of the following ways:
* <ul>
* <li>as a type-level annotation on any class directly or indirectly annotated with
@@ -139,22 +139,24 @@ public class ProxyFactoryBeanTests {
private void testDoubleTargetSourceIsRejected(String name) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(DBL_TARGETSOURCE_CONTEXT, CLASS));
assertThatExceptionOfType(BeanCreationException.class).as("Should not allow TargetSource to be specified in interceptorNames as well as targetSource property")
.isThrownBy(() -> bf.getBean(name))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("TargetSource");
.isThrownBy(() -> bf.getBean(name))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("TargetSource");
}
@Test
public void testTargetSourceNotAtEndOfInterceptorNamesIsRejected() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(NOTLAST_TARGETSOURCE_CONTEXT, CLASS));
assertThatExceptionOfType(BeanCreationException.class).as("TargetSource or non-advised object must be last in interceptorNames")
.isThrownBy(() -> bf.getBean("targetSourceNotLast"))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("interceptorNames");
.isThrownBy(() -> bf.getBean("targetSourceNotLast"))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("interceptorNames");
}
@Test
@@ -171,7 +173,7 @@ public class ProxyFactoryBeanTests {
assertThat(cba.getCalls()).isEqualTo(1);
ProxyFactoryBean pfb = (ProxyFactoryBean) bf.getBean("&directTarget");
assertThat(TestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(TestBean.class);
}
@Test
@@ -181,7 +183,7 @@ public class ProxyFactoryBeanTests {
ITestBean tb = (ITestBean) bf.getBean("viaTargetSource");
assertThat(tb.getName()).isEqualTo("Adam");
ProxyFactoryBean pfb = (ProxyFactoryBean) bf.getBean("&viaTargetSource");
assertThat(TestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(TestBean.class);
}
@Test
@@ -190,11 +192,15 @@ public class ProxyFactoryBeanTests {
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(TARGETSOURCE_CONTEXT, CLASS));
ITestBean tb = (ITestBean) bf.getBean("noTarget");
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
tb.getName())
.withMessage("getName");
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(tb::getName).withMessage("getName");
FactoryBean<?> pfb = (ProxyFactoryBean) bf.getBean("&noTarget");
assertThat(ITestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(ITestBean.class);
}
@Test
public void testGetObjectTypeOnUninitializedFactoryBean() {
ProxyFactoryBean pfb = new ProxyFactoryBean();
assertThat(pfb.getObjectType()).isNull();
}
/**
@@ -227,12 +233,12 @@ public class ProxyFactoryBeanTests {
@Test
public void testPrototypeInstancesAreNotEqual() {
assertThat(ITestBean.class.isAssignableFrom(factory.getType("prototype"))).as("Has correct object type").isTrue();
assertThat(factory.getType("prototype")).isAssignableTo(ITestBean.class);
ITestBean test2 = (ITestBean) factory.getBean("prototype");
ITestBean test2_1 = (ITestBean) factory.getBean("prototype");
assertThat(test2).as("Prototype instances !=").isNotSameAs(test2_1);
assertThat(test2).as("Prototype instances equal").isEqualTo(test2_1);
assertThat(ITestBean.class.isAssignableFrom(factory.getType("prototype"))).as("Has correct object type").isTrue();
assertThat(factory.getType("prototype")).isAssignableTo(ITestBean.class);
}
/**
@@ -291,13 +297,13 @@ public class ProxyFactoryBeanTests {
@Test
public void testCanGetFactoryReferenceAndManipulate() {
ProxyFactoryBean config = (ProxyFactoryBean) factory.getBean("&test1");
assertThat(ITestBean.class.isAssignableFrom(config.getObjectType())).as("Has correct object type").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("test1"))).as("Has correct object type").isTrue();
assertThat(config.getObjectType()).isAssignableTo(ITestBean.class);
assertThat(factory.getType("test1")).isAssignableTo(ITestBean.class);
// Trigger lazy initialization.
config.getObject();
assertThat(config.getAdvisors().length).as("Have one advisors").isEqualTo(1);
assertThat(ITestBean.class.isAssignableFrom(config.getObjectType())).as("Has correct object type").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("test1"))).as("Has correct object type").isTrue();
assertThat(config.getObjectType()).isAssignableTo(ITestBean.class);
assertThat(factory.getType("test1")).isAssignableTo(ITestBean.class);
ITestBean tb = (ITestBean) factory.getBean("test1");
// no exception
@@ -543,6 +543,25 @@ public class CommonAnnotationBeanPostProcessorTests {
assertThat(tb.getName()).isEqualTo("notLazyAnymore");
}
@Test
public void testLazyResolutionWithFallbackTypeMatch() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
CommonAnnotationBeanPostProcessor bpp = new CommonAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(LazyResourceCglibInjectionBean.class));
bf.registerBeanDefinition("tb", new RootBeanDefinition(TestBean.class));
LazyResourceCglibInjectionBean bean = (LazyResourceCglibInjectionBean) bf.getBean("annotatedBean");
assertThat(bf.containsSingleton("tb")).isFalse();
bean.testBean.setName("notLazyAnymore");
assertThat(bf.containsSingleton("tb")).isTrue();
TestBean tb = (TestBean) bf.getBean("tb");
assertThat(tb.getName()).isEqualTo("notLazyAnymore");
}
public static class AnnotatedInitDestroyBean {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -36,6 +36,10 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
protected static class GenericTestEvent<T> extends ApplicationEvent {
@@ -51,6 +55,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class SmartGenericTestEvent<T> extends GenericTestEvent<T> implements ResolvableTypeProvider {
private final ResolvableType resolvableType;
@@ -67,6 +72,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class StringEvent extends GenericTestEvent<String> {
public StringEvent(Object source, String payload) {
@@ -74,6 +80,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class LongEvent extends GenericTestEvent<Long> {
public LongEvent(Object source, Long payload) {
@@ -81,31 +88,31 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
static class GenericEventListener implements ApplicationListener<GenericTestEvent<?>> {
@Override
public void onApplicationEvent(GenericTestEvent<?> event) {
}
}
static class ObjectEventListener implements ApplicationListener<GenericTestEvent<Object>> {
@Override
public void onApplicationEvent(GenericTestEvent<Object> event) {
}
}
static class UpperBoundEventListener
implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
static class UpperBoundEventListener implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
@Override
public void onApplicationEvent(GenericTestEvent<? extends RuntimeException> event) {
}
}
static class StringEventListener implements ApplicationListener<GenericTestEvent<String>> {
@Override
@@ -113,6 +120,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
@SuppressWarnings("rawtypes")
static class RawApplicationListener implements ApplicationListener {
@@ -121,10 +129,10 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
static class TestEvents {
public GenericTestEvent<?> wildcardEvent;
}
}
@@ -104,7 +104,8 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test
public void genericListenerStrictTypeSubClass() {
supportsEventType(false, ObjectEventListener.class, ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
supportsEventType(false, ObjectEventListener.class,
ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -161,7 +161,8 @@ public interface GeneratedFiles {
private static String getClassNamePath(String className) {
Assert.hasLength(className, "'className' must not be empty");
Assert.isTrue(isJavaIdentifier(className), "'className' must be a valid identifier");
Assert.isTrue(isJavaIdentifier(className),
"'className' must be a valid identifier, got '" + className + "'");
return ClassUtils.convertClassNameToResourcePath(className) + ".java";
}
@@ -159,6 +159,10 @@ public class BindingReflectionHintsRegistrar {
for (ResolvableType genericResolvableType : resolvableType.getGenerics()) {
collectReferencedTypes(types, genericResolvableType);
}
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && superClass != Object.class && superClass != Record.class && superClass != Enum.class) {
types.add(superClass);
}
}
}
@@ -156,12 +156,10 @@ public class FilePatternResourceHintsRegistrar {
private final List<String> fileExtensions = new ArrayList<>();
private Builder() {
// no-op
}
/**
* Consider the specified classpath locations.
* <p>A location can either be a special {@value ResourceUtils#CLASSPATH_URL_PREFIX}
@@ -254,7 +252,6 @@ public class FilePatternResourceHintsRegistrar {
public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) {
build().registerHints(hints, classLoader);
}
}
}
@@ -16,7 +16,6 @@
package org.springframework.cglib.beans;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Map;
@@ -42,155 +41,154 @@ import org.springframework.cglib.core.TypeUtils;
@SuppressWarnings({"rawtypes", "unchecked"})
abstract public class BeanCopier
{
private static final BeanCopierKey KEY_FACTORY =
(BeanCopierKey)KeyFactory.create(BeanCopierKey.class);
private static final Type CONVERTER =
TypeUtils.parseType("org.springframework.cglib.core.Converter");
private static final Type BEAN_COPIER =
TypeUtils.parseType("org.springframework.cglib.beans.BeanCopier");
private static final Signature COPY =
new Signature("copy", Type.VOID_TYPE, new Type[]{ Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER });
private static final Signature CONVERT =
TypeUtils.parseSignature("Object convert(Object, Class, Object)");
private static final BeanCopierKey KEY_FACTORY =
(BeanCopierKey)KeyFactory.create(BeanCopierKey.class);
private static final Type CONVERTER =
TypeUtils.parseType("org.springframework.cglib.core.Converter");
private static final Type BEAN_COPIER =
TypeUtils.parseType("org.springframework.cglib.beans.BeanCopier");
private static final Signature COPY =
new Signature("copy", Type.VOID_TYPE, new Type[]{ Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER });
private static final Signature CONVERT =
TypeUtils.parseSignature("Object convert(Object, Class, Object)");
interface BeanCopierKey {
public Object newInstance(String source, String target, boolean useConverter);
}
interface BeanCopierKey {
public Object newInstance(String source, String target, boolean useConverter);
}
public static BeanCopier create(Class source, Class target, boolean useConverter) {
Generator gen = new Generator();
gen.setSource(source);
gen.setTarget(target);
gen.setUseConverter(useConverter);
return gen.create();
}
public static BeanCopier create(Class source, Class target, boolean useConverter) {
Generator gen = new Generator();
gen.setSource(source);
gen.setTarget(target);
gen.setUseConverter(useConverter);
return gen.create();
}
abstract public void copy(Object from, Object to, Converter converter);
abstract public void copy(Object from, Object to, Converter converter);
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanCopier.class.getName());
private Class source;
private Class target;
private boolean useConverter;
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanCopier.class.getName());
private Class source;
private Class target;
private boolean useConverter;
public Generator() {
super(SOURCE);
}
public Generator() {
super(SOURCE);
}
public void setSource(Class source) {
if(!Modifier.isPublic(source.getModifiers())){
setNamePrefix(source.getName());
}
this.source = source;
}
public void setSource(Class source) {
this.source = source;
// SPRING PATCH BEGIN
setContextClass(source);
setNamePrefix(source.getName());
// SPRING PATCH END
}
public void setTarget(Class target) {
if(!Modifier.isPublic(target.getModifiers())){
setNamePrefix(target.getName());
}
this.target = target;
public void setTarget(Class target) {
this.target = target;
// SPRING PATCH BEGIN
setContextClass(target);
setNamePrefix(target.getName());
// SPRING PATCH END
}
}
public void setUseConverter(boolean useConverter) {
this.useConverter = useConverter;
}
public void setUseConverter(boolean useConverter) {
this.useConverter = useConverter;
}
@Override
@Override
protected ClassLoader getDefaultClassLoader() {
return source.getClassLoader();
}
return source.getClassLoader();
}
@Override
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(source);
}
return ReflectUtils.getProtectionDomain(source);
}
public BeanCopier create() {
Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);
return (BeanCopier)super.create(key);
}
public BeanCopier create() {
Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);
return (BeanCopier)super.create(key);
}
@Override
@Override
public void generateClass(ClassVisitor v) {
Type sourceType = Type.getType(source);
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
BEAN_COPIER,
null,
Constants.SOURCE_FILE);
Type sourceType = Type.getType(source);
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
BEAN_COPIER,
null,
Constants.SOURCE_FILE);
EmitUtils.null_constructor(ce);
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);
PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);
EmitUtils.null_constructor(ce);
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);
PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);
Map names = new HashMap();
for (PropertyDescriptor getter : getters) {
names.put(getter.getName(), getter);
}
Local targetLocal = e.make_local();
Local sourceLocal = e.make_local();
if (useConverter) {
e.load_arg(1);
e.checkcast(targetType);
e.store_local(targetLocal);
e.load_arg(0);
e.checkcast(sourceType);
e.store_local(sourceLocal);
} else {
e.load_arg(1);
e.checkcast(targetType);
e.load_arg(0);
e.checkcast(sourceType);
}
for (PropertyDescriptor setter : setters) {
PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
if (getter != null) {
MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
if (useConverter) {
Type setterType = write.getSignature().getArgumentTypes()[0];
e.load_local(targetLocal);
e.load_arg(2);
e.load_local(sourceLocal);
e.invoke(read);
e.box(read.getSignature().getReturnType());
EmitUtils.load_class(e, setterType);
e.push(write.getSignature().getName());
e.invoke_interface(CONVERTER, CONVERT);
e.unbox_or_zero(setterType);
e.invoke(write);
} else if (compatible(getter, setter)) {
e.dup2();
e.invoke(read);
e.invoke(write);
}
}
}
e.return_value();
e.end_method();
ce.end_class();
}
Map names = new HashMap();
for (PropertyDescriptor getter : getters) {
names.put(getter.getName(), getter);
}
Local targetLocal = e.make_local();
Local sourceLocal = e.make_local();
if (useConverter) {
e.load_arg(1);
e.checkcast(targetType);
e.store_local(targetLocal);
e.load_arg(0);
e.checkcast(sourceType);
e.store_local(sourceLocal);
} else {
e.load_arg(1);
e.checkcast(targetType);
e.load_arg(0);
e.checkcast(sourceType);
}
for (PropertyDescriptor setter : setters) {
PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
if (getter != null) {
MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
if (useConverter) {
Type setterType = write.getSignature().getArgumentTypes()[0];
e.load_local(targetLocal);
e.load_arg(2);
e.load_local(sourceLocal);
e.invoke(read);
e.box(read.getSignature().getReturnType());
EmitUtils.load_class(e, setterType);
e.push(write.getSignature().getName());
e.invoke_interface(CONVERTER, CONVERT);
e.unbox_or_zero(setterType);
e.invoke(write);
} else if (compatible(getter, setter)) {
e.dup2();
e.invoke(read);
e.invoke(write);
}
}
}
e.return_value();
e.end_method();
ce.end_class();
}
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
// TODO: allow automatic widening conversions?
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
}
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
// TODO: allow automatic widening conversions?
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
}
@Override
@Override
protected Object firstInstance(Class type) {
return ReflectUtils.newInstance(type);
}
return ReflectUtils.newInstance(type);
}
@Override
@Override
protected Object nextInstance(Object instance) {
return instance;
}
}
return instance;
}
}
}
@@ -36,131 +36,131 @@ import org.springframework.cglib.core.ReflectUtils;
@SuppressWarnings({"rawtypes", "unchecked"})
public class BeanGenerator extends AbstractClassGenerator
{
private static final Source SOURCE = new Source(BeanGenerator.class.getName());
private static final BeanGeneratorKey KEY_FACTORY =
(BeanGeneratorKey)KeyFactory.create(BeanGeneratorKey.class);
private static final Source SOURCE = new Source(BeanGenerator.class.getName());
private static final BeanGeneratorKey KEY_FACTORY =
(BeanGeneratorKey)KeyFactory.create(BeanGeneratorKey.class);
interface BeanGeneratorKey {
public Object newInstance(String superclass, Map props);
}
interface BeanGeneratorKey {
public Object newInstance(String superclass, Map props);
}
private Class superclass;
private Map props = new HashMap();
private boolean classOnly;
private Class superclass;
private Map props = new HashMap();
private boolean classOnly;
public BeanGenerator() {
super(SOURCE);
}
public BeanGenerator() {
super(SOURCE);
}
/**
* Set the class which the generated class will extend. The class
* must not be declared as final, and must have a non-private
* no-argument constructor.
* @param superclass class to extend, or null to extend Object
*/
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.equals(Object.class)) {
superclass = null;
}
this.superclass = superclass;
/**
* Set the class which the generated class will extend. The class
* must not be declared as final, and must have a non-private
* no-argument constructor.
* @param superclass class to extend, or null to extend Object
*/
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.equals(Object.class)) {
superclass = null;
}
this.superclass = superclass;
// SPRING PATCH BEGIN
setContextClass(superclass);
// SPRING PATCH END
}
}
public void addProperty(String name, Class type) {
if (props.containsKey(name)) {
throw new IllegalArgumentException("Duplicate property name \"" + name + "\"");
}
props.put(name, Type.getType(type));
}
public void addProperty(String name, Class type) {
if (props.containsKey(name)) {
throw new IllegalArgumentException("Duplicate property name \"" + name + "\"");
}
props.put(name, Type.getType(type));
}
@Override
@Override
protected ClassLoader getDefaultClassLoader() {
if (superclass != null) {
return superclass.getClassLoader();
} else {
return null;
}
}
if (superclass != null) {
return superclass.getClassLoader();
} else {
return null;
}
}
@Override
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(superclass);
}
return ReflectUtils.getProtectionDomain(superclass);
}
public Object create() {
classOnly = false;
return createHelper();
}
public Object create() {
classOnly = false;
return createHelper();
}
public Object createClass() {
classOnly = true;
return createHelper();
}
public Object createClass() {
classOnly = true;
return createHelper();
}
private Object createHelper() {
if (superclass != null) {
setNamePrefix(superclass.getName());
}
String superName = (superclass != null) ? superclass.getName() : "java.lang.Object";
Object key = KEY_FACTORY.newInstance(superName, props);
return super.create(key);
}
private Object createHelper() {
if (superclass != null) {
setNamePrefix(superclass.getName());
}
String superName = (superclass != null) ? superclass.getName() : "java.lang.Object";
Object key = KEY_FACTORY.newInstance(superName, props);
return super.create(key);
}
@Override
@Override
public void generateClass(ClassVisitor v) throws Exception {
int size = props.size();
String[] names = (String[])props.keySet().toArray(new String[size]);
Type[] types = new Type[size];
for (int i = 0; i < size; i++) {
types[i] = (Type)props.get(names[i]);
}
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
superclass != null ? Type.getType(superclass) : Constants.TYPE_OBJECT,
null,
null);
EmitUtils.null_constructor(ce);
EmitUtils.add_properties(ce, names, types);
ce.end_class();
}
int size = props.size();
String[] names = (String[])props.keySet().toArray(new String[size]);
Type[] types = new Type[size];
for (int i = 0; i < size; i++) {
types[i] = (Type)props.get(names[i]);
}
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
superclass != null ? Type.getType(superclass) : Constants.TYPE_OBJECT,
null,
null);
EmitUtils.null_constructor(ce);
EmitUtils.add_properties(ce, names, types);
ce.end_class();
}
@Override
@Override
protected Object firstInstance(Class type) {
if (classOnly) {
return type;
} else {
return ReflectUtils.newInstance(type);
}
}
if (classOnly) {
return type;
} else {
return ReflectUtils.newInstance(type);
}
}
@Override
@Override
protected Object nextInstance(Object instance) {
Class protoclass = (instance instanceof Class<?> clazz) ? clazz : instance.getClass();
if (classOnly) {
return protoclass;
} else {
return ReflectUtils.newInstance(protoclass);
}
}
Class protoclass = (instance instanceof Class<?> clazz) ? clazz : instance.getClass();
if (classOnly) {
return protoclass;
} else {
return ReflectUtils.newInstance(protoclass);
}
}
public static void addProperties(BeanGenerator gen, Map props) {
for (Iterator it = props.keySet().iterator(); it.hasNext();) {
String name = (String)it.next();
gen.addProperty(name, (Class)props.get(name));
}
}
public static void addProperties(BeanGenerator gen, Map props) {
for (Iterator it = props.keySet().iterator(); it.hasNext();) {
String name = (String)it.next();
gen.addProperty(name, (Class)props.get(name));
}
}
public static void addProperties(BeanGenerator gen, Class type) {
addProperties(gen, ReflectUtils.getBeanProperties(type));
}
public static void addProperties(BeanGenerator gen, Class type) {
addProperties(gen, ReflectUtils.getBeanProperties(type));
}
public static void addProperties(BeanGenerator gen, PropertyDescriptor[] descriptors) {
for (PropertyDescriptor descriptor : descriptors) {
gen.addProperty(descriptor.getName(), descriptor.getPropertyType());
}
}
public static void addProperties(BeanGenerator gen, PropertyDescriptor[] descriptors) {
for (PropertyDescriptor descriptor : descriptors) {
gen.addProperty(descriptor.getName(), descriptor.getPropertyType());
}
}
}
@@ -41,309 +41,309 @@ import org.springframework.cglib.core.ReflectUtils;
*/
@SuppressWarnings({"rawtypes", "unchecked"})
abstract public class BeanMap implements Map {
/**
* Limit the properties reflected in the key set of the map
* to readable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_GETTER = 1;
/**
* Limit the properties reflected in the key set of the map
* to readable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_GETTER = 1;
/**
* Limit the properties reflected in the key set of the map
* to writable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_SETTER = 2;
/**
* Limit the properties reflected in the key set of the map
* to writable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_SETTER = 2;
/**
* Helper method to create a new <code>BeanMap</code>. For finer
* control over the generated instance, use a new instance of
* <code>BeanMap.Generator</code> instead of this static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
public static BeanMap create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
/**
* Helper method to create a new <code>BeanMap</code>. For finer
* control over the generated instance, use a new instance of
* <code>BeanMap.Generator</code> instead of this static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
public static BeanMap create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanMap.class.getName());
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanMap.class.getName());
private static final BeanMapKey KEY_FACTORY =
(BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);
private static final BeanMapKey KEY_FACTORY =
(BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);
interface BeanMapKey {
public Object newInstance(Class type, int require);
}
interface BeanMapKey {
public Object newInstance(Class type, int require);
}
private Object bean;
private Class beanClass;
private int require;
private Object bean;
private Class beanClass;
private int require;
public Generator() {
super(SOURCE);
}
public Generator() {
super(SOURCE);
}
/**
* Set the bean that the generated map should reflect. The bean may be swapped
* out for another bean of the same type using {@link #setBean}.
* Calling this method overrides any value previously set using {@link #setBeanClass}.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param bean the initial bean
*/
public void setBean(Object bean) {
this.bean = bean;
if (bean != null) {
/**
* Set the bean that the generated map should reflect. The bean may be swapped
* out for another bean of the same type using {@link #setBean}.
* Calling this method overrides any value previously set using {@link #setBeanClass}.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param bean the initial bean
*/
public void setBean(Object bean) {
this.bean = bean;
if (bean != null) {
beanClass = bean.getClass();
// SPRING PATCH BEGIN
setContextClass(beanClass);
// SPRING PATCH END
}
}
}
/**
* Set the class of the bean that the generated map should support.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param beanClass the class of the bean
*/
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
/**
* Set the class of the bean that the generated map should support.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param beanClass the class of the bean
*/
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
/**
* Limit the properties reflected by the generated map.
* @param require any combination of {@link #REQUIRE_GETTER} and
* {@link #REQUIRE_SETTER}; default is zero (any property allowed)
*/
public void setRequire(int require) {
this.require = require;
}
/**
* Limit the properties reflected by the generated map.
* @param require any combination of {@link #REQUIRE_GETTER} and
* {@link #REQUIRE_SETTER}; default is zero (any property allowed)
*/
public void setRequire(int require) {
this.require = require;
}
@Override
@Override
protected ClassLoader getDefaultClassLoader() {
return beanClass.getClassLoader();
}
return beanClass.getClassLoader();
}
@Override
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(beanClass);
}
return ReflectUtils.getProtectionDomain(beanClass);
}
/**
* Create a new instance of the <code>BeanMap</code>. An existing
* generated class will be reused if possible.
*/
public BeanMap create() {
if (beanClass == null) {
/**
* Create a new instance of the <code>BeanMap</code>. An existing
* generated class will be reused if possible.
*/
public BeanMap create() {
if (beanClass == null) {
throw new IllegalArgumentException("Class of bean unknown");
}
setNamePrefix(beanClass.getName());
return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require));
}
setNamePrefix(beanClass.getName());
return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require));
}
@Override
@Override
public void generateClass(ClassVisitor v) throws Exception {
new BeanMapEmitter(v, getClassName(), beanClass, require);
}
new BeanMapEmitter(v, getClassName(), beanClass, require);
}
@Override
@Override
protected Object firstInstance(Class type) {
return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean);
}
return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean);
}
@Override
@Override
protected Object nextInstance(Object instance) {
return ((BeanMap)instance).newInstance(bean);
}
}
return ((BeanMap)instance).newInstance(bean);
}
}
/**
* Create a new <code>BeanMap</code> instance using the specified bean.
* This is faster than using the {@link #create} static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
abstract public BeanMap newInstance(Object bean);
/**
* Create a new <code>BeanMap</code> instance using the specified bean.
* This is faster than using the {@link #create} static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
abstract public BeanMap newInstance(Object bean);
/**
* Get the type of a property.
* @param name the name of the JavaBean property
* @return the type of the property, or null if the property does not exist
*/
abstract public Class getPropertyType(String name);
/**
* Get the type of a property.
* @param name the name of the JavaBean property
* @return the type of the property, or null if the property does not exist
*/
abstract public Class getPropertyType(String name);
protected Object bean;
protected Object bean;
protected BeanMap() {
}
protected BeanMap() {
}
protected BeanMap(Object bean) {
setBean(bean);
}
protected BeanMap(Object bean) {
setBean(bean);
}
@Override
@Override
public Object get(Object key) {
return get(bean, key);
}
return get(bean, key);
}
@Override
@Override
public Object put(Object key, Object value) {
return put(bean, key, value);
}
return put(bean, key, value);
}
/**
* Get the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param bean the bean to query; must be compatible with the type of
* this <code>BeanMap</code>
* @param key must be a String
* @return the current value, or null if there is no matching property
*/
abstract public Object get(Object bean, Object key);
/**
* Get the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param bean the bean to query; must be compatible with the type of
* this <code>BeanMap</code>
* @param key must be a String
* @return the current value, or null if there is no matching property
*/
abstract public Object get(Object bean, Object key);
/**
* Set the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param key must be a String
* @return the old value, if there was one, or null
*/
abstract public Object put(Object bean, Object key, Object value);
/**
* Set the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param key must be a String
* @return the old value, if there was one, or null
*/
abstract public Object put(Object bean, Object key, Object value);
/**
* Change the underlying bean this map should use.
* @param bean the new JavaBean
* @see #getBean
*/
public void setBean(Object bean) {
this.bean = bean;
}
/**
* Change the underlying bean this map should use.
* @param bean the new JavaBean
* @see #getBean
*/
public void setBean(Object bean) {
this.bean = bean;
}
/**
* Return the bean currently in use by this map.
* @return the current JavaBean
* @see #setBean
*/
public Object getBean() {
return bean;
}
/**
* Return the bean currently in use by this map.
* @return the current JavaBean
* @see #setBean
*/
public Object getBean() {
return bean;
}
@Override
@Override
public void clear() {
throw new UnsupportedOperationException();
}
throw new UnsupportedOperationException();
}
@Override
@Override
public boolean containsKey(Object key) {
return keySet().contains(key);
}
return keySet().contains(key);
}
@Override
@Override
public boolean containsValue(Object value) {
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object v = get(it.next());
if (((value == null) && (v == null)) || (value != null && value.equals(v))) {
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object v = get(it.next());
if (((value == null) && (v == null)) || (value != null && value.equals(v))) {
return true;
}
}
return false;
}
}
return false;
}
@Override
@Override
public int size() {
return keySet().size();
}
return keySet().size();
}
@Override
@Override
public boolean isEmpty() {
return size() == 0;
}
return size() == 0;
}
@Override
@Override
public Object remove(Object key) {
throw new UnsupportedOperationException();
}
throw new UnsupportedOperationException();
}
@Override
@Override
public void putAll(Map t) {
for (Object key : t.keySet()) {
put(key, t.get(key));
}
}
for (Object key : t.keySet()) {
put(key, t.get(key));
}
}
@Override
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof Map other)) {
return false;
}
if (size() != other.size()) {
return false;
}
for (Object key : keySet()) {
if (!other.containsKey(key)) {
return false;
}
Object v1 = get(key);
Object v2 = other.get(key);
if (!((v1 == null) ? v2 == null : v1.equals(v2))) {
return false;
}
}
return true;
}
if (o == null || !(o instanceof Map other)) {
return false;
}
if (size() != other.size()) {
return false;
}
for (Object key : keySet()) {
if (!other.containsKey(key)) {
return false;
}
Object v1 = get(key);
Object v2 = other.get(key);
if (!((v1 == null) ? v2 == null : v1.equals(v2))) {
return false;
}
}
return true;
}
@Override
@Override
public int hashCode() {
int code = 0;
for (Object key : keySet()) {
Object value = get(key);
code += ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
return code;
}
int code = 0;
for (Object key : keySet()) {
Object value = get(key);
code += ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
return code;
}
// TODO: optimize
@Override
// TODO: optimize
@Override
public Set entrySet() {
HashMap copy = new HashMap();
for (Object key : keySet()) {
copy.put(key, get(key));
}
return Collections.unmodifiableMap(copy).entrySet();
}
HashMap copy = new HashMap();
for (Object key : keySet()) {
copy.put(key, get(key));
}
return Collections.unmodifiableMap(copy).entrySet();
}
@Override
@Override
public Collection values() {
Set keys = keySet();
List values = new ArrayList(keys.size());
for (Iterator it = keys.iterator(); it.hasNext();) {
values.add(get(it.next()));
}
return Collections.unmodifiableCollection(values);
}
Set keys = keySet();
List values = new ArrayList(keys.size());
for (Iterator it = keys.iterator(); it.hasNext();) {
values.add(get(it.next()));
}
return Collections.unmodifiableCollection(values);
}
/*
* @see java.util.AbstractMap#toString
*/
@Override
/*
* @see java.util.AbstractMap#toString
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append('{');
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object key = it.next();
sb.append(key);
sb.append('=');
sb.append(get(key));
if (it.hasNext()) {
sb.append(", ");
}
}
sb.append('}');
return sb.toString();
}
{
StringBuilder sb = new StringBuilder();
sb.append('{');
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object key = it.next();
sb.append(key);
sb.append('=');
sb.append(get(key));
if (it.hasNext()) {
sb.append(", ");
}
}
sb.append('}');
return sb.toString();
}
}
@@ -28,123 +28,123 @@ import org.springframework.cglib.core.ReflectUtils;
@SuppressWarnings({"rawtypes", "unchecked"})
abstract public class BulkBean
{
private static final BulkBeanKey KEY_FACTORY =
(BulkBeanKey)KeyFactory.create(BulkBeanKey.class);
private static final BulkBeanKey KEY_FACTORY =
(BulkBeanKey)KeyFactory.create(BulkBeanKey.class);
interface BulkBeanKey {
public Object newInstance(String target, String[] getters, String[] setters, String[] types);
}
interface BulkBeanKey {
public Object newInstance(String target, String[] getters, String[] setters, String[] types);
}
protected Class target;
protected String[] getters, setters;
protected Class[] types;
protected Class target;
protected String[] getters, setters;
protected Class[] types;
protected BulkBean() { }
protected BulkBean() { }
abstract public void getPropertyValues(Object bean, Object[] values);
abstract public void setPropertyValues(Object bean, Object[] values);
abstract public void getPropertyValues(Object bean, Object[] values);
abstract public void setPropertyValues(Object bean, Object[] values);
public Object[] getPropertyValues(Object bean) {
Object[] values = new Object[getters.length];
getPropertyValues(bean, values);
return values;
}
public Object[] getPropertyValues(Object bean) {
Object[] values = new Object[getters.length];
getPropertyValues(bean, values);
return values;
}
public Class[] getPropertyTypes() {
return types.clone();
}
public Class[] getPropertyTypes() {
return types.clone();
}
public String[] getGetters() {
return getters.clone();
}
public String[] getGetters() {
return getters.clone();
}
public String[] getSetters() {
return setters.clone();
}
public String[] getSetters() {
return setters.clone();
}
public static BulkBean create(Class target, String[] getters, String[] setters, Class[] types) {
Generator gen = new Generator();
gen.setTarget(target);
gen.setGetters(getters);
gen.setSetters(setters);
gen.setTypes(types);
return gen.create();
}
public static BulkBean create(Class target, String[] getters, String[] setters, Class[] types) {
Generator gen = new Generator();
gen.setTarget(target);
gen.setGetters(getters);
gen.setSetters(setters);
gen.setTypes(types);
return gen.create();
}
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BulkBean.class.getName());
private Class target;
private String[] getters;
private String[] setters;
private Class[] types;
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BulkBean.class.getName());
private Class target;
private String[] getters;
private String[] setters;
private Class[] types;
public Generator() {
super(SOURCE);
}
public Generator() {
super(SOURCE);
}
public void setTarget(Class target) {
this.target = target;
public void setTarget(Class target) {
this.target = target;
// SPRING PATCH BEGIN
setContextClass(target);
// SPRING PATCH END
}
}
public void setGetters(String[] getters) {
this.getters = getters;
}
public void setGetters(String[] getters) {
this.getters = getters;
}
public void setSetters(String[] setters) {
this.setters = setters;
}
public void setSetters(String[] setters) {
this.setters = setters;
}
public void setTypes(Class[] types) {
this.types = types;
}
public void setTypes(Class[] types) {
this.types = types;
}
@Override
@Override
protected ClassLoader getDefaultClassLoader() {
return target.getClassLoader();
}
return target.getClassLoader();
}
@Override
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(target);
}
return ReflectUtils.getProtectionDomain(target);
}
public BulkBean create() {
setNamePrefix(target.getName());
String targetClassName = target.getName();
String[] typeClassNames = ReflectUtils.getNames(types);
Object key = KEY_FACTORY.newInstance(targetClassName, getters, setters, typeClassNames);
return (BulkBean)super.create(key);
}
public BulkBean create() {
setNamePrefix(target.getName());
String targetClassName = target.getName();
String[] typeClassNames = ReflectUtils.getNames(types);
Object key = KEY_FACTORY.newInstance(targetClassName, getters, setters, typeClassNames);
return (BulkBean)super.create(key);
}
@Override
@Override
public void generateClass(ClassVisitor v) throws Exception {
new BulkBeanEmitter(v, getClassName(), target, getters, setters, types);
}
new BulkBeanEmitter(v, getClassName(), target, getters, setters, types);
}
@Override
@Override
protected Object firstInstance(Class type) {
BulkBean instance = (BulkBean)ReflectUtils.newInstance(type);
instance.target = target;
BulkBean instance = (BulkBean)ReflectUtils.newInstance(type);
instance.target = target;
int length = getters.length;
instance.getters = new String[length];
System.arraycopy(getters, 0, instance.getters, 0, length);
int length = getters.length;
instance.getters = new String[length];
System.arraycopy(getters, 0, instance.getters, 0, length);
instance.setters = new String[length];
System.arraycopy(setters, 0, instance.setters, 0, length);
instance.setters = new String[length];
System.arraycopy(setters, 0, instance.setters, 0, length);
instance.types = new Class[types.length];
System.arraycopy(types, 0, instance.types, 0, types.length);
instance.types = new Class[types.length];
System.arraycopy(types, 0, instance.types, 0, types.length);
return instance;
}
return instance;
}
@Override
@Override
protected Object nextInstance(Object instance) {
return instance;
}
}
return instance;
}
}
}
@@ -36,111 +36,111 @@ import org.springframework.cglib.core.TypeUtils;
@SuppressWarnings({"rawtypes", "unchecked"})
public class ImmutableBean
{
private static final Type ILLEGAL_STATE_EXCEPTION =
TypeUtils.parseType("IllegalStateException");
private static final Signature CSTRUCT_OBJECT =
TypeUtils.parseConstructor("Object");
private static final Class[] OBJECT_CLASSES = { Object.class };
private static final String FIELD_NAME = "CGLIB$RWBean";
private static final Type ILLEGAL_STATE_EXCEPTION =
TypeUtils.parseType("IllegalStateException");
private static final Signature CSTRUCT_OBJECT =
TypeUtils.parseConstructor("Object");
private static final Class[] OBJECT_CLASSES = { Object.class };
private static final String FIELD_NAME = "CGLIB$RWBean";
private ImmutableBean() {
}
private ImmutableBean() {
}
public static Object create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
public static Object create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(ImmutableBean.class.getName());
private Object bean;
private Class target;
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(ImmutableBean.class.getName());
private Object bean;
private Class target;
public Generator() {
super(SOURCE);
}
public Generator() {
super(SOURCE);
}
public void setBean(Object bean) {
this.bean = bean;
target = bean.getClass();
public void setBean(Object bean) {
this.bean = bean;
target = bean.getClass();
// SPRING PATCH BEGIN
setContextClass(target);
// SPRING PATCH END
}
}
@Override
@Override
protected ClassLoader getDefaultClassLoader() {
return target.getClassLoader();
}
return target.getClassLoader();
}
@Override
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(target);
}
return ReflectUtils.getProtectionDomain(target);
}
public Object create() {
String name = target.getName();
setNamePrefix(name);
return super.create(name);
}
public Object create() {
String name = target.getName();
setNamePrefix(name);
return super.create(name);
}
@Override
@Override
public void generateClass(ClassVisitor v) {
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
targetType,
null,
Constants.SOURCE_FILE);
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
targetType,
null,
Constants.SOURCE_FILE);
ce.declare_field(Constants.ACC_FINAL | Constants.ACC_PRIVATE, FIELD_NAME, targetType, null);
ce.declare_field(Constants.ACC_FINAL | Constants.ACC_PRIVATE, FIELD_NAME, targetType, null);
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null);
e.load_this();
e.super_invoke_constructor();
e.load_this();
e.load_arg(0);
e.checkcast(targetType);
e.putfield(FIELD_NAME);
e.return_value();
e.end_method();
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null);
e.load_this();
e.super_invoke_constructor();
e.load_this();
e.load_arg(0);
e.checkcast(targetType);
e.putfield(FIELD_NAME);
e.return_value();
e.end_method();
PropertyDescriptor[] descriptors = ReflectUtils.getBeanProperties(target);
Method[] getters = ReflectUtils.getPropertyMethods(descriptors, true, false);
Method[] setters = ReflectUtils.getPropertyMethods(descriptors, false, true);
PropertyDescriptor[] descriptors = ReflectUtils.getBeanProperties(target);
Method[] getters = ReflectUtils.getPropertyMethods(descriptors, true, false);
Method[] setters = ReflectUtils.getPropertyMethods(descriptors, false, true);
for (Method getter2 : getters) {
MethodInfo getter = ReflectUtils.getMethodInfo(getter2);
e = EmitUtils.begin_method(ce, getter, Constants.ACC_PUBLIC);
e.load_this();
e.getfield(FIELD_NAME);
e.invoke(getter);
e.return_value();
e.end_method();
}
for (Method getter2 : getters) {
MethodInfo getter = ReflectUtils.getMethodInfo(getter2);
e = EmitUtils.begin_method(ce, getter, Constants.ACC_PUBLIC);
e.load_this();
e.getfield(FIELD_NAME);
e.invoke(getter);
e.return_value();
e.end_method();
}
for (Method setter2 : setters) {
MethodInfo setter = ReflectUtils.getMethodInfo(setter2);
e = EmitUtils.begin_method(ce, setter, Constants.ACC_PUBLIC);
e.throw_exception(ILLEGAL_STATE_EXCEPTION, "Bean is immutable");
e.end_method();
}
for (Method setter2 : setters) {
MethodInfo setter = ReflectUtils.getMethodInfo(setter2);
e = EmitUtils.begin_method(ce, setter, Constants.ACC_PUBLIC);
e.throw_exception(ILLEGAL_STATE_EXCEPTION, "Bean is immutable");
e.end_method();
}
ce.end_class();
}
ce.end_class();
}
@Override
@Override
protected Object firstInstance(Class type) {
return ReflectUtils.newInstance(type, OBJECT_CLASSES, new Object[]{ bean });
}
return ReflectUtils.newInstance(type, OBJECT_CLASSES, new Object[]{ bean });
}
// TODO: optimize
@Override
// TODO: optimize
@Override
protected Object nextInstance(Object instance) {
return firstInstance(instance.getClass());
}
}
return firstInstance(instance.getClass());
}
}
}
@@ -138,7 +138,7 @@ public class ResolvableType implements Serializable {
/**
* Private constructor used to create a new {@link ResolvableType} for cache key purposes,
* Private constructor used to create a new {@code ResolvableType} for cache key purposes,
* with no upfront resolution.
*/
private ResolvableType(
@@ -153,7 +153,7 @@ public class ResolvableType implements Serializable {
}
/**
* Private constructor used to create a new {@link ResolvableType} for cache value purposes,
* Private constructor used to create a new {@code ResolvableType} for cache value purposes,
* with upfront resolution and a pre-calculated hash.
* @since 4.2
*/
@@ -169,7 +169,7 @@ public class ResolvableType implements Serializable {
}
/**
* Private constructor used to create a new {@link ResolvableType} for uncached purposes,
* Private constructor used to create a new {@code ResolvableType} for uncached purposes,
* with upfront resolution but lazily calculated hash.
*/
private ResolvableType(Type type, @Nullable TypeProvider typeProvider,
@@ -184,7 +184,7 @@ public class ResolvableType implements Serializable {
}
/**
* Private constructor used to create a new {@link ResolvableType} on a {@link Class} basis.
* Private constructor used to create a new {@code ResolvableType} on a {@link Class} basis.
* <p>Avoids all {@code instanceof} checks in order to create a straight {@link Class} wrapper.
* @since 4.2
*/
@@ -223,7 +223,7 @@ public class ResolvableType implements Serializable {
/**
* Return the underlying source of the resolvable type. Will return a {@link Field},
* {@link MethodParameter} or {@link Type} depending on how the {@link ResolvableType}
* {@link MethodParameter} or {@link Type} depending on how the {@code ResolvableType}
* was constructed. This method is primarily to provide access to additional type
* information or meta-data that alternative JVM languages may provide.
*/
@@ -340,13 +340,14 @@ public class ResolvableType implements Serializable {
}
}
if (ourResolved == null) {
ourResolved = resolve(Object.class);
ourResolved = toClass();
}
Class<?> otherResolved = other.toClass();
// We need an exact type match for generics
// List<CharSequence> is not assignable from List<String>
if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) {
if (exactMatch ? !ourResolved.equals(otherResolved) :
!ClassUtils.isAssignable(ourResolved, otherResolved)) {
return false;
}
@@ -357,13 +358,15 @@ public class ResolvableType implements Serializable {
if (ourGenerics.length != typeGenerics.length) {
return false;
}
if (matchedBefore == null) {
matchedBefore = new IdentityHashMap<>(1);
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
return false;
if (ourGenerics.length > 0) {
if (matchedBefore == null) {
matchedBefore = new IdentityHashMap<>(1);
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
return false;
}
}
}
}
@@ -428,12 +431,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return this type as a {@link ResolvableType} of the specified class. Searches
* Return this type as a {@code ResolvableType} of the specified class. Searches
* {@link #getSuperType() supertype} and {@link #getInterfaces() interface}
* hierarchies to find a match, returning {@link #NONE} if this type does not
* implement or extend the specified class.
* @param type the required type (typically narrowed)
* @return a {@link ResolvableType} representing this object as the specified
* @return a {@code ResolvableType} representing this object as the specified
* type, or {@link #NONE} if not resolvable as that type
* @see #asCollection()
* @see #asMap()
@@ -458,9 +461,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} representing the direct supertype of this type.
* Return a {@code ResolvableType} representing the direct supertype of this type.
* <p>If no supertype is available this method returns {@link #NONE}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @see #getInterfaces()
*/
public ResolvableType getSuperType() {
@@ -487,10 +490,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} array representing the direct interfaces
* Return a {@code ResolvableType} array representing the direct interfaces
* implemented by this type. If this type does not implement any interfaces an
* empty array is returned.
* <p>Note: The resulting {@link ResolvableType} instances may not be {@link Serializable}.
* <p>Note: The resulting {@code ResolvableType} instances may not be {@link Serializable}.
* @see #getSuperType()
*/
public ResolvableType[] getInterfaces() {
@@ -618,17 +621,17 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified nesting level.
* Return a {@code ResolvableType} for the specified nesting level.
* <p>See {@link #getNested(int, Map)} for details.
* @param nestingLevel the nesting level
* @return the {@link ResolvableType} type, or {@code #NONE}
* @return the {@code ResolvableType} type, or {@code #NONE}
*/
public ResolvableType getNested(int nestingLevel) {
return getNested(nestingLevel, null);
}
/**
* Return a {@link ResolvableType} for the specified nesting level.
* Return a {@code ResolvableType} for the specified nesting level.
* <p>The nesting level refers to the specific generic parameter that should be returned.
* A nesting level of 1 indicates this type; 2 indicates the first nested generic;
* 3 the second; and so on. For example, given {@code List<Set<Integer>>} level 1 refers
@@ -645,7 +648,7 @@ public class ResolvableType implements Serializable {
* current type, 2 for the first nested generic, 3 for the second and so on
* @param typeIndexesPerLevel a map containing the generic index for a given
* nesting level (may be {@code null})
* @return a {@link ResolvableType} for the nested level, or {@link #NONE}
* @return a {@code ResolvableType} for the nested level, or {@link #NONE}
*/
public ResolvableType getNested(int nestingLevel, @Nullable Map<Integer, Integer> typeIndexesPerLevel) {
ResolvableType result = this;
@@ -667,7 +670,7 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} representing the generic parameter for the
* Return a {@code ResolvableType} representing the generic parameter for the
* given indexes. Indexes are zero based; for example given the type
* {@code Map<Integer, List<String>>}, {@code getGeneric(0)} will access the
* {@code Integer}. Nested generics can be accessed by specifying multiple indexes;
@@ -677,7 +680,7 @@ public class ResolvableType implements Serializable {
* <p>If no generic is available at the specified indexes {@link #NONE} is returned.
* @param indexes the indexes that refer to the generic parameter
* (may be omitted to return the first generic)
* @return a {@link ResolvableType} for the specified generic, or {@link #NONE}
* @return a {@code ResolvableType} for the specified generic, or {@link #NONE}
* @see #hasGenerics()
* @see #getGenerics()
* @see #resolveGeneric(int...)
@@ -700,12 +703,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return an array of {@link ResolvableType ResolvableTypes} representing the generic parameters of
* Return an array of {@code ResolvableType ResolvableTypes} representing the generic parameters of
* this type. If no generics are available an empty array is returned. If you need to
* access a specific generic consider using the {@link #getGeneric(int...)} method as
* it allows access to nested generics and protects against
* {@code IndexOutOfBoundsExceptions}.
* @return an array of {@link ResolvableType ResolvableTypes} representing the generic parameters
* @return an array of {@code ResolvableType ResolvableTypes} representing the generic parameters
* (never {@code null})
* @see #hasGenerics()
* @see #getGeneric(int...)
@@ -839,7 +842,7 @@ public class ResolvableType implements Serializable {
/**
* Resolve this type by a single level, returning the resolved value or {@link #NONE}.
* <p>Note: The returned {@link ResolvableType} should only be used as an intermediary
* <p>Note: The returned {@code ResolvableType} should only be used as an intermediary
* as it cannot be serialized.
*/
ResolvableType resolveType() {
@@ -958,7 +961,7 @@ public class ResolvableType implements Serializable {
}
/**
* Adapts this {@link ResolvableType} to a {@link VariableResolver}.
* Adapts this {@code ResolvableType} to a {@link VariableResolver}.
*/
@Nullable
VariableResolver asVariableResolver() {
@@ -1004,12 +1007,12 @@ public class ResolvableType implements Serializable {
// Factory methods
/**
* Return a {@link ResolvableType} for the specified {@link Class},
* Return a {@code ResolvableType} for the specified {@link Class},
* using the full generic type information for assignability checks.
* <p>For example: {@code ResolvableType.forClass(MyArrayList.class)}.
* @param clazz the class to introspect ({@code null} is semantically
* equivalent to {@code Object.class} for typical use cases here)
* @return a {@link ResolvableType} for the specified class
* @return a {@code ResolvableType} for the specified class
* @see #forClass(Class, Class)
* @see #forClassWithGenerics(Class, Class...)
*/
@@ -1018,13 +1021,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Class},
* Return a {@code ResolvableType} for the specified {@link Class},
* doing assignability checks against the raw class only (analogous to
* {@link Class#isAssignableFrom}, which this serves as a wrapper for).
* <p>For example: {@code ResolvableType.forRawClass(List.class)}.
* @param clazz the class to introspect ({@code null} is semantically
* equivalent to {@code Object.class} for typical use cases here)
* @return a {@link ResolvableType} for the specified class
* @return a {@code ResolvableType} for the specified class
* @since 4.2
* @see #forClass(Class)
* @see #getRawClass()
@@ -1048,12 +1051,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified base type
* Return a {@code ResolvableType} for the specified base type
* (interface or base class) with a given implementation class.
* <p>For example: {@code ResolvableType.forClass(List.class, MyArrayList.class)}.
* @param baseType the base type (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified base type backed by the
* @return a {@code ResolvableType} for the specified base type backed by the
* given implementation class
* @see #forClass(Class)
* @see #forClassWithGenerics(Class, Class...)
@@ -1065,10 +1068,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
* Return a {@code ResolvableType} for the specified {@link Class} with pre-declared generics.
* @param clazz the class (or interface) to introspect
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @return a {@code ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, ResolvableType...)
*/
public static ResolvableType forClassWithGenerics(Class<?> clazz, Class<?>... generics) {
@@ -1082,10 +1085,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
* Return a {@code ResolvableType} for the specified {@link Class} with pre-declared generics.
* @param clazz the class (or interface) to introspect
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @return a {@code ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClassWithGenerics(Class<?> clazz, ResolvableType... generics) {
@@ -1106,12 +1109,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified instance. The instance does not
* Return a {@code ResolvableType} for the specified instance. The instance does not
* convey generic information but if it implements {@link ResolvableTypeProvider} a
* more precise {@link ResolvableType} can be used than the simple one based on
* more precise {@code ResolvableType} can be used than the simple one based on
* the {@link #forClass(Class) Class instance}.
* @param instance the instance (possibly {@code null})
* @return a {@link ResolvableType} for the specified instance,
* @return a {@code ResolvableType} for the specified instance,
* or {@code NONE} for {@code null}
* @since 4.2
* @see ResolvableTypeProvider
@@ -1127,9 +1130,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field}.
* Return a {@code ResolvableType} for the specified {@link Field}.
* @param field the source field
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field, Class)
*/
public static ResolvableType forField(Field field) {
@@ -1138,13 +1141,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* Return a {@code ResolvableType} for the specified {@link Field} with a given
* implementation.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation class.
* @param field the source field
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, Class<?> implementationClass) {
@@ -1154,13 +1157,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* Return a {@code ResolvableType} for the specified {@link Field} with a given
* implementation.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation type.
* @param field the source field
* @param implementationType the implementation type
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, @Nullable ResolvableType implementationType) {
@@ -1171,7 +1174,7 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with the
* Return a {@code ResolvableType} for the specified {@link Field} with the
* given nesting level.
* @param field the source field
* @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
@@ -1184,7 +1187,7 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* Return a {@code ResolvableType} for the specified {@link Field} with a given
* implementation and the given nesting level.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation class.
@@ -1192,7 +1195,7 @@ public class ResolvableType implements Serializable {
* @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
* generic type; etc)
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, int nestingLevel, @Nullable Class<?> implementationClass) {
@@ -1202,10 +1205,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Constructor} parameter.
* Return a {@code ResolvableType} for the specified {@link Constructor} parameter.
* @param constructor the source constructor (must not be {@code null})
* @param parameterIndex the parameter index
* @return a {@link ResolvableType} for the specified constructor parameter
* @return a {@code ResolvableType} for the specified constructor parameter
* @see #forConstructorParameter(Constructor, int, Class)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex) {
@@ -1214,14 +1217,14 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Constructor} parameter
* Return a {@code ResolvableType} for the specified {@link Constructor} parameter
* with a given implementation. Use this variant when the class that declares the
* constructor includes generic parameter variables that are satisfied by the
* implementation class.
* @param constructor the source constructor (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified constructor parameter
* @return a {@code ResolvableType} for the specified constructor parameter
* @see #forConstructorParameter(Constructor, int)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex,
@@ -1233,9 +1236,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* Return a {@code ResolvableType} for the specified {@link Method} return type.
* @param method the source for the method return type
* @return a {@link ResolvableType} for the specified method return
* @return a {@code ResolvableType} for the specified method return
* @see #forMethodReturnType(Method, Class)
*/
public static ResolvableType forMethodReturnType(Method method) {
@@ -1244,12 +1247,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* Return a {@code ResolvableType} for the specified {@link Method} return type.
* <p>Use this variant when the class that declares the method includes generic
* parameter variables that are satisfied by the implementation class.
* @param method the source for the method return type
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method return
* @return a {@code ResolvableType} for the specified method return
* @see #forMethodReturnType(Method)
*/
public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) {
@@ -1259,10 +1262,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} parameter.
* Return a {@code ResolvableType} for the specified {@link Method} parameter.
* @param method the source method (must not be {@code null})
* @param parameterIndex the parameter index
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int, Class)
* @see #forMethodParameter(MethodParameter)
*/
@@ -1272,13 +1275,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} parameter with a
* Return a {@code ResolvableType} for the specified {@link Method} parameter with a
* given implementation. Use this variant when the class that declares the method
* includes generic parameter variables that are satisfied by the implementation class.
* @param method the source method (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int, Class)
* @see #forMethodParameter(MethodParameter)
*/
@@ -1289,9 +1292,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter}.
* Return a {@code ResolvableType} for the specified {@link MethodParameter}.
* @param methodParameter the source method parameter (must not be {@code null})
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter) {
@@ -1299,12 +1302,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter} with a
* Return a {@code ResolvableType} for the specified {@link MethodParameter} with a
* given implementation type. Use this variant when the class that declares the method
* includes generic parameter variables that are satisfied by the implementation type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param implementationType the implementation type
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter,
@@ -1319,11 +1322,11 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter},
* Return a {@code ResolvableType} for the specified {@link MethodParameter},
* overriding the target type to resolve with a specific given type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param targetType the type to resolve (a part of the method parameter's type)
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter, @Nullable Type targetType) {
@@ -1332,13 +1335,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter} at
* Return a {@code ResolvableType} for the specified {@link MethodParameter} at
* a specific nesting level, overriding the target type to resolve with a specific
* given type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param targetType the type to resolve (a part of the method parameter's type)
* @param nestingLevel the nesting level to use
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @since 5.2
* @see #forMethodParameter(Method, int)
*/
@@ -1351,9 +1354,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} as an array of the specified {@code componentType}.
* Return a {@code ResolvableType} as an array of the specified {@code componentType}.
* @param componentType the component type
* @return a {@link ResolvableType} as an array of the specified component type
* @return a {@code ResolvableType} as an array of the specified component type
*/
public static ResolvableType forArrayComponent(ResolvableType componentType) {
Assert.notNull(componentType, "Component type must not be null");
@@ -1362,10 +1365,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Type}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* Return a {@code ResolvableType} for the specified {@link Type}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @param type the source type (potentially {@code null})
* @return a {@link ResolvableType} for the specified {@link Type}
* @return a {@code ResolvableType} for the specified {@link Type}
* @see #forType(Type, ResolvableType)
*/
public static ResolvableType forType(@Nullable Type type) {
@@ -1373,12 +1376,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by the given
* Return a {@code ResolvableType} for the specified {@link Type} backed by the given
* owner type.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @param type the source type or {@code null}
* @param owner the owner type used to resolve variables
* @return a {@link ResolvableType} for the specified {@link Type} and owner
* @return a {@code ResolvableType} for the specified {@link Type} and owner
* @see #forType(Type)
*/
public static ResolvableType forType(@Nullable Type type, @Nullable ResolvableType owner) {
@@ -1391,10 +1394,10 @@ public class ResolvableType implements Serializable {
/**
* Return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* Return a {@code ResolvableType} for the specified {@link ParameterizedTypeReference}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @param typeReference the reference to obtain the source type from
* @return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference}
* @return a {@code ResolvableType} for the specified {@link ParameterizedTypeReference}
* @since 4.3.12
* @see #forType(Type)
*/
@@ -1403,23 +1406,23 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by a given
* Return a {@code ResolvableType} for the specified {@link Type} backed by a given
* {@link VariableResolver}.
* @param type the source type or {@code null}
* @param variableResolver the variable resolver or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
* @return a {@code ResolvableType} for the specified {@link Type} and {@link VariableResolver}
*/
static ResolvableType forType(@Nullable Type type, @Nullable VariableResolver variableResolver) {
return forType(type, null, variableResolver);
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by a given
* Return a {@code ResolvableType} for the specified {@link Type} backed by a given
* {@link VariableResolver}.
* @param type the source type or {@code null}
* @param typeProvider the type provider or {@code null}
* @param variableResolver the variable resolver or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
* @return a {@code ResolvableType} for the specified {@link Type} and {@link VariableResolver}
*/
static ResolvableType forType(
@Nullable Type type, @Nullable TypeProvider typeProvider, @Nullable VariableResolver variableResolver) {
@@ -103,8 +103,8 @@ final class AnnotationTypeMapping {
private final Set<Method> claimedAliases = new HashSet<>();
AnnotationTypeMapping(@Nullable AnnotationTypeMapping source,
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
AnnotationTypeMapping(@Nullable AnnotationTypeMapping source, Class<? extends Annotation> annotationType,
@Nullable Annotation annotation, Set<Class<? extends Annotation>> visitedAnnotationTypes) {
this.source = source;
this.root = (source != null ? source.getRoot() : this);
@@ -124,7 +124,7 @@ final class AnnotationTypeMapping {
processAliases();
addConventionMappings();
addConventionAnnotationValues();
this.synthesizable = computeSynthesizableFlag();
this.synthesizable = computeSynthesizableFlag(visitedAnnotationTypes);
}
@@ -374,7 +374,10 @@ final class AnnotationTypeMapping {
}
@SuppressWarnings("unchecked")
private boolean computeSynthesizableFlag() {
private boolean computeSynthesizableFlag(Set<Class<? extends Annotation>> visitedAnnotationTypes) {
// Track that we have visited the current annotation type.
visitedAnnotationTypes.add(this.annotationType);
// Uses @AliasFor for local aliases?
for (int index : this.aliasMappings) {
if (index != -1) {
@@ -403,8 +406,12 @@ final class AnnotationTypeMapping {
if (type.isAnnotation() || (type.isArray() && type.getComponentType().isAnnotation())) {
Class<? extends Annotation> annotationType =
(Class<? extends Annotation>) (type.isAnnotation() ? type : type.getComponentType());
if (annotationType != this.annotationType) {
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
// Ensure we have not yet visited the current nested annotation type, in order
// to avoid infinite recursion for JVM languages other than Java that support
// recursive annotation definitions.
if (visitedAnnotationTypes.add(annotationType)) {
AnnotationTypeMapping mapping =
AnnotationTypeMappings.forAnnotationType(annotationType, visitedAnnotationTypes).get(0);
if (mapping.isSynthesizable()) {
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -20,8 +20,10 @@ import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
@@ -40,6 +42,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
* be searched once, regardless of how many times they are actually used.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
* @see AnnotationTypeMapping
*/
@@ -60,19 +63,21 @@ final class AnnotationTypeMappings {
private AnnotationTypeMappings(RepeatableContainers repeatableContainers,
AnnotationFilter filter, Class<? extends Annotation> annotationType) {
AnnotationFilter filter, Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
this.repeatableContainers = repeatableContainers;
this.filter = filter;
this.mappings = new ArrayList<>();
addAllMappings(annotationType);
addAllMappings(annotationType, visitedAnnotationTypes);
this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet);
}
private void addAllMappings(Class<? extends Annotation> annotationType) {
private void addAllMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
addIfPossible(queue, null, annotationType, null);
addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
while (!queue.isEmpty()) {
AnnotationTypeMapping mapping = queue.removeFirst();
this.mappings.add(mapping);
@@ -102,14 +107,15 @@ final class AnnotationTypeMappings {
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source, Annotation ann) {
addIfPossible(queue, source, ann.annotationType(), ann);
addIfPossible(queue, source, ann.annotationType(), ann, new HashSet<>());
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,
Class<? extends Annotation> annotationType, @Nullable Annotation ann) {
Class<? extends Annotation> annotationType, @Nullable Annotation ann,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
try {
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann));
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes));
}
catch (Exception ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
@@ -166,20 +172,22 @@ final class AnnotationTypeMappings {
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) {
return forAnnotationType(annotationType, AnnotationFilter.PLAIN);
return forAnnotationType(annotationType, new HashSet<>());
}
/**
* Create {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the source annotation type
* @param annotationFilter the annotation filter used to limit which
* annotations are considered
* @param visitedAnnotationTypes the set of annotations that we have already
* visited; used to avoid infinite recursion for recursive annotations which
* some JVM languages support (such as Kotlin)
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(
Class<? extends Annotation> annotationType, AnnotationFilter annotationFilter) {
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(), annotationFilter);
return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(),
AnnotationFilter.PLAIN, visitedAnnotationTypes);
}
/**
@@ -194,15 +202,35 @@ final class AnnotationTypeMappings {
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
return forAnnotationType(annotationType, repeatableContainers, annotationFilter, new HashSet<>());
}
/**
* Create {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the source annotation type
* @param repeatableContainers the repeatable containers that may be used by
* the meta-annotations
* @param annotationFilter the annotation filter used to limit which
* annotations are considered
* @param visitedAnnotationTypes the set of annotations that we have already
* visited; used to avoid infinite recursion for recursive annotations which
* some JVM languages support (such as Kotlin)
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
return standardRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
}
if (repeatableContainers == RepeatableContainers.none()) {
return noRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
}
return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType);
return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType,
visitedAnnotationTypes);
}
static void clearCache() {
@@ -235,14 +263,20 @@ final class AnnotationTypeMappings {
/**
* Get or create {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the annotation type
* @param visitedAnnotationTypes the set of annotations that we have already
* visited; used to avoid infinite recursion for recursive annotations which
* some JVM languages support (such as Kotlin)
* @return a new or existing {@link AnnotationTypeMappings} instance
*/
AnnotationTypeMappings get(Class<? extends Annotation> annotationType) {
return this.mappings.computeIfAbsent(annotationType, this::createMappings);
AnnotationTypeMappings get(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
}
AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType) {
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType);
private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType,
visitedAnnotationTypes);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -51,8 +51,7 @@ public final class CharSequenceEncoder extends AbstractEncoder<CharSequence> {
*/
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private final ConcurrentMap<Charset, Float> charsetToMaxBytesPerChar =
new ConcurrentHashMap<>(3);
private final ConcurrentMap<Charset, Float> charsetToMaxBytesPerChar = new ConcurrentHashMap<>(3);
private CharSequenceEncoder(MimeType... mimeTypes) {
@@ -105,8 +104,8 @@ public final class CharSequenceEncoder extends AbstractEncoder<CharSequence> {
}
int calculateCapacity(CharSequence sequence, Charset charset) {
float maxBytesPerChar = this.charsetToMaxBytesPerChar
.computeIfAbsent(charset, cs -> cs.newEncoder().maxBytesPerChar());
float maxBytesPerChar = this.charsetToMaxBytesPerChar.computeIfAbsent(charset,
cs -> cs.newEncoder().maxBytesPerChar());
float maxBytesForSequence = sequence.length() * maxBytesPerChar;
return (int) Math.ceil(maxBytesForSequence);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -89,7 +89,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
/**
* Name of reserved default profile name: {@value}. If no default profile names are
* explicitly and no active profile names are explicitly set, this profile will
* explicitly set and no active profile names are explicitly set, this profile will
* automatically be activated by default.
* @see #getReservedDefaultProfiles
* @see ConfigurableEnvironment#setDefaultProfiles
@@ -97,7 +97,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
* @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
* @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
*/
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
public static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
protected final Log logger = LogFactory.getLog(getClass());
@@ -313,7 +313,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
Assert.notNull(dest, "Dest must not be null");
dest = dest.duplicate().clear();
dest.put(destPos, this.byteBuf.nioBuffer(), srcPos, length);
dest.put(destPos, this.byteBuf.nioBuffer(srcPos, length), 0, length);
}
@Override
@@ -490,8 +490,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
String filePath = new File(path).getAbsolutePath();
int prefixIndex = filePath.indexOf(':');
if (prefixIndex == 1) {
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
filePath = StringUtils.capitalize(filePath);
// Possibly a drive prefix on Windows (for example, "c:"), so we prepend a slash
// and convert the drive letter to uppercase for consistent duplicate detection.
filePath = "/" + StringUtils.capitalize(filePath);
}
// Since '#' can appear in directories/filenames, java.net.URL should not treat it as a fragment
filePath = StringUtils.replace(filePath, "#", "%23");
@@ -498,7 +498,6 @@ public class SpringFactoriesLoader {
/**
* Strategy for resolving constructor arguments based on their type.
*
* @since 6.0
* @see ArgumentResolver#of(Class, Object)
* @see ArgumentResolver#ofSupplied(Class, Supplier)
@@ -595,13 +594,11 @@ public class SpringFactoriesLoader {
*/
static ArgumentResolver from(Function<Class<?>, Object> function) {
return new ArgumentResolver() {
@SuppressWarnings("unchecked")
@Override
public <T> T resolve(Class<T> type) {
return (T) function.apply(type);
}
};
}
}
@@ -609,7 +606,6 @@ public class SpringFactoriesLoader {
/**
* Strategy for handling a failure that occurs when instantiating a factory.
*
* @since 6.0
* @see FailureHandler#throwing()
* @see FailureHandler#logging(Log)
@@ -671,7 +667,7 @@ public class SpringFactoriesLoader {
static FailureHandler handleMessage(BiConsumer<Supplier<String>, Throwable> messageHandler) {
return (factoryType, factoryImplementationName, failure) -> {
Supplier<String> messageSupplier = () -> "Unable to instantiate factory class [%s] for factory type [%s]"
.formatted(factoryImplementationName, factoryType.getName());
.formatted(factoryImplementationName, factoryType.getName());
messageHandler.accept(messageSupplier, failure);
};
}
@@ -56,6 +56,7 @@ import org.springframework.lang.Nullable;
*
* @author Phillip Webb
* @author Juergen Hoeller
* @author Brian Clozel
* @since 3.2
* @param <K> the key type
* @param <V> the value type
@@ -568,7 +569,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
* references that have been garbage collected.
* @param allowResize if resizing is permitted
*/
protected final void restructureIfNecessary(boolean allowResize) {
void restructureIfNecessary(boolean allowResize) {
int currCount = this.count.get();
boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold);
Reference<K, V> ref = this.referenceManager.pollForPurge();
@@ -581,7 +582,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
boolean needsResize;
lock();
try {
int countAfterRestructure = this.count.get();
int expectedCount = this.count.get();
Set<Reference<K, V>> toPurge = Collections.emptySet();
if (ref != null) {
toPurge = new HashSet<>();
@@ -590,11 +591,11 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
ref = this.referenceManager.pollForPurge();
}
}
countAfterRestructure -= toPurge.size();
expectedCount -= toPurge.size();
// Recalculate taking into account count inside lock and items that
// will be purged
needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
// Estimate new count, taking into account count inside lock and items that
// will be purged.
needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold);
boolean resizing = false;
int restructureSize = this.references.length;
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
@@ -607,6 +608,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
(resizing ? createReferenceArray(restructureSize) : this.references);
// Restructure
int newCount = 0;
for (int i = 0; i < this.references.length; i++) {
ref = this.references[i];
if (!resizing) {
@@ -615,10 +617,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
while (ref != null) {
if (!toPurge.contains(ref)) {
Entry<K, V> entry = ref.get();
// Also filter out null references that are now null
// they should be polled the queue in a later restructure call.
if (entry != null) {
int index = getIndex(ref.getHash(), restructured);
restructured[index] = this.referenceManager.createReference(
entry, ref.getHash(), restructured[index]);
newCount++;
}
}
ref = ref.getNext();
@@ -630,7 +635,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
this.references = restructured;
this.resizeThreshold = (int) (this.references.length * getLoadFactor());
}
this.count.set(Math.max(countAfterRestructure, 0));
this.count.set(Math.max(newCount, 0));
}
finally {
unlock();
@@ -667,14 +672,14 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
/**
* Return the size of the current references array.
*/
public final int getSize() {
public int getSize() {
return this.references.length;
}
/**
* Return the total number of references in this segment.
*/
public final int getCount() {
public int getCount() {
return this.count.get();
}
}
@@ -362,7 +362,9 @@ public abstract class MimeTypeUtils {
*/
public static <T extends MimeType> void sortBySpecificity(List<T> mimeTypes) {
Assert.notNull(mimeTypes, "'mimeTypes' must not be null");
Assert.isTrue(mimeTypes.size() <= 50, "Too many elements");
if (mimeTypes.size() >= 50) {
throw new InvalidMimeTypeException(mimeTypes.toString(), "Too many elements");
}
bubbleSort(mimeTypes, MimeType::isLessSpecific);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -78,7 +78,7 @@ class GeneratedFilesTests {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.generatedFiles
.addSourceFile("com/example/HelloWorld.java", "{}"))
.withMessage("'className' must be a valid identifier");
.withMessage("'className' must be a valid identifier, got 'com/example/HelloWorld.java'");
}
@Test
@@ -63,6 +63,28 @@ class BindingReflectionHintsRegistrarTests {
});
}
@Test
void registerTypeForSerializationWithExtendingClass() {
bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleExtendingClass.class);
assertThat(this.hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleEmptyClass.class));
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
},
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleExtendingClass.class));
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
});
}
@Test
void registerTypeForSerializationWithNoProperty() {
bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithNoProperty.class);
@@ -284,6 +306,9 @@ class BindingReflectionHintsRegistrarTests {
static class SampleEmptyClass {
}
static class SampleExtendingClass extends SampleEmptyClass {
}
static class SampleClassWithNoProperty {
String name() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -16,7 +16,6 @@
package org.springframework.core;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@@ -82,10 +81,7 @@ class AttributeAccessorSupportTests {
void attributeNames() {
this.attributeAccessor.setAttribute(NAME, VALUE);
this.attributeAccessor.setAttribute("abc", "123");
String[] attributeNames = this.attributeAccessor.attributeNames();
Arrays.sort(attributeNames);
assertThat(Arrays.binarySearch(attributeNames, "abc")).isEqualTo(0);
assertThat(Arrays.binarySearch(attributeNames, NAME)).isEqualTo(1);
assertThat(this.attributeAccessor.attributeNames()).contains("abc", NAME);
}
@SuppressWarnings("serial")
@@ -200,8 +200,8 @@ class ResolvableTypeTests {
@Test
void forFieldMustNotBeNull() throws Exception {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forField(null))
.withMessage("Field must not be null");
.isThrownBy(() -> ResolvableType.forField(null))
.withMessage("Field must not be null");
}
@Test
@@ -214,8 +214,8 @@ class ResolvableTypeTests {
@Test
void forConstructorParameterMustNotBeNull() throws Exception {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forConstructorParameter(null, 0))
.withMessage("Constructor must not be null");
.isThrownBy(() -> ResolvableType.forConstructorParameter(null, 0))
.withMessage("Constructor must not be null");
}
@Test
@@ -228,8 +228,8 @@ class ResolvableTypeTests {
@Test
void forMethodParameterByIndexMustNotBeNull() throws Exception {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forMethodParameter(null, 0))
.withMessage("Method must not be null");
.isThrownBy(() -> ResolvableType.forMethodParameter(null, 0))
.withMessage("Method must not be null");
}
@Test
@@ -268,8 +268,8 @@ class ResolvableTypeTests {
@Test
void forMethodParameterMustNotBeNull() throws Exception {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forMethodParameter(null))
.withMessage("MethodParameter must not be null");
.isThrownBy(() -> ResolvableType.forMethodParameter(null))
.withMessage("MethodParameter must not be null");
}
@Test // SPR-16210
@@ -295,8 +295,8 @@ class ResolvableTypeTests {
@Test
void forMethodReturnMustNotBeNull() throws Exception {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forMethodReturnType(null))
.withMessage("Method must not be null");
.isThrownBy(() -> ResolvableType.forMethodReturnType(null))
.withMessage("Method must not be null");
}
@Test
@@ -343,7 +343,7 @@ class ResolvableTypeTests {
ResolvableType type = ResolvableType.forField(field);
assertThat(type.isArray()).isTrue();
assertThat(type.getComponentType().getType())
.isEqualTo(((Class) field.getGenericType()).getComponentType());
.isEqualTo(((Class) field.getGenericType()).getComponentType());
}
@Test
@@ -686,7 +686,6 @@ class ResolvableTypeTests {
assertThat(type.resolve()).isEqualTo(CharSequence.class);
}
@Test
void resolveBoundedTypeVariableWildcardResult() throws Exception {
ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableWildcardResult"));
@@ -701,30 +700,26 @@ class ResolvableTypeTests {
@Test
void resolveTypeVariableFromSimpleInterfaceType() {
ResolvableType type = ResolvableType.forClass(
MySimpleInterfaceType.class).as(MyInterfaceType.class);
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceType.class).as(MyInterfaceType.class);
assertThat(type.resolveGeneric()).isEqualTo(String.class);
}
@Test
void resolveTypeVariableFromSimpleCollectionInterfaceType() {
ResolvableType type = ResolvableType.forClass(
MyCollectionInterfaceType.class).as(MyInterfaceType.class);
ResolvableType type = ResolvableType.forClass(MyCollectionInterfaceType.class).as(MyInterfaceType.class);
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
}
@Test
void resolveTypeVariableFromSimpleSuperclassType() {
ResolvableType type = ResolvableType.forClass(
MySimpleSuperclassType.class).as(MySuperclassType.class);
ResolvableType type = ResolvableType.forClass(MySimpleSuperclassType.class).as(MySuperclassType.class);
assertThat(type.resolveGeneric()).isEqualTo(String.class);
}
@Test
void resolveTypeVariableFromSimpleCollectionSuperclassType() {
ResolvableType type = ResolvableType.forClass(
MyCollectionSuperclassType.class).as(MySuperclassType.class);
ResolvableType type = ResolvableType.forClass(MyCollectionSuperclassType.class).as(MySuperclassType.class);
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
}
@@ -751,8 +746,7 @@ class ResolvableTypeTests {
void resolveTypeVariableFromSuperType() throws Exception {
ResolvableType type = ResolvableType.forClass(ExtendsList.class);
assertThat(type.resolve()).isEqualTo(ExtendsList.class);
assertThat(type.asCollection().resolveGeneric())
.isEqualTo(CharSequence.class);
assertThat(type.asCollection().resolveGeneric()).isEqualTo(CharSequence.class);
}
@Test
@@ -964,8 +958,8 @@ class ResolvableTypeTests {
@Test
void isAssignableFromMustNotBeNull() throws Exception {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forClass(Object.class).isAssignableFrom((ResolvableType) null))
.withMessage("ResolvableType must not be null");
.isThrownBy(() -> ResolvableType.forClass(Object.class).isAssignableFrom((ResolvableType) null))
.withMessage("ResolvableType must not be null");
}
@Test
@@ -1004,6 +998,7 @@ class ResolvableTypeTests {
void isAssignableFromCannotBeResolved() throws Exception {
ResolvableType objectType = ResolvableType.forClass(Object.class);
ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o"));
assertThat(unresolvableVariable.resolve()).isNull();
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable);
assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType);
@@ -1220,9 +1215,9 @@ class ResolvableTypeTests {
@Test
void forClassWithMismatchedGenerics() throws Exception {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forClassWithGenerics(Map.class, Integer.class))
.withMessageContaining("Mismatched number of generics specified for")
.withMessageContaining("java.util.Map<K,V>");
.isThrownBy(() -> ResolvableType.forClassWithGenerics(Map.class, Integer.class))
.withMessageContaining("Mismatched number of generics specified for")
.withMessageContaining("java.util.Map<K,V>");
}
@Test
@@ -1277,7 +1272,7 @@ class ResolvableTypeTests {
}
@Test
void hasUnresolvableGenericsWhenImplementesRawInterface() throws Exception {
void hasUnresolvableGenericsWhenImplementingRawInterface() throws Exception {
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceTypeWithImplementsRaw.class);
for (ResolvableType generic : type.getGenerics()) {
assertThat(generic.resolve()).isNotNull();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -80,7 +80,7 @@ class AnnotationTypeMappingsTests {
@Test
void forAnnotationTypeWhenRepeatableMetaAnnotationIsFiltered() {
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(WithRepeatedMetaAnnotations.class,
Repeating.class.getName()::equals);
RepeatableContainers.standardRepeatables(), Repeating.class.getName()::equals);
assertThat(getAll(mappings)).flatExtracting(AnnotationTypeMapping::getAnnotationType)
.containsExactly(WithRepeatedMetaAnnotations.class);
}
@@ -120,9 +120,9 @@ class ClassPathResourceTests {
Resource relative = resource.createRelative("subdir");
assertThat(relative).isEqualTo(new ClassPathResource("dir/subdir"));
}
}
@Nested
class GetInputStream {
@@ -151,9 +151,9 @@ class ClassPathResourceTests {
.isThrownBy(resource::getInputStream)
.withMessageContaining(ABSOLUTE_PATH_TO_NONEXISTENT_RESOURCE);
}
}
@Nested
class GetDescription {
@@ -190,9 +190,9 @@ class ClassPathResourceTests {
private static void assertDescription(ClassPathResource resource) {
assertThat(resource.getDescription()).isEqualTo("class path resource [%s]", ABSOLUTE_PATH_TO_NONEXISTENT_RESOURCE);
}
}
@Nested
class GetPath {
@@ -209,9 +209,9 @@ class ClassPathResourceTests {
assertThat(((ClassPathResource) new ClassPathResource("", getClass()).createRelative("/test.html")).getPath()).isEqualTo("test.html");
assertThat(((ClassPathResource) new ClassPathResource("", getClass()).createRelative("test.html")).getPath()).isEqualTo(PACKAGE_PATH + "/test.html");
}
}
@Test
void directoryNotReadable() throws Exception {
Resource fileDir = new ClassPathResource("org/springframework/core");
@@ -173,9 +173,9 @@ class ResourceTests {
Resource resource = new ByteArrayResource("testString".getBytes(), "my description");
assertThat(resource.getDescription().contains("my description")).isTrue();
}
}
@Nested
class InputStreamResourceTests {
@@ -215,6 +215,7 @@ class ResourceTests {
}
}
@Nested
class FileSystemResourceTests {
@@ -286,6 +287,7 @@ class ResourceTests {
}
}
@Nested
class UrlResourceTests {
@@ -421,6 +423,7 @@ class ResourceTests {
}
}
@Nested
class AbstractResourceTests {
@@ -467,7 +470,6 @@ class ResourceTests {
};
assertThat(resource.contentLength()).isEqualTo(3L);
}
}
}
@@ -796,10 +796,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
if (!(bufferFactory instanceof Netty5DataBufferFactory)) {
assertThat(result).isEqualTo(new byte[]{'b', 'c'});
}
else {
assertThat(result).isEqualTo(new byte[]{'b', 0});
release(slice);
}
release(buffer);
}
@@ -938,4 +934,15 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
release(buffer);
}
@ParameterizedDataBufferAllocatingTest // gh-31605
void shouldHonorSourceBuffersReadPosition(DataBufferFactory bufferFactory) {
DataBuffer dataBuffer = bufferFactory.wrap("ab".getBytes(StandardCharsets.UTF_8));
dataBuffer.readPosition(1);
ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount());
dataBuffer.toByteBuffer(byteBuffer);
assertThat(StandardCharsets.UTF_8.decode(byteBuffer).toString()).isEqualTo("b");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -39,6 +39,7 @@ class PooledDataBufferTests {
}
}
@Nested
class UnpooledByteBufAllocatorWithPreferDirectFalseTests implements PooledDataBufferTestingTrait {
@@ -48,6 +49,7 @@ class PooledDataBufferTests {
}
}
@Nested
class PooledByteBufAllocatorWithPreferDirectTrueTests implements PooledDataBufferTestingTrait {
@@ -57,6 +59,7 @@ class PooledDataBufferTests {
}
}
@Nested
class PooledByteBufAllocatorWithPreferDirectFalseTests implements PooledDataBufferTestingTrait {
@@ -66,6 +69,7 @@ class PooledDataBufferTests {
}
}
interface PooledDataBufferTestingTrait {
DataBufferFactory createDataBufferFactory();
@@ -92,7 +96,6 @@ class PooledDataBufferTests {
buffer.release();
assertThatIllegalStateException().isThrownBy(buffer::release);
}
}
}
@@ -48,7 +48,9 @@ class PropertySourceProcessorTests {
private static final String PROPS_FILE = ClassUtils.classPackageAsResourcePath(PropertySourceProcessorTests.class) + "/test.properties";
private final StandardEnvironment environment = new StandardEnvironment();
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
private final PropertySourceProcessor processor = new PropertySourceProcessor(environment, resourceLoader);
@@ -59,12 +61,14 @@ class PropertySourceProcessorTests {
@Test
void processorRegistersPropertySource() throws Exception {
PropertySourceDescriptor descriptor = new PropertySourceDescriptor(List.of(PROPS_FILE), false, null, DefaultPropertySourceFactory.class, null);
PropertySourceDescriptor descriptor = new PropertySourceDescriptor(
List.of(PROPS_FILE), false, null, DefaultPropertySourceFactory.class, null);
processor.processPropertySource(descriptor);
assertThat(environment.getPropertySources()).hasSize(3);
assertThat(environment.getProperty("enigma")).isEqualTo("42");
}
@Nested
class FailOnErrorTests {
@@ -86,9 +90,9 @@ class PropertySourceProcessorTests {
assertThatExceptionOfType(exceptionType).isThrownBy(() -> processor.processPropertySource(descriptor));
assertThat(environment.getPropertySources()).hasSize(2);
}
}
@Nested
class IgnoreResourceNotFoundTests {
@@ -127,7 +131,6 @@ class PropertySourceProcessorTests {
assertThatNoException().isThrownBy(() -> processor.processPropertySource(descriptor));
assertThat(environment.getPropertySources()).hasSize(2);
}
}
@@ -223,7 +223,6 @@ class SpringFactoriesLoaderTests {
assertThat(messages).hasSize(1);
assertThat(messages.get(0)).startsWith("Unable to instantiate factory class");
}
}
@@ -292,9 +291,9 @@ class SpringFactoriesLoaderTests {
assertThat(resolver.resolve(Integer.class)).isEqualTo(123);
assertThat(resolver.resolve(Long.class)).isEqualTo(234L);
}
}
@Nested
class FactoryInstantiatorTests {
@@ -350,14 +349,12 @@ class SpringFactoriesLoaderTests {
}
static class DefaultConstructor {
}
static class SingleConstructor {
SingleConstructor(String arg) {
}
}
static class MultiplePrivateAndSinglePublicConstructor {
@@ -368,7 +365,6 @@ class SpringFactoriesLoaderTests {
private MultiplePrivateAndSinglePublicConstructor(String arg, boolean extra) {
}
}
static class MultiplePackagePrivateAndSinglePublicConstructor {
@@ -379,22 +375,18 @@ class SpringFactoriesLoaderTests {
MultiplePackagePrivateAndSinglePublicConstructor(String arg, boolean extra) {
}
}
static class SinglePackagePrivateConstructor {
SinglePackagePrivateConstructor(String arg) {
}
}
static class SinglePrivateConstructor {
private SinglePrivateConstructor(String arg) {
}
}
static class MultiplePackagePrivateConstructors {
@@ -405,11 +397,10 @@ class SpringFactoriesLoaderTests {
MultiplePackagePrivateConstructors(String arg, boolean extra) {
}
}
}
private static class LimitedClassLoader extends URLClassLoader {
private static final ClassLoader constructorArgumentFactories = new LimitedClassLoader("constructor-argument-factories");
@@ -428,7 +419,6 @@ class SpringFactoriesLoaderTests {
throw new IllegalStateException(ex);
}
}
}
}
@@ -451,8 +451,9 @@ class MimeTypeTests {
MimeType audioWave = new MimeType("audio", "wave");
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
List<MimeType> mimeTypes = new ArrayList<>(List.of(MimeTypeUtils.ALL, audio, audioWave, audioBasic,
audioBasicLevel));
List<MimeType> mimeTypes = new ArrayList<>(
List.of(MimeTypeUtils.ALL, audio, audioWave, audioBasic, audioBasicLevel));
MimeTypeUtils.sortBySpecificity(mimeTypes);
assertThat(mimeTypes).containsExactly(audioWave, audioBasicLevel, audioBasic, audio, MimeTypeUtils.ALL);
@@ -0,0 +1,31 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class FilterWithAlias(
@get:AliasFor("name")
val value: String = "",
@get:AliasFor("value")
val name: String = "",
val and: FiltersWithoutAlias = FiltersWithoutAlias()
)
@@ -0,0 +1,25 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class FiltersWithoutAlias(
vararg val value: FilterWithAlias
)
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test
*
* @author Sam Brannen
* @author Juergen Hoeller
* @author Lorenz Simon
* @since 5.3.16
*/
class KotlinMergedAnnotationsTests {
@@ -74,6 +75,35 @@ class KotlinMergedAnnotationsTests {
assertThat(synthesizedFriends).hasSize(2)
}
@Test // gh-28012
fun recursiveNestedAnnotationWithAlias() {
val method = javaClass.getMethod("filterWithAliasMethod")
// MergedAnnotations
val mergedAnnotations = MergedAnnotations.from(method)
assertThat(mergedAnnotations.isPresent(FilterWithAlias::class.java)).isTrue();
// MergedAnnotation
val mergedAnnotation = MergedAnnotation.from(method.getAnnotation(FilterWithAlias::class.java))
assertThat(mergedAnnotation).isNotNull();
// Synthesized Annotations
val fooFilter = mergedAnnotation.synthesize()
assertThat(fooFilter.value).isEqualTo("foo")
assertThat(fooFilter.name).isEqualTo("foo")
val filters = fooFilter.and
assertThat(filters.value).hasSize(2)
val barFilter = filters.value[0]
assertThat(barFilter.value).isEqualTo("bar")
assertThat(barFilter.name).isEqualTo("bar")
assertThat(barFilter.and.value).isEmpty()
val bazFilter = filters.value[1]
assertThat(bazFilter.value).isEqualTo("baz")
assertThat(bazFilter.name).isEqualTo("baz")
assertThat(bazFilter.and.value).isEmpty()
}
@PersonWithAlias("jane", friends = [PersonWithAlias("john"), PersonWithAlias("sally")])
fun personWithAliasMethod() {
@@ -83,4 +113,8 @@ class KotlinMergedAnnotationsTests {
fun personWithoutAliasMethod() {
}
@FilterWithAlias("foo", and = FiltersWithoutAlias(FilterWithAlias("bar"), FilterWithAlias("baz")))
fun filterWithAliasMethod() {
}
}
@@ -19,7 +19,10 @@ package org.springframework.expression.spel.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.SmartClassLoader;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.spel.SpelEvaluationException;
@@ -48,6 +51,8 @@ public class StandardTypeLocator implements TypeLocator {
private final List<String> importPrefixes = new ArrayList<>(1);
private final Map<String, Class<?>> typeCache = new ConcurrentHashMap<>();
/**
* Create a {@code StandardTypeLocator} for the default {@link ClassLoader}
@@ -110,6 +115,21 @@ public class StandardTypeLocator implements TypeLocator {
*/
@Override
public Class<?> findType(String typeName) throws EvaluationException {
Class<?> cachedType = this.typeCache.get(typeName);
if (cachedType != null) {
return cachedType;
}
Class<?> loadedType = loadType(typeName);
if (loadedType != null &&
!(this.classLoader instanceof SmartClassLoader scl && scl.isClassReloadable(loadedType))) {
this.typeCache.put(typeName, loadedType);
return loadedType;
}
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
}
@Nullable
private Class<?> loadType(String typeName) {
try {
return ClassUtils.forName(typeName, this.classLoader);
}
@@ -125,7 +145,7 @@ public class StandardTypeLocator implements TypeLocator {
// might be a different prefix
}
}
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
return null;
}
}
@@ -145,9 +145,12 @@ final class LogAdapter {
private static final LoggerContext loggerContext =
LogManager.getContext(Log4jLog.class.getClassLoader(), false);
private final ExtendedLogger logger;
private final String name;
private final transient ExtendedLogger logger;
public Log4jLog(String name) {
this.name = name;
LoggerContext context = loggerContext;
if (context == null) {
// Circular call in early-init scenario -> static field not initialized yet
@@ -261,6 +264,10 @@ final class LogAdapter {
this.logger.logIfEnabled(FQCN, level, null, message, exception);
}
}
protected Object readResolve() {
return new Log4jLog(this.name);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -16,7 +16,6 @@
package org.springframework.jdbc.core.metadata;
import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -115,28 +114,28 @@ public class CallMetaDataContext {
}
/**
* Specify a limited set of in parameters to be used.
* Specify a limited set of the {@code in} parameters to be used.
*/
public void setLimitedInParameterNames(Set<String> limitedInParameterNames) {
this.limitedInParameterNames = limitedInParameterNames;
}
/**
* Get a limited set of in parameters to be used.
* Get the limited set of the {@code in} parameters to be used.
*/
public Set<String> getLimitedInParameterNames() {
return this.limitedInParameterNames;
}
/**
* Specify the names of the out parameters.
* Specify the names of the {@code out} parameters.
*/
public void setOutParameterNames(List<String> outParameterNames) {
this.outParameterNames = outParameterNames;
}
/**
* Get a list of the out parameter names.
* Get the list of the {@code out} parameter names.
*/
public List<String> getOutParameterNames() {
return this.outParameterNames;
@@ -434,14 +433,14 @@ public class CallMetaDataContext {
if (paramNameToUse == null) {
paramNameToUse = "";
}
if (meta.getParameterType() == DatabaseMetaData.procedureColumnOut) {
if (meta.isOutParameter()) {
workParams.add(provider.createDefaultOutParameter(paramNameToUse, meta));
outParamNames.add(paramNameToUse);
if (logger.isDebugEnabled()) {
logger.debug("Added meta-data out parameter for '" + paramNameToUse + "'");
}
}
else if (meta.getParameterType() == DatabaseMetaData.procedureColumnInOut) {
else if (meta.isInOutParameter()) {
workParams.add(provider.createDefaultInOutParameter(paramNameToUse, meta));
outParamNames.add(paramNameToUse);
if (logger.isDebugEnabled()) {
@@ -554,7 +553,7 @@ public class CallMetaDataContext {
Map<String, String> callParameterNames = CollectionUtils.newHashMap(this.callParameters.size());
for (SqlParameter parameter : this.callParameters) {
if (parameter.isInputValueProvided()) {
String parameterName = parameter.getName();
String parameterName = parameter.getName();
String parameterNameToMatch = provider.parameterNameToUse(parameterName);
if (parameterNameToMatch != null) {
callParameterNames.put(parameterNameToMatch.toLowerCase(), parameterName);
@@ -606,7 +605,7 @@ public class CallMetaDataContext {
int i = 0;
for (SqlParameter parameter : this.callParameters) {
if (parameter.isInputValueProvided()) {
String parameterName = parameter.getName();
String parameterName = parameter.getName();
matchedParameters.put(parameterName, parameterValues[i++]);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -97,6 +97,28 @@ public class CallParameterMetaData {
this.parameterType == DatabaseMetaData.procedureColumnResult));
}
/**
* Determine whether the declared parameter qualifies as an 'out' parameter
* for our purposes: type {@link DatabaseMetaData#procedureColumnOut},
* or in case of a function, {@link DatabaseMetaData#functionColumnOut}.
* @since 5.3.31
*/
public boolean isOutParameter() {
return (this.function ? this.parameterType == DatabaseMetaData.functionColumnOut :
this.parameterType == DatabaseMetaData.procedureColumnOut);
}
/**
* Determine whether the declared parameter qualifies as an 'in-out' parameter
* for our purposes: type {@link DatabaseMetaData#procedureColumnInOut},
* or in case of a function, {@link DatabaseMetaData#functionColumnInOut}.
* @since 5.3.31
*/
public boolean isInOutParameter() {
return (this.function ? this.parameterType == DatabaseMetaData.functionColumnInOut :
this.parameterType == DatabaseMetaData.procedureColumnInOut);
}
/**
* Return the parameter SQL type.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -35,7 +35,8 @@ import org.springframework.util.StringUtils;
/**
* A generic implementation of the {@link CallMetaDataProvider} interface.
* This class can be extended to provide database specific behavior.
*
* <p>This class can be extended to provide database specific behavior.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -113,7 +114,7 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
@Nullable String schemaName, @Nullable String procedureName) throws SQLException {
this.procedureColumnMetaDataUsed = true;
processProcedureColumns(databaseMetaData, catalogName, schemaName, procedureName);
processProcedureColumns(databaseMetaData, catalogName, schemaName, procedureName);
}
@Override
@@ -124,52 +125,19 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
@Override
@Nullable
public String procedureNameToUse(@Nullable String procedureName) {
if (procedureName == null) {
return null;
}
else if (isStoresUpperCaseIdentifiers()) {
return procedureName.toUpperCase();
}
else if (isStoresLowerCaseIdentifiers()) {
return procedureName.toLowerCase();
}
else {
return procedureName;
}
return identifierNameToUse(procedureName);
}
@Override
@Nullable
public String catalogNameToUse(@Nullable String catalogName) {
if (catalogName == null) {
return null;
}
else if (isStoresUpperCaseIdentifiers()) {
return catalogName.toUpperCase();
}
else if (isStoresLowerCaseIdentifiers()) {
return catalogName.toLowerCase();
}
else {
return catalogName;
}
return identifierNameToUse(catalogName);
}
@Override
@Nullable
public String schemaNameToUse(@Nullable String schemaName) {
if (schemaName == null) {
return null;
}
else if (isStoresUpperCaseIdentifiers()) {
return schemaName.toUpperCase();
}
else if (isStoresLowerCaseIdentifiers()) {
return schemaName.toLowerCase();
}
else {
return schemaName;
}
return identifierNameToUse(schemaName);
}
@Override
@@ -197,18 +165,7 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
@Override
@Nullable
public String parameterNameToUse(@Nullable String parameterName) {
if (parameterName == null) {
return null;
}
else if (isStoresUpperCaseIdentifiers()) {
return parameterName.toUpperCase();
}
else if (isStoresLowerCaseIdentifiers()) {
return parameterName.toLowerCase();
}
else {
return parameterName;
}
return identifierNameToUse(parameterName);
}
@Override
@@ -316,6 +273,22 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
}
@Nullable
private String identifierNameToUse(@Nullable String identifierName) {
if (identifierName == null) {
return null;
}
else if (isStoresUpperCaseIdentifiers()) {
return identifierName.toUpperCase();
}
else if (isStoresLowerCaseIdentifiers()) {
return identifierName.toLowerCase();
}
else {
return identifierName;
}
}
/**
* Process the procedure column meta-data.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -526,17 +526,6 @@ public interface NamedParameterJdbcOperations {
int update(String sql, SqlParameterSource paramSource, KeyHolder generatedKeyHolder, String[] keyColumnNames)
throws DataAccessException;
/**
* Executes a batch using the supplied SQL statement with the batch of supplied arguments.
* @param sql the SQL statement to execute
* @param batchValues the array of Maps containing the batch of arguments for the query
* @return an array containing the numbers of rows affected by each update in the batch
* (may also contain special JDBC-defined negative values for affected rows such as
* {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED})
* @throws DataAccessException if there is any problem issuing the update
*/
int[] batchUpdate(String sql, Map<String, ?>[] batchValues);
/**
* Execute a batch using the supplied SQL statement with the batch of supplied arguments.
* @param sql the SQL statement to execute
@@ -549,4 +538,15 @@ public interface NamedParameterJdbcOperations {
*/
int[] batchUpdate(String sql, SqlParameterSource[] batchArgs);
/**
* Executes a batch using the supplied SQL statement with the batch of supplied arguments.
* @param sql the SQL statement to execute
* @param batchValues the array of Maps containing the batch of arguments for the query
* @return an array containing the numbers of rows affected by each update in the batch
* (may also contain special JDBC-defined negative values for affected rows such as
* {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED})
* @throws DataAccessException if there is any problem issuing the update
*/
int[] batchUpdate(String sql, Map<String, ?>[] batchValues);
}
@@ -359,11 +359,6 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
return getJdbcOperations().update(psc, generatedKeyHolder);
}
@Override
public int[] batchUpdate(String sql, Map<String, ?>[] batchValues) {
return batchUpdate(sql, SqlParameterSourceUtils.createBatch(batchValues));
}
@Override
public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs) {
if (batchArgs.length == 0) {
@@ -388,6 +383,11 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
});
}
@Override
public int[] batchUpdate(String sql, Map<String, ?>[] batchValues) {
return batchUpdate(sql, SqlParameterSourceUtils.createBatch(batchValues));
}
/**
* Build a {@link PreparedStatementCreator} based on the given SQL and named parameters.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -55,7 +55,7 @@ public abstract class NamedParameterUtils {
* Set of characters that qualify as parameter separators,
* indicating that a parameter name in an SQL String has ended.
*/
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^]";
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^";
/**
* An index with separator flags per character code.
@@ -142,16 +142,25 @@ public abstract class NamedParameterUtils {
j++;
}
else {
while (j < statement.length && !isParameterSeparator(statement[j])) {
boolean paramWithSquareBrackets = false;
while (j < statement.length) {
c = statement[j];
if (isParameterSeparator(c)) {
break;
}
if (c == '[') {
paramWithSquareBrackets = true;
}
else if (c == ']') {
if (!paramWithSquareBrackets) {
break;
}
paramWithSquareBrackets = false;
}
j++;
}
if (j - i > 1) {
parameter = sql.substring(i + 1, j);
if (j < statement.length && statement[j] == ']' && parameter.contains("[")) {
// preserve end bracket for index/key
j++;
parameter = sql.substring(i + 1, j);
}
namedParameterCount = addNewNamedParameter(
namedParameters, namedParameterCount, parameter);
totalParameterCount = addNamedParameter(
@@ -89,6 +89,14 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
"61" // Oracle: deadlock
);
private static final Set<Integer> DUPLICATE_KEY_ERROR_CODES = Set.of(
1, // Oracle
301, // SAP HANA
1062, // MySQL/MariaDB
2601, // MS SQL Server
2627 // MS SQL Server
);
@Override
@Nullable
@@ -155,18 +163,16 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
/**
* Check whether the given SQL state (and the associated error code in case
* Check whether the given SQL state and the associated error code (in case
* of a generic SQL state value) indicate a {@link DuplicateKeyException}:
* either SQL state 23505 as a specific indication, or the generic SQL state
* 23000 with well-known vendor codes (1 for Oracle, 1062 for MySQL/MariaDB,
* 2601/2627 for MS SQL Server).
* 23000 with a well-known vendor code.
* @param sqlState the SQL state value
* @param errorCode the error code value
* @param errorCode the error code
*/
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
return ("23505".equals(sqlState) ||
("23000".equals(sqlState) &&
(errorCode == 1 || errorCode == 1062 || errorCode == 2601 || errorCode == 2627)));
("23000".equals(sqlState) && DUPLICATE_KEY_ERROR_CODES.contains(errorCode)));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -330,6 +330,18 @@ public class NamedParameterUtilsTests {
assertThat(sqlToUse).isEqualTo("SELECT ARRAY[?]");
}
@Test // gh-31596
void paramNameWithNestedSquareBrackets() {
String sql = "insert into GeneratedAlways (id, first_name, last_name) values " +
"(:records[0].id, :records[0].firstName, :records[0].lastName), " +
"(:records[1].id, :records[1].firstName, :records[1].lastName)";
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
assertThat(parsedSql.getParameterNames()).containsOnly(
"records[0].id", "records[0].firstName", "records[0].lastName",
"records[1].id", "records[1].firstName", "records[1].lastName");
}
@Test // gh-27925
void namedParamMapReference() {
String sql = "insert into foos (id) values (:headers[id])";
@@ -19,7 +19,6 @@ package org.springframework.jdbc.support;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import javax.sql.DataSource;
@@ -41,108 +40,108 @@ import static org.mockito.Mockito.verify;
* @author Thomas Risberg
* @author Stephane Nicoll
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class SQLErrorCodesFactoryTests {
class SQLErrorCodesFactoryTests {
/**
* Check that a default instance returns empty error codes for an unknown database.
*/
@Test
public void testDefaultInstanceWithNoSuchDatabase() {
void defaultInstanceWithNoSuchDatabase() {
SQLErrorCodes sec = SQLErrorCodesFactory.getInstance().getErrorCodes("xx");
assertThat(sec.getBadSqlGrammarCodes().length).isEqualTo(0);
assertThat(sec.getDataIntegrityViolationCodes().length).isEqualTo(0);
assertThat(sec.getBadSqlGrammarCodes()).isEmpty();
assertThat(sec.getDataIntegrityViolationCodes()).isEmpty();
}
/**
* Check that a known database produces recognizable codes.
*/
@Test
public void testDefaultInstanceWithOracle() {
void defaultInstanceWithOracle() {
SQLErrorCodes sec = SQLErrorCodesFactory.getInstance().getErrorCodes("Oracle");
assertIsOracle(sec);
}
private void assertIsOracle(SQLErrorCodes sec) {
assertThat(sec.getBadSqlGrammarCodes().length).isGreaterThan(0);
assertThat(sec.getDataIntegrityViolationCodes().length).isGreaterThan(0);
assertThat(sec.getBadSqlGrammarCodes()).isNotEmpty();
assertThat(sec.getDataIntegrityViolationCodes()).isNotEmpty();
// These had better be a Bad SQL Grammar code
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "942")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "6550")).isGreaterThanOrEqualTo(0);
assertThat(sec.getBadSqlGrammarCodes()).contains("942");
assertThat(sec.getBadSqlGrammarCodes()).contains("6550");
// This had better NOT be
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "9xx42")).isLessThan(0);
assertThat(sec.getBadSqlGrammarCodes()).doesNotContain("9xx42");
}
private void assertIsSQLServer(SQLErrorCodes sec) {
assertThat(sec.getDatabaseProductName()).isEqualTo("Microsoft SQL Server");
assertThat(sec.getBadSqlGrammarCodes().length).isGreaterThan(0);
assertThat(sec.getBadSqlGrammarCodes()).isNotEmpty();
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "156")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "170")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "207")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "208")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "209")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "9xx42")).isLessThan(0);
assertThat(sec.getBadSqlGrammarCodes()).contains("156");
assertThat(sec.getBadSqlGrammarCodes()).contains("170");
assertThat(sec.getBadSqlGrammarCodes()).contains("207");
assertThat(sec.getBadSqlGrammarCodes()).contains("208");
assertThat(sec.getBadSqlGrammarCodes()).contains("209");
assertThat(sec.getBadSqlGrammarCodes()).doesNotContain("9xx42");
assertThat(sec.getPermissionDeniedCodes().length).isGreaterThan(0);
assertThat(Arrays.binarySearch(sec.getPermissionDeniedCodes(), "229")).isGreaterThanOrEqualTo(0);
assertThat(sec.getPermissionDeniedCodes()).isNotEmpty();
assertThat(sec.getPermissionDeniedCodes()).contains("229");
assertThat(sec.getDuplicateKeyCodes().length).isGreaterThan(0);
assertThat(Arrays.binarySearch(sec.getDuplicateKeyCodes(), "2601")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getDuplicateKeyCodes(), "2627")).isGreaterThanOrEqualTo(0);
assertThat(sec.getDuplicateKeyCodes()).isNotEmpty();
assertThat(sec.getDuplicateKeyCodes()).contains("2601");
assertThat(sec.getDuplicateKeyCodes()).contains("2627");
assertThat(sec.getDataIntegrityViolationCodes().length).isGreaterThan(0);
assertThat(Arrays.binarySearch(sec.getDataIntegrityViolationCodes(), "544")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getDataIntegrityViolationCodes(), "8114")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getDataIntegrityViolationCodes(), "8115")).isGreaterThanOrEqualTo(0);
assertThat(sec.getDataIntegrityViolationCodes()).isNotEmpty();
assertThat(sec.getDataIntegrityViolationCodes()).contains("544");
assertThat(sec.getDataIntegrityViolationCodes()).contains("8114");
assertThat(sec.getDataIntegrityViolationCodes()).contains("8115");
assertThat(sec.getDataAccessResourceFailureCodes().length).isGreaterThan(0);
assertThat(Arrays.binarySearch(sec.getDataAccessResourceFailureCodes(), "4060")).isGreaterThanOrEqualTo(0);
assertThat(sec.getDataAccessResourceFailureCodes()).isNotEmpty();
assertThat(sec.getDataAccessResourceFailureCodes()).contains("4060");
assertThat(sec.getCannotAcquireLockCodes().length).isGreaterThan(0);
assertThat(Arrays.binarySearch(sec.getCannotAcquireLockCodes(), "1222")).isGreaterThanOrEqualTo(0);
assertThat(sec.getCannotAcquireLockCodes()).isNotEmpty();
assertThat(sec.getCannotAcquireLockCodes()).contains("1222");
assertThat(sec.getDeadlockLoserCodes().length).isGreaterThan(0);
assertThat(Arrays.binarySearch(sec.getDeadlockLoserCodes(), "1205")).isGreaterThanOrEqualTo(0);
assertThat(sec.getDeadlockLoserCodes()).isNotEmpty();
assertThat(sec.getDeadlockLoserCodes()).contains("1205");
}
private void assertIsHsql(SQLErrorCodes sec) {
assertThat(sec.getBadSqlGrammarCodes().length).isGreaterThan(0);
assertThat(sec.getDataIntegrityViolationCodes().length).isGreaterThan(0);
assertThat(sec.getBadSqlGrammarCodes()).isNotEmpty();
assertThat(sec.getDataIntegrityViolationCodes()).isNotEmpty();
// This had better be a Bad SQL Grammar code
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "-22")).isGreaterThanOrEqualTo(0);
assertThat(sec.getBadSqlGrammarCodes()).contains("-22");
// This had better NOT be
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "-9")).isLessThan(0);
assertThat(sec.getBadSqlGrammarCodes()).doesNotContain("-9");
}
private void assertIsDB2(SQLErrorCodes sec) {
assertThat(sec.getBadSqlGrammarCodes().length).isGreaterThan(0);
assertThat(sec.getDataIntegrityViolationCodes().length).isGreaterThan(0);
assertThat(sec.getBadSqlGrammarCodes()).isNotEmpty();
assertThat(sec.getDataIntegrityViolationCodes()).isNotEmpty();
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "942")).isLessThan(0);
assertThat(sec.getBadSqlGrammarCodes()).doesNotContain("942");
// This had better NOT be
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "-204")).isGreaterThanOrEqualTo(0);
assertThat(sec.getBadSqlGrammarCodes()).contains("-204");
}
private void assertIsHana(SQLErrorCodes sec) {
assertThat(sec.getBadSqlGrammarCodes().length).isGreaterThan(0);
assertThat(sec.getDataIntegrityViolationCodes().length).isGreaterThan(0);
assertThat(Arrays.binarySearch(sec.getBadSqlGrammarCodes(), "368")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getPermissionDeniedCodes(), "10")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getDuplicateKeyCodes(), "301")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getDataIntegrityViolationCodes(), "461")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getDataAccessResourceFailureCodes(), "-813")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getInvalidResultSetAccessCodes(), "582")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getCannotAcquireLockCodes(), "131")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getCannotSerializeTransactionCodes(), "138")).isGreaterThanOrEqualTo(0);
assertThat(Arrays.binarySearch(sec.getDeadlockLoserCodes(), "133")).isGreaterThanOrEqualTo(0);
assertThat(sec.getBadSqlGrammarCodes()).isNotEmpty();
assertThat(sec.getDataIntegrityViolationCodes()).isNotEmpty();
assertThat(sec.getBadSqlGrammarCodes()).contains("368");
assertThat(sec.getPermissionDeniedCodes()).contains("10");
assertThat(sec.getDuplicateKeyCodes()).contains("301");
assertThat(sec.getDataIntegrityViolationCodes()).contains("461");
assertThat(sec.getDataAccessResourceFailureCodes()).contains("-813");
assertThat(sec.getInvalidResultSetAccessCodes()).contains("582");
assertThat(sec.getCannotAcquireLockCodes()).contains("131");
assertThat(sec.getCannotSerializeTransactionCodes()).contains("138");
assertThat(sec.getDeadlockLoserCodes()).contains("133");
}
@Test
public void testLookupOrder() {
void lookupOrder() {
class TestSQLErrorCodesFactory extends SQLErrorCodesFactory {
private int lookups = 0;
@Override
@@ -163,15 +162,15 @@ public class SQLErrorCodesFactoryTests {
// Should have failed to load without error
TestSQLErrorCodesFactory sf = new TestSQLErrorCodesFactory();
assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes().length).isEqualTo(0);
assertThat(sf.getErrorCodes("Oracle").getDataIntegrityViolationCodes().length).isEqualTo(0);
assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes()).isEmpty();
assertThat(sf.getErrorCodes("Oracle").getDataIntegrityViolationCodes()).isEmpty();
}
/**
* Check that user defined error codes take precedence.
*/
@Test
public void testFindUserDefinedCodes() {
void findUserDefinedCodes() {
class TestSQLErrorCodesFactory extends SQLErrorCodesFactory {
@Override
protected Resource loadResource(String path) {
@@ -184,14 +183,12 @@ public class SQLErrorCodesFactoryTests {
// Should have loaded without error
TestSQLErrorCodesFactory sf = new TestSQLErrorCodesFactory();
assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes().length).isEqualTo(0);
assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()).hasSize(2);
assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()[0]).isEqualTo("1");
assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()[1]).isEqualTo("2");
assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes()).isEmpty();
assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()).containsExactly("1", "2");
}
@Test
public void testInvalidUserDefinedCodeFormat() {
void invalidUserDefinedCodeFormat() {
class TestSQLErrorCodesFactory extends SQLErrorCodesFactory {
@Override
protected Resource loadResource(String path) {
@@ -205,7 +202,7 @@ public class SQLErrorCodesFactoryTests {
// Should have failed to load without error
TestSQLErrorCodesFactory sf = new TestSQLErrorCodesFactory();
assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes().length).isEqualTo(0);
assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes()).isEmpty();
assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()).isEmpty();
}
@@ -213,7 +210,7 @@ public class SQLErrorCodesFactoryTests {
* Check that custom error codes take precedence.
*/
@Test
public void testFindCustomCodes() {
void findCustomCodes() {
class TestSQLErrorCodesFactory extends SQLErrorCodesFactory {
@Override
protected Resource loadResource(String path) {
@@ -227,14 +224,13 @@ public class SQLErrorCodesFactoryTests {
// Should have loaded without error
TestSQLErrorCodesFactory sf = new TestSQLErrorCodesFactory();
assertThat(sf.getErrorCodes("Oracle").getCustomTranslations()).hasSize(1);
CustomSQLErrorCodesTranslation translation =
sf.getErrorCodes("Oracle").getCustomTranslations()[0];
CustomSQLErrorCodesTranslation translation = sf.getErrorCodes("Oracle").getCustomTranslations()[0];
assertThat(translation.getExceptionClass()).isEqualTo(CustomErrorCodeException.class);
assertThat(translation.getErrorCodes()).hasSize(1);
}
@Test
public void testDataSourceWithNullMetadata() throws Exception {
void dataSourceWithNullMetadata() throws Exception {
Connection connection = mock();
DataSource dataSource = mock();
given(dataSource.getConnection()).willReturn(connection);
@@ -250,7 +246,7 @@ public class SQLErrorCodesFactoryTests {
}
@Test
public void testGetFromDataSourceWithSQLException() throws Exception {
void getFromDataSourceWithSQLException() throws Exception {
SQLException expectedSQLException = new SQLException();
DataSource dataSource = mock();
@@ -284,25 +280,25 @@ public class SQLErrorCodesFactoryTests {
}
@Test
public void testSQLServerRecognizedFromMetadata() throws Exception {
void sqlServerRecognizedFromMetadata() throws Exception {
SQLErrorCodes sec = getErrorCodesFromDataSource("MS-SQL", null);
assertIsSQLServer(sec);
}
@Test
public void testOracleRecognizedFromMetadata() throws Exception {
void oracleRecognizedFromMetadata() throws Exception {
SQLErrorCodes sec = getErrorCodesFromDataSource("Oracle", null);
assertIsOracle(sec);
}
@Test
public void testHsqlRecognizedFromMetadata() throws Exception {
void hsqlRecognizedFromMetadata() throws Exception {
SQLErrorCodes sec = getErrorCodesFromDataSource("HSQL Database Engine", null);
assertIsHsql(sec);
}
@Test
public void testDB2RecognizedFromMetadata() throws Exception {
void dB2RecognizedFromMetadata() throws Exception {
SQLErrorCodes sec = getErrorCodesFromDataSource("DB2", null);
assertIsDB2(sec);
sec = getErrorCodesFromDataSource("DB2/", null);
@@ -312,7 +308,7 @@ public class SQLErrorCodesFactoryTests {
}
@Test
public void testHanaIsRecognizedFromMetadata() throws Exception {
void hanaIsRecognizedFromMetadata() throws Exception {
SQLErrorCodes sec = getErrorCodesFromDataSource("SAP DB", null);
assertIsHana(sec);
}
@@ -321,7 +317,7 @@ public class SQLErrorCodesFactoryTests {
* Check that wild card database name works.
*/
@Test
public void testWildCardNameRecognized() throws Exception {
void wildCardNameRecognized() throws Exception {
class WildcardSQLErrorCodesFactory extends SQLErrorCodesFactory {
@Override
protected Resource loadResource(String path) {
@@ -34,81 +34,89 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link SQLStateSQLExceptionTranslator}.
*
* @author Rick Evans
* @author Juergen Hoeller
* @author Chris Beams
*/
public class SQLStateSQLExceptionTranslatorTests {
class SQLStateSQLExceptionTranslatorTests {
private final SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator();
@Test
public void translateNullException() {
assertThatIllegalArgumentException().isThrownBy(() ->
new SQLStateSQLExceptionTranslator().translate("", "", null));
void translateNullException() {
assertThatIllegalArgumentException().isThrownBy(() -> translator.translate("", "", null));
}
@Test
public void translateBadSqlGrammar() {
doTest("07", BadSqlGrammarException.class);
void translateBadSqlGrammar() {
assertTranslation("07", BadSqlGrammarException.class);
}
@Test
public void translateDataIntegrityViolation() {
doTest("23", DataIntegrityViolationException.class);
void translateDataIntegrityViolation() {
assertTranslation("23", DataIntegrityViolationException.class);
}
@Test
public void translateDuplicateKey() {
doTest("23505", DuplicateKeyException.class);
void translateDuplicateKey() {
assertTranslation("23505", DuplicateKeyException.class);
}
@Test
public void translateDuplicateKeyOracle() {
doTest("23000", 1, DuplicateKeyException.class);
void translateDuplicateKeyOracle() {
assertTranslation("23000", 1, DuplicateKeyException.class);
}
@Test
public void translateDuplicateKeyMySQL() {
doTest("23000", 1062, DuplicateKeyException.class);
void translateDuplicateKeyMySQL() {
assertTranslation("23000", 1062, DuplicateKeyException.class);
}
@Test
public void translateDuplicateKeyMSSQL1() {
doTest("23000", 2601, DuplicateKeyException.class);
void translateDuplicateKeyMSSQL1() {
assertTranslation("23000", 2601, DuplicateKeyException.class);
}
@Test
public void translateDuplicateKeyMSSQL2() {
doTest("23000", 2627, DuplicateKeyException.class);
void translateDuplicateKeyMSSQL2() {
assertTranslation("23000", 2627, DuplicateKeyException.class);
}
@Test // gh-31554
void translateDuplicateKeySapHana() {
assertTranslation("23000", 301, DuplicateKeyException.class);
}
@Test
public void translateDataAccessResourceFailure() {
doTest("53", DataAccessResourceFailureException.class);
void translateDataAccessResourceFailure() {
assertTranslation("53", DataAccessResourceFailureException.class);
}
@Test
public void translateTransientDataAccessResourceFailure() {
doTest("S1", TransientDataAccessResourceException.class);
void translateTransientDataAccessResourceFailure() {
assertTranslation("S1", TransientDataAccessResourceException.class);
}
@Test
public void translatePessimisticLockingFailure() {
doTest("40", PessimisticLockingFailureException.class);
void translatePessimisticLockingFailure() {
assertTranslation("40", PessimisticLockingFailureException.class);
}
@Test
public void translateCannotAcquireLock() {
doTest("40001", CannotAcquireLockException.class);
void translateCannotAcquireLock() {
assertTranslation("40001", CannotAcquireLockException.class);
}
@Test
public void translateUncategorized() {
doTest("00000000", null);
void translateUncategorized() {
assertTranslation("00000000", null);
}
@Test
public void invalidSqlStateCode() {
doTest("NO SUCH CODE", null);
void invalidSqlStateCode() {
assertTranslation("NO SUCH CODE", null);
}
/**
@@ -117,19 +125,18 @@ public class SQLStateSQLExceptionTranslatorTests {
* Bug 729170
*/
@Test
public void malformedSqlStateCodes() {
doTest(null, null);
doTest("", null);
doTest("I", null);
void malformedSqlStateCodes() {
assertTranslation(null, null);
assertTranslation("", null);
assertTranslation("I", null);
}
private void doTest(@Nullable String sqlState, @Nullable Class<?> dataAccessExceptionType) {
doTest(sqlState, 0, dataAccessExceptionType);
private void assertTranslation(@Nullable String sqlState, @Nullable Class<?> dataAccessExceptionType) {
assertTranslation(sqlState, 0, dataAccessExceptionType);
}
private void doTest(@Nullable String sqlState, int errorCode, @Nullable Class<?> dataAccessExceptionType) {
SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator();
private void assertTranslation(@Nullable String sqlState, int errorCode, @Nullable Class<?> dataAccessExceptionType) {
SQLException ex = new SQLException("reason", sqlState, errorCode);
DataAccessException dax = translator.translate("task", "SQL", ex);
@@ -201,7 +201,7 @@ public final class MessageBuilder<T> {
* @since 4.1
*/
@SuppressWarnings("unchecked")
public static <T> Message<T> createMessage(@Nullable T payload, MessageHeaders messageHeaders) {
public static <T> Message<T> createMessage(T payload, MessageHeaders messageHeaders) {
Assert.notNull(payload, "Payload must not be null");
Assert.notNull(messageHeaders, "MessageHeaders must not be null");
if (payload instanceof Throwable throwable) {
@@ -16,6 +16,8 @@
package org.springframework.r2dbc.connection;
import java.util.Set;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.R2dbcBadGrammarException;
@@ -69,6 +71,14 @@ public abstract class ConnectionFactoryUtils {
*/
public static final int CONNECTION_SYNCHRONIZATION_ORDER = 1000;
private static final Set<Integer> DUPLICATE_KEY_ERROR_CODES = Set.of(
1, // Oracle
301, // SAP HANA
1062, // MySQL/MariaDB
2601, // MS SQL Server
2627 // MS SQL Server
);
/**
* Obtain a {@link Connection} from the given {@link ConnectionFactory}.
@@ -247,16 +257,17 @@ public abstract class ConnectionFactoryUtils {
}
/**
* Check whether the given SQL state (and the associated error code in case
* of a generic SQL state value) indicate a duplicate key exception. See
* {@code org.springframework.jdbc.support.SQLStateSQLExceptionTranslator#indicatesDuplicateKey}.
* Check whether the given SQL state and the associated error code (in case
* of a generic SQL state value) indicate a duplicate key exception:
* either SQL state 23505 as a specific indication, or the generic SQL state
* 23000 with a well-known vendor code.
* @param sqlState the SQL state value
* @param errorCode the error code value
* @param errorCode the error code
* @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator#indicatesDuplicateKey
*/
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
return ("23505".equals(sqlState) ||
("23000".equals(sqlState) &&
(errorCode == 1 || errorCode == 1062 || errorCode == 2601 || errorCode == 2627)));
("23000".equals(sqlState) && DUPLICATE_KEY_ERROR_CODES.contains(errorCode)));
}
/**
@@ -67,7 +67,7 @@ abstract class NamedParameterUtils {
* Set of characters that qualify as parameter separators,
* indicating that a parameter name in an SQL String has ended.
*/
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^]";
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^";
/**
* An index with separator flags per character code.
@@ -83,12 +83,12 @@ abstract class NamedParameterUtils {
// -------------------------------------------------------------------------
// Core methods used by NamedParameterSupport.
// Core methods used by NamedParameterExpander
// -------------------------------------------------------------------------
/**
* Parse the SQL statement and locate any placeholders or named parameters.
* Named parameters are substituted for a R2DBC placeholder.
* Named parameters are substituted for an R2DBC placeholder.
* @param sql the SQL statement
* @return the parsed statement, represented as {@link ParsedSql} instance
*/
@@ -154,16 +154,25 @@ abstract class NamedParameterUtils {
j++;
}
else {
while (j < statement.length && !isParameterSeparator(statement[j])) {
boolean paramWithSquareBrackets = false;
while (j < statement.length) {
c = statement[j];
if (isParameterSeparator(c)) {
break;
}
if (c == '[') {
paramWithSquareBrackets = true;
}
else if (c == ']') {
if (!paramWithSquareBrackets) {
break;
}
paramWithSquareBrackets = false;
}
j++;
}
if (j - i > 1) {
parameter = sql.substring(i + 1, j);
if (j < statement.length && statement[j] == ']' && parameter.contains("[")) {
// preserve end bracket for index/key
j++;
parameter = sql.substring(i + 1, j);
}
namedParameterCount = addNewNamedParameter(
namedParameters, namedParameterCount, parameter);
totalParameterCount = addNamedParameter(
@@ -261,7 +270,7 @@ abstract class NamedParameterUtils {
/**
* Parse the SQL statement and locate any placeholders or named parameters. Named
* parameters are substituted for a R2DBC placeholder, and any select list is expanded
* parameters are substituted for an R2DBC placeholder, and any select list is expanded
* to the required number of placeholders. Select lists may contain an array of objects,
* and in that case the placeholders will be grouped and enclosed with parentheses.
* This allows for the use of "expression lists" in the SQL statement like:
@@ -45,17 +45,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Mark Paluch
* @author Juergen Hoeller
*/
public class ConnectionFactoryUtilsUnitTests {
class ConnectionFactoryUtilsUnitTests {
@Test
public void shouldTranslateTransientResourceException() {
void shouldTranslateTransientResourceException() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcTransientResourceException(""));
assertThat(exception).isExactlyInstanceOf(TransientDataAccessResourceException.class);
}
@Test
public void shouldTranslateRollbackException() {
void shouldTranslateRollbackException() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcRollbackException());
assertThat(exception).isExactlyInstanceOf(PessimisticLockingFailureException.class);
@@ -66,28 +66,28 @@ public class ConnectionFactoryUtilsUnitTests {
}
@Test
public void shouldTranslateTimeoutException() {
void shouldTranslateTimeoutException() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcTimeoutException());
assertThat(exception).isExactlyInstanceOf(QueryTimeoutException.class);
}
@Test
public void shouldNotTranslateUnknownExceptions() {
void shouldNotTranslateUnknownExceptions() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new MyTransientException());
assertThat(exception).isExactlyInstanceOf(UncategorizedR2dbcException.class);
}
@Test
public void shouldTranslateNonTransientResourceException() {
void shouldTranslateNonTransientResourceException() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcNonTransientResourceException());
assertThat(exception).isExactlyInstanceOf(DataAccessResourceFailureException.class);
}
@Test
public void shouldTranslateIntegrityViolationException() {
void shouldTranslateIntegrityViolationException() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException());
assertThat(exception).isExactlyInstanceOf(DataIntegrityViolationException.class);
@@ -98,37 +98,41 @@ public class ConnectionFactoryUtilsUnitTests {
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 1));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
assertThat(exception).as("Oracle").isExactlyInstanceOf(DuplicateKeyException.class);
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 301));
assertThat(exception).as("SAP HANA").isExactlyInstanceOf(DuplicateKeyException.class);
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 1062));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
assertThat(exception).as("MySQL/MariaDB").isExactlyInstanceOf(DuplicateKeyException.class);
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 2601));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
assertThat(exception).as("MS SQL Server").isExactlyInstanceOf(DuplicateKeyException.class);
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 2627));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
assertThat(exception).as("MS SQL Server").isExactlyInstanceOf(DuplicateKeyException.class);
}
@Test
public void shouldTranslatePermissionDeniedException() {
void shouldTranslatePermissionDeniedException() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcPermissionDeniedException());
assertThat(exception).isExactlyInstanceOf(PermissionDeniedDataAccessException.class);
}
@Test
public void shouldTranslateBadSqlGrammarException() {
void shouldTranslateBadSqlGrammarException() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcBadGrammarException());
assertThat(exception).isExactlyInstanceOf(BadSqlGrammarException.class);
}
@Test
public void messageGeneration() {
void messageGeneration() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK",
"SOME-SQL", new R2dbcTransientResourceException("MESSAGE"));
assertThat(exception).isExactlyInstanceOf(
@@ -136,7 +140,7 @@ public class ConnectionFactoryUtilsUnitTests {
}
@Test
public void messageGenerationNullSQL() {
void messageGenerationNullSQL() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK", null,
new R2dbcTransientResourceException("MESSAGE"));
assertThat(exception).isExactlyInstanceOf(
@@ -144,7 +148,7 @@ public class ConnectionFactoryUtilsUnitTests {
}
@Test
public void messageGenerationNullMessage() {
void messageGenerationNullMessage() {
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK",
"SOME-SQL", new R2dbcTransientResourceException());
assertThat(exception).isExactlyInstanceOf(
@@ -216,6 +216,19 @@ public class ProblemDetail {
this.properties.put(name, value);
}
/**
* Setter for the {@link #getProperties() properties map}.
* <p>By default, this is not set.
* <p>When Jackson JSON is present on the classpath, any properties set here
* are rendered as top level key-value pairs in the output JSON. Otherwise,
* they are rendered as a {@code "properties"} sub-map.
* @param properties the properties map
* @since 6.0.14
*/
public void setProperties(@Nullable Map<String, Object> properties) {
this.properties = properties;
}
/**
* Return a generic map of properties that are not known ahead of time,
* possibly {@code null} if no properties have been added. To add a property,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -151,7 +151,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
* <p>Note that this property is ignored when
* {@linkplain #setStreaming(boolean) streaming} is enabled, or when
* {@link #setMaxInMemorySize(int) maxInMemorySize} is set to -1.
* @see Schedulers#newBoundedElastic
* @see Schedulers#boundedElastic
*/
public void setBlockingOperationScheduler(Scheduler blockingOperationScheduler) {
Assert.notNull(blockingOperationScheduler, "'blockingOperationScheduler' must not be null");
@@ -20,12 +20,14 @@ import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.http.ProblemDetail;
import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} implementation that registers binding reflection entries
* for {@link ProblemDetail} serialization support with Jackson.
*
* @author Brian Clozel
* @author Stephane Nicoll
* @since 6.0.5
*/
class ProblemDetailRuntimeHints implements RuntimeHintsRegistrar {
@@ -34,6 +36,12 @@ class ProblemDetailRuntimeHints implements RuntimeHintsRegistrar {
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
bindingRegistrar.registerReflectionHints(hints.reflection(), ProblemDetail.class);
if (ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader)) {
bindingRegistrar.registerReflectionHints(hints.reflection(), ProblemDetailJacksonXmlMixin.class);
}
else if (ClassUtils.isPresent("com.fasterxml.jackson.annotation.JacksonAnnotation", classLoader)) {
bindingRegistrar.registerReflectionHints(hints.reflection(), ProblemDetailJacksonMixin.class);
}
}
}
@@ -16,9 +16,14 @@
package org.springframework.http.server.observation;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.observation.ServerHttpObservationDocumentation.HighCardinalityKeyNames;
@@ -55,6 +60,8 @@ public class DefaultServerRequestObservationConvention implements ServerRequestO
private static final KeyValue HTTP_URL_UNKNOWN = KeyValue.of(HighCardinalityKeyNames.HTTP_URL, "UNKNOWN");
private static final Set<String> HTTP_METHODS = Stream.of(HttpMethod.values()).map(HttpMethod::name).collect(Collectors.toUnmodifiableSet());
private final String name;
@@ -102,9 +109,13 @@ public class DefaultServerRequestObservationConvention implements ServerRequestO
}
protected KeyValue method(ServerRequestObservationContext context) {
return (context.getCarrier() != null) ?
KeyValue.of(LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod()) :
METHOD_UNKNOWN;
if (context.getCarrier() != null) {
String httpMethod = context.getCarrier().getMethod();
if (HTTP_METHODS.contains(httpMethod)) {
return KeyValue.of(LowCardinalityKeyNames.METHOD, httpMethod);
}
}
return METHOD_UNKNOWN;
}
protected KeyValue status(ServerRequestObservationContext context) {
@@ -16,9 +16,12 @@
package org.springframework.http.server.reactive.observation;
import java.util.Set;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation.HighCardinalityKeyNames;
@@ -55,6 +58,8 @@ public class DefaultServerRequestObservationConvention implements ServerRequestO
private static final KeyValue HTTP_URL_UNKNOWN = KeyValue.of(HighCardinalityKeyNames.HTTP_URL, "UNKNOWN");
private static final Set<HttpMethod> HTTP_METHODS = Set.of(HttpMethod.values());
private final String name;
@@ -102,9 +107,13 @@ public class DefaultServerRequestObservationConvention implements ServerRequestO
}
protected KeyValue method(ServerRequestObservationContext context) {
return (context.getCarrier() != null) ?
KeyValue.of(LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod().name()) :
METHOD_UNKNOWN;
if (context.getCarrier() != null) {
HttpMethod method = context.getCarrier().getMethod();
if (HTTP_METHODS.contains(method)) {
return KeyValue.of(LowCardinalityKeyNames.METHOD, method.name());
}
}
return METHOD_UNKNOWN;
}
protected KeyValue status(ServerRequestObservationContext context) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -88,7 +88,7 @@ public class MissingPathVariableException extends MissingRequestValueException {
@Override
public HttpStatusCode getStatusCode() {
return HttpStatus.INTERNAL_SERVER_ERROR;
return (isMissingAfterConversion() ? HttpStatus.BAD_REQUEST : HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -18,12 +18,14 @@ package org.springframework.web.filter.reactive;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import org.reactivestreams.Publisher;
import reactor.core.observability.DefaultSignalListener;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention;
@@ -99,40 +101,69 @@ public class ServerHttpObservationFilter implements WebFilter {
ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange.getRequest(),
exchange.getResponse(), exchange.getAttributes());
exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
return chain.filter(exchange).transformDeferred(call -> filter(exchange, observationContext, call));
return chain.filter(exchange).tap(() -> new ObservationSignalListener(observationContext));
}
private Publisher<Void> filter(ServerWebExchange exchange, ServerRequestObservationContext observationContext, Mono<Void> call) {
Observation observation = ServerHttpObservationDocumentation.HTTP_REACTIVE_SERVER_REQUESTS.observation(this.observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);
observation.start();
return call.doOnEach(signal -> {
Throwable throwable = signal.getThrowable();
if (throwable != null) {
if (DISCONNECTED_CLIENT_EXCEPTIONS.contains(throwable.getClass().getSimpleName())) {
observationContext.setConnectionAborted(true);
}
observationContext.setError(throwable);
}
onTerminalSignal(observation, exchange);
})
.doOnCancel(() -> {
observationContext.setConnectionAborted(true);
observation.stop();
})
.contextWrite(context -> context.put(ObservationThreadLocalAccessor.KEY, observation));
}
private final class ObservationSignalListener extends DefaultSignalListener<Void> {
private void onTerminalSignal(Observation observation, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
observation.stop();
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS = Set.of("AbortedException",
"ClientAbortException", "EOFException", "EofException");
private final ServerRequestObservationContext observationContext;
private final Observation observation;
private AtomicBoolean observationRecorded = new AtomicBoolean();
public ObservationSignalListener(ServerRequestObservationContext observationContext) {
this.observationContext = observationContext;
this.observation = ServerHttpObservationDocumentation.HTTP_REACTIVE_SERVER_REQUESTS.observation(observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry);
}
else {
response.beforeCommit(() -> {
observation.stop();
return Mono.empty();
});
@Override
public void doOnSubscription() throws Throwable {
this.observation.start();
}
@Override
public Context addToContext(Context originalContext) {
return originalContext.put(ObservationThreadLocalAccessor.KEY, this.observation);
}
@Override
public void doOnCancel() throws Throwable {
if (this.observationRecorded.compareAndSet(false, true)) {
this.observationContext.setConnectionAborted(true);
this.observation.stop();
}
}
@Override
public void doOnComplete() throws Throwable {
if (this.observationRecorded.compareAndSet(false, true)) {
ServerHttpResponse response = this.observationContext.getResponse();
if (response.isCommitted()) {
this.observation.stop();
}
else {
response.beforeCommit(() -> {
this.observation.stop();
return Mono.empty();
});
}
}
}
@Override
public void doOnError(Throwable error) throws Throwable {
if (this.observationRecorded.compareAndSet(false, true)) {
if (DISCONNECTED_CLIENT_EXCEPTIONS.contains(error.getClass().getSimpleName())) {
this.observationContext.setConnectionAborted(true);
}
this.observationContext.setError(error);
this.observation.stop();
}
}
}
@@ -247,18 +247,20 @@ public class DefaultServerWebExchange implements ServerWebExchange {
@Override
public Mono<Void> cleanupMultipart() {
if (this.multipartRead) {
return getMultipartData()
.onErrorResume(t -> Mono.empty()) // ignore errors reading multipart data
.flatMapIterable(Map::values)
.flatMapIterable(Function.identity())
.flatMap(part -> part.delete()
.onErrorResume(ex -> Mono.empty()))
.then();
}
else {
return Mono.empty();
}
return Mono.defer(() -> {
if (this.multipartRead) {
return getMultipartData()
.onErrorComplete()
.flatMapIterable(Map::values)
.flatMapIterable(Function.identity())
.flatMap(part -> part.delete()
.onErrorComplete())
.then();
}
else {
return Mono.empty();
}
});
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -42,11 +42,11 @@ public class CookieWebSessionIdResolver implements WebSessionIdResolver {
private Duration cookieMaxAge = Duration.ofSeconds(-1);
@Nullable
private Consumer<ResponseCookie.ResponseCookieBuilder> cookieInitializer = null;
private Consumer<ResponseCookie.ResponseCookieBuilder> initializer = null;
/**
* Set the name of the cookie to use for the session ID.
* Set the name for the session id cookie.
* <p>By default set to "SESSION".
* @param cookieName the cookie name
*/
@@ -63,32 +63,32 @@ public class CookieWebSessionIdResolver implements WebSessionIdResolver {
}
/**
* Set the value for the "Max-Age" attribute of the cookie that holds the
* session ID.
* <p>For the range of values see {@link ResponseCookie#getMaxAge()}.
* <p>By default set to -1.
* Set the "Max-Age" attribute for the session id cookie.
* <p>By default set to -1 in which case the cookie is removed when the
* browser is closed.
* @param maxAge the maxAge duration value
* @see ResponseCookie#getMaxAge()
*/
public void setCookieMaxAge(Duration maxAge) {
this.cookieMaxAge = maxAge;
}
/**
* Get the configured "Max-Age" attribute value for the session cookie.
* Get the configured "Max-Age" for the session id cookie.
*/
public Duration getCookieMaxAge() {
return this.cookieMaxAge;
}
/**
* Add a {@link Consumer} for a {@code ResponseCookieBuilder} that will be invoked
* for each cookie being built, just before the call to {@code build()}.
* @param initializer consumer for a cookie builder
* Add a {@link Consumer} to further initialize the session id cookie
* after {@link #getCookieName()} and {@link #getCookieMaxAge()} are applied.
* @param initializer consumer to initialize the cookie with
* @since 5.1
*/
public void addCookieInitializer(Consumer<ResponseCookie.ResponseCookieBuilder> initializer) {
this.cookieInitializer = this.cookieInitializer != null ?
this.cookieInitializer.andThen(initializer) : initializer;
this.initializer = this.initializer != null ?
this.initializer.andThen(initializer) : initializer;
}
@@ -105,31 +105,29 @@ public class CookieWebSessionIdResolver implements WebSessionIdResolver {
@Override
public void setSessionId(ServerWebExchange exchange, String id) {
Assert.notNull(id, "'id' is required");
ResponseCookie cookie = initSessionCookie(exchange, id, getCookieMaxAge());
ResponseCookie cookie = initCookie(exchange, id).build();
exchange.getResponse().getCookies().set(this.cookieName, cookie);
}
@Override
public void expireSession(ServerWebExchange exchange) {
ResponseCookie cookie = initSessionCookie(exchange, "", Duration.ZERO);
ResponseCookie cookie = initCookie(exchange, "").maxAge(0).build();
exchange.getResponse().getCookies().set(this.cookieName, cookie);
}
private ResponseCookie initSessionCookie(
ServerWebExchange exchange, String id, Duration maxAge) {
ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie.from(this.cookieName, id)
private ResponseCookie.ResponseCookieBuilder initCookie(ServerWebExchange exchange, String id) {
ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(this.cookieName, id)
.path(exchange.getRequest().getPath().contextPath().value() + "/")
.maxAge(maxAge)
.maxAge(getCookieMaxAge())
.httpOnly(true)
.secure("https".equalsIgnoreCase(exchange.getRequest().getURI().getScheme()))
.sameSite("Lax");
if (this.cookieInitializer != null) {
this.cookieInitializer.accept(cookieBuilder);
if (this.initializer != null) {
this.initializer.accept(builder);
}
return cookieBuilder.build();
return builder;
}
}
@@ -37,6 +37,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class ProblemDetailRuntimeHintsTests {
private static final List<String> METHOD_NAMES = List.of("getType", "getTitle",
"getStatus", "getDetail", "getInstance", "getProperties");
private final RuntimeHints hints = new RuntimeHints();
@BeforeEach
@@ -48,9 +51,17 @@ class ProblemDetailRuntimeHintsTests {
@Test
void getterMethodsShouldHaveReflectionHints() {
List<String> methodNames = List.of("getType", "getTitle", "getStatus", "getDetail", "getInstance", "getProperties");
for (String methodName : methodNames) {
assertThat(RuntimeHintsPredicates.reflection().onMethod(ProblemDetail.class, methodName)).accepts(this.hints);
for (String methodName : METHOD_NAMES) {
assertThat(RuntimeHintsPredicates.reflection()
.onMethod(ProblemDetail.class, methodName)).accepts(this.hints);
}
}
@Test
void mixinShouldHaveReflectionHints() {
for (String methodName : METHOD_NAMES) {
assertThat(RuntimeHintsPredicates.reflection()
.onMethod(ProblemDetailJacksonXmlMixin.class, methodName)).accepts(this.hints);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -124,4 +124,17 @@ class DefaultServerRequestObservationConventionTests {
.contains(KeyValue.of("http.url", "/test/notFound"));
}
@Test
void addsKeyValuesForUnknownHttpMethodExchange() {
this.request.setMethod("SPRING");
this.request.setRequestURI("/test");
this.response.setStatus(404);
assertThat(this.convention.getLowCardinalityKeyValues(this.context)).hasSize(5)
.contains(KeyValue.of("method", "UNKNOWN"), KeyValue.of("uri", "NOT_FOUND"), KeyValue.of("status", "404"),
KeyValue.of("exception", "none"), KeyValue.of("outcome", "CLIENT_ERROR"));
assertThat(this.convention.getHighCardinalityKeyValues(this.context)).hasSize(1)
.contains(KeyValue.of("http.url", "/test"));
}
}
@@ -20,6 +20,7 @@ import io.micrometer.common.KeyValue;
import io.micrometer.observation.Observation;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.server.MockServerWebExchange;
@@ -172,4 +173,17 @@ class DefaultServerRequestObservationConventionTests {
KeyValue.of("exception", "none"), KeyValue.of("outcome", "UNKNOWN"));
}
@Test
void addsKeyValuesForUnknownHttpMethodExchange() {
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.method(HttpMethod.valueOf("SPRING"), "/test"));
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes());
exchange.getResponse().setRawStatusCode(404);
assertThat(this.convention.getLowCardinalityKeyValues(context)).hasSize(5)
.contains(KeyValue.of("method", "UNKNOWN"), KeyValue.of("uri", "NOT_FOUND"), KeyValue.of("status", "404"),
KeyValue.of("exception", "none"), KeyValue.of("outcome", "CLIENT_ERROR"));
assertThat(this.convention.getHighCardinalityKeyValues(context)).hasSize(1)
.contains(KeyValue.of("http.url", "/test"));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseCookie;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.server.MockServerWebExchange;
@@ -33,18 +34,14 @@ public class CookieWebSessionIdResolverTests {
private final CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
private final ServerWebExchange exchange =
MockServerWebExchange.from(MockServerHttpRequest.get("https://example.org/path"));
@Test
public void setSessionId() {
MockServerHttpRequest request = MockServerHttpRequest.get("https://example.org/path").build();
MockServerWebExchange exchange = MockServerWebExchange.from(request);
this.resolver.setSessionId(exchange, "123");
MultiValueMap<String, ResponseCookie> cookies = exchange.getResponse().getCookies();
assertThat(cookies).hasSize(1);
ResponseCookie cookie = cookies.getFirst(this.resolver.getCookieName());
assertThat(cookie).isNotNull();
assertThat(cookie.toString()).isEqualTo("SESSION=123; Path=/; Secure; HttpOnly; SameSite=Lax");
this.resolver.setSessionId(this.exchange, "123");
assertCookieValue("SESSION=123; Path=/; Secure; HttpOnly; SameSite=Lax");
}
@Test
@@ -52,16 +49,26 @@ public class CookieWebSessionIdResolverTests {
this.resolver.addCookieInitializer(builder -> builder.domain("example.org"));
this.resolver.addCookieInitializer(builder -> builder.sameSite("Strict"));
this.resolver.addCookieInitializer(builder -> builder.secure(false));
this.resolver.setSessionId(this.exchange, "123");
MockServerHttpRequest request = MockServerHttpRequest.get("https://example.org/path").build();
MockServerWebExchange exchange = MockServerWebExchange.from(request);
this.resolver.setSessionId(exchange, "123");
assertCookieValue("SESSION=123; Path=/; Domain=example.org; HttpOnly; SameSite=Strict");
}
MultiValueMap<String, ResponseCookie> cookies = exchange.getResponse().getCookies();
@Test
public void expireSessionWhenMaxAgeSetViaInitializer() {
this.resolver.addCookieInitializer(builder -> builder.maxAge(600));
this.resolver.expireSession(this.exchange);
assertCookieValue("SESSION=; Path=/; Max-Age=0; " +
"Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; SameSite=Lax");
}
private void assertCookieValue(String expected) {
MultiValueMap<String, ResponseCookie> cookies = this.exchange.getResponse().getCookies();
assertThat(cookies).hasSize(1);
ResponseCookie cookie = cookies.getFirst(this.resolver.getCookieName());
assertThat(cookie).isNotNull();
assertThat(cookie.toString()).isEqualTo("SESSION=123; Path=/; Domain=example.org; HttpOnly; SameSite=Strict");
assertThat(cookie.toString()).isEqualTo(expected);
}
}
@@ -0,0 +1,34 @@
/*
* 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.
* 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.http
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
/**
* Kotlin tests for [ProblemDetail].
*/
class KotlinProblemDetailTests {
@Test // gh-31430
fun propertiesSetter() {
val problemDetail = ProblemDetail()
val map = mapOf("foo" to "bar")
problemDetail.properties = map
assertThat(problemDetail.properties).isEqualTo(map)
}
}
@@ -79,7 +79,9 @@ final class DefaultWebClient implements WebClient {
private static final Mono<ClientResponse> NO_HTTP_CLIENT_RESPONSE_ERROR = Mono.error(
() -> new IllegalStateException("The underlying HTTP client completed without emitting a response."));
private static final DefaultClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention();
private static final DefaultClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION =
new DefaultClientRequestObservationConvention();
private final ExchangeFunction exchangeFunction;
@@ -119,10 +121,10 @@ final class DefaultWebClient implements WebClient {
this.uriBuilderFactory = uriBuilderFactory;
this.defaultHeaders = defaultHeaders;
this.defaultCookies = defaultCookies;
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
this.defaultRequest = defaultRequest;
this.defaultStatusHandlers = initStatusHandlers(statusHandlerMap);
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
this.builder = builder;
}
@@ -698,7 +700,7 @@ final class DefaultWebClient implements WebClient {
private static URI getUriToLog(URI uri) {
if (StringUtils.hasText(uri.getQuery())) {
try {
uri = new URI(uri.getScheme(), uri.getHost(), uri.getPath(), null);
uri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
}
catch (URISyntaxException ex) {
// ignore
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -16,7 +16,6 @@
package org.springframework.web.reactive.result.method.annotation;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -71,12 +70,17 @@ public class MatrixVariableMapMethodArgumentResolver extends HandlerMethodArgume
Map<String, MultiValueMap<String, String>> matrixVariables =
exchange.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE);
MultiValueMap<String, String> map = mapMatrixVariables(parameter, matrixVariables);
return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
}
if (CollectionUtils.isEmpty(matrixVariables)) {
return Collections.emptyMap();
}
private MultiValueMap<String,String> mapMatrixVariables(MethodParameter parameter,
@Nullable Map<String, MultiValueMap<String, String>> matrixVariables) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
if (CollectionUtils.isEmpty(matrixVariables)) {
return map;
}
MatrixVariable annotation = parameter.getParameterAnnotation(MatrixVariable.class);
Assert.state(annotation != null, "No MatrixVariable annotation");
String pathVariable = annotation.pathVar();
@@ -84,7 +88,7 @@ public class MatrixVariableMapMethodArgumentResolver extends HandlerMethodArgume
if (!pathVariable.equals(ValueConstants.DEFAULT_NONE)) {
MultiValueMap<String, String> mapForPathVariable = matrixVariables.get(pathVariable);
if (mapForPathVariable == null) {
return Collections.emptyMap();
return map;
}
map.putAll(mapForPathVariable);
}
@@ -97,8 +101,7 @@ public class MatrixVariableMapMethodArgumentResolver extends HandlerMethodArgume
});
}
}
return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
return map;
}
private boolean isSingleValueMap(MethodParameter parameter) {
@@ -1198,23 +1198,6 @@ class WebClientIntegrationTests {
expectRequestCount(1);
}
@ParameterizedWebClientTest
void invalidDomain(ClientHttpConnector connector) {
startServer(connector);
String url = "http://example.invalid";
Mono<Void> result = this.webClient.get().uri(url).retrieve().bodyToMono(Void.class);
StepVerifier.create(result)
.expectErrorSatisfies(throwable -> {
assertThat(throwable).isInstanceOf(WebClientRequestException.class);
WebClientRequestException ex = (WebClientRequestException) throwable;
assertThat(ex.getMethod()).isEqualTo(HttpMethod.GET);
assertThat(ex.getUri()).isEqualTo(URI.create(url));
})
.verify();
}
@ParameterizedWebClientTest
void malformedResponseChunksOnBodilessEntity(ClientHttpConnector connector) {
Mono<?> result = doMalformedChunkedResponseTest(connector, ResponseSpec::toBodilessEntity);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -139,6 +140,19 @@ public class MatrixVariablesMapMethodArgumentResolverTests {
assertThat(mapAll.get("colors")).isEqualTo("red");
}
@Test
public void resolveMultiValueMapArgumentNoParams() {
MethodParameter param = this.testMethod.annot(matrixAttribute().noPathVar())
.arg(MultiValueMap.class, String.class, String.class);
Object result = this.resolver.resolveArgument(param,
new BindingContext(), this.exchange).block(Duration.ZERO);
assertThat(result).isInstanceOf(MultiValueMap.class)
.asInstanceOf(InstanceOfAssertFactories.MAP).isEmpty();
}
@Test
public void resolveArgumentNoParams() throws Exception {
@@ -982,7 +982,10 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic
return;
}
}
super.doTrace(request, response);
// Work around until https://github.com/jakartaee/servlet/pull/545 is fixed and in use
if (request.getDispatcherType() != DispatcherType.ERROR) {
super.doTrace(request, response);
}
}
/**
@@ -24,11 +24,17 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
@@ -65,11 +71,17 @@ import org.springframework.web.util.pattern.PathPatternParser;
* request.
* </ul>
*
* <p><strong>Note:</strong> This is primarily an SPI to allow Spring Security
* <p>Note that this is primarily an SPI to allow Spring Security
* to align its pattern matching with the same pattern matching that would be
* used in Spring MVC for a given request, in order to avoid security issues.
* Use of this introspector should be avoided for other purposes because it
* incurs the overhead of resolving the handler for a request.
*
* <p>Use of this component incurs the performance overhead of mapping the
* request, and should not be repeated multiple times per request.
* {@link #createCacheFilter()} exposes a Filter to cache the results.
* Applications that rely on Spring Security don't need to deploy this Filter
* since Spring Security doe that. However, other custom security layers, used
* in place of Spring Security that use this component should deploy the cache
* Filter with requirements described in the Javadoc for the method.
*
* @author Rossen Stoyanchev
* @since 4.3.1
@@ -77,6 +89,12 @@ import org.springframework.web.util.pattern.PathPatternParser;
public class HandlerMappingIntrospector
implements CorsConfigurationSource, ApplicationContextAware, InitializingBean {
private static final Log logger = LogFactory.getLog(HandlerMappingIntrospector.class.getName());
private static final String CACHED_RESULT_ATTRIBUTE =
HandlerMappingIntrospector.class.getName() + ".CachedResult";
@Nullable
private ApplicationContext applicationContext;
@@ -85,6 +103,8 @@ public class HandlerMappingIntrospector
private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternMappings = Collections.emptyMap();
private final CacheResultLogHelper cacheLogHelper = new CacheResultLogHelper();
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
@@ -153,6 +173,82 @@ public class HandlerMappingIntrospector
}
/**
* {@link Filter} that looks up the {@code MatchableHandlerMapping} and
* {@link CorsConfiguration} for the request proactively before delegating
* to the rest of the chain, caching the result in a request attribute, and
* restoring it after the chain returns.
* <p><strong>Note:</strong> Applications that rely on Spring Security do
* not use this component directly and should not deploy the filter instead
* allowing Spring Security to do it. Other custom security layers used in
* place of Spring Security that also rely on {@code HandlerMappingIntrospector}
* should deploy this filter ahead of other filters where lookups are
* performed, and should also make sure the filter is configured to handle
* all dispatcher types.
* @return the Filter instance to use
* @since 6.0.14
*/
public Filter createCacheFilter() {
return (request, response, chain) -> {
CachedResult previous = setCache((HttpServletRequest) request);
chain.doFilter(request, response);
resetCache(request, previous);
};
}
/**
* Perform a lookup and save the {@link CachedResult} as a request attribute.
* This method can be invoked from a filter before subsequent calls to
* {@link #getMatchableHandlerMapping(HttpServletRequest)} and
* {@link #getCorsConfiguration(HttpServletRequest)} to avoid repeated lookups.
* @param request the current request
* @return the previous {@link CachedResult}, if there is one from a parent dispatch
* @since 6.0.14
*/
@Nullable
private CachedResult setCache(HttpServletRequest request) {
CachedResult previous = (CachedResult) request.getAttribute(CACHED_RESULT_ATTRIBUTE);
if (previous == null || !previous.matches(request)) {
HttpServletRequest wrapped = new AttributesPreservingRequest(request);
CachedResult result;
try {
// Try to get both in one lookup (with ignoringException=false)
result = doWithHandlerMapping(wrapped, false, (mapping, executionChain) -> {
MatchableHandlerMapping matchableMapping = createMatchableHandlerMapping(mapping, wrapped);
CorsConfiguration corsConfig = getCorsConfiguration(executionChain, wrapped);
return new CachedResult(request, matchableMapping, corsConfig, null, null);
});
}
catch (Exception ex) {
try {
// Try CorsConfiguration at least with ignoreException=true
AttributesPreservingRequest requestToUse = new AttributesPreservingRequest(request);
result = doWithHandlerMapping(requestToUse, true, (mapping, executionChain) -> {
CorsConfiguration corsConfig = getCorsConfiguration(executionChain, wrapped);
return new CachedResult(request, null, corsConfig, ex, null);
});
}
catch (Exception ex2) {
result = new CachedResult(request, null, null, ex, new IllegalStateException(ex2));
}
}
if (result == null) {
result = new CachedResult(request, null, null, null, null);
}
request.setAttribute(CACHED_RESULT_ATTRIBUTE, result);
}
return previous;
}
/**
* Restore a previous {@link CachedResult}. This method can be invoked from
* a filter after delegating to the rest of the chain.
* @since 6.0.14
*/
private void resetCache(ServletRequest request, @Nullable CachedResult cachedResult) {
request.setAttribute(CACHED_RESULT_ATTRIBUTE, cachedResult);
}
/**
* Find the {@link HandlerMapping} that would handle the given request and
* return a {@link MatchableHandlerMapping} to use for path matching.
@@ -164,39 +260,62 @@ public class HandlerMappingIntrospector
*/
@Nullable
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request);
CachedResult result = CachedResult.getResultFor(request);
if (result != null) {
return result.getHandlerMapping();
}
this.cacheLogHelper.logHandlerMappingCacheMiss(request);
HttpServletRequest requestToUse = new AttributesPreservingRequest(request);
return doWithHandlerMapping(requestToUse, false,
(mapping, executionChain) -> createMatchableHandlerMapping(mapping, requestToUse));
}
return doWithHandlerMapping(wrappedRequest, false, (mapping, executionChain) -> {
if (mapping instanceof MatchableHandlerMapping) {
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping);
if (pathPatternMapping != null) {
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest);
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath);
}
else {
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath);
}
private MatchableHandlerMapping createMatchableHandlerMapping(HandlerMapping mapping, HttpServletRequest request) {
if (mapping instanceof MatchableHandlerMapping) {
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping);
if (pathPatternMapping != null) {
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath);
}
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
});
else {
String lookupPath = (String) request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath);
}
}
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
}
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request);
return doWithHandlerMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) {
if (interceptor instanceof CorsConfigurationSource ccs) {
return ccs.getCorsConfiguration(wrappedRequest);
}
CachedResult result = CachedResult.getResultFor(request);
if (result != null) {
return result.getCorsConfig();
}
this.cacheLogHelper.logCorsConfigCacheMiss(request);
try {
boolean ignoreException = true;
AttributesPreservingRequest requestToUse = new AttributesPreservingRequest(request);
return doWithHandlerMapping(requestToUse, ignoreException,
(handlerMapping, executionChain) -> getCorsConfiguration(executionChain, requestToUse));
}
catch (Exception ex) {
// HandlerMapping exceptions are ignored. More basic error like parsing the request path.
throw new IllegalStateException(ex);
}
}
@Nullable
private static CorsConfiguration getCorsConfiguration(HandlerExecutionChain chain, HttpServletRequest request) {
for (HandlerInterceptor interceptor : chain.getInterceptorList()) {
if (interceptor instanceof CorsConfigurationSource source) {
return source.getCorsConfiguration(request);
}
if (executionChain.getHandler() instanceof CorsConfigurationSource ccs) {
return ccs.getCorsConfiguration(wrappedRequest);
}
return null;
});
}
if (chain.getHandler() instanceof CorsConfigurationSource source) {
return source.getCorsConfiguration(request);
}
return null;
}
@Nullable
@@ -237,15 +356,112 @@ public class HandlerMappingIntrospector
return null;
}
@Nullable
private <T> T doWithHandlerMappingIgnoringException(
HttpServletRequest request, BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) {
try {
return doWithHandlerMapping(request, true, matchHandler);
/**
* Container for a {@link MatchableHandlerMapping} and {@link CorsConfiguration}
* for a given request matched by dispatcher type and requestURI.
* @since 6.0.14
*/
@SuppressWarnings("serial")
private static final class CachedResult {
private final DispatcherType dispatcherType;
private final String requestURI;
@Nullable
private final MatchableHandlerMapping handlerMapping;
@Nullable
private final CorsConfiguration corsConfig;
@Nullable
private final Exception failure;
@Nullable
private final IllegalStateException corsConfigFailure;
private CachedResult(HttpServletRequest request,
@Nullable MatchableHandlerMapping mapping, @Nullable CorsConfiguration config,
@Nullable Exception failure, @Nullable IllegalStateException corsConfigFailure) {
this.dispatcherType = request.getDispatcherType();
this.requestURI = request.getRequestURI();
this.handlerMapping = mapping;
this.corsConfig = config;
this.failure = failure;
this.corsConfigFailure = corsConfigFailure;
}
catch (Exception ex) {
throw new IllegalStateException("HandlerMapping exception not suppressed", ex);
public boolean matches(HttpServletRequest request) {
return (this.dispatcherType.equals(request.getDispatcherType()) &&
this.requestURI.matches(request.getRequestURI()));
}
@Nullable
public MatchableHandlerMapping getHandlerMapping() throws Exception {
if (this.failure != null) {
throw this.failure;
}
return this.handlerMapping;
}
@Nullable
public CorsConfiguration getCorsConfig() {
if (this.corsConfigFailure != null) {
throw this.corsConfigFailure;
}
return this.corsConfig;
}
@Override
public String toString() {
return "CachedResult for " + this.dispatcherType + " dispatch to '" + this.requestURI + "'";
}
/**
* Return a {@link CachedResult} that matches the given request.
*/
@Nullable
public static CachedResult getResultFor(HttpServletRequest request) {
CachedResult result = (CachedResult) request.getAttribute(CACHED_RESULT_ATTRIBUTE);
return (result != null && result.matches(request) ? result : null);
}
}
private static class CacheResultLogHelper {
private final Map<String, AtomicInteger> counters =
Map.of("MatchableHandlerMapping", new AtomicInteger(), "CorsConfiguration", new AtomicInteger());
public void logHandlerMappingCacheMiss(HttpServletRequest request) {
logCacheMiss("MatchableHandlerMapping", request);
}
public void logCorsConfigCacheMiss(HttpServletRequest request) {
logCacheMiss("CorsConfiguration", request);
}
private void logCacheMiss(String label, HttpServletRequest request) {
AtomicInteger counter = this.counters.get(label);
Assert.notNull(counter, "Expected '" + label + "' counter.");
String message = getLogMessage(label, request);
if (logger.isWarnEnabled() && counter.getAndIncrement() == 0) {
logger.warn(message + " This is logged once only at WARN level, and every time at TRACE.");
}
else if (logger.isTraceEnabled()) {
logger.trace("No CachedResult, performing " + label + " lookup instead.");
}
}
private static String getLogMessage(String label, HttpServletRequest request) {
return "Cache miss for " + request.getDispatcherType() + " dispatch to '" + request.getRequestURI() + "' " +
"(previous " + request.getAttribute(CACHED_RESULT_ATTRIBUTE) + "). " +
"Performing " + label + " lookup.";
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -16,7 +16,6 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -68,11 +67,17 @@ public class MatrixVariableMapMethodArgumentResolver implements HandlerMethodArg
(Map<String, MultiValueMap<String, String>>) request.getAttribute(
HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (CollectionUtils.isEmpty(matrixVariables)) {
return Collections.emptyMap();
}
MultiValueMap<String, String> map = mapMatrixVariables(parameter, matrixVariables);
return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
}
private MultiValueMap<String,String> mapMatrixVariables(MethodParameter parameter,
@Nullable Map<String, MultiValueMap<String, String>> matrixVariables) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
if (CollectionUtils.isEmpty(matrixVariables)) {
return map;
}
MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class);
Assert.state(ann != null, "No MatrixVariable annotation");
String pathVariable = ann.pathVar();
@@ -80,7 +85,7 @@ public class MatrixVariableMapMethodArgumentResolver implements HandlerMethodArg
if (!pathVariable.equals(ValueConstants.DEFAULT_NONE)) {
MultiValueMap<String, String> mapForPathVariable = matrixVariables.get(pathVariable);
if (mapForPathVariable == null) {
return Collections.emptyMap();
return map;
}
map.putAll(mapForPathVariable);
}
@@ -93,8 +98,7 @@ public class MatrixVariableMapMethodArgumentResolver implements HandlerMethodArg
});
}
}
return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
return map;
}
private boolean isSingleValueMap(MethodParameter parameter) {
@@ -16,12 +16,20 @@
package org.springframework.web.servlet.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
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;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -37,6 +45,7 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.function.RouterFunction;
@@ -44,7 +53,9 @@ import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.function.support.RouterFunctionMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.testfixture.servlet.MockFilterChain;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
@@ -137,7 +148,7 @@ public class HandlerMappingIntrospectorTests {
@Test
void getMatchableWhereHandlerMappingDoesNotImplementMatchableInterface() {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
cxt.registerSingleton("mapping", TestHandlerMapping.class);
cxt.registerBean("mapping", HandlerMapping.class, () -> request -> new HandlerExecutionChain(new Object()));
cxt.refresh();
MockHttpServletRequest request = new MockHttpServletRequest();
@@ -193,7 +204,69 @@ public class HandlerMappingIntrospectorTests {
assertThat(corsConfig.getAllowedMethods()).isEqualTo(Collections.singletonList("POST"));
}
private HandlerMappingIntrospector initIntrospector(WebApplicationContext context) {
@Test
void cacheFilter() throws Exception {
CorsConfiguration corsConfig = new CorsConfiguration();
TestMatchableHandlerMapping mapping = new TestMatchableHandlerMapping();
mapping.registerHandler("/test", new TestHandler(corsConfig));
HandlerMappingIntrospector introspector = initIntrospector(mapping);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain(
new TestServlet(), introspector.createCacheFilter(), new AuthFilter(introspector, corsConfig));
filterChain.doFilter(request, response);
assertThat(response.getContentAsString()).isEqualTo("Success");
assertThat(mapping.getInvocationCount()).isEqualTo(1);
assertThat(mapping.getMatchCount()).isEqualTo(1);
}
@Test
void cacheFilterWithNestedDispatch() throws Exception {
CorsConfiguration corsConfig1 = new CorsConfiguration();
CorsConfiguration corsConfig2 = new CorsConfiguration();
TestMatchableHandlerMapping mapping1 = new TestMatchableHandlerMapping();
TestMatchableHandlerMapping mapping2 = new TestMatchableHandlerMapping();
mapping1.registerHandler("/1", new TestHandler(corsConfig1));
mapping2.registerHandler("/2", new TestHandler(corsConfig2));
HandlerMappingIntrospector introspector = initIntrospector(mapping1, mapping2);
MockFilterChain filterChain = new MockFilterChain(
new TestServlet(),
introspector.createCacheFilter(),
new AuthFilter(introspector, corsConfig1),
(req, res, chain) -> chain.doFilter(new MockHttpServletRequest("GET", "/2"), res),
introspector.createCacheFilter(),
new AuthFilter(introspector, corsConfig2));
MockHttpServletResponse response = new MockHttpServletResponse();
filterChain.doFilter(new MockHttpServletRequest("GET", "/1"), response);
assertThat(response.getContentAsString()).isEqualTo("Success");
assertThat(mapping1.getInvocationCount()).isEqualTo(2);
assertThat(mapping2.getInvocationCount()).isEqualTo(1);
assertThat(mapping1.getMatchCount()).isEqualTo(1);
assertThat(mapping2.getMatchCount()).isEqualTo(1);
}
private HandlerMappingIntrospector initIntrospector(TestMatchableHandlerMapping... mappings) {
StaticWebApplicationContext context = new StaticWebApplicationContext();
int index = 0;
for (TestMatchableHandlerMapping mapping : mappings) {
context.registerBean("mapping" + index++, TestMatchableHandlerMapping.class, () -> mapping);
}
context.refresh();
return initIntrospector(context);
}
private static HandlerMappingIntrospector initIntrospector(WebApplicationContext context) {
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
introspector.setApplicationContext(context);
introspector.afterPropertiesSet();
@@ -201,15 +274,6 @@ public class HandlerMappingIntrospectorTests {
}
private static class TestHandlerMapping implements HandlerMapping {
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) {
return new HandlerExecutionChain(new Object());
}
}
@Configuration
static class TestConfig {
@@ -248,6 +312,7 @@ public class HandlerMappingIntrospectorTests {
}
}
private static class TestPathPatternParser extends PathPatternParser {
private final List<String> parsedPatterns = new ArrayList<>();
@@ -264,4 +329,87 @@ public class HandlerMappingIntrospectorTests {
}
}
private static class TestMatchableHandlerMapping extends SimpleUrlHandlerMapping {
private int invocationCount;
private int matchCount;
public int getInvocationCount() {
return this.invocationCount;
}
public int getMatchCount() {
return this.matchCount;
}
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
this.invocationCount++;
Object handler = super.getHandlerInternal(request);
if (handler != null) {
this.matchCount++;
}
return handler;
}
}
private static class TestHandler implements CorsConfigurationSource {
private final CorsConfiguration corsConfig;
private TestHandler(CorsConfiguration corsConfig) {
this.corsConfig = corsConfig;
}
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.corsConfig;
}
}
private static class AuthFilter implements Filter {
private final HandlerMappingIntrospector introspector;
private final CorsConfiguration corsConfig;
private AuthFilter(HandlerMappingIntrospector introspector, CorsConfiguration corsConfig) {
this.introspector = introspector;
this.corsConfig = corsConfig;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try {
for (int i = 0; i < 10; i++) {
HttpServletRequest httpRequest = (HttpServletRequest) req;
assertThat(introspector.getMatchableHandlerMapping(httpRequest)).isNotNull();
assertThat(introspector.getCorsConfiguration(httpRequest)).isSameAs(corsConfig);
}
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
chain.doFilter(req, res);
}
}
private static class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) {
try {
res.getWriter().print("Success");
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -155,6 +156,18 @@ public class MatrixVariablesMapMethodArgumentResolverTests {
assertThat(map).isEqualTo(Collections.emptyMap());
}
@Test
public void resolveMultiValueMapArgumentNoParams() throws Exception {
MethodParameter param = this.testMethod.annot(matrixAttribute().noPathVar())
.arg(MultiValueMap.class, String.class, String.class);
Object result = this.resolver.resolveArgument(param, this.mavContainer, this.webRequest, null);
assertThat(result).isInstanceOf(MultiValueMap.class)
.asInstanceOf(InstanceOfAssertFactories.MAP).isEmpty();
}
@Test
public void resolveArgumentNoMatch() throws Exception {
MultiValueMap<String, String> params2 = getVariablesFor("planes");