mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28bb639736 | |||
| 770cbd2fb5 | |||
| e5f04e5ddf | |||
| c18784678d | |||
| 51cdff591c | |||
| d93114df9a | |||
| 5ac4c3bd76 | |||
| 05c3ffb2fb | |||
| 19e8ed130c | |||
| 4464251754 | |||
| 8868fe2ea5 | |||
| c373f496f3 | |||
| eb1883bdc4 | |||
| df6f66110f | |||
| ac235a0c43 | |||
| a4e3af5cbe | |||
| e71117dcdf | |||
| d5874ab99e | |||
| 99327b7db1 | |||
| 3e06441d97 | |||
| 01f2925048 | |||
| 44a37000ec | |||
| 53fe5fafed | |||
| b9bd98fc5b | |||
| 7714110940 | |||
| 75d1278bde | |||
| 620f558547 | |||
| 1e78cc35e5 | |||
| 11fdb5ba17 | |||
| 9957bb6918 | |||
| 6a7a0bddb7 | |||
| 5c012bbb0c | |||
| 5df6e8825d | |||
| 654e822676 | |||
| e943058b18 | |||
| cafb38ad1d | |||
| e778d2e908 | |||
| c5bcfc7682 | |||
| 5752e03d97 | |||
| dc26d3b0ec | |||
| 7f94c64b72 | |||
| 50e55d5219 | |||
| fcd4ba2f1f | |||
| e73107341c | |||
| f8a33cd66e | |||
| f088d4a05b | |||
| 84c28995fb | |||
| 925fa0272b | |||
| 09aa59f9e7 | |||
| 7874a59771 | |||
| 4dab35205d | |||
| 5c6b9be3a1 | |||
| 33fba8ea0c | |||
| 9aa707ec4b | |||
| ea30c8fb5b | |||
| 6bdf7ad36a | |||
| d2108d2db6 | |||
| f9be717602 | |||
| bb446a3905 | |||
| f3dce4bb9a | |||
| 70cb96c1d8 | |||
| 6ec264252b | |||
| 187b4e5ea6 | |||
| 3bc607df53 | |||
| 69c92f9ac7 | |||
| 875eeabb6f | |||
| 7a60e2024b | |||
| da95542d8f | |||
| 6669ab1ae7 | |||
| d05ac097dd | |||
| 35103b0cd1 | |||
| 3b50f992fe |
+1
-1
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
version=6.0.13-SNAPSHOT
|
||||
version=6.0.14
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+2
-1
@@ -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 ->
|
||||
|
||||
+6
-6
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
+4
@@ -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);
|
||||
|
||||
+2
-4
@@ -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)) {
|
||||
|
||||
+4
-6
@@ -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
|
||||
|
||||
+26
-20
@@ -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
|
||||
|
||||
+19
@@ -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 {
|
||||
|
||||
|
||||
+16
-8
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -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) {
|
||||
|
||||
+13
-6
@@ -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;
|
||||
}
|
||||
|
||||
+55
-21
@@ -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);
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+3
-2
@@ -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");
|
||||
|
||||
+1
-5
@@ -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
|
||||
|
||||
+25
@@ -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() {
|
||||
|
||||
+2
-6
@@ -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();
|
||||
|
||||
+2
-2
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-2
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-3
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-12
@@ -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()
|
||||
|
||||
)
|
||||
+25
@@ -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
|
||||
|
||||
)
|
||||
+34
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+9
-10
@@ -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++]);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-1
@@ -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.
|
||||
*/
|
||||
|
||||
+24
-51
@@ -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.
|
||||
*/
|
||||
|
||||
+12
-12
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -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.
|
||||
|
||||
+17
-8
@@ -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(
|
||||
|
||||
+12
-6
@@ -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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
-1
@@ -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])";
|
||||
|
||||
+70
-74
@@ -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) {
|
||||
|
||||
+45
-38
@@ -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);
|
||||
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+17
-6
@@ -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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+19
-10
@@ -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:
|
||||
|
||||
+20
-16
@@ -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,
|
||||
|
||||
+2
-2
@@ -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");
|
||||
|
||||
+8
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-3
@@ -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) {
|
||||
|
||||
+12
-3
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+63
-32
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-12
@@ -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
|
||||
|
||||
+21
-23
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-1
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-15
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
+6
-4
@@ -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
|
||||
|
||||
+11
-8
@@ -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) {
|
||||
|
||||
-17
@@ -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);
|
||||
|
||||
+15
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+251
-35
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-8
@@ -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) {
|
||||
|
||||
+159
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-1
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user