mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 053d8e25f4 | |||
| 8334388e20 | |||
| 757b713f22 | |||
| a065563484 | |||
| 6162d89042 | |||
| 5c537db2cc | |||
| 849553dc8e | |||
| 7240a5a669 | |||
| b00c387775 | |||
| 8396c071af | |||
| bb35e9f11c | |||
| a0319b1f91 | |||
| 5973a17253 | |||
| ed7243259b | |||
| a391db2ef5 | |||
| 957202523c | |||
| a3c9166da7 | |||
| 0c3dd8cb00 | |||
| 56d8ec25e8 | |||
| fb9db146f2 | |||
| dcf98bc55c | |||
| b9cd06a487 | |||
| 91ff706e8c | |||
| 103057de2a | |||
| b6d1e88563 | |||
| af1e9da3d7 | |||
| bc5e395a42 | |||
| 1ec3cb4d5f | |||
| 7b7126ae3d | |||
| 9f1332c716 | |||
| 2ff93f4207 | |||
| 3a6c7786a1 | |||
| 539a098f8c | |||
| ecfd78ff93 | |||
| 1e0a85368e | |||
| c49d5dc860 | |||
| 58af70f2e3 | |||
| c6e73b5d1d | |||
| f531cc9fdf | |||
| 1977f31821 | |||
| cf3ed229de | |||
| 154dad6587 | |||
| fd8b4d5936 | |||
| 13defc09f9 | |||
| 9e512315ab | |||
| c750efb0a6 | |||
| 0033edad45 | |||
| 183cd4c8cd | |||
| 6bd0ed8e79 | |||
| dcb7922a24 | |||
| 05a9cc26ae | |||
| 05beb4ad4d | |||
| 5236b35687 | |||
| 7482ccc18f | |||
| b3fb9f4e31 | |||
| add3b1a7f4 | |||
| 476aae5ce8 | |||
| be68a777b6 | |||
| 9a0bfd7306 | |||
| 91a0c28fa9 | |||
| c0e8a36592 | |||
| 836289315d | |||
| 08b77dd0ad | |||
| 5eb16a6a17 | |||
| d4705804cc |
@@ -24,14 +24,14 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
|
||||
java-version: |
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
|
||||
@@ -17,14 +17,14 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
shell: bash
|
||||
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
|
||||
- name: Sync
|
||||
uses: spring-io/central-publish-action@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.0
|
||||
uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0
|
||||
with:
|
||||
token: ${{ inputs.central-token-password }}
|
||||
token-name: ${{ inputs.central-token-username }}
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
|
||||
@@ -30,23 +30,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: 'v0.0.2'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
run: ./gradlew spring-framework-release-verification-tests:test
|
||||
- name: Upload Build Reports on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
+3
-3
@@ -91,14 +91,14 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
//"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.14.1/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
// "https://junit.org/junit5/docs/5.14.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
|
||||
@@ -104,7 +104,7 @@ Note that pointcut definitions are generally matched against any intercepted met
|
||||
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
|
||||
potential non-public interactions through proxies, it needs to be defined accordingly.
|
||||
|
||||
If your interception needs include method calls or even constructors within the target
|
||||
If your interception needs to include method calls or even constructors within the target
|
||||
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
|
||||
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
|
||||
with different characteristics, so be sure to make yourself familiar with weaving
|
||||
|
||||
@@ -6,7 +6,7 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
|
||||
Variable names must begin with a letter (as defined below), an underscore, or a dollar
|
||||
sign.
|
||||
|
||||
Variable names must be composed of one or more of the following supported types of
|
||||
|
||||
@@ -15,7 +15,7 @@ configuration options. You should instantiate the `SimpleJdbcInsert` in the data
|
||||
layer's initialization method. For this example, the initializing method is the
|
||||
`setDataSource` method. You do not need to subclass the `SimpleJdbcInsert` class. Instead,
|
||||
you can create a new instance and set the table name by using the `withTableName` method.
|
||||
Configuration methods for this class follow the `fluid` style that returns the instance
|
||||
Configuration methods for this class follow the `fluent` style that returns the instance
|
||||
of the `SimpleJdbcInsert`, which lets you chain all configuration methods. The following
|
||||
example uses only one configuration method (we show examples of multiple methods later):
|
||||
|
||||
@@ -349,11 +349,11 @@ parameters return the data read from the table.
|
||||
|
||||
You can declare `SimpleJdbcCall` in a manner similar to declaring `SimpleJdbcInsert`. You
|
||||
should instantiate and configure the class in the initialization method of your data-access
|
||||
layer. Compared to the `StoredProcedure` class, you need not create a subclass
|
||||
and you need not to declare parameters that can be looked up in the database metadata.
|
||||
The following example of a `SimpleJdbcCall` configuration uses the preceding stored
|
||||
procedure (the only configuration option, in addition to the `DataSource`, is the name
|
||||
of the stored procedure):
|
||||
layer. In contrast to the `StoredProcedure` class, you do not need to create a subclass,
|
||||
and you do not need to declare parameters that can be looked up in the database metadata.
|
||||
The following `SimpleJdbcCall` configuration example uses the preceding stored procedure.
|
||||
The only configuration option (other than the `DataSource`) is the name of the stored
|
||||
procedure.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
|
||||
|
||||
|
||||
|
||||
[[appendix.xsd-schemas]]
|
||||
== XML Schemas
|
||||
|
||||
This part of the appendix lists XML schemas related to integration technologies.
|
||||
|
||||
|
||||
|
||||
[[appendix.xsd-schemas-jee]]
|
||||
=== The `jee` Schema
|
||||
|
||||
@@ -172,7 +167,7 @@ different properties with `jee`:
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -184,7 +179,7 @@ without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -200,7 +195,7 @@ with `jee`:
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -215,7 +210,7 @@ and a number of properties without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -234,7 +229,7 @@ and a number of properties with `jee`:
|
||||
|
||||
The `<jee:remote-slsb/>` element configures a reference to a `remote` EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -251,7 +246,7 @@ without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -313,7 +308,7 @@ xref:integration/jmx/naming.adoc#jmx-context-mbeanexport[Configuring Annotation-
|
||||
=== The `cache` Schema
|
||||
|
||||
You can use the `cache` elements to enable support for Spring's `@CacheEvict`, `@CachePut`,
|
||||
and `@Caching` annotations. It it also supports declarative XML-based caching. See
|
||||
and `@Caching` annotations. The `cache` schema also supports declarative XML-based caching. See
|
||||
xref:integration/cache/annotations.adoc#cache-annotation-enable[Enabling Caching Annotations] and
|
||||
xref:integration/cache/declarative-xml.adoc[Declarative XML-based Caching] for details.
|
||||
|
||||
|
||||
@@ -13,4 +13,4 @@ running the Kotlin compiler with its `-java-parameters` flag for standard Java p
|
||||
|
||||
You can declare configuration classes as
|
||||
{kotlin-docs}/nested-classes.html[top level or nested but not inner],
|
||||
since the later requires a reference to the outer class.
|
||||
since the latter requires a reference to the outer class.
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ default mode may be set via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
The default mode may also be configured as a
|
||||
https://docs.junit.org/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
|
||||
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter].
|
||||
|
||||
If the `spring.test.constructor.autowire.mode` property is not set, test class
|
||||
constructors will not be automatically autowired.
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ TestNG:
|
||||
* Dependency injection for test constructors, test methods, and test lifecycle callback
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
|
||||
Injection with the `SpringExtension`] for further details.
|
||||
* Powerful support for link:https://docs.junit.org/current/user-guide/#extensions-conditions[conditional
|
||||
* Powerful support for link:https://docs.junit.org/current/extensions/conditional-test-execution.html[conditional
|
||||
test execution] based on SpEL expressions, environment variables, system properties,
|
||||
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
@@ -160,7 +160,7 @@ for further details.
|
||||
=== Dependency Injection with the `SpringExtension`
|
||||
|
||||
The `SpringExtension` implements the
|
||||
link:https://docs.junit.org/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
|
||||
link:https://docs.junit.org/current/extensions/parameter-resolution.html[`ParameterResolver`]
|
||||
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
|
||||
constructors, test methods, and test lifecycle callback methods.
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
already set.
|
||||
- For content negotiation, the Jackson `HttpMessageConverter` prefers
|
||||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
- The Jackson JSON and XML message converters use "application/problem+json" or
|
||||
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
|
||||
to ensure they are favored for content negotiation.
|
||||
|
||||
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
|
||||
@@ -418,16 +418,23 @@ See the section on xref:web/webflux-cors.adoc[CORS] and the xref:web/webflux-cor
|
||||
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
|
||||
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
|
||||
|
||||
Adding trailing slash variants to all mapping declarations is not the best way to handle this use case.
|
||||
The `UrlHandlerFilter` web filter has been designed for this purpose. It can be configured to:
|
||||
Spring provides `UrlHandlerFilter` that removes the trailing slash from URL paths to ensure a consistent view of paths with or without a trailing slash.
|
||||
This is important to avoid a mismatch between URL-based authorization decisions and web framework request mappings.
|
||||
The filter can remove the trailing slash in one of a couple of ways:
|
||||
|
||||
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
|
||||
* mutate the request to act as if the request was sent without a trailing slash and continue the processing of the request.
|
||||
* respond with an HTTP redirect status that sends clients to the same path without a trailing slash.
|
||||
* mutate the request to remove the trailing slash.
|
||||
|
||||
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
|
||||
|
||||
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
|
||||
|
||||
Keep in mind the following:
|
||||
|
||||
- the root path `"/"` is excluded from trailing slash handling.
|
||||
- `@RequestMapping("/")` adds a trailing slash to a type-level mapping, and therefore will
|
||||
not map when trailing slash handling applies; use `@RequestMapping` (no path attribute) instead.
|
||||
|
||||
|
||||
[[webflux-exception-handler]]
|
||||
== Exceptions
|
||||
|
||||
@@ -120,17 +120,26 @@ See the sections on xref:web/webmvc-cors.adoc[CORS] and the xref:web/webmvc-cors
|
||||
== URL Handler
|
||||
[.small]#xref:web/webflux/reactive-spring.adoc#filters.url-handler[See equivalent in the Reactive stack]#
|
||||
|
||||
In previous Spring Framework versions, Spring MVC could be configured to ignore trailing slashes in URL paths
|
||||
when mapping incoming requests on controller methods. This could be done by enabling the `setUseTrailingSlashMatch`
|
||||
option on the `PathMatchConfigurer`. This means that sending a "GET /home/" request would be handled by a controller
|
||||
method annotated with `@GetMapping("/home")`.
|
||||
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
|
||||
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
|
||||
|
||||
This option has been retired, but applications are still expected to handle such requests in a safe way.
|
||||
The `UrlHandlerFilter` Servlet filter has been designed for this purpose. It can be configured to:
|
||||
Spring provides `UrlHandlerFilter` that removes the trailing slash from URL paths to ensure a consistent view of paths with or without a trailing slash.
|
||||
This is important to avoid a mismatch between URL-based authorization decisions and web framework request mappings.
|
||||
The filter can remove the trailing slash in one of a couple of ways:
|
||||
|
||||
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
|
||||
* wrap the request to act as if the request was sent without a trailing slash and continue the processing of the request.
|
||||
* respond with an HTTP redirect status that sends clients to the same path without a trailing slash.
|
||||
* wrap the request to remove the trailing slash.
|
||||
|
||||
NOTE: Historically Spring MVC supported trailing slash matching of URL paths.
|
||||
This capability was deprecated in 6.0 for security reasons and removed in 7.0 with
|
||||
`UrlHandlerFilter` providing a safer alternative.
|
||||
|
||||
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
|
||||
|
||||
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
|
||||
|
||||
Keep in mind the following:
|
||||
|
||||
- the root path `"/"` is excluded from trailing slash handling.
|
||||
- `@RequestMapping("/")` adds a trailing slash to a type-level mapping, and therefore will
|
||||
not map when trailing slash handling applies; use `@RequestMapping` (no path attribute) instead.
|
||||
|
||||
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
already set.
|
||||
- For content negotiation, the Jackson `HttpMessageConverter` prefers
|
||||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
- The Jackson JSON and XML codecs use "application/problem+json" or
|
||||
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
|
||||
to ensure they are favored for content negotiation.
|
||||
|
||||
To enable RFC 9457 responses for Spring MVC exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"antora": "3.2.0-alpha.4",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.2",
|
||||
"@antora/collector-extension": "1.0.0-alpha.3",
|
||||
"antora": "3.2.0-alpha.11",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.5",
|
||||
"@antora/collector-extension": "1.0.2",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.7",
|
||||
"fast-xml-parser": "4.5.2",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
"fast-xml-parser": "5.3.4",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -200,6 +200,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
SPRING FRAMEWORK ${version} SUBCOMPONENTS:
|
||||
@@ -212,7 +213,7 @@ code for these subcomponents is subject to the terms and
|
||||
conditions of the following licenses.
|
||||
|
||||
|
||||
>>> ASM 9.1 (org.ow2.asm:asm:9.1, org.ow2.asm:asm-commons:9.1):
|
||||
>>> ASM 9.9.1 (org.ow2.asm:asm:9.9.1):
|
||||
|
||||
Copyright (c) 2000-2011 INRIA, France Telecom
|
||||
All rights reserved.
|
||||
@@ -249,10 +250,8 @@ Copyright (c) 1999-2009, OW2 Consortium <https://www.ow2.org/>
|
||||
|
||||
>>> CGLIB 3.3 (cglib:cglib:3.3):
|
||||
|
||||
Per the LICENSE file in the CGLIB JAR distribution downloaded from
|
||||
https://github.com/cglib/cglib/releases/download/RELEASE_3_3_0/cglib-3.3.0.jar,
|
||||
CGLIB 3.3 is licensed under the Apache License, version 2.0, the text of which
|
||||
is included above.
|
||||
Per the LICENSE file in the CGLIB distribution, CGLIB 3.3 is licensed
|
||||
under the Apache License, version 2.0, the text of which is included above.
|
||||
|
||||
|
||||
>>> JavaPoet 1.13.0 (com.squareup:javapoet:1.13.0):
|
||||
@@ -263,18 +262,18 @@ JavaPoet 1.13.0 is licensed under the Apache License, version 2.0, the text of
|
||||
which is included above.
|
||||
|
||||
|
||||
>>> Objenesis 3.4 (org.objenesis:objenesis:3.4):
|
||||
>>> Objenesis 3.5 (org.objenesis:objenesis:3.5):
|
||||
|
||||
Per the LICENSE file in the Objenesis ZIP distribution downloaded from
|
||||
http://objenesis.org/download.html, Objenesis 3.4 is licensed under the
|
||||
Per the LICENSE file in the Objenesis distribution downloaded from
|
||||
http://objenesis.org/download.html, Objenesis 3.5 is licensed under the
|
||||
Apache License, version 2.0, the text of which is included above.
|
||||
|
||||
Per the NOTICE file in the Objenesis ZIP distribution downloaded from
|
||||
Per the NOTICE file in the Objenesis distribution downloaded from
|
||||
http://objenesis.org/download.html and corresponding to section 4d of the
|
||||
Apache License, Version 2.0, in this case for Objenesis:
|
||||
|
||||
Objenesis
|
||||
Copyright 2006-2019 Joe Walnes, Henri Tremblay, Leonardo Mesquita
|
||||
Copyright 2006-2026 Joe Walnes, Henri Tremblay, Leonardo Mesquita
|
||||
|
||||
|
||||
===============================================================================
|
||||
|
||||
@@ -8,20 +8,20 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.14"))
|
||||
api(platform("io.netty:netty-bom:4.1.128.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.15.9"))
|
||||
api(platform("io.netty:netty-bom:4.1.130.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.13"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.15"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.30"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.6"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.7"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.30"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.30"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.14.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.20.0"))
|
||||
api(platform("org.junit:junit-bom:5.14.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.21.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.4")
|
||||
@@ -31,7 +31,7 @@ dependencies {
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.13.2")
|
||||
api("com.google.protobuf:protobuf-java-util:4.32.1")
|
||||
api("com.google.protobuf:protobuf-java-util:4.33.4")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.10.0")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
@@ -90,11 +90,11 @@ dependencies {
|
||||
api("junit:junit:4.13.2")
|
||||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.17.7")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.7")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.7")
|
||||
api("org.apache.activemq:artemis-jakarta-client:2.42.0")
|
||||
api("org.apache.activemq:artemis-junit-5:2.42.0")
|
||||
api("org.apache.activemq:activemq-broker:5.17.7")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.7")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.7")
|
||||
api("org.apache.activemq:artemis-jakarta-client:2.42.0")
|
||||
api("org.apache.activemq:artemis-junit-5:2.42.0")
|
||||
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")
|
||||
@@ -129,7 +129,7 @@ dependencies {
|
||||
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.4")
|
||||
api("org.htmlunit:htmlunit:4.18.0")
|
||||
api("org.htmlunit:htmlunit:4.21.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.13.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
@@ -137,8 +137,8 @@ dependencies {
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.4")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.38.0")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.40.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.40.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
api("org.testng:testng:7.11.0")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.15
|
||||
version=6.2.16
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+3
@@ -489,6 +489,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getPropertyType(String propertyName) throws BeansException {
|
||||
if (this.wrappedObject == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
PropertyHandler ph = getPropertyHandler(propertyName);
|
||||
if (ph != null) {
|
||||
|
||||
@@ -192,7 +192,7 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
|
||||
protected PropertyHandler getLocalPropertyHandler(String propertyName) {
|
||||
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
|
||||
return (pd != null ? new BeanPropertyHandler((GenericTypeAwarePropertyDescriptor) pd) : null);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected FieldPropertyHandler getLocalPropertyHandler(String propertyName) {
|
||||
protected PropertyHandler getLocalPropertyHandler(String propertyName) {
|
||||
FieldPropertyHandler propertyHandler = this.fieldMap.get(propertyName);
|
||||
if (propertyHandler == null) {
|
||||
Field field = ReflectionUtils.findField(getWrappedClass(), propertyName);
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
abstract class PropertyDescriptorUtils {
|
||||
|
||||
@@ -99,14 +101,13 @@ abstract class PropertyDescriptorUtils {
|
||||
}
|
||||
else {
|
||||
Method readMethod = pd.getReadMethod();
|
||||
if (readMethod == null ||
|
||||
(readMethod.getReturnType() == method.getReturnType() && method.getName().startsWith("is"))) {
|
||||
if (readMethod == null || readMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
|
||||
pd.setReadMethod(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
pd = new BasicPropertyDescriptor(propertyName, (!setter ? method : null), (setter ? method : null));
|
||||
pd = new BasicPropertyDescriptor(propertyName, beanClass, (!setter ? method : null), (setter ? method : null));
|
||||
pdMap.put(propertyName, pd);
|
||||
}
|
||||
}
|
||||
@@ -264,6 +265,8 @@ abstract class PropertyDescriptorUtils {
|
||||
*/
|
||||
private static class BasicPropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
private final Class<?> beanClass;
|
||||
|
||||
@Nullable
|
||||
private Method readMethod;
|
||||
|
||||
@@ -272,10 +275,11 @@ abstract class PropertyDescriptorUtils {
|
||||
|
||||
private final List<Method> alternativeWriteMethods = new ArrayList<>();
|
||||
|
||||
public BasicPropertyDescriptor(String propertyName, @Nullable Method readMethod, @Nullable Method writeMethod)
|
||||
public BasicPropertyDescriptor(String propertyName, Class<?> beanClass, @Nullable Method readMethod, @Nullable Method writeMethod)
|
||||
throws IntrospectionException {
|
||||
|
||||
super(propertyName, readMethod, writeMethod);
|
||||
this.beanClass = beanClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,14 +311,24 @@ abstract class PropertyDescriptorUtils {
|
||||
public Method getWriteMethod() {
|
||||
if (this.writeMethod == null && !this.alternativeWriteMethods.isEmpty()) {
|
||||
if (this.readMethod == null) {
|
||||
return this.alternativeWriteMethods.get(0);
|
||||
this.writeMethod = this.alternativeWriteMethods.get(0);
|
||||
}
|
||||
else {
|
||||
for (Method method : this.alternativeWriteMethods) {
|
||||
// Check subtype match first.
|
||||
if (this.readMethod.getReturnType().isAssignableFrom(method.getParameterTypes()[0])) {
|
||||
this.writeMethod = method;
|
||||
break;
|
||||
}
|
||||
// Check exact match against resolved generic parameter type as a fallback.
|
||||
if (!(method.getGenericParameterTypes()[0] instanceof Class<?>)) {
|
||||
Class<?> resolvedParameterType =
|
||||
ResolvableType.forMethodParameter(method, 0, this.beanClass).toClass();
|
||||
if (this.readMethod.getReturnType().equals(resolvedParameterType)) {
|
||||
this.writeMethod = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,5 +336,4 @@ abstract class PropertyDescriptorUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -33,10 +33,12 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.beans.PropertyDescriptorUtilsPropertyResolutionTests.ServiceWithOverriddenGetterAndOverloadedSetter;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.beans.testfixture.beans.DerivedTestBean;
|
||||
@@ -67,14 +69,14 @@ class BeanUtilsTests {
|
||||
|
||||
@Test
|
||||
void instantiateClassGivenInterface() {
|
||||
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() ->
|
||||
BeanUtils.instantiateClass(List.class));
|
||||
assertThatExceptionOfType(FatalBeanException.class)
|
||||
.isThrownBy(() -> BeanUtils.instantiateClass(List.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instantiateClassGivenClassWithoutDefaultConstructor() {
|
||||
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() ->
|
||||
BeanUtils.instantiateClass(CustomDateEditor.class));
|
||||
assertThatExceptionOfType(FatalBeanException.class)
|
||||
.isThrownBy(() -> BeanUtils.instantiateClass(CustomDateEditor.class));
|
||||
}
|
||||
|
||||
@Test // gh-22531
|
||||
@@ -91,16 +93,16 @@ class BeanUtilsTests {
|
||||
void instantiateClassWithFewerArgsThanParameters() throws NoSuchMethodException {
|
||||
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor();
|
||||
|
||||
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() ->
|
||||
BeanUtils.instantiateClass(constructor, null, null, "foo"));
|
||||
assertThatExceptionOfType(BeanInstantiationException.class)
|
||||
.isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, "foo"));
|
||||
}
|
||||
|
||||
@Test // gh-22531
|
||||
void instantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException {
|
||||
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor();
|
||||
|
||||
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() ->
|
||||
BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null));
|
||||
assertThatExceptionOfType(BeanInstantiationException.class)
|
||||
.isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null));
|
||||
}
|
||||
|
||||
@Test // gh-22531, gh-27390
|
||||
@@ -157,267 +159,6 @@ class BeanUtilsTests {
|
||||
assertThat(BeanUtils.findEditorByConvention(Resource.class).getClass()).isEqualTo(ResourceEditor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyProperties() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("rod");
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("touchy");
|
||||
TestBean tb2 = new TestBean();
|
||||
assertThat(tb2.getName()).as("Name empty").isNull();
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
BeanUtils.copyProperties(tb, tb2);
|
||||
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
|
||||
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithDifferentTypes1() throws Exception {
|
||||
DerivedTestBean tb = new DerivedTestBean();
|
||||
tb.setName("rod");
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("touchy");
|
||||
TestBean tb2 = new TestBean();
|
||||
assertThat(tb2.getName()).as("Name empty").isNull();
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
BeanUtils.copyProperties(tb, tb2);
|
||||
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
|
||||
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithDifferentTypes2() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("rod");
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("touchy");
|
||||
DerivedTestBean tb2 = new DerivedTestBean();
|
||||
assertThat(tb2.getName()).as("Name empty").isNull();
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
BeanUtils.copyProperties(tb, tb2);
|
||||
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
|
||||
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Integer} can be copied to {@code Number}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesFromSubTypeToSuperType() {
|
||||
IntegerHolder integerHolder = new IntegerHolder();
|
||||
integerHolder.setNumber(42);
|
||||
NumberHolder numberHolder = new NumberHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerHolder, numberHolder);
|
||||
assertThat(integerHolder.getNumber()).isEqualTo(42);
|
||||
assertThat(numberHolder.getNumber()).isEqualTo(42);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can be copied to {@code List<Integer>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() {
|
||||
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
|
||||
integerListHolder1.getList().add(42);
|
||||
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder1, integerListHolder2);
|
||||
assertThat(integerListHolder1.getList()).containsExactly(42);
|
||||
assertThat(integerListHolder2.getList()).containsExactly(42);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<?>} can be copied to {@code List<?>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() {
|
||||
List<?> list = List.of("foo", 42);
|
||||
WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1();
|
||||
wildcardListHolder1.setList(list);
|
||||
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
|
||||
assertThat(wildcardListHolder2.getList()).isEmpty();
|
||||
|
||||
BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2);
|
||||
assertThat(wildcardListHolder1.getList()).isEqualTo(list);
|
||||
assertThat(wildcardListHolder2.getList()).isEqualTo(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can be copied to {@code List<?>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() {
|
||||
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
|
||||
integerListHolder1.getList().add(42);
|
||||
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2);
|
||||
assertThat(integerListHolder1.getList()).containsExactly(42);
|
||||
assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can be copied to {@code List<? extends Number>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() {
|
||||
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
|
||||
integerListHolder1.getList().add(42);
|
||||
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder1, numberListHolder);
|
||||
assertThat(integerListHolder1.getList()).containsExactly(42);
|
||||
assertThat(numberListHolder.getList()).isEqualTo(List.of(42));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Number} can NOT be copied to {@code Integer}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesDoesNotCopyFromSuperTypeToSubType() {
|
||||
NumberHolder numberHolder = new NumberHolder();
|
||||
numberHolder.setNumber(42);
|
||||
IntegerHolder integerHolder = new IntegerHolder();
|
||||
|
||||
BeanUtils.copyProperties(numberHolder, integerHolder);
|
||||
assertThat(numberHolder.getNumber()).isEqualTo(42);
|
||||
assertThat(integerHolder.getNumber()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can NOT be copied to {@code List<Long>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesDoesNotHonorGenericTypeMismatches() {
|
||||
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
|
||||
integerListHolder.getList().add(42);
|
||||
LongListHolder longListHolder = new LongListHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder, longListHolder);
|
||||
assertThat(integerListHolder.getList()).containsExactly(42);
|
||||
assertThat(longListHolder.getList()).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can NOT be copied to {@code List<Number>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() {
|
||||
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
|
||||
integerListHolder.getList().add(42);
|
||||
NumberListHolder numberListHolder = new NumberListHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder, numberListHolder);
|
||||
assertThat(integerListHolder.getList()).containsExactly(42);
|
||||
assertThat(numberListHolder.getList()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-26531
|
||||
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception {
|
||||
Order original = new Order("test", List.of("foo", "bar"));
|
||||
|
||||
// Create a Proxy that loses the generic type information for the getLineItems() method.
|
||||
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
|
||||
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original));
|
||||
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
|
||||
.contains("java.util.List<java.lang.String>");
|
||||
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
|
||||
.contains("java.util.List")
|
||||
.doesNotContain("<java.lang.String>");
|
||||
|
||||
// Ensure that our custom Proxy works as expected.
|
||||
assertThat(proxy.getId()).isEqualTo("test");
|
||||
assertThat(proxy.getLineItems()).containsExactly("foo", "bar");
|
||||
|
||||
// Copy from proxy to target.
|
||||
Order target = new Order();
|
||||
BeanUtils.copyProperties(proxy, target);
|
||||
assertThat(target.getId()).isEqualTo("test");
|
||||
assertThat(target.getLineItems()).containsExactly("foo", "bar");
|
||||
}
|
||||
|
||||
@Test // gh-32888
|
||||
public void copyPropertiesWithGenericCglibClass() {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(User.class);
|
||||
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
|
||||
User user = (User) enhancer.create();
|
||||
user.setId(1);
|
||||
user.setName("proxy");
|
||||
user.setAddress("addr");
|
||||
|
||||
User target = new User();
|
||||
BeanUtils.copyProperties(user, target);
|
||||
assertThat(target.getId()).isEqualTo(user.getId());
|
||||
assertThat(target.getName()).isEqualTo(user.getName());
|
||||
assertThat(target.getAddress()).isEqualTo(user.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithEditable() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
assertThat(tb.getName()).as("Name empty").isNull();
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("bla");
|
||||
TestBean tb2 = new TestBean();
|
||||
tb2.setName("rod");
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
|
||||
// "touchy" should not be copied: it's not defined in ITestBean
|
||||
BeanUtils.copyProperties(tb, tb2, ITestBean.class);
|
||||
assertThat(tb2.getName()).as("Name copied").isNull();
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(32);
|
||||
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithIgnore() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
assertThat(tb.getName()).as("Name empty").isNull();
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("bla");
|
||||
TestBean tb2 = new TestBean();
|
||||
tb2.setName("rod");
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
|
||||
// "spouse", "touchy", "age" should not be copied
|
||||
BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age");
|
||||
assertThat(tb2.getName()).as("Name copied").isNull();
|
||||
assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithIgnoredNonExistingProperty() {
|
||||
NameAndSpecialProperty source = new NameAndSpecialProperty();
|
||||
source.setName("name");
|
||||
TestBean target = new TestBean();
|
||||
BeanUtils.copyProperties(source, target, "specialProperty");
|
||||
assertThat(target.getName()).isEqualTo("name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithInvalidProperty() {
|
||||
InvalidProperty source = new InvalidProperty();
|
||||
source.setName("name");
|
||||
source.setFlag1(true);
|
||||
source.setFlag2(true);
|
||||
InvalidProperty target = new InvalidProperty();
|
||||
BeanUtils.copyProperties(source, target);
|
||||
assertThat(target.getName()).isEqualTo("name");
|
||||
assertThat((boolean) target.getFlag1()).isTrue();
|
||||
assertThat(target.getFlag2()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveSimpleSignature() throws Exception {
|
||||
Method desiredMethod = MethodSignatureBean.class.getMethod("doSomething");
|
||||
@@ -427,14 +168,14 @@ class BeanUtilsTests {
|
||||
|
||||
@Test
|
||||
void resolveInvalidSignatureEndParen() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class));
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveInvalidSignatureStartParen() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class));
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -538,6 +279,283 @@ class BeanUtilsTests {
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class CopyPropertiesTests {
|
||||
|
||||
@Test
|
||||
void copyProperties() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("rod");
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("touchy");
|
||||
TestBean tb2 = new TestBean();
|
||||
assertThat(tb2.getName()).as("Name empty").isNull();
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
BeanUtils.copyProperties(tb, tb2);
|
||||
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
|
||||
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithDifferentTypes1() throws Exception {
|
||||
DerivedTestBean tb = new DerivedTestBean();
|
||||
tb.setName("rod");
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("touchy");
|
||||
TestBean tb2 = new TestBean();
|
||||
assertThat(tb2.getName()).as("Name empty").isNull();
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
BeanUtils.copyProperties(tb, tb2);
|
||||
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
|
||||
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithDifferentTypes2() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("rod");
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("touchy");
|
||||
DerivedTestBean tb2 = new DerivedTestBean();
|
||||
assertThat(tb2.getName()).as("Name empty").isNull();
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
BeanUtils.copyProperties(tb, tb2);
|
||||
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
|
||||
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Integer} can be copied to {@code Number}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesFromSubTypeToSuperType() {
|
||||
IntegerHolder integerHolder = new IntegerHolder();
|
||||
integerHolder.setNumber(42);
|
||||
NumberHolder numberHolder = new NumberHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerHolder, numberHolder);
|
||||
assertThat(integerHolder.getNumber()).isEqualTo(42);
|
||||
assertThat(numberHolder.getNumber()).isEqualTo(42);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can be copied to {@code List<Integer>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() {
|
||||
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
|
||||
integerListHolder1.getList().add(42);
|
||||
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder1, integerListHolder2);
|
||||
assertThat(integerListHolder1.getList()).containsExactly(42);
|
||||
assertThat(integerListHolder2.getList()).containsExactly(42);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<?>} can be copied to {@code List<?>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() {
|
||||
List<?> list = List.of("foo", 42);
|
||||
WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1();
|
||||
wildcardListHolder1.setList(list);
|
||||
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
|
||||
assertThat(wildcardListHolder2.getList()).isEmpty();
|
||||
|
||||
BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2);
|
||||
assertThat(wildcardListHolder1.getList()).isEqualTo(list);
|
||||
assertThat(wildcardListHolder2.getList()).isEqualTo(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can be copied to {@code List<?>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() {
|
||||
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
|
||||
integerListHolder1.getList().add(42);
|
||||
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2);
|
||||
assertThat(integerListHolder1.getList()).containsExactly(42);
|
||||
assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can be copied to {@code List<? extends Number>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() {
|
||||
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
|
||||
integerListHolder1.getList().add(42);
|
||||
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder1, numberListHolder);
|
||||
assertThat(integerListHolder1.getList()).containsExactly(42);
|
||||
assertThat(numberListHolder.getList()).isEqualTo(List.of(42));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Number} can NOT be copied to {@code Integer}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesDoesNotCopyFromSuperTypeToSubType() {
|
||||
NumberHolder numberHolder = new NumberHolder();
|
||||
numberHolder.setNumber(42);
|
||||
IntegerHolder integerHolder = new IntegerHolder();
|
||||
|
||||
BeanUtils.copyProperties(numberHolder, integerHolder);
|
||||
assertThat(numberHolder.getNumber()).isEqualTo(42);
|
||||
assertThat(integerHolder.getNumber()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can NOT be copied to {@code List<Long>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesDoesNotHonorGenericTypeMismatches() {
|
||||
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
|
||||
integerListHolder.getList().add(42);
|
||||
LongListHolder longListHolder = new LongListHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder, longListHolder);
|
||||
assertThat(integerListHolder.getList()).containsExactly(42);
|
||||
assertThat(longListHolder.getList()).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code List<Integer>} can NOT be copied to {@code List<Number>}.
|
||||
*/
|
||||
@Test
|
||||
void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() {
|
||||
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
|
||||
integerListHolder.getList().add(42);
|
||||
NumberListHolder numberListHolder = new NumberListHolder();
|
||||
|
||||
BeanUtils.copyProperties(integerListHolder, numberListHolder);
|
||||
assertThat(integerListHolder.getList()).containsExactly(42);
|
||||
assertThat(numberListHolder.getList()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-26531
|
||||
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception {
|
||||
Order original = new Order("test", List.of("foo", "bar"));
|
||||
|
||||
// Create a Proxy that loses the generic type information for the getLineItems() method.
|
||||
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
|
||||
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original));
|
||||
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
|
||||
.contains("java.util.List<java.lang.String>");
|
||||
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
|
||||
.contains("java.util.List")
|
||||
.doesNotContain("<java.lang.String>");
|
||||
|
||||
// Ensure that our custom Proxy works as expected.
|
||||
assertThat(proxy.getId()).isEqualTo("test");
|
||||
assertThat(proxy.getLineItems()).containsExactly("foo", "bar");
|
||||
|
||||
// Copy from proxy to target.
|
||||
Order target = new Order();
|
||||
BeanUtils.copyProperties(proxy, target);
|
||||
assertThat(target.getId()).isEqualTo("test");
|
||||
assertThat(target.getLineItems()).containsExactly("foo", "bar");
|
||||
}
|
||||
|
||||
@Test // gh-32888
|
||||
void copyPropertiesWithGenericCglibClass() {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(User.class);
|
||||
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
|
||||
User user = (User) enhancer.create();
|
||||
user.setId(1);
|
||||
user.setName("proxy");
|
||||
user.setAddress("addr");
|
||||
|
||||
User target = new User();
|
||||
BeanUtils.copyProperties(user, target);
|
||||
assertThat(target.getId()).isEqualTo(user.getId());
|
||||
assertThat(target.getName()).isEqualTo(user.getName());
|
||||
assertThat(target.getAddress()).isEqualTo(user.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithEditable() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
assertThat(tb.getName()).as("Name empty").isNull();
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("bla");
|
||||
TestBean tb2 = new TestBean();
|
||||
tb2.setName("rod");
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
|
||||
// "touchy" should not be copied: it's not defined in ITestBean
|
||||
BeanUtils.copyProperties(tb, tb2, ITestBean.class);
|
||||
assertThat(tb2.getName()).as("Name copied").isNull();
|
||||
assertThat(tb2.getAge()).as("Age copied").isEqualTo(32);
|
||||
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithIgnore() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
assertThat(tb.getName()).as("Name empty").isNull();
|
||||
tb.setAge(32);
|
||||
tb.setTouchy("bla");
|
||||
TestBean tb2 = new TestBean();
|
||||
tb2.setName("rod");
|
||||
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
|
||||
|
||||
// "spouse", "touchy", "age" should not be copied
|
||||
BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age");
|
||||
assertThat(tb2.getName()).as("Name copied").isNull();
|
||||
assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0);
|
||||
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithIgnoredNonExistingProperty() {
|
||||
NameAndSpecialProperty source = new NameAndSpecialProperty();
|
||||
source.setName("name");
|
||||
TestBean target = new TestBean();
|
||||
BeanUtils.copyProperties(source, target, "specialProperty");
|
||||
assertThat(target.getName()).isEqualTo("name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithInvalidProperty() {
|
||||
InvalidProperty source = new InvalidProperty();
|
||||
source.setName("name");
|
||||
source.setFlag1(true);
|
||||
source.setFlag2(true);
|
||||
InvalidProperty target = new InvalidProperty();
|
||||
BeanUtils.copyProperties(source, target);
|
||||
assertThat(target.getName()).isEqualTo("name");
|
||||
assertThat((boolean) target.getFlag1()).isTrue();
|
||||
assertThat(target.getFlag2()).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-36019
|
||||
void copyPropertiesHonorsGenericsInTypeHieararchyAndIgnoresOverloadedSetterMethod() {
|
||||
var source = new ServiceWithOverriddenGetterAndOverloadedSetter();
|
||||
var target = new ServiceWithOverriddenGetterAndOverloadedSetter();
|
||||
|
||||
source.setId(1);
|
||||
BeanUtils.copyProperties(source, target);
|
||||
|
||||
assertThat(target.getId()).isEqualTo(source.getId()).isEqualTo("1");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public record RecordWithMultiplePublicConstructors(String value, String name) {
|
||||
@SuppressWarnings("unused")
|
||||
public RecordWithMultiplePublicConstructors(String value) {
|
||||
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author 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.beans;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.Parameter;
|
||||
import org.junit.jupiter.params.ParameterizedClass;
|
||||
import org.junit.jupiter.params.provider.FieldSource;
|
||||
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
import static org.assertj.core.api.SoftAssertions.assertSoftly;
|
||||
import static org.junit.jupiter.api.Named.named;
|
||||
|
||||
/**
|
||||
* Unit tests for property descriptor resolution via
|
||||
* {@link PropertyDescriptorUtils#determineBasicProperties(Class)}.
|
||||
*
|
||||
* <p>Results are compared to the behavior of the standard {@link Introspector}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.16
|
||||
*/
|
||||
@ParameterizedClass(name = "{0}")
|
||||
@FieldSource("resolvers")
|
||||
class PropertyDescriptorUtilsPropertyResolutionTests {
|
||||
|
||||
static final List<Named<PropertiesResolver>> resolvers = List.of(
|
||||
named("Basic Properties", new BasicPropertiesResolver()),
|
||||
named("Standard Properties", new StandardPropertiesResolver()));
|
||||
|
||||
|
||||
@Parameter
|
||||
PropertiesResolver resolver;
|
||||
|
||||
|
||||
@Test
|
||||
void determineBasicPropertiesWithUnresolvedGenericsInInterface() {
|
||||
var pdMap = resolver.resolve(GenericService.class);
|
||||
|
||||
assertThat(pdMap).containsOnlyKeys("id");
|
||||
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void determineBasicPropertiesWithUnresolvedGenericsInSubInterface() {
|
||||
// FYI: java.beans.Introspector does not resolve properties for sub-interfaces.
|
||||
assumeThat(resolver).isNotInstanceOf(StandardPropertiesResolver.class);
|
||||
|
||||
var pdMap = resolver.resolve(SubGenericService.class);
|
||||
|
||||
assertThat(pdMap).containsOnlyKeys("id");
|
||||
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvePropertiesWithUnresolvedGenericsInClass() {
|
||||
var pdMap = resolver.resolve(BaseService.class);
|
||||
|
||||
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
|
||||
}
|
||||
|
||||
@Test // gh-36019
|
||||
void resolvePropertiesInSubclassWithOverriddenGetterAndSetter() {
|
||||
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndSetter.class);
|
||||
|
||||
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, String.class);
|
||||
}
|
||||
|
||||
@Test // gh-36019
|
||||
void resolvePropertiesWithUnresolvedGenericsInSubclassWithOverloadedSetter() {
|
||||
var pdMap = resolver.resolve(ServiceWithOverloadedSetter.class);
|
||||
|
||||
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
|
||||
}
|
||||
|
||||
@Test // gh-36019
|
||||
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetter() {
|
||||
var pdMap = resolver.resolve(ServiceWithOverriddenGetter.class);
|
||||
|
||||
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
|
||||
}
|
||||
|
||||
@Test // gh-36019
|
||||
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetterAndOverloadedSetter() {
|
||||
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndOverloadedSetter.class);
|
||||
|
||||
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
|
||||
}
|
||||
|
||||
|
||||
private static void assertReadAndWriteMethodsForClassAndId(Map<String, List<PropertyDescriptor>> pdMap,
|
||||
Class<?> readType, Class<?> writeType) {
|
||||
|
||||
assertThat(pdMap).containsOnlyKeys("class", "id");
|
||||
assertReadAndWriteMethodsForClass(pdMap.get("class"));
|
||||
assertReadAndWriteMethodsForId(pdMap.get("id"), readType, writeType);
|
||||
}
|
||||
|
||||
private static void assertReadAndWriteMethodsForClass(List<PropertyDescriptor> pds) {
|
||||
assertThat(pds).hasSize(1);
|
||||
var pd = pds.get(0);
|
||||
assertThat(pd.getName()).isEqualTo("class");
|
||||
|
||||
var readMethod = pd.getReadMethod();
|
||||
assertThat(readMethod.getName()).isEqualTo("getClass");
|
||||
assertThat(readMethod.getReturnType()).as("read type").isEqualTo(Class.class);
|
||||
assertThat(readMethod.getParameterCount()).isZero();
|
||||
|
||||
assertThat(pd.getWriteMethod()).as("write method").isNull();
|
||||
}
|
||||
|
||||
private static void assertReadAndWriteMethodsForId(List<PropertyDescriptor> pds, Class<?> readType, Class<?> writeType) {
|
||||
assertThat(pds).hasSize(1);
|
||||
var pd = pds.get(0);
|
||||
assertThat(pd.getName()).isEqualTo("id");
|
||||
|
||||
var readMethod = pd.getReadMethod();
|
||||
var writeMethod = pd.getWriteMethod();
|
||||
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(readMethod.getName()).isEqualTo("getId");
|
||||
softly.assertThat(readMethod.getReturnType()).as("read type").isEqualTo(readType);
|
||||
softly.assertThat(readMethod.getParameterCount()).isZero();
|
||||
|
||||
softly.assertThat(writeMethod).as("write method").isNotNull();
|
||||
if (writeMethod != null) {
|
||||
softly.assertThat(writeMethod.getName()).isEqualTo("setId");
|
||||
softly.assertThat(writeMethod.getReturnType()).isEqualTo(void.class);
|
||||
softly.assertThat(writeMethod.getParameterCount()).isEqualTo(1);
|
||||
softly.assertThat(writeMethod.getParameterTypes()[0]).as("write type").isEqualTo(writeType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Map<String, List<PropertyDescriptor>> toMap(Stream<? extends PropertyDescriptor> stream) {
|
||||
return stream.collect(groupingBy(PropertyDescriptor::getName, toList()));
|
||||
}
|
||||
|
||||
|
||||
interface PropertiesResolver {
|
||||
|
||||
Map<String, List<PropertyDescriptor>> resolve(Class<?> beanClass);
|
||||
}
|
||||
|
||||
static class BasicPropertiesResolver implements PropertiesResolver {
|
||||
|
||||
@Override
|
||||
public Map<String, List<PropertyDescriptor>> resolve(Class<?> beanClass) {
|
||||
try {
|
||||
var pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
|
||||
return toMap(pds.stream());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class StandardPropertiesResolver implements PropertiesResolver {
|
||||
|
||||
@Override
|
||||
public Map<String, List<PropertyDescriptor>> resolve(Class<?> beanClass) {
|
||||
try {
|
||||
var beanInfo = Introspector.getBeanInfo(beanClass);
|
||||
return toMap(Arrays.stream(beanInfo.getPropertyDescriptors()));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface GenericService<T> {
|
||||
|
||||
void setId(T id);
|
||||
|
||||
T getId();
|
||||
}
|
||||
|
||||
interface SubGenericService<T> extends GenericService<T> {
|
||||
}
|
||||
|
||||
static class BaseService<T> {
|
||||
|
||||
private T id;
|
||||
|
||||
public T getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(T id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
static class ServiceWithOverriddenGetterAndSetter extends BaseService<String>
|
||||
implements SubGenericService<String> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
super.setId(id);
|
||||
}
|
||||
}
|
||||
|
||||
static class ServiceWithOverloadedSetter extends BaseService<String>
|
||||
implements SubGenericService<String> {
|
||||
|
||||
public void setId(int id) {
|
||||
setId(String.valueOf(id));
|
||||
}
|
||||
}
|
||||
|
||||
static class ServiceWithOverriddenGetter extends BaseService<String>
|
||||
implements SubGenericService<String> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return super.getId();
|
||||
}
|
||||
}
|
||||
|
||||
static class ServiceWithOverriddenGetterAndOverloadedSetter extends BaseService<String>
|
||||
implements SubGenericService<String> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
setId(String.valueOf(id));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -380,7 +380,7 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
|
||||
// For DependencyDescriptor resolution
|
||||
private Map<String, TestBean> mapWithRef;
|
||||
private Map<String, TestBean> mapWithTypes;
|
||||
Map<String, TestBean> mapWithRef;
|
||||
Map<String, TestBean> mapWithTypes;
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class CaffeineReactiveCachingTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeConfig.class})
|
||||
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeWithoutNullValuesConfig.class})
|
||||
void cacheHitDetermination(Class<?> configClass) {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
|
||||
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
|
||||
@@ -106,9 +106,8 @@ class CaffeineReactiveCachingTests {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeConfig.class})
|
||||
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeWithoutNullValuesConfig.class})
|
||||
void fluxCacheDoesntDependOnFirstRequest(Class<?> configClass) {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
|
||||
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
|
||||
|
||||
+3
@@ -39,10 +39,12 @@ class CacheEvaluationContextFactory {
|
||||
@Nullable
|
||||
private Supplier<ParameterNameDiscoverer> parameterNameDiscoverer;
|
||||
|
||||
|
||||
CacheEvaluationContextFactory(StandardEvaluationContext originalContext) {
|
||||
this.originalContext = originalContext;
|
||||
}
|
||||
|
||||
|
||||
public void setParameterNameDiscoverer(Supplier<ParameterNameDiscoverer> parameterNameDiscoverer) {
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
}
|
||||
@@ -54,6 +56,7 @@ class CacheEvaluationContextFactory {
|
||||
return this.parameterNameDiscoverer.get();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link CacheEvaluationContext} for the specified operation.
|
||||
* @param rootObject the {@code root} object to use for the context
|
||||
|
||||
+28
-7
@@ -27,6 +27,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -1067,11 +1068,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isStartupShutdownThreadStuck()) {
|
||||
active.set(false);
|
||||
if (!tryLockForShutdown()) {
|
||||
return;
|
||||
}
|
||||
startupShutdownLock.lock();
|
||||
try {
|
||||
doClose();
|
||||
}
|
||||
@@ -1084,6 +1083,30 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to acquire the common startup/shutdown lock, backing out if
|
||||
* the main startup/shutdown thread is stuck or on interruption.
|
||||
* @see #isStartupShutdownThreadStuck()
|
||||
*/
|
||||
private boolean tryLockForShutdown() {
|
||||
try {
|
||||
while (!this.startupShutdownLock.tryLock(100, TimeUnit.MILLISECONDS)) {
|
||||
if (!this.active.get() || this.closed.get()) {
|
||||
return false;
|
||||
}
|
||||
if (isStartupShutdownThreadStuck()) {
|
||||
this.active.set(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an active startup/shutdown thread is currently stuck,
|
||||
* for example, through a {@code System.exit} call in a user component.
|
||||
@@ -1095,7 +1118,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
activeThread.interrupt();
|
||||
try {
|
||||
// Leave just a little bit of time for the interruption to show effect
|
||||
Thread.sleep(1);
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
@@ -1117,12 +1140,10 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
if (isStartupShutdownThreadStuck()) {
|
||||
this.active.set(false);
|
||||
if (!tryLockForShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.startupShutdownLock.lock();
|
||||
try {
|
||||
this.startupShutdownThread = Thread.currentThread();
|
||||
|
||||
|
||||
+14
-16
@@ -377,47 +377,45 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
protected void postProcessConfiguration(Configuration<?> configuration) {
|
||||
}
|
||||
|
||||
private ValidatorFactory obtainValidatorFactory() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Validator getValidator() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory.getValidator();
|
||||
return obtainValidatorFactory().getValidator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidatorContext usingContext() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory.usingContext();
|
||||
return obtainValidatorFactory().usingContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageInterpolator getMessageInterpolator() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory.getMessageInterpolator();
|
||||
return obtainValidatorFactory().getMessageInterpolator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraversableResolver getTraversableResolver() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory.getTraversableResolver();
|
||||
return obtainValidatorFactory().getTraversableResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstraintValidatorFactory getConstraintValidatorFactory() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory.getConstraintValidatorFactory();
|
||||
return obtainValidatorFactory().getConstraintValidatorFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterNameProvider getParameterNameProvider() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory.getParameterNameProvider();
|
||||
return obtainValidatorFactory().getParameterNameProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClockProvider getClockProvider() {
|
||||
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
|
||||
return this.validatorFactory.getClockProvider();
|
||||
return obtainValidatorFactory().getClockProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -427,8 +425,8 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
try {
|
||||
return super.unwrap(type);
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// Ignore - we'll try ValidatorFactory unwrapping next
|
||||
catch (ValidationException ignored) {
|
||||
// Trying ValidatorFactory unwrapping next
|
||||
}
|
||||
}
|
||||
if (this.validatorFactory != null) {
|
||||
|
||||
+3
-1
@@ -26,7 +26,7 @@ import org.springframework.context.support.MessageSourceResourceBundle;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of Hibernate Validator 4.3/5.x's {@link ResourceBundleLocator} interface,
|
||||
* Implementation of Hibernate Validator's {@link ResourceBundleLocator} interface,
|
||||
* exposing a Spring {@link MessageSource} as localized {@link MessageSourceResourceBundle}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
@@ -39,6 +39,7 @@ public class MessageSourceResourceBundleLocator implements ResourceBundleLocator
|
||||
|
||||
private final MessageSource messageSource;
|
||||
|
||||
|
||||
/**
|
||||
* Build a MessageSourceResourceBundleLocator for the given MessageSource.
|
||||
* @param messageSource the Spring MessageSource to wrap
|
||||
@@ -48,6 +49,7 @@ public class MessageSourceResourceBundleLocator implements ResourceBundleLocator
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResourceBundle getResourceBundle(Locale locale) {
|
||||
return new MessageSourceResourceBundle(this.messageSource, locale);
|
||||
|
||||
-5
@@ -134,20 +134,15 @@ class ResourceElementResolverMethodTests {
|
||||
|
||||
private String one;
|
||||
|
||||
private String test;
|
||||
|
||||
private Integer count;
|
||||
|
||||
public void setOne(String one) {
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
public void setTest(String test) {
|
||||
this.test = test;
|
||||
}
|
||||
|
||||
public void setCount(Integer count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -184,7 +184,7 @@ class PeriodicTriggerTests {
|
||||
void equalsVerification() {
|
||||
PeriodicTrigger trigger1 = new PeriodicTrigger(Duration.ofMillis(3000));
|
||||
PeriodicTrigger trigger2 = new PeriodicTrigger(Duration.ofMillis(3000));
|
||||
assertThat(trigger1.equals(new String("not a trigger"))).isFalse();
|
||||
assertThat(trigger1).isNotEqualTo(new String("not a trigger"));
|
||||
assertThat(trigger1).isNotEqualTo(null);
|
||||
assertThat(trigger1).isEqualTo(trigger1);
|
||||
assertThat(trigger2).isEqualTo(trigger2);
|
||||
|
||||
+15
-29
@@ -60,18 +60,13 @@ class BshScriptFactoryTests {
|
||||
Calculator calc = (Calculator) ctx.getBean("calculator");
|
||||
Messenger messenger = (Messenger) ctx.getBean("messenger");
|
||||
|
||||
boolean condition3 = calc instanceof Refreshable;
|
||||
assertThat(condition3).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
boolean condition2 = messenger instanceof Refreshable;
|
||||
assertThat(condition2).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(calc).as("Scripted object should not be instance of Refreshable").isNotInstanceOf(Refreshable.class);
|
||||
assertThat(messenger).as("Scripted object should not be instance of Refreshable").isNotInstanceOf(Refreshable.class);
|
||||
|
||||
assertThat(calc).isEqualTo(calc);
|
||||
assertThat(messenger).isEqualTo(messenger);
|
||||
boolean condition1 = !messenger.equals(calc);
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(messenger.hashCode()).isNotEqualTo(calc.hashCode());
|
||||
boolean condition = !messenger.toString().equals(calc.toString());
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(messenger.toString()).isNotEqualTo(calc.toString());
|
||||
|
||||
assertThat(calc.add(2, 3)).isEqualTo(5);
|
||||
|
||||
@@ -144,8 +139,7 @@ class BshScriptFactoryTests {
|
||||
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger).as("Scripted object should not be instance of Refreshable").isNotInstanceOf(Refreshable.class);
|
||||
|
||||
assertThat(messenger2).isNotSameAs(messenger);
|
||||
assertThat(messenger2.getClass()).isSameAs(messenger.getClass());
|
||||
@@ -164,8 +158,7 @@ class BshScriptFactoryTests {
|
||||
Messenger messenger = (Messenger) ctx.getBean("messenger");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
|
||||
assertThat(messenger).as("Should be an instance of Refreshable").isInstanceOf(Refreshable.class);
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -185,8 +178,7 @@ class BshScriptFactoryTests {
|
||||
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
|
||||
assertThat(messenger).as("Should be an instance of Refreshable").isInstanceOf(Refreshable.class);
|
||||
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
assertThat(messenger2.getMessage()).isEqualTo("Hello World!");
|
||||
@@ -206,9 +198,9 @@ class BshScriptFactoryTests {
|
||||
|
||||
@Test
|
||||
void scriptCompilationException() {
|
||||
assertThatExceptionOfType(NestedRuntimeException.class).isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext("org/springframework/scripting/bsh/bshBrokenContext.xml"))
|
||||
.matches(ex -> ex.contains(ScriptCompilationException.class));
|
||||
assertThatExceptionOfType(NestedRuntimeException.class)
|
||||
.isThrownBy(() -> new ClassPathXmlApplicationContext("org/springframework/scripting/bsh/bshBrokenContext.xml"))
|
||||
.matches(ex -> ex.contains(ScriptCompilationException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,20 +219,17 @@ class BshScriptFactoryTests {
|
||||
|
||||
@Test
|
||||
void ctorWithNullScriptSourceLocator() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new BshScriptFactory(null, Messenger.class));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new BshScriptFactory(null, Messenger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ctorWithEmptyScriptSourceLocator() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new BshScriptFactory("", Messenger.class));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new BshScriptFactory("", Messenger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ctorWithWhitespacedScriptSourceLocator() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new BshScriptFactory("\n ", Messenger.class));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new BshScriptFactory("\n ", Messenger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -255,8 +244,7 @@ class BshScriptFactoryTests {
|
||||
|
||||
Messenger messenger = (Messenger) ctx.getBean("messenger");
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).isFalse();
|
||||
assertThat(messenger).isNotInstanceOf(Refreshable.class);
|
||||
|
||||
Messenger messengerImpl = (Messenger) ctx.getBean("messengerImpl");
|
||||
assertThat(messengerImpl.getMessage()).isEqualTo("Hello World!");
|
||||
@@ -305,8 +293,7 @@ class BshScriptFactoryTests {
|
||||
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bsh-with-xsd.xml", getClass());
|
||||
Calculator calculator = (Calculator) ctx.getBean("calculator");
|
||||
assertThat(calculator).isNotNull();
|
||||
boolean condition = calculator instanceof Refreshable;
|
||||
assertThat(condition).isFalse();
|
||||
assertThat(calculator).isNotInstanceOf(Refreshable.class);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@@ -315,8 +302,7 @@ class BshScriptFactoryTests {
|
||||
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bsh-with-xsd.xml", getClass());
|
||||
Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger");
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Messenger should be Refreshable").isTrue();
|
||||
assertThat(messenger).as("Messenger should be Refreshable").isInstanceOf(Refreshable.class);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
|
||||
+26
-53
@@ -78,18 +78,14 @@ public class GroovyScriptFactoryTests {
|
||||
assertThat(AopUtils.isAopProxy(calc)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
|
||||
boolean condition3 = calc instanceof Refreshable;
|
||||
assertThat(condition3).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
boolean condition2 = messenger instanceof Refreshable;
|
||||
assertThat(condition2).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(calc instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
assertThat(calc).isEqualTo(calc);
|
||||
assertThat(messenger).isEqualTo(messenger);
|
||||
boolean condition1 = !messenger.equals(calc);
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(messenger).isNotEqualTo(calc);
|
||||
assertThat(messenger.hashCode()).isNotEqualTo(calc.hashCode());
|
||||
boolean condition = !messenger.toString().equals(calc.toString());
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(messenger.toString()).isNotEqualTo(calc.toString());
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -111,18 +107,14 @@ public class GroovyScriptFactoryTests {
|
||||
assertThat(AopUtils.isAopProxy(calc)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
|
||||
boolean condition3 = calc instanceof Refreshable;
|
||||
assertThat(condition3).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
boolean condition2 = messenger instanceof Refreshable;
|
||||
assertThat(condition2).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(calc instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
assertThat(calc).isEqualTo(calc);
|
||||
assertThat(messenger).isEqualTo(messenger);
|
||||
boolean condition1 = !messenger.equals(calc);
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(messenger).isNotEqualTo(calc);
|
||||
assertThat(messenger.hashCode()).isNotEqualTo(calc.hashCode());
|
||||
boolean condition = !messenger.toString().equals(calc.toString());
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(messenger.toString()).isNotEqualTo(calc.toString());
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -138,8 +130,7 @@ public class GroovyScriptFactoryTests {
|
||||
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
assertThat(messenger2).isNotSameAs(messenger);
|
||||
assertThat(messenger2.getClass()).isSameAs(messenger.getClass());
|
||||
@@ -158,8 +149,7 @@ public class GroovyScriptFactoryTests {
|
||||
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
assertThat(messenger2).isNotSameAs(messenger);
|
||||
assertThat(messenger2.getClass()).isSameAs(messenger.getClass());
|
||||
@@ -178,8 +168,7 @@ public class GroovyScriptFactoryTests {
|
||||
Messenger messenger = (Messenger) ctx.getBean("messengerInstance");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -193,8 +182,7 @@ public class GroovyScriptFactoryTests {
|
||||
Messenger messenger = (Messenger) ctx.getBean("messengerInstance");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -208,8 +196,7 @@ public class GroovyScriptFactoryTests {
|
||||
Messenger messenger = (Messenger) ctx.getBean("messengerInstanceInline");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -223,8 +210,7 @@ public class GroovyScriptFactoryTests {
|
||||
Messenger messenger = (Messenger) ctx.getBean("messengerInstanceInline");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -237,8 +223,7 @@ public class GroovyScriptFactoryTests {
|
||||
Messenger messenger = (Messenger) ctx.getBean("messenger");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
|
||||
assertThat(messenger instanceof Refreshable).as("Should be an instance of Refreshable").isTrue();
|
||||
|
||||
String desiredMessage = "Hello World!";
|
||||
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
|
||||
@@ -257,8 +242,7 @@ public class GroovyScriptFactoryTests {
|
||||
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
|
||||
assertThat(messenger instanceof Refreshable).as("Should be an instance of Refreshable").isTrue();
|
||||
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
assertThat(messenger2.getMessage()).isEqualTo("Hello World!");
|
||||
@@ -371,8 +355,7 @@ public class GroovyScriptFactoryTests {
|
||||
CallCounter countingAspect = (CallCounter) ctx.getBean("getMessageAspect");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).isFalse();
|
||||
assertThat(messenger instanceof Refreshable).isFalse();
|
||||
assertThat(countingAspect.getCalls()).isEqualTo(0);
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
assertThat(countingAspect.getCalls()).isEqualTo(1);
|
||||
@@ -404,8 +387,7 @@ public class GroovyScriptFactoryTests {
|
||||
assertThat(ObjectUtils.containsElement(bd.getDependsOn(), "messenger")).isTrue();
|
||||
Calculator calculator = (Calculator) ctx.getBean("calculator");
|
||||
assertThat(calculator).isNotNull();
|
||||
boolean condition = calculator instanceof Refreshable;
|
||||
assertThat(condition).isFalse();
|
||||
assertThat(calculator instanceof Refreshable).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -417,8 +399,7 @@ public class GroovyScriptFactoryTests {
|
||||
CallCounter countingAspect = (CallCounter) ctx.getBean("getMessageAspect");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(messenger instanceof Refreshable).isTrue();
|
||||
assertThat(countingAspect.getCalls()).isEqualTo(0);
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
assertThat(countingAspect.getCalls()).isEqualTo(1);
|
||||
@@ -435,8 +416,7 @@ public class GroovyScriptFactoryTests {
|
||||
Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(messenger instanceof Refreshable).isTrue();
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
|
||||
assertThat(ctx.getBeansOfType(ConcreteMessenger.class)).containsValue((ConcreteMessenger) messenger);
|
||||
@@ -487,8 +467,7 @@ public class GroovyScriptFactoryTests {
|
||||
assertThat(Arrays.asList(ctx.getBeanNamesForType(Messenger.class))).contains("refreshableMessenger");
|
||||
Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger");
|
||||
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
|
||||
boolean condition = messenger instanceof Refreshable;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(messenger instanceof Refreshable).isTrue();
|
||||
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
|
||||
}
|
||||
|
||||
@@ -542,20 +521,17 @@ public class GroovyScriptFactoryTests {
|
||||
// expect the exception we threw in the custom metaclass to show it got invoked
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext(xmlFile);
|
||||
Calculator calc = (Calculator) ctx.getBean("delegatingCalculator");
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
calc.add(1, 2))
|
||||
.withMessage("Gotcha");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> calc.add(1, 2))
|
||||
.withMessage("Gotcha");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFactoryBean() {
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext("groovyContext.xml", getClass());
|
||||
Object factory = context.getBean("&factory");
|
||||
boolean condition1 = factory instanceof FactoryBean;
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(factory instanceof FactoryBean).isTrue();
|
||||
Object result = context.getBean("factory");
|
||||
boolean condition = result instanceof String;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(result).isEqualTo("test");
|
||||
}
|
||||
|
||||
@@ -563,11 +539,8 @@ public class GroovyScriptFactoryTests {
|
||||
void testRefreshableFactoryBean() {
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext("groovyContext.xml", getClass());
|
||||
Object factory = context.getBean("&refreshableFactory");
|
||||
boolean condition1 = factory instanceof FactoryBean;
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(factory instanceof FactoryBean).isTrue();
|
||||
Object result = context.getBean("refreshableFactory");
|
||||
boolean condition = result instanceof String;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(result).isEqualTo("test");
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -43,6 +43,7 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
|
||||
|
||||
private final String[] ignoredPackages;
|
||||
|
||||
|
||||
public InvocationsRecorderClassTransformer(String[] instrumentedPackages, String[] ignoredPackages) {
|
||||
Assert.notNull(instrumentedPackages, "instrumentedPackages must not be null");
|
||||
Assert.notNull(ignoredPackages, "ignoredPackages must not be null");
|
||||
@@ -55,6 +56,7 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] transform(@Nullable ClassLoader classLoader, String className, Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
@@ -101,7 +103,7 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
|
||||
fileReader.accept(classVisitor, 0);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
System.err.println("Failed to transform class: " + ex);
|
||||
return classfileBuffer;
|
||||
}
|
||||
if (classVisitor.isTransformed()) {
|
||||
@@ -109,4 +111,5 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
|
||||
}
|
||||
return classfileBuffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ public final class RuntimeHintsAgent {
|
||||
private static boolean loaded = false;
|
||||
|
||||
private RuntimeHintsAgent() {
|
||||
|
||||
}
|
||||
|
||||
public static void premain(@Nullable String agentArgs, Instrumentation inst) {
|
||||
@@ -64,6 +63,7 @@ public final class RuntimeHintsAgent {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
|
||||
private static final class ParsedArguments {
|
||||
|
||||
List<String> instrumentedPackages;
|
||||
@@ -104,6 +104,6 @@ public final class RuntimeHintsAgent {
|
||||
}
|
||||
return new ParsedArguments(included, excluded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ multiRelease {
|
||||
}
|
||||
|
||||
def javapoetVersion = "1.13.0"
|
||||
def objenesisVersion = "3.4"
|
||||
def objenesisVersion = "3.5"
|
||||
|
||||
configurations {
|
||||
java21Api.extendsFrom(api)
|
||||
|
||||
@@ -95,7 +95,7 @@ public class Attribute {
|
||||
* a Code attribute that contains labels.
|
||||
* @deprecated no longer used by ASM.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = false)
|
||||
protected Label[] getLabels() {
|
||||
return new Label[0];
|
||||
}
|
||||
@@ -174,6 +174,7 @@ public class Attribute {
|
||||
* ClassReader overrides {@link ClassReader#readLabel}. Hence {@link #read(ClassReader, int, int,
|
||||
* char[], int, Label[])} must not manually create {@link Label} instances.
|
||||
*
|
||||
* @param classReader the class that contains the attribute to be read.
|
||||
* @param bytecodeOffset a bytecode offset in a method.
|
||||
* @param labels the already created labels, indexed by their offset. If a label already exists
|
||||
* for bytecodeOffset this method does not create a new one. Otherwise it stores the new label
|
||||
|
||||
@@ -100,7 +100,7 @@ public class ClassReader {
|
||||
* @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will
|
||||
* eventually be deleted.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = false)
|
||||
// DontCheck(MemberName): can't be renamed (for backward binary compatibility).
|
||||
public final byte[] b;
|
||||
|
||||
@@ -197,7 +197,7 @@ public class ClassReader {
|
||||
// Check the class' major_version. This field is after the magic and minor_version fields, which
|
||||
// use 4 and 2 bytes respectively.
|
||||
// SPRING PATCH: leniently try to parse newer class files as well
|
||||
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
|
||||
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V27) {
|
||||
// throw new IllegalArgumentException(
|
||||
// "Unsupported class file major version " + readShort(classFileOffset + 6));
|
||||
// }
|
||||
@@ -3550,6 +3550,9 @@ public class ClassReader {
|
||||
final char[] charBuffer,
|
||||
final int codeAttributeOffset,
|
||||
final Label[] labels) {
|
||||
if (length > classFileBuffer.length - offset) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
for (Attribute attributePrototype : attributePrototypes) {
|
||||
if (attributePrototype.type.equals(type)) {
|
||||
return attributePrototype.read(
|
||||
|
||||
@@ -897,7 +897,7 @@ public class ClassWriter extends ClassVisitor {
|
||||
* @deprecated this method is superseded by {@link #newHandle(int, String, String, String,
|
||||
* boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = false)
|
||||
public int newHandle(
|
||||
final int tag, final String owner, final String name, final String descriptor) {
|
||||
return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
|
||||
|
||||
@@ -71,7 +71,7 @@ public final class Handle {
|
||||
* @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String,
|
||||
* boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = false)
|
||||
public Handle(final int tag, final String owner, final String name, final String descriptor) {
|
||||
this(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ public abstract class MethodVisitor {
|
||||
* @param descriptor the method's descriptor (see {@link Type}).
|
||||
* @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = false)
|
||||
public void visitMethodInsn(
|
||||
final int opcode, final String owner, final String name, final String descriptor) {
|
||||
int opcodeAndSource = opcode | (api < Opcodes.ASM5 ? Opcodes.SOURCE_DEPRECATED : 0);
|
||||
|
||||
@@ -291,6 +291,7 @@ public interface Opcodes {
|
||||
int V24 = 0 << 16 | 68;
|
||||
int V25 = 0 << 16 | 69;
|
||||
int V26 = 0 << 16 | 70;
|
||||
int V27 = 0 << 16 | 71;
|
||||
|
||||
/**
|
||||
* Version flag indicating that the class is using 'preview' features.
|
||||
|
||||
@@ -168,6 +168,7 @@ public interface Resource extends InputStreamSource {
|
||||
|
||||
/**
|
||||
* Determine the content length for this resource.
|
||||
* @return the content length (or -1 if undetermined)
|
||||
* @throws IOException if the resource cannot be resolved
|
||||
* (in the file system or as some other known physical resource type)
|
||||
*/
|
||||
@@ -175,6 +176,7 @@ public interface Resource extends InputStreamSource {
|
||||
|
||||
/**
|
||||
* Determine the last-modified timestamp for this resource.
|
||||
* @return the last-modified timestamp (or 0 if not known)
|
||||
* @throws IOException if the resource cannot be resolved
|
||||
* (in the file system or as some other known physical resource type)
|
||||
*/
|
||||
|
||||
@@ -1205,7 +1205,13 @@ public abstract class DataBufferUtils {
|
||||
long pos = this.position.get();
|
||||
Attachment attachment = new Attachment(byteBuffer, dataBuffer, iterator);
|
||||
this.writing.set(true);
|
||||
this.channel.write(byteBuffer, pos, attachment, this);
|
||||
try {
|
||||
this.channel.write(byteBuffer, pos, attachment, this);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
// If the exception escapes, route it to the failure handler
|
||||
failed(ex, attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,10 @@ import org.springframework.util.concurrent.FutureUtils;
|
||||
|
||||
/**
|
||||
* Extended interface for asynchronous {@link TaskExecutor} implementations,
|
||||
* offering support for {@link java.util.concurrent.Callable}.
|
||||
*
|
||||
* <p>Note: The {@link java.util.concurrent.Executors} class includes a set of
|
||||
* methods that can convert some other common closure-like objects, for example,
|
||||
* {@link java.security.PrivilegedAction} to {@link Callable} before executing them.
|
||||
* offering support for {@link Future}/{@link CompletableFuture} handles.
|
||||
*
|
||||
* <p>Implementing this interface also indicates that the {@link #execute(Runnable)}
|
||||
* method will not execute its Runnable in the caller's thread but rather
|
||||
* method will not execute the given Runnable in the caller's thread but rather
|
||||
* asynchronously in some other thread.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
@@ -125,8 +121,7 @@ public interface AsyncTaskExecutor extends TaskExecutor {
|
||||
|
||||
/**
|
||||
* Submit a {@code Callable} task for execution, receiving a {@code CompletableFuture}
|
||||
* representing that task. The Future will return the Callable's result upon
|
||||
* completion.
|
||||
* representing that task. The Future will return the Callable's result upon completion.
|
||||
* @param task the {@code Callable} to execute (never {@code null})
|
||||
* @return a {@code CompletableFuture} representing pending completion of the task
|
||||
* @throws TaskRejectedException if the given task was not accepted
|
||||
|
||||
+6
-5
@@ -32,13 +32,13 @@ import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureTask;
|
||||
|
||||
/**
|
||||
* {@link TaskExecutor} implementation that fires up a new Thread for each task,
|
||||
* executing it asynchronously. Provides a virtual thread option on JDK 21.
|
||||
* {@link TaskExecutor} implementation that fires up a new Thread for each task.
|
||||
* Provides a {@link #setVirtualThreads virtual threads} option on JDK 21+.
|
||||
*
|
||||
* <p>Supports a graceful shutdown through {@link #setTaskTerminationTimeout},
|
||||
* at the expense of task tracking overhead per execution thread at runtime.
|
||||
* Supports limiting concurrent threads through {@link #setConcurrencyLimit}.
|
||||
* By default, the number of concurrent task executions is unlimited.
|
||||
* Supports limiting concurrent threads through {@link #setConcurrencyLimit};
|
||||
* by default, the number of concurrent task executions is unlimited.
|
||||
*
|
||||
* <p><b>NOTE: This implementation does not reuse threads!</b> Consider a
|
||||
* thread-pooling TaskExecutor implementation instead, in particular for
|
||||
@@ -217,10 +217,11 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
|
||||
/**
|
||||
* Specify whether to reject tasks when the concurrency limit has been reached,
|
||||
* throwing {@link TaskRejectedException} on any further submission attempts.
|
||||
* throwing {@link TaskRejectedException} on any further execution attempts.
|
||||
* <p>The default is {@code false}, blocking the caller until the submission can
|
||||
* be accepted. Switch this to {@code true} for immediate rejection instead.
|
||||
* @since 6.2.6
|
||||
* @see #setConcurrencyLimit
|
||||
*/
|
||||
public void setRejectTasksWhenLimitReached(boolean rejectTasksWhenLimitReached) {
|
||||
this.rejectTasksWhenLimitReached = rejectTasksWhenLimitReached;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Spring's repackaging of
|
||||
* <a href="http://objenesis.org">Objenesis 3.4</a>
|
||||
* <a href="http://objenesis.org">Objenesis 3.5</a>
|
||||
* (with SpringObjenesis entry point; for internal use only).
|
||||
*
|
||||
* <p>This repackaging technique avoids any potential conflicts with
|
||||
|
||||
@@ -386,6 +386,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
@Override
|
||||
@Nullable
|
||||
public V computeIfAbsent(@Nullable K key, Function<? super K, ? extends V> mappingFunction) {
|
||||
// Avoid locking if entry is present
|
||||
Reference<K, V> ref = getReference(key, Restructure.NEVER);
|
||||
Entry<K, V> entry = (ref != null ? ref.get() : null);
|
||||
if (entry != null) {
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
|
||||
@Override
|
||||
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
|
||||
@@ -406,6 +413,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
@Override
|
||||
@Nullable
|
||||
public V computeIfPresent(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
// Avoid locking if entry is absent
|
||||
Reference<K, V> ref = getReference(key, Restructure.NEVER);
|
||||
Entry<K, V> entry = (ref != null ? ref.get() : null);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
|
||||
@Override
|
||||
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -30,9 +28,11 @@ import java.nio.file.Files;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Simple utility methods for file and stream copying. All copy methods use a block size
|
||||
* of 4096 bytes, and close all affected streams when done. A variation of the copy
|
||||
* methods from this class that leave streams open can be found in {@link StreamUtils}.
|
||||
* Simple utility methods for file and stream copying.
|
||||
*
|
||||
* <p>All copy methods use a block size of {@value #BUFFER_SIZE} bytes and
|
||||
* close all affected streams when done. A variation of the copy methods from
|
||||
* this class that leave streams open can be found in {@link StreamUtils}.
|
||||
*
|
||||
* <p>Mainly for use within the framework, but also useful for application code.
|
||||
*
|
||||
@@ -76,7 +76,7 @@ public abstract class FileCopyUtils {
|
||||
public static void copy(byte[] in, File out) throws IOException {
|
||||
Assert.notNull(in, "No input byte array specified");
|
||||
Assert.notNull(out, "No output File specified");
|
||||
copy(new ByteArrayInputStream(in), Files.newOutputStream(out.toPath()));
|
||||
Files.write(out.toPath(), in);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ public abstract class FileCopyUtils {
|
||||
*/
|
||||
public static byte[] copyToByteArray(File in) throws IOException {
|
||||
Assert.notNull(in, "No input File specified");
|
||||
return copyToByteArray(Files.newInputStream(in.toPath()));
|
||||
return Files.readAllBytes(in.toPath());
|
||||
}
|
||||
|
||||
|
||||
@@ -125,12 +125,9 @@ public abstract class FileCopyUtils {
|
||||
Assert.notNull(in, "No input byte array specified");
|
||||
Assert.notNull(out, "No OutputStream specified");
|
||||
|
||||
try {
|
||||
try (out) {
|
||||
out.write(in);
|
||||
}
|
||||
finally {
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,21 +164,11 @@ public abstract class FileCopyUtils {
|
||||
Assert.notNull(in, "No Reader specified");
|
||||
Assert.notNull(out, "No Writer specified");
|
||||
|
||||
try {
|
||||
int charCount = 0;
|
||||
char[] buffer = new char[BUFFER_SIZE];
|
||||
int charsRead;
|
||||
while ((charsRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, charsRead);
|
||||
charCount += charsRead;
|
||||
}
|
||||
try (in; out) {
|
||||
int charCount = (int) in.transferTo(out);
|
||||
out.flush();
|
||||
return charCount;
|
||||
}
|
||||
finally {
|
||||
close(in);
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,12 +182,9 @@ public abstract class FileCopyUtils {
|
||||
Assert.notNull(in, "No input String specified");
|
||||
Assert.notNull(out, "No Writer specified");
|
||||
|
||||
try {
|
||||
try (out) {
|
||||
out.write(in);
|
||||
}
|
||||
finally {
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,18 +204,4 @@ public abstract class FileCopyUtils {
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to close the supplied {@link Closeable}, silently swallowing any
|
||||
* exceptions.
|
||||
* @param closeable the {@code Closeable} to close
|
||||
*/
|
||||
private static void close(Closeable closeable) {
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -237,7 +237,8 @@ class MethodParameterTests {
|
||||
assertThat(m3.getTypeIndexForCurrentLevel()).isEqualTo(3);
|
||||
}
|
||||
|
||||
public int method(String p1, long p2) {
|
||||
|
||||
public int method(String str, long lng) {
|
||||
return 42;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -362,12 +362,12 @@ public abstract class NamedParameterUtils {
|
||||
for (int i = 0; i < paramNames.size(); i++) {
|
||||
String paramName = paramNames.get(i);
|
||||
try {
|
||||
SqlParameter param = findParameter(declaredParams, paramName, i);
|
||||
Object paramValue = paramSource.getValue(paramName);
|
||||
if (paramValue instanceof SqlParameterValue) {
|
||||
paramArray[i] = paramValue;
|
||||
}
|
||||
else {
|
||||
SqlParameter param = findParameter(declaredParams, paramName, i);
|
||||
paramArray[i] = (param != null ? new SqlParameterValue(param, paramValue) :
|
||||
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
|
||||
}
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ final class DefaultJdbcClient implements JdbcClient {
|
||||
}
|
||||
|
||||
public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate) {
|
||||
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
|
||||
Assert.notNull(jdbcTemplate, "NamedParameterJdbcTemplate must not be null");
|
||||
this.classicOps = jdbcTemplate.getJdbcOperations();
|
||||
this.namedParamOps = jdbcTemplate;
|
||||
}
|
||||
|
||||
+16
-6
@@ -29,7 +29,7 @@ import org.springframework.jdbc.core.SqlTypeValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Object to represent a binary parameter value for a SQL statement, for example,
|
||||
* Object to represent a binary parameter value for an SQL statement, for example,
|
||||
* a binary stream for a BLOB or a LONGVARBINARY or PostgreSQL BYTEA column.
|
||||
*
|
||||
* <p>Designed for use with {@link org.springframework.jdbc.core.JdbcTemplate}
|
||||
@@ -37,7 +37,7 @@ import org.springframework.lang.Nullable;
|
||||
* passed in as a parameter value wrapping the target content value.
|
||||
*
|
||||
* <p>Can be combined with {@link org.springframework.jdbc.core.SqlParameterValue}
|
||||
* for specifying a SQL type, for example,
|
||||
* for specifying an SQL type, for example,
|
||||
* {@code new SqlParameterValue(Types.BLOB, new SqlBinaryValue(myContent))}.
|
||||
* With most database drivers, the type hint is not actually necessary.
|
||||
*
|
||||
@@ -70,7 +70,7 @@ public class SqlBinaryValue implements SqlTypeValue {
|
||||
/**
|
||||
* Create a new {@code SqlBinaryValue} for the given content.
|
||||
* @param stream the content stream
|
||||
* @param length the length of the content
|
||||
* @param length the length of the content (or -1 if undetermined)
|
||||
*/
|
||||
public SqlBinaryValue(InputStream stream, long length) {
|
||||
this.content = stream;
|
||||
@@ -82,7 +82,7 @@ public class SqlBinaryValue implements SqlTypeValue {
|
||||
* <p>Consider specifying a {@link Resource} with content length support
|
||||
* when available: {@link SqlBinaryValue#SqlBinaryValue(Resource)}.
|
||||
* @param resource the resource to obtain a content stream from
|
||||
* @param length the length of the content
|
||||
* @param length the length of the content (or -1 if undetermined)
|
||||
*/
|
||||
public SqlBinaryValue(InputStreamSource resource, long length) {
|
||||
this.content = resource;
|
||||
@@ -146,10 +146,20 @@ public class SqlBinaryValue implements SqlTypeValue {
|
||||
throws SQLException {
|
||||
|
||||
if (sqlType == Types.BLOB) {
|
||||
ps.setBlob(paramIndex, is, length);
|
||||
if (length >= 0) {
|
||||
ps.setBlob(paramIndex, is, length);
|
||||
}
|
||||
else {
|
||||
ps.setBlob(paramIndex, is);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ps.setBinaryStream(paramIndex, is, length);
|
||||
if (length >= 0) {
|
||||
ps.setBinaryStream(paramIndex, is, length);
|
||||
}
|
||||
else {
|
||||
ps.setBinaryStream(paramIndex, is);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-9
@@ -28,7 +28,7 @@ import org.springframework.jdbc.core.SqlTypeValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Object to represent a character-based parameter value for a SQL statement,
|
||||
* Object to represent a character-based parameter value for an SQL statement,
|
||||
* for example, a character stream for a CLOB/NCLOB or a LONGVARCHAR column.
|
||||
*
|
||||
* <p>Designed for use with {@link org.springframework.jdbc.core.JdbcTemplate}
|
||||
@@ -36,7 +36,7 @@ import org.springframework.lang.Nullable;
|
||||
* passed in as a parameter value wrapping the target content value.
|
||||
*
|
||||
* <p>Can be combined with {@link org.springframework.jdbc.core.SqlParameterValue}
|
||||
* for specifying a SQL type, for example,
|
||||
* for specifying an SQL type, for example,
|
||||
* {@code new SqlParameterValue(Types.CLOB, new SqlCharacterValue(myContent))}.
|
||||
* With most database drivers, the type hint is not actually necessary.
|
||||
*
|
||||
@@ -77,7 +77,7 @@ public class SqlCharacterValue implements SqlTypeValue {
|
||||
/**
|
||||
* Create a new {@code SqlCharacterValue} for the given content.
|
||||
* @param reader the content reader
|
||||
* @param length the length of the content
|
||||
* @param length the length of the content (or -1 if undetermined)
|
||||
*/
|
||||
public SqlCharacterValue(Reader reader, long length) {
|
||||
this.content = reader;
|
||||
@@ -87,7 +87,7 @@ public class SqlCharacterValue implements SqlTypeValue {
|
||||
/**
|
||||
* Create a new {@code SqlCharacterValue} for the given content.
|
||||
* @param asciiStream the content as ASCII stream
|
||||
* @param length the length of the content
|
||||
* @param length the length of the content (or -1 if undetermined)
|
||||
*/
|
||||
public SqlCharacterValue(InputStream asciiStream, long length) {
|
||||
this.content = asciiStream;
|
||||
@@ -108,8 +108,8 @@ public class SqlCharacterValue implements SqlTypeValue {
|
||||
else if (this.content instanceof Reader reader) {
|
||||
setReader(ps, paramIndex, sqlType, reader, this.length);
|
||||
}
|
||||
else if (this.content instanceof InputStream asciiStream) {
|
||||
ps.setAsciiStream(paramIndex, asciiStream, this.length);
|
||||
else if (this.content instanceof InputStream inputStream) {
|
||||
setInputStream(ps, paramIndex, inputStream, this.length);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Illegal content type: " + this.content.getClass().getName());
|
||||
@@ -134,13 +134,39 @@ public class SqlCharacterValue implements SqlTypeValue {
|
||||
throws SQLException {
|
||||
|
||||
if (sqlType == Types.CLOB) {
|
||||
ps.setClob(paramIndex, reader, length);
|
||||
if (length >= 0) {
|
||||
ps.setClob(paramIndex, reader, length);
|
||||
}
|
||||
else {
|
||||
ps.setClob(paramIndex, reader);
|
||||
}
|
||||
}
|
||||
else if (sqlType == Types.NCLOB) {
|
||||
ps.setNClob(paramIndex, reader, length);
|
||||
if (length >= 0) {
|
||||
ps.setNClob(paramIndex, reader, length);
|
||||
}
|
||||
else {
|
||||
ps.setNClob(paramIndex, reader);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ps.setCharacterStream(paramIndex, reader, length);
|
||||
if (length >= 0) {
|
||||
ps.setCharacterStream(paramIndex, reader, length);
|
||||
}
|
||||
else {
|
||||
ps.setCharacterStream(paramIndex, reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setInputStream(PreparedStatement ps, int paramIndex, InputStream is, long length)
|
||||
throws SQLException {
|
||||
|
||||
if (length >= 0) {
|
||||
ps.setAsciiStream(paramIndex, is, length);
|
||||
}
|
||||
else {
|
||||
ps.setAsciiStream(paramIndex, is);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+36
@@ -65,6 +65,15 @@ class SqlBinaryValueTests {
|
||||
verify(ps).setBinaryStream(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withInputStreamUndeterminedLength() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
|
||||
SqlBinaryValue value = new SqlBinaryValue(content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
|
||||
verify(ps).setBinaryStream(1, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withInputStreamForBlob() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
|
||||
@@ -74,6 +83,15 @@ class SqlBinaryValueTests {
|
||||
verify(ps).setBlob(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withInputStreamForBlobUndeterminedLength() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
|
||||
SqlBinaryValue value = new SqlBinaryValue(content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, Types.BLOB, null);
|
||||
verify(ps).setBlob(1, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withInputStreamSource() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
|
||||
@@ -83,6 +101,15 @@ class SqlBinaryValueTests {
|
||||
verify(ps).setBinaryStream(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withInputStreamSourceUndeterminedLength() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
|
||||
SqlBinaryValue value = new SqlBinaryValue(() -> content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
|
||||
verify(ps).setBinaryStream(1, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withInputStreamSourceForBlob() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
|
||||
@@ -92,6 +119,15 @@ class SqlBinaryValueTests {
|
||||
verify(ps).setBlob(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withInputStreamSourceForBlobUndeterminedLength() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
|
||||
SqlBinaryValue value = new SqlBinaryValue(() -> content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, Types.BLOB, null);
|
||||
verify(ps).setBlob(1, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withResource() throws SQLException {
|
||||
byte[] content = new byte[] {0, 1, 2};
|
||||
|
||||
+36
@@ -104,6 +104,15 @@ class SqlCharacterValueTests {
|
||||
verify(ps).setCharacterStream(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withReaderUndeterminedLength() throws SQLException {
|
||||
Reader content = new StringReader("abc");
|
||||
SqlCharacterValue value = new SqlCharacterValue(content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
|
||||
verify(ps).setCharacterStream(1, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withReaderForClob() throws SQLException {
|
||||
Reader content = new StringReader("abc");
|
||||
@@ -113,6 +122,15 @@ class SqlCharacterValueTests {
|
||||
verify(ps).setClob(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withReaderForClobUndeterminedLength() throws SQLException {
|
||||
Reader content = new StringReader("abc");
|
||||
SqlCharacterValue value = new SqlCharacterValue(content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, Types.CLOB, null);
|
||||
verify(ps).setClob(1, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withReaderForNClob() throws SQLException {
|
||||
Reader content = new StringReader("abc");
|
||||
@@ -122,6 +140,15 @@ class SqlCharacterValueTests {
|
||||
verify(ps).setNClob(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withReaderForNClobUndeterminedLength() throws SQLException {
|
||||
Reader content = new StringReader("abc");
|
||||
SqlCharacterValue value = new SqlCharacterValue(content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, Types.NCLOB, null);
|
||||
verify(ps).setNClob(1, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withAsciiStream() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream("abc".getBytes(StandardCharsets.US_ASCII));
|
||||
@@ -131,4 +158,13 @@ class SqlCharacterValueTests {
|
||||
verify(ps).setAsciiStream(1, content, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withAsciiStreamUndeterminedLength() throws SQLException {
|
||||
InputStream content = new ByteArrayInputStream("abc".getBytes(StandardCharsets.US_ASCII));
|
||||
SqlCharacterValue value = new SqlCharacterValue(content, -1);
|
||||
PreparedStatement ps = mock();
|
||||
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
|
||||
verify(ps).setAsciiStream(1, content);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* Specifies a basic set of JMS operations.
|
||||
*
|
||||
* <p>Implemented by {@link JmsTemplate}. Not often used but a useful option
|
||||
* to enhance testability, as it can easily be mocked or stubbed.
|
||||
* <p>Implemented by {@link JmsTemplate}. Not often used directly, but a useful
|
||||
* option to enhance testability, as it can easily be mocked or stubbed.
|
||||
*
|
||||
* <p>Provides {@code JmsTemplate's} {@code send(..)} and
|
||||
* {@code receive(..)} methods that mirror various JMS API methods.
|
||||
* See the JMS specification and javadocs for details on those methods.
|
||||
*
|
||||
* <p>Provides also basic request reply operation using a temporary
|
||||
* queue to collect the reply.
|
||||
* <p>Provides also basic request-reply operations using a temporary queue to
|
||||
* receive the reply.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Juergen Hoeller
|
||||
@@ -59,7 +59,7 @@ public interface JmsOperations {
|
||||
|
||||
/**
|
||||
* Send messages to the default JMS destination (or one specified
|
||||
* for each send operation). The callback gives access to the JMS Session
|
||||
* for each send operation). The callback provides access to the JMS Session
|
||||
* and MessageProducer in order to perform complex send operations.
|
||||
* @param action callback object that exposes the session/producer pair
|
||||
* @return the result object from working with the session
|
||||
@@ -69,7 +69,7 @@ public interface JmsOperations {
|
||||
<T> T execute(ProducerCallback<T> action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send messages to a JMS destination. The callback gives access to the JMS Session
|
||||
* Send messages to a JMS destination. The callback provides access to the JMS Session
|
||||
* and MessageProducer in order to perform complex send operations.
|
||||
* @param destination the destination to send messages to
|
||||
* @param action callback object that exposes the session/producer pair
|
||||
@@ -80,7 +80,7 @@ public interface JmsOperations {
|
||||
<T> T execute(Destination destination, ProducerCallback<T> action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send messages to a JMS destination. The callback gives access to the JMS Session
|
||||
* Send messages to a JMS destination. The callback provides access to the JMS Session
|
||||
* and MessageProducer in order to perform complex send operations.
|
||||
* @param destinationName the name of the destination to send messages to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
@@ -377,7 +377,7 @@ public interface JmsOperations {
|
||||
/**
|
||||
* Send a request message and receive the reply from a default destination. The
|
||||
* {@link MessageCreator} callback creates the message given a Session. A temporary
|
||||
* queue is created as part of this operation and is set in the {@code JMSReplyTO}
|
||||
* queue is created as part of this operation and is set in the {@code JMSReplyTo}
|
||||
* header of the message.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @param messageCreator callback to create a request message
|
||||
@@ -390,10 +390,10 @@ public interface JmsOperations {
|
||||
Message sendAndReceive(MessageCreator messageCreator) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send a message and receive the reply from the specified destination. The
|
||||
* {@link MessageCreator} callback creates the message given a Session. A temporary
|
||||
* queue is created as part of this operation and is set in the {@code JMSReplyTO}
|
||||
* header of the message.
|
||||
* Send a message and receive the reply from the specified destination.
|
||||
* <p>The {@link MessageCreator} callback creates the message given a Session.
|
||||
* A temporary queue is created as part of this operation and is set in the
|
||||
* {@code JMSReplyTo} header of the message.
|
||||
* @param destination the destination to send this message to
|
||||
* @param messageCreator callback to create a message
|
||||
* @return the reply, possibly {@code null} if the message could not be received,
|
||||
@@ -405,10 +405,10 @@ public interface JmsOperations {
|
||||
Message sendAndReceive(Destination destination, MessageCreator messageCreator) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send a message and receive the reply from the specified destination. The
|
||||
* {@link MessageCreator} callback creates the message given a Session. A temporary
|
||||
* queue is created as part of this operation and is set in the {@code JMSReplyTO}
|
||||
* header of the message.
|
||||
* Send a message and receive the reply from the specified destination.
|
||||
* <p>The {@link MessageCreator} callback creates the message given a Session.
|
||||
* A temporary queue is created as part of this operation and is set in the
|
||||
* {@code JMSReplyTo} header of the message.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param messageCreator callback to create a message
|
||||
@@ -426,7 +426,7 @@ public interface JmsOperations {
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Browse messages in the default JMS queue. The callback gives access to the JMS
|
||||
* Browse messages in the default JMS queue. The callback provides access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
* @return the result object from working with the session
|
||||
@@ -436,7 +436,7 @@ public interface JmsOperations {
|
||||
<T> T browse(BrowserCallback<T> action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse messages in a JMS queue. The callback gives access to the JMS Session
|
||||
* Browse messages in a JMS queue. The callback provides access to the JMS Session
|
||||
* and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queue the queue to browse
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
@@ -447,7 +447,7 @@ public interface JmsOperations {
|
||||
<T> T browse(Queue queue, BrowserCallback<T> action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse messages in a JMS queue. The callback gives access to the JMS Session
|
||||
* Browse messages in a JMS queue. The callback provides access to the JMS Session
|
||||
* and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queueName the name of the queue to browse
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
@@ -459,7 +459,7 @@ public interface JmsOperations {
|
||||
<T> T browse(String queueName, BrowserCallback<T> action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse selected messages in a JMS queue. The callback gives access to the JMS
|
||||
* Browse selected messages in a JMS queue. The callback provides access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param messageSelector the JMS message selector expression (or {@code null} if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
@@ -471,7 +471,7 @@ public interface JmsOperations {
|
||||
<T> T browseSelected(@Nullable String messageSelector, BrowserCallback<T> action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse selected messages in a JMS queue. The callback gives access to the JMS
|
||||
* Browse selected messages in a JMS queue. The callback provides access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queue the queue to browse
|
||||
* @param messageSelector the JMS message selector expression (or {@code null} if none).
|
||||
@@ -484,7 +484,7 @@ public interface JmsOperations {
|
||||
<T> T browseSelected(Queue queue, @Nullable String messageSelector, BrowserCallback<T> action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse selected messages in a JMS queue. The callback gives access to the JMS
|
||||
* Browse selected messages in a JMS queue. The callback provides access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queueName the name of the queue to browse
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
|
||||
+2
-4
@@ -197,9 +197,7 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
|
||||
|
||||
/**
|
||||
* Initialize this container.
|
||||
* <p>Creates a JMS Connection, starts the {@link jakarta.jms.Connection}
|
||||
* (if {@link #setAutoStartup(boolean) "autoStartup"} hasn't been turned off),
|
||||
* and calls {@link #doInitialize()}.
|
||||
* <p>Marks the container as active and calls {@link #doInitialize()}.
|
||||
* @throws org.springframework.jms.JmsException if startup failed
|
||||
*/
|
||||
public void initialize() throws JmsException {
|
||||
@@ -489,7 +487,7 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
|
||||
/**
|
||||
* Stop the shared Connection.
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see jakarta.jms.Connection#start()
|
||||
* @see jakarta.jms.Connection#stop()
|
||||
*/
|
||||
protected void stopSharedConnection() throws JMSException {
|
||||
this.sharedConnectionLock.lock();
|
||||
|
||||
+2
-1
@@ -1026,14 +1026,15 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
|
||||
private static class MessageRejectedWhileStoppingException extends RuntimeException {
|
||||
}
|
||||
|
||||
|
||||
private abstract static class MicrometerInstrumentation {
|
||||
|
||||
static Session instrumentSession(Session session, ObservationRegistry registry) {
|
||||
return JmsInstrumentation.instrumentSession(session, registry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private abstract static class ObservationFactory {
|
||||
|
||||
private static final JmsProcessObservationConvention DEFAULT_CONVENTION = new DefaultJmsProcessObservationConvention();
|
||||
|
||||
+10
-8
@@ -280,8 +280,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
||||
* between recovery attempts. If the {@link BackOffExecution} implementation
|
||||
* returns {@link BackOffExecution#STOP}, this listener container will not further
|
||||
* attempt to recover.
|
||||
* <p>The {@link #setRecoveryInterval(long) recovery interval} is ignored
|
||||
* when this property is set.
|
||||
* <p>Note that setting the {@linkplain #setRecoveryInterval(long) recovery
|
||||
* interval} overrides this property.
|
||||
* @since 4.1
|
||||
*/
|
||||
public void setBackOff(BackOff backOff) {
|
||||
@@ -290,10 +290,12 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
||||
|
||||
/**
|
||||
* Specify the interval between recovery attempts, in <b>milliseconds</b>.
|
||||
* The default is 5000 ms, that is, 5 seconds. This is a convenience method
|
||||
* to create a {@link FixedBackOff} with the specified interval.
|
||||
* <p>For more recovery options, consider specifying a {@link BackOff}
|
||||
* instance instead.
|
||||
* <p>The default is 5000 ms, that is, 5 seconds.
|
||||
* <p>This is a convenience method to create a {@link FixedBackOff} with the
|
||||
* specified interval. For more recovery options, consider specifying a
|
||||
* {@link #setBackOff(BackOff) BackOff} instance instead. Note, however, that
|
||||
* explicitly setting the {@link #setBackOff(BackOff) BackOff} overrides this
|
||||
* property.
|
||||
* @see #setBackOff(BackOff)
|
||||
* @see #handleListenerSetupFailure
|
||||
*/
|
||||
@@ -1085,7 +1087,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Recovery during active operation..
|
||||
// Recovery during active operation...
|
||||
if (alreadyRecovered) {
|
||||
logger.debug("Setup of JMS message listener invoker failed - already recovered by other invoker", ex);
|
||||
}
|
||||
@@ -1519,7 +1521,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
||||
/**
|
||||
* Apply the back-off time once. In a regular scenario, the back-off is only applied if we
|
||||
* failed to recover with the broker. This additional wait period avoids a burst retry
|
||||
* scenario when the broker is actually up but something else if failing (i.e. listener
|
||||
* scenario when the broker is actually up but something else is failing (i.e. listener
|
||||
* specific).
|
||||
*/
|
||||
private void waitBeforeRecoveryAttempt() {
|
||||
|
||||
+1
-1
@@ -242,7 +242,7 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter
|
||||
return !isStrictContentTypeMatch();
|
||||
}
|
||||
for (MimeType current : getSupportedMimeTypes()) {
|
||||
if (current.getType().equals(mimeType.getType()) && current.getSubtype().equals(mimeType.getSubtype())) {
|
||||
if (current.includes(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -54,7 +54,7 @@ class MappingJackson2MessageConverterTests {
|
||||
}
|
||||
|
||||
@Test // SPR-12724
|
||||
public void mimetypeParametrizedConstructor() {
|
||||
void mimetypeParametrizedConstructor() {
|
||||
MimeType mimetype = new MimeType("application", "xml", StandardCharsets.UTF_8);
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(mimetype);
|
||||
assertThat(converter.getSupportedMimeTypes()).contains(mimetype);
|
||||
@@ -63,7 +63,7 @@ class MappingJackson2MessageConverterTests {
|
||||
}
|
||||
|
||||
@Test // SPR-12724
|
||||
public void mimetypesParametrizedConstructor() {
|
||||
void mimetypesParametrizedConstructor() {
|
||||
MimeType jsonMimetype = new MimeType("application", "json", StandardCharsets.UTF_8);
|
||||
MimeType xmlMimetype = new MimeType("application", "xml", StandardCharsets.UTF_8);
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(jsonMimetype, xmlMimetype);
|
||||
@@ -72,6 +72,22 @@ class MappingJackson2MessageConverterTests {
|
||||
.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportJsonMimeType() {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build();
|
||||
assertThat(converter.supportsMimeType(message.getHeaders())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportVendorJsonMimeTypes() {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "application/vnd.springframework.type+json").build();
|
||||
assertThat(converter.supportsMimeType(message.getHeaders())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromMessage() {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
|
||||
+5
-5
@@ -69,7 +69,7 @@ import org.springframework.util.CollectionUtils;
|
||||
* a Spring application context.
|
||||
*
|
||||
* <p>Encapsulates the common functionality between the different JPA bootstrap
|
||||
* contracts (standalone as well as container).
|
||||
* contracts: standalone as well as container.
|
||||
*
|
||||
* <p>Implements support for standard JPA configuration conventions as well as
|
||||
* Spring's customizable {@link JpaVendorAdapter} mechanism, and controls the
|
||||
@@ -178,10 +178,10 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the EntityManagerFactory configuration.
|
||||
* <p>Default is none, indicating the default EntityManagerFactory
|
||||
* configuration. The persistence provider will throw an exception if
|
||||
* ambiguous EntityManager configurations are found.
|
||||
* Specify the name of the persistence unit configuration to use.
|
||||
* <p>Default is none, indicating the default persistence unit configuration.
|
||||
* The persistence provider will throw an exception if ambiguous persistence
|
||||
* unit configurations are found.
|
||||
* @see jakarta.persistence.Persistence#createEntityManagerFactory(String)
|
||||
*/
|
||||
public void setPersistenceUnitName(@Nullable String persistenceUnitName) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.orm.jpa;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
@@ -78,7 +79,7 @@ public interface JpaDialect extends PersistenceExceptionTranslator {
|
||||
* @throws org.springframework.transaction.TransactionException in case of invalid arguments
|
||||
* @see #cleanupTransaction
|
||||
* @see jakarta.persistence.EntityTransaction#begin
|
||||
* @see org.springframework.jdbc.datasource.DataSourceUtils#prepareConnectionForTransaction
|
||||
* @see org.springframework.jdbc.datasource.DataSourceUtils#prepareConnectionForTransaction(Connection, TransactionDefinition)
|
||||
*/
|
||||
@Nullable
|
||||
Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
|
||||
|
||||
+4
-6
@@ -75,14 +75,11 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Rod Johnson
|
||||
* @since 2.0
|
||||
* @see #setPersistenceXmlLocation
|
||||
* @see #setJpaProperties
|
||||
* @see #setJpaVendorAdapter
|
||||
* @see #setLoadTimeWeaver
|
||||
* @see #setDataSource
|
||||
* @see EntityManagerFactoryInfo
|
||||
* @see LocalEntityManagerFactoryBean
|
||||
* @see org.springframework.orm.jpa.support.SharedEntityManagerBean
|
||||
* @see jakarta.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -133,9 +130,10 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the specified persistence unit name as the name of the default
|
||||
* persistence unit, if applicable.
|
||||
* <p><b>NOTE: Only applied if no external PersistenceUnitManager specified.</b>
|
||||
* Specify the name of the persistence unit configuration to use.
|
||||
* <p>Uses the specified persistence unit name as the name of the
|
||||
* default persistence unit, if applicable. Otherwise, it selects
|
||||
* among the available persistence units.
|
||||
* @see DefaultPersistenceUnitManager#setDefaultPersistenceUnitName
|
||||
*/
|
||||
@Override
|
||||
|
||||
+7
-5
@@ -51,10 +51,8 @@ import org.springframework.lang.Nullable;
|
||||
* @since 2.0
|
||||
* @see #setJpaProperties
|
||||
* @see #setJpaVendorAdapter
|
||||
* @see JpaTransactionManager#setEntityManagerFactory
|
||||
* @see #setDataSource
|
||||
* @see LocalContainerEntityManagerFactoryBean
|
||||
* @see org.springframework.jndi.JndiObjectFactoryBean
|
||||
* @see org.springframework.orm.jpa.support.SharedEntityManagerBean
|
||||
* @see jakarta.persistence.Persistence#createEntityManagerFactory
|
||||
* @see jakarta.persistence.spi.PersistenceProvider#createEntityManagerFactory
|
||||
*/
|
||||
@@ -63,6 +61,8 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
|
||||
private static final String DATASOURCE_PROPERTY = "jakarta.persistence.dataSource";
|
||||
|
||||
private static final String NON_JTA_DATASOURCE_PROPERTY = "jakarta.persistence.nonJtaDataSource";
|
||||
|
||||
|
||||
/**
|
||||
* Specify the JDBC DataSource that the JPA persistence provider is supposed
|
||||
@@ -78,9 +78,11 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
public void setDataSource(@Nullable DataSource dataSource) {
|
||||
if (dataSource != null) {
|
||||
getJpaPropertyMap().put(DATASOURCE_PROPERTY, dataSource);
|
||||
getJpaPropertyMap().put(NON_JTA_DATASOURCE_PROPERTY, dataSource);
|
||||
}
|
||||
else {
|
||||
getJpaPropertyMap().remove(DATASOURCE_PROPERTY);
|
||||
getJpaPropertyMap().remove(NON_JTA_DATASOURCE_PROPERTY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +113,8 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
// Create EntityManagerFactory directly through PersistenceProvider.
|
||||
EntityManagerFactory emf = provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap());
|
||||
if (emf == null) {
|
||||
throw new IllegalStateException(
|
||||
"PersistenceProvider [" + provider + "] did not return an EntityManagerFactory for name '" +
|
||||
throw new PersistenceException(
|
||||
"PersistenceProvider [" + provider + "] could not find persistence unit for name '" +
|
||||
getPersistenceUnitName() + "'");
|
||||
}
|
||||
return emf;
|
||||
|
||||
+8
-2
@@ -168,7 +168,7 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
|
||||
* This is useful to defer the early transaction begin that obtaining a
|
||||
* JDBC Connection implies within an EclipseLink EntityManager.
|
||||
*/
|
||||
private static class EclipseLinkConnectionHandle implements ConnectionHandle {
|
||||
private class EclipseLinkConnectionHandle implements ConnectionHandle {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
@@ -182,7 +182,13 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
if (this.connection == null) {
|
||||
this.connection = this.entityManager.unwrap(Connection.class);
|
||||
transactionIsolationLock.lock();
|
||||
try {
|
||||
this.connection = this.entityManager.unwrap(Connection.class);
|
||||
}
|
||||
finally {
|
||||
transactionIsolationLock.unlock();
|
||||
}
|
||||
}
|
||||
return this.connection;
|
||||
}
|
||||
|
||||
+4
-7
@@ -49,8 +49,7 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
|
||||
|
||||
@Test
|
||||
protected void testEntityManagerFactoryImplementsEntityManagerFactoryInfo() {
|
||||
boolean condition = entityManagerFactory instanceof EntityManagerFactoryInfo;
|
||||
assertThat(condition).as("Must have introduced config interface").isTrue();
|
||||
assertThat(entityManagerFactory).isInstanceOf(EntityManagerFactoryInfo.class);
|
||||
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) entityManagerFactory;
|
||||
assertThat(emfi.getPersistenceUnitName()).isEqualTo("Person");
|
||||
assertThat(emfi.getPersistenceUnitInfo()).as("PersistenceUnitInfo must be available").isNotNull();
|
||||
@@ -172,8 +171,8 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
|
||||
|
||||
@Test
|
||||
void testEntityManagerProxyRejectsProgrammaticTxManagement() {
|
||||
assertThatIllegalStateException().as("Should not be able to create transactions on container managed EntityManager").isThrownBy(
|
||||
sharedEntityManager::getTransaction);
|
||||
assertThatIllegalStateException().as("Should not be able to create transactions on container managed EntityManager")
|
||||
.isThrownBy(sharedEntityManager::getTransaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -234,9 +233,7 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
|
||||
q.setFlushMode(FlushModeType.AUTO);
|
||||
List<Person> people = q.getResultList();
|
||||
assertThat(people).isEmpty();
|
||||
assertThatException()
|
||||
.isThrownBy(q::getSingleResult)
|
||||
.withMessageContaining("closed");
|
||||
assertThatException().isThrownBy(q::getSingleResult).withMessageContaining("closed");
|
||||
// We would typically expect an IllegalStateException, but Hibernate throws a
|
||||
// PersistenceException. So we assert the contents of the exception message instead.
|
||||
|
||||
|
||||
+2
-4
@@ -88,8 +88,7 @@ class EntityManagerFactoryUtilsTests {
|
||||
IllegalStateException ise = new IllegalStateException();
|
||||
DataAccessException dex = EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ise);
|
||||
assertThat(dex.getCause()).isSameAs(ise);
|
||||
boolean condition = dex instanceof InvalidDataAccessApiUsageException;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(dex).isInstanceOf(InvalidDataAccessApiUsageException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,8 +96,7 @@ class EntityManagerFactoryUtilsTests {
|
||||
IllegalArgumentException iae = new IllegalArgumentException();
|
||||
DataAccessException dex = EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(iae);
|
||||
assertThat(dex.getCause()).isSameAs(iae);
|
||||
boolean condition = dex instanceof InvalidDataAccessApiUsageException;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(dex).isInstanceOf(InvalidDataAccessApiUsageException.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,4 +47,5 @@ public class Car {
|
||||
String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,4 +86,5 @@ public class Employee {
|
||||
@PreRemove
|
||||
public void preRemove() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.orm.jpa.domain;
|
||||
|
||||
public class EmployeeCategory {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
@@ -26,4 +27,5 @@ public class EmployeeCategory {
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -17,7 +17,9 @@
|
||||
package org.springframework.orm.jpa.domain;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
@Converter
|
||||
public class EmployeeCategoryConverter implements AttributeConverter<EmployeeCategory, String> {
|
||||
|
||||
@Override
|
||||
@@ -37,4 +39,5 @@ public class EmployeeCategoryConverter implements AttributeConverter<EmployeeCat
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.orm.jpa.domain;
|
||||
|
||||
public class EmployeeKind {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
@@ -26,4 +27,5 @@ public class EmployeeKind {
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
package org.springframework.orm.jpa.domain;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
@Converter(autoApply = true)
|
||||
public class EmployeeKindConverter implements AttributeConverter<EmployeeKind, String> {
|
||||
|
||||
@Override
|
||||
@@ -37,4 +39,5 @@ public class EmployeeKindConverter implements AttributeConverter<EmployeeKind, S
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,4 +27,5 @@ public class EmployeeLocation {
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -39,4 +39,5 @@ public class EmployeeLocationConverter implements AttributeConverter<EmployeeLoc
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -39,8 +39,7 @@ class EclipseLinkEntityManagerFactoryIntegrationTests extends AbstractContainerE
|
||||
|
||||
@Test
|
||||
void testCanCastSharedEntityManagerProxyToEclipseLinkEntityManager() {
|
||||
boolean condition = sharedEntityManager instanceof JpaEntityManager;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(sharedEntityManager).isInstanceOf(JpaEntityManager.class);
|
||||
JpaEntityManager eclipselinkEntityManager = (JpaEntityManager) sharedEntityManager;
|
||||
assertThat(eclipselinkEntityManager.getActiveSession()).isNotNull();
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author 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.orm.jpa.eclipselink;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class EclipseLinkEntityManagerFactoryScanIntegrationTests extends EclipseLinkEntityManagerFactoryIntegrationTests {
|
||||
|
||||
@Override
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] {"/org/springframework/orm/jpa/eclipselink/eclipselink-manager-scan.xml",
|
||||
"/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml"};
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author 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.orm.jpa.hibernate;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class HibernateEntityManagerFactoryScanIntegrationTests extends HibernateEntityManagerFactoryIntegrationTests {
|
||||
|
||||
@Override
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] {"/org/springframework/orm/jpa/hibernate/hibernate-manager-scan.xml",
|
||||
"/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml"};
|
||||
}
|
||||
|
||||
}
|
||||
+1
-2
@@ -48,8 +48,7 @@ class HibernateMultiEntityManagerFactoryIntegrationTests extends AbstractContain
|
||||
@Override
|
||||
@Test
|
||||
protected void testEntityManagerFactoryImplementsEntityManagerFactoryInfo() {
|
||||
boolean condition = this.entityManagerFactory instanceof EntityManagerFactoryInfo;
|
||||
assertThat(condition).as("Must have introduced config interface").isTrue();
|
||||
assertThat(this.entityManagerFactory).isInstanceOf(EntityManagerFactoryInfo.class);
|
||||
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) this.entityManagerFactory;
|
||||
assertThat(emfi.getPersistenceUnitName()).isEqualTo("Drivers");
|
||||
assertThat(emfi.getPersistenceUnitInfo()).as("PersistenceUnitInfo must be available").isNotNull();
|
||||
|
||||
+2
-1
@@ -71,7 +71,8 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
PersistenceManagedTypes persistenceManagedTypes = freshApplicationContext.getBean(
|
||||
"persistenceManagedTypes", PersistenceManagedTypes.class);
|
||||
assertThat(persistenceManagedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
DriversLicense.class.getName(), Person.class.getName(), Employee.class.getName(),
|
||||
Person.class.getName(), DriversLicense.class.getName(), Employee.class.getName(),
|
||||
EmployeeCategoryConverter.class.getName(), EmployeeKindConverter.class.getName(),
|
||||
EmployeeLocationConverter.class.getName(), Car.class.getName());
|
||||
assertThat(persistenceManagedTypes.getManagedPackages()).isEmpty();
|
||||
assertThat(freshApplicationContext.getBean(
|
||||
|
||||
+10
-4
@@ -26,6 +26,8 @@ import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.orm.jpa.domain.Car;
|
||||
import org.springframework.orm.jpa.domain.DriversLicense;
|
||||
import org.springframework.orm.jpa.domain.Employee;
|
||||
import org.springframework.orm.jpa.domain.EmployeeCategoryConverter;
|
||||
import org.springframework.orm.jpa.domain.EmployeeKindConverter;
|
||||
import org.springframework.orm.jpa.domain.EmployeeLocationConverter;
|
||||
import org.springframework.orm.jpa.domain.Person;
|
||||
import org.springframework.orm.jpa.domain2.entity.User;
|
||||
@@ -44,15 +46,17 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
*/
|
||||
class PersistenceManagedTypesScannerTests {
|
||||
|
||||
public static final DefaultResourceLoader RESOURCE_LOADER = new DefaultResourceLoader();
|
||||
private final DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
|
||||
private final PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(resourceLoader);
|
||||
|
||||
private final PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(RESOURCE_LOADER);
|
||||
|
||||
@Test
|
||||
void scanPackageWithOnlyEntities() {
|
||||
PersistenceManagedTypes managedTypes = this.scanner.scan("org.springframework.orm.jpa.domain");
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
Person.class.getName(), DriversLicense.class.getName(), Employee.class.getName(),
|
||||
EmployeeCategoryConverter.class.getName(), EmployeeKindConverter.class.getName(),
|
||||
EmployeeLocationConverter.class.getName(), Car.class.getName());
|
||||
assertThat(managedTypes.getManagedPackages()).isEmpty();
|
||||
}
|
||||
@@ -61,11 +65,13 @@ class PersistenceManagedTypesScannerTests {
|
||||
void scanPackageInvokesManagedClassNamesFilter() {
|
||||
ManagedClassNameFilter filter = mock(ManagedClassNameFilter.class);
|
||||
given(filter.matches(anyString())).willReturn(true);
|
||||
new PersistenceManagedTypesScanner(RESOURCE_LOADER, filter)
|
||||
new PersistenceManagedTypesScanner(resourceLoader, filter)
|
||||
.scan("org.springframework.orm.jpa.domain");
|
||||
verify(filter).matches(Person.class.getName());
|
||||
verify(filter).matches(DriversLicense.class.getName());
|
||||
verify(filter).matches(Employee.class.getName());
|
||||
verify(filter).matches(EmployeeCategoryConverter.class.getName());
|
||||
verify(filter).matches(EmployeeKindConverter.class.getName());
|
||||
verify(filter).matches(EmployeeLocationConverter.class.getName());
|
||||
verify(filter).matches(Car.class.getName());
|
||||
verifyNoMoreInteractions(filter);
|
||||
@@ -75,7 +81,7 @@ class PersistenceManagedTypesScannerTests {
|
||||
void scanPackageWithUseManagedClassNamesFilter() {
|
||||
List<String> candidates = List.of(Person.class.getName(), DriversLicense.class.getName());
|
||||
PersistenceManagedTypes managedTypes = new PersistenceManagedTypesScanner(
|
||||
RESOURCE_LOADER, candidates::contains).scan("org.springframework.orm.jpa.domain");
|
||||
resourceLoader, candidates::contains).scan("org.springframework.orm.jpa.domain");
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
Person.class.getName(), DriversLicense.class.getName());
|
||||
assertThat(managedTypes.getManagedPackages()).isEmpty();
|
||||
|
||||
+1
-2
@@ -58,8 +58,7 @@ class SharedEntityManagerFactoryTests {
|
||||
assertThat(proxyFactoryBean.getObject()).isSameAs(proxy);
|
||||
assertThat(proxy.contains(o)).isFalse();
|
||||
|
||||
boolean condition = proxy instanceof EntityManagerProxy;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(proxy).isInstanceOf(EntityManagerProxy.class);
|
||||
EntityManagerProxy emProxy = (EntityManagerProxy) proxy;
|
||||
assertThatIllegalStateException().as("outside of transaction").isThrownBy(
|
||||
emProxy::getTargetEntityManager);
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="persistenceUnitName" value="Person"/>
|
||||
<property name="packagesToScan" value="org.springframework.orm.jpa.domain*"/>
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="jpaVendorAdapter">
|
||||
<bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
|
||||
<property name="database" value="HSQL"/>
|
||||
<property name="showSql" value="true"/>
|
||||
<property name="generateDdl" value="true"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="jpaPropertyMap">
|
||||
<props>
|
||||
<prop key="eclipselink.weaving">false</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
|
||||
<property name="entityManagerFactory" ref="entityManagerFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
-4
@@ -5,10 +5,6 @@
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="persistenceXmlLocation" value="org/springframework/orm/jpa/domain/persistence.xml"/>
|
||||
<!--
|
||||
<property name="persistenceUnitName" value="Person"/>
|
||||
<property name="packagesToScan" value="org.springframework.orm.jpa.domain"/>
|
||||
-->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="jpaVendorAdapter">
|
||||
<bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" primary="true">
|
||||
<property name="persistenceUnitName" value="Person"/>
|
||||
<property name="packagesToScan" value="org.springframework.orm.jpa.domain*"/>
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="jpaVendorAdapter">
|
||||
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
|
||||
<property name="database" value="HSQL"/>
|
||||
<property name="showSql" value="true"/>
|
||||
<property name="generateDdl" value="true"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="jpaPropertyMap">
|
||||
<props>
|
||||
<prop key="hibernate.current_session_context_class">org.springframework.orm.jpa.hibernate.SpringSessionContext</prop>
|
||||
<prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
|
||||
</props>
|
||||
</property>
|
||||
<property name="bootstrapExecutor">
|
||||
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
|
||||
<property name="entityManagerFactory" ref="entityManagerFactory"/>
|
||||
</bean>
|
||||
|
||||
<bean id="hibernateStatistics" factory-bean="entityManagerFactory" factory-method="getStatistics" lazy-init="true"/>
|
||||
|
||||
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
|
||||
|
||||
</beans>
|
||||
@@ -91,7 +91,7 @@ public @interface TestConstructor {
|
||||
* {@link org.springframework.core.SpringProperties SpringProperties}
|
||||
* mechanism.
|
||||
* <p>This property may also be configured as a
|
||||
* <a href="https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params">JUnit
|
||||
* <a href="https://docs.junit.org/current/running-tests/configuration-parameters.html">JUnit
|
||||
* Platform configuration parameter</a>.
|
||||
* @see #autowireMode
|
||||
*/
|
||||
|
||||
+16
-5
@@ -30,6 +30,7 @@ import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
@@ -193,11 +194,21 @@ class WiretapConnector implements ClientHttpConnector {
|
||||
// 1. Mock server never consumed request body (for example, error before read)
|
||||
// 2. FluxExchangeResult: getResponseBodyContent called before getResponseBody
|
||||
//noinspection ConstantConditions
|
||||
(this.publisher != null ? this.publisher : this.publisherNested)
|
||||
.onErrorMap(ex -> new IllegalStateException(
|
||||
"Content has not been consumed, and " +
|
||||
"an error was raised while attempting to produce it.", ex))
|
||||
.subscribe();
|
||||
if (this.publisher != null) {
|
||||
this.publisher.doOnNext(DataBufferUtils::release)
|
||||
.onErrorMap(ex -> new IllegalStateException(
|
||||
"Content has not been consumed, and " +
|
||||
"an error was raised while attempting to produce it.", ex))
|
||||
.subscribe();
|
||||
}
|
||||
else if (this.publisherNested != null) {
|
||||
this.publisherNested
|
||||
.map(pub -> Flux.from(pub).doOnNext(DataBufferUtils::release))
|
||||
.onErrorMap(ex -> new IllegalStateException(
|
||||
"Content has not been consumed, and " +
|
||||
"an error was raised while attempting to produce it.", ex))
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
return this.content.asMono();
|
||||
});
|
||||
|
||||
+60
-1
@@ -17,11 +17,18 @@
|
||||
package org.springframework.test.web.reactive.server;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.core.testfixture.io.buffer.LeakAwareDataBufferFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
@@ -30,6 +37,7 @@ import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
|
||||
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFunctions;
|
||||
|
||||
@@ -44,11 +52,19 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
public class WiretapConnectorTests {
|
||||
|
||||
private final LeakAwareDataBufferFactory bufferFactory =
|
||||
new LeakAwareDataBufferFactory(new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT));
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
this.bufferFactory.checkForLeaks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void captureAndClaim() {
|
||||
ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/test");
|
||||
ClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
|
||||
ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(Mono.just(response));
|
||||
ClientHttpConnector connector = createConnector(request, response);
|
||||
|
||||
ClientRequest clientRequest = ClientRequest.create(HttpMethod.GET, URI.create("/test"))
|
||||
.header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, "1").build();
|
||||
@@ -62,4 +78,47 @@ public class WiretapConnectorTests {
|
||||
assertThat(result.getUrl().toString()).isEqualTo("/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReleaseBuffers() {
|
||||
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/test");
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
|
||||
response.setBody(Flux.just(toDataBuffer("Hello Spring")));
|
||||
ClientHttpConnector connector = createConnector(request, response);
|
||||
|
||||
ClientRequest clientRequest = ClientRequest.create(HttpMethod.GET, URI.create("/test"))
|
||||
.header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, "1").build();
|
||||
|
||||
WiretapConnector wiretapConnector = new WiretapConnector(connector);
|
||||
ExchangeFunction function = ExchangeFunctions.create(wiretapConnector);
|
||||
function.exchange(clientRequest).block(ofMillis(0));
|
||||
ExchangeResult result = wiretapConnector.getExchangeResult("1", null, Duration.ZERO);
|
||||
result.getResponseBodyContent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReleaseBuffersOnlyOnce() {
|
||||
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/test");
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
|
||||
response.setBody(Flux.just(toDataBuffer("Hello Spring"), toDataBuffer("Hello Spring"), toDataBuffer("Hello Spring"), toDataBuffer("Hello Spring")));
|
||||
ClientHttpConnector connector = createConnector(request, response);
|
||||
|
||||
ClientRequest clientRequest = ClientRequest.create(HttpMethod.GET, URI.create("/test"))
|
||||
.header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, "1").build();
|
||||
|
||||
WiretapConnector wiretapConnector = new WiretapConnector(connector);
|
||||
ExchangeFunction function = ExchangeFunctions.create(wiretapConnector);
|
||||
function.exchange(clientRequest).flatMap(ClientResponse::releaseBody).block(ofMillis(0));
|
||||
ExchangeResult result = wiretapConnector.getExchangeResult("1", null, Duration.ZERO);
|
||||
result.getResponseBodyContent();
|
||||
}
|
||||
|
||||
private ClientHttpConnector createConnector(ClientHttpRequest request, ClientHttpResponse response) {
|
||||
return (method, uri, fn) -> fn.apply(request).then(Mono.just(response));
|
||||
}
|
||||
|
||||
private DataBuffer toDataBuffer(String s) {
|
||||
DataBuffer buffer = this.bufferFactory.allocateBuffer(256);
|
||||
buffer.write(s.getBytes(StandardCharsets.UTF_8));
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author 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.transaction.jta;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import jakarta.transaction.InvalidTransactionException;
|
||||
import jakarta.transaction.NotSupportedException;
|
||||
import jakarta.transaction.SystemException;
|
||||
import jakarta.transaction.Transaction;
|
||||
import jakarta.transaction.TransactionManager;
|
||||
import jakarta.transaction.UserTransaction;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionSystemException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Special {@link JtaTransactionManager} variant for Oracle WebLogic 15.1.1 and higher.
|
||||
* Supports the full power of Spring's transaction definitions on WebLogic's
|
||||
* transaction coordinator, <i>beyond standard JTA</i>: transaction names,
|
||||
* per-transaction isolation levels, and proper resuming of transactions in all cases.
|
||||
*
|
||||
* <p>Uses WebLogic's special {@code begin(name)} method to start a JTA transaction,
|
||||
* in order to make <b>Spring-driven transactions visible in WebLogic's transaction
|
||||
* monitor</b>. In case of Spring's declarative transactions, the exposed name will
|
||||
* (by default) be the fully-qualified class name + "." + method name.
|
||||
*
|
||||
* <p>Supports a <b>per-transaction isolation level</b> through WebLogic's corresponding
|
||||
* JTA transaction property "ISOLATION LEVEL". This will apply the specified isolation
|
||||
* level (e.g. ISOLATION_SERIALIZABLE) to all JDBC Connections that participate in the
|
||||
* given transaction.
|
||||
*
|
||||
* <p>Invokes WebLogic's special {@code forceResume} method if standard JTA resume
|
||||
* failed, to <b>also resume if the target transaction was marked rollback-only</b>.
|
||||
* If you're not relying on this feature of transaction suspension in the first
|
||||
* place, Spring's standard JtaTransactionManager will behave properly too.
|
||||
*
|
||||
* <p>By default, the JTA UserTransaction and TransactionManager handles are
|
||||
* fetched directly from WebLogic's {@code TransactionHelper}. This can be
|
||||
* overridden by specifying "userTransaction"/"userTransactionName" and
|
||||
* "transactionManager"/"transactionManagerName", passing in existing handles
|
||||
* or specifying corresponding JNDI locations to look up.
|
||||
*
|
||||
* <p>Note: This class was initially removed as of Spring Framework 6.0 but then
|
||||
* brought back after the WebLogic 15.1.1 release which finally delivers Jakarta EE 9
|
||||
* compatibility. As of Spring Framework 6.2.16, it is available again for manual
|
||||
* configuration - as a replacement for the standard {@link JtaTransactionManager}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.2.16
|
||||
* @see org.springframework.transaction.TransactionDefinition#getName()
|
||||
* @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class WebLogicJtaTransactionManager extends JtaTransactionManager {
|
||||
|
||||
private static final String USER_TRANSACTION_CLASS_NAME = "weblogic.transaction.UserTransaction";
|
||||
|
||||
private static final String CLIENT_TRANSACTION_MANAGER_CLASS_NAME = "weblogic.transaction.ClientTransactionManager";
|
||||
|
||||
private static final String TRANSACTION_CLASS_NAME = "weblogic.transaction.Transaction";
|
||||
|
||||
private static final String TRANSACTION_HELPER_CLASS_NAME = "weblogic.transaction.TransactionHelper";
|
||||
|
||||
private static final String ISOLATION_LEVEL_KEY = "ISOLATION LEVEL";
|
||||
|
||||
|
||||
private boolean weblogicUserTransactionAvailable;
|
||||
|
||||
@Nullable
|
||||
private Method beginWithNameMethod;
|
||||
|
||||
@Nullable
|
||||
private Method beginWithNameAndTimeoutMethod;
|
||||
|
||||
private boolean weblogicTransactionManagerAvailable;
|
||||
|
||||
@Nullable
|
||||
private Method forceResumeMethod;
|
||||
|
||||
@Nullable
|
||||
private Method setPropertyMethod;
|
||||
|
||||
@Nullable
|
||||
private Object transactionHelper;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws TransactionSystemException {
|
||||
super.afterPropertiesSet();
|
||||
loadWebLogicTransactionClasses();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected UserTransaction retrieveUserTransaction() throws TransactionSystemException {
|
||||
Object helper = loadWebLogicTransactionHelper();
|
||||
try {
|
||||
logger.trace("Retrieving JTA UserTransaction from WebLogic TransactionHelper");
|
||||
Method getUserTransactionMethod = helper.getClass().getMethod("getUserTransaction");
|
||||
return (UserTransaction) getUserTransactionMethod.invoke(this.transactionHelper);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new TransactionSystemException(
|
||||
"WebLogic's TransactionHelper.getUserTransaction() method failed", ex.getTargetException());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new TransactionSystemException(
|
||||
"Could not invoke WebLogic's TransactionHelper.getUserTransaction() method", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected TransactionManager retrieveTransactionManager() throws TransactionSystemException {
|
||||
Object helper = loadWebLogicTransactionHelper();
|
||||
try {
|
||||
logger.trace("Retrieving JTA TransactionManager from WebLogic TransactionHelper");
|
||||
Method getTransactionManagerMethod = helper.getClass().getMethod("getTransactionManager");
|
||||
return (TransactionManager) getTransactionManagerMethod.invoke(this.transactionHelper);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new TransactionSystemException(
|
||||
"WebLogic's TransactionHelper.getTransactionManager() method failed", ex.getTargetException());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new TransactionSystemException(
|
||||
"Could not invoke WebLogic's TransactionHelper.getTransactionManager() method", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Object loadWebLogicTransactionHelper() throws TransactionSystemException {
|
||||
Object helper = this.transactionHelper;
|
||||
if (helper == null) {
|
||||
try {
|
||||
Class<?> transactionHelperClass = getClass().getClassLoader().loadClass(TRANSACTION_HELPER_CLASS_NAME);
|
||||
Method getTransactionHelperMethod = transactionHelperClass.getMethod("getTransactionHelper");
|
||||
helper = getTransactionHelperMethod.invoke(null);
|
||||
this.transactionHelper = helper;
|
||||
logger.trace("WebLogic TransactionHelper found");
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new TransactionSystemException(
|
||||
"WebLogic's TransactionHelper.getTransactionHelper() method failed", ex.getTargetException());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new TransactionSystemException(
|
||||
"Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
return helper;
|
||||
}
|
||||
|
||||
private void loadWebLogicTransactionClasses() throws TransactionSystemException {
|
||||
try {
|
||||
Class<?> userTransactionClass = getClass().getClassLoader().loadClass(USER_TRANSACTION_CLASS_NAME);
|
||||
this.weblogicUserTransactionAvailable = userTransactionClass.isInstance(getUserTransaction());
|
||||
if (this.weblogicUserTransactionAvailable) {
|
||||
this.beginWithNameMethod = userTransactionClass.getMethod("begin", String.class);
|
||||
this.beginWithNameAndTimeoutMethod = userTransactionClass.getMethod("begin", String.class, int.class);
|
||||
logger.debug("Support for WebLogic transaction names available");
|
||||
}
|
||||
else {
|
||||
logger.debug("Support for WebLogic transaction names not available");
|
||||
}
|
||||
|
||||
// Obtain WebLogic ClientTransactionManager interface.
|
||||
Class<?> transactionManagerClass =
|
||||
getClass().getClassLoader().loadClass(CLIENT_TRANSACTION_MANAGER_CLASS_NAME);
|
||||
logger.trace("WebLogic ClientTransactionManager found");
|
||||
|
||||
this.weblogicTransactionManagerAvailable = transactionManagerClass.isInstance(getTransactionManager());
|
||||
if (this.weblogicTransactionManagerAvailable) {
|
||||
Class<?> transactionClass = getClass().getClassLoader().loadClass(TRANSACTION_CLASS_NAME);
|
||||
this.forceResumeMethod = transactionManagerClass.getMethod("forceResume", Transaction.class);
|
||||
this.setPropertyMethod = transactionClass.getMethod("setProperty", String.class, Serializable.class);
|
||||
logger.debug("Support for WebLogic forceResume available");
|
||||
}
|
||||
else {
|
||||
logger.debug("Support for WebLogic forceResume not available");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new TransactionSystemException(
|
||||
"Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private TransactionManager obtainTransactionManager() {
|
||||
TransactionManager tm = getTransactionManager();
|
||||
Assert.state(tm != null, "No TransactionManager set");
|
||||
return tm;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition)
|
||||
throws NotSupportedException, SystemException {
|
||||
|
||||
int timeout = determineTimeout(definition);
|
||||
|
||||
// Apply transaction name (if any) to WebLogic transaction.
|
||||
if (this.weblogicUserTransactionAvailable && definition.getName() != null) {
|
||||
try {
|
||||
if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) {
|
||||
/*
|
||||
weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut;
|
||||
wut.begin(definition.getName(), timeout);
|
||||
*/
|
||||
Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized");
|
||||
this.beginWithNameAndTimeoutMethod.invoke(txObject.getUserTransaction(), definition.getName(), timeout);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut;
|
||||
wut.begin(definition.getName());
|
||||
*/
|
||||
Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized");
|
||||
this.beginWithNameMethod.invoke(txObject.getUserTransaction(), definition.getName());
|
||||
}
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new TransactionSystemException(
|
||||
"WebLogic's UserTransaction.begin() method failed", ex.getTargetException());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new TransactionSystemException(
|
||||
"Could not invoke WebLogic's UserTransaction.begin() method", ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No WebLogic UserTransaction available or no transaction name specified
|
||||
// -> standard JTA begin call.
|
||||
applyTimeout(txObject, timeout);
|
||||
txObject.getUserTransaction().begin();
|
||||
}
|
||||
|
||||
// Specify isolation level, if any, through corresponding WebLogic transaction property.
|
||||
if (this.weblogicTransactionManagerAvailable) {
|
||||
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
|
||||
try {
|
||||
Transaction tx = obtainTransactionManager().getTransaction();
|
||||
Integer isolationLevel = definition.getIsolationLevel();
|
||||
/*
|
||||
weblogic.transaction.Transaction wtx = (weblogic.transaction.Transaction) tx;
|
||||
wtx.setProperty(ISOLATION_LEVEL_KEY, isolationLevel);
|
||||
*/
|
||||
Assert.state(this.setPropertyMethod != null, "WebLogic JTA API not initialized");
|
||||
this.setPropertyMethod.invoke(tx, ISOLATION_LEVEL_KEY, isolationLevel);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new TransactionSystemException(
|
||||
"WebLogic's Transaction.setProperty(String, Serializable) method failed", ex.getTargetException());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new TransactionSystemException(
|
||||
"Could not invoke WebLogic's Transaction.setProperty(String, Serializable) method", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
applyIsolationLevel(txObject, definition.getIsolationLevel());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doJtaResume(@Nullable JtaTransactionObject txObject, Object suspendedTransaction)
|
||||
throws InvalidTransactionException, SystemException {
|
||||
|
||||
try {
|
||||
obtainTransactionManager().resume((Transaction) suspendedTransaction);
|
||||
}
|
||||
catch (InvalidTransactionException ex) {
|
||||
if (!this.weblogicTransactionManagerAvailable) {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Standard JTA resume threw InvalidTransactionException: " + ex.getMessage() +
|
||||
" - trying WebLogic JTA forceResume");
|
||||
}
|
||||
/*
|
||||
weblogic.transaction.TransactionManager wtm =
|
||||
(weblogic.transaction.TransactionManager) getTransactionManager();
|
||||
wtm.forceResume(suspendedTransaction);
|
||||
*/
|
||||
try {
|
||||
Assert.state(this.forceResumeMethod != null, "WebLogic JTA API not initialized");
|
||||
this.forceResumeMethod.invoke(getTransactionManager(), suspendedTransaction);
|
||||
}
|
||||
catch (InvocationTargetException ex2) {
|
||||
throw new TransactionSystemException(
|
||||
"WebLogic's TransactionManager.forceResume(Transaction) method failed", ex2.getTargetException());
|
||||
}
|
||||
catch (Exception ex2) {
|
||||
throw new TransactionSystemException(
|
||||
"Could not access WebLogic's TransactionManager.forceResume(Transaction) method", ex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction createTransaction(@Nullable String name, int timeout) throws NotSupportedException, SystemException {
|
||||
if (this.weblogicUserTransactionAvailable && name != null) {
|
||||
try {
|
||||
if (timeout >= 0) {
|
||||
Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized");
|
||||
this.beginWithNameAndTimeoutMethod.invoke(getUserTransaction(), name, timeout);
|
||||
}
|
||||
else {
|
||||
Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized");
|
||||
this.beginWithNameMethod.invoke(getUserTransaction(), name);
|
||||
}
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
if (ex.getTargetException() instanceof NotSupportedException) {
|
||||
throw (NotSupportedException) ex.getTargetException();
|
||||
}
|
||||
else if (ex.getTargetException() instanceof SystemException) {
|
||||
throw (SystemException) ex.getTargetException();
|
||||
}
|
||||
else if (ex.getTargetException() instanceof RuntimeException) {
|
||||
throw (RuntimeException) ex.getTargetException();
|
||||
}
|
||||
else {
|
||||
throw new SystemException(
|
||||
"WebLogic's begin() method failed with an unexpected error: " + ex.getTargetException());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new SystemException("Could not invoke WebLogic's UserTransaction.begin() method: " + ex);
|
||||
}
|
||||
return new ManagedTransactionAdapter(obtainTransactionManager());
|
||||
}
|
||||
|
||||
else {
|
||||
// No name specified - standard JTA is sufficient.
|
||||
return super.createTransaction(name, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -76,6 +76,8 @@ dependencies {
|
||||
testImplementation(testFixtures(project(":spring-beans")))
|
||||
testImplementation(testFixtures(project(":spring-context")))
|
||||
testImplementation(testFixtures(project(":spring-core")))
|
||||
testImplementation(testFixtures(project(":spring-messaging")))
|
||||
testImplementation(testFixtures(project(":spring-tx")))
|
||||
testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
|
||||
testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user