Compare commits

...

81 Commits

Author SHA1 Message Date
Spring Buildmaster 5d85e7f81e Release version 5.2.13.RELEASE 2021-02-16 11:39:32 +00:00
Juergen Hoeller 940f57d023 Document ASM version as 7.x (effectively 7.3 at present) 2021-02-16 00:15:50 +01:00
Rossen Stoyanchev 29799be474 Upgrade to Dysprosium-SR17
Closes gh-26549
2021-02-15 17:33:30 +00:00
Juergen Hoeller 67979c93c3 Upgrade to Hibernate ORM 5.4.28 and OpenPDF 1.3.25 2021-02-15 17:34:29 +01:00
Juergen Hoeller 4ae3ab14ec Avoid unnecessary wrapping for SqlParameterValue
Closes gh-26471
2021-02-15 11:49:59 +01:00
Juergen Hoeller 81f0d76f3d Upgrade to Apache Johnzon 1.2.10, Joda-Time 2.10.10, RxJava 2.2.21 2021-02-14 18:49:33 +01:00
Juergen Hoeller 9f1e822f3e Polishing 2021-02-14 18:48:49 +01:00
Juergen Hoeller b4baa86bfa Close mapping streams after the ValidatorFactory has been built
Closes gh-26418
2021-02-14 18:48:31 +01:00
Juergen Hoeller 81be4c22d0 Re-resolve cached arguments in case of NoSuchBeanDefinitionException
Closes gh-26517

(cherry picked from commit 99a1388bbd)
2021-02-14 18:17:06 +01:00
Juergen Hoeller a17c2ccb4e Preserve resolved destroy method name in RootBeanDefinition
Closes gh-26498

(cherry picked from commit 809813dd52)
2021-02-14 18:16:11 +01:00
Juergen Hoeller 64df93168d Consider non-initialized holders as equal to empty holders
Closes gh-26433

(cherry picked from commit d5e5dcb7e1)
2021-02-14 18:08:20 +01:00
Juergen Hoeller 27c5480c82 Fail early FactoryBean instantiation for LinkageError
Closes gh-26425

(cherry picked from commit defc2466b0)
2021-02-14 18:08:15 +01:00
Sam Brannen 594ec8f8a9 Update copyright date in reference manual 2021-02-10 17:57:23 +01:00
Arjen Poutsma c09b2513e0 Ensure StringDecoder supports multiline delimiters
This commit makes sure the StringDecoder supports stripping off
multi-line delimiters, such as \r\n. Specifically, we ensure that the
delimiter is stripped from the joined buffer.

Closes gh-26511
2021-02-08 15:59:02 +01:00
Sam Brannen 6c22f7ef5e Fix StoredProcedure documentation in reference manual
This commit aligns the documentation in the reference manual with the
actual source code for StoredProcedure with regard to public execute()
methods.

Closes gh-26505
2021-02-04 11:12:31 +01:00
Sam Brannen 236623f630 Do not retain partial column metadata in SimpleJdbcInsert
Prior to this commit, if an SQLException was thrown while retrieving
column metadata from the database, SimpleJdbcInsert would generate an
INSERT statement that was syntactically valid but missing columns,
which could lead to data silently missing in the database (for nullable
columns).

This commit fixes this by clearing all collected column metadata if an
SQLException is thrown while processing the metadata. The result is
that an InvalidDataAccessApiUsageException will be thrown later while
generating the INSERT statement. The exception message now contains an
additional hint to make use of SimpleJdbcInsert#usingColumns() in order
to ensure that all required columns are included in the generated
INSERT statement.

SimpleJdbcCall can also encounter an SQLException while retrieving
column metadata for a stored procedure/function, but an exception is
not thrown since a later invocation of the stored procedure/function
will likely fail anyway due to missing arguments. Consequently, this
commit only improves the warning level log message by including a hint
to make use of SimpleJdbcCall#addDeclaredParameter().

Closes gh-26486
2021-02-03 18:53:32 +01:00
Sam Brannen e18fba1626 Polishing 2021-02-03 18:52:15 +01:00
Sam Brannen 070c596a66 Use api-spring-framework attribute for links to SpringProperties
See gh-26492
2021-02-02 14:04:20 +01:00
Sam Brannen 7a9bf1578e Link to SpringProperties Javadoc from the Testing chapter
Closes gh-26492
2021-02-02 13:52:08 +01:00
Rossen Stoyanchev 8a150ee3a4 Fix order of headers in DefaultHandlerExceptionResolver
Closes gh-26470
2021-01-29 20:49:19 +00:00
Rossen Stoyanchev 6051f4ecbd Handle forwarded header parse issues
Closes gh-26459
2021-01-29 20:48:51 +00:00
Rossen Stoyanchev 698e74f7cd WebSocketExtension#equals matches sub-classes too
Closes gh-26449
2021-01-29 20:48:51 +00:00
Sam Brannen 51079a40ae Upgrade io.spring.ge.conventions plugin to version 0.0.7
This will hopefully allow the build to pass again on the CI server.
2021-01-27 18:09:13 +01:00
Sam Brannen 94ac2e4de7 Fix UriComponentsBuilder examples in ref docs
Closes gh-26453
2021-01-27 15:17:32 +01:00
Stephane Nicoll 271a9097ac Clarify behaviour of condition attribute of CachePut
Closes gh-26404
2021-01-19 15:15:38 +01:00
Juergen Hoeller 3c030edbf2 Upgrade to RxJava 2.2.20, OpenPDF 1.3.24, Hibernate ORM 5.4.27, Joda-Time 2.10.9, Apache Johnzon 1.2.9 2021-01-14 18:30:52 +01:00
Sam Brannen 059cff52b7 Remove obsolete commandName attribute in spring-form.tld
Since support for the commandName attribute was removed from the
implementation of FormTag in Spring Framework 5.0, the presence of the
commandName attribute in the spring-form.tld file is no longer valid
and can lead one to assume that the commandName attribute is still
supported -- for example when using code completion in a JSP editor.

This commit therefore removes the obsolete commandName attribute in
spring-form.tld.

Closes gh-26337
2021-01-03 17:50:15 +01:00
Sam Brannen 619a3edae5 Fix syntax in Kotlin example 2020-12-19 15:30:42 +01:00
Juergen Hoeller eb3811e590 Upgrade to Hibernate Validator 6.1.7, Caffeine 2.8.8, Joda-Time 2.10.8 2020-12-17 11:06:16 +01:00
Juergen Hoeller f011e58f90 Polishing 2020-12-17 11:05:58 +01:00
Juergen Hoeller 7325a863bb Consistent declarations and assertions in MockMultipartFile
See gh-26261

(cherry picked from commit fbd2ffdd23)
2020-12-17 10:50:47 +01:00
Brian Clozel fe26b7d3fd Fix NPE when calling NettyHeadersAdapter.add()
Prior to this commit, the `NettyHeadersAdapter` would directly delegate
the `add()` and `set()` calls to the adapted
`io.netty.handler.codec.http.HttpHeaders`. This implementation rejects
`null` values with exceptions.

This commit aligns the behavior here with other implementations, by not
rejecting null values but simply ignoring them.

Fixes gh-26277
(cherry-picked from commit 83c19cd60e)
2020-12-15 13:36:03 +01:00
Juergen Hoeller cfdceae70f Defensively handle loadClass null result in BeanUtils.findEditorByConvention
Closes gh-26252

(cherry picked from commit 2a47751fcd)
2020-12-10 16:26:47 +01:00
Spring Buildmaster 17647801aa Next Development Version 2020-12-09 06:34:43 +00:00
Rossen Stoyanchev 33476a2eae Backport fixes for discarding data buffers
Closes gh-26232
2020-12-08 22:00:28 +00:00
Brian Clozel a8091b916b Avoid closing Jackson JsonGenerator for error cases
Prior to this commit, a change introduced in gh-25910 would close the
`JsonGenerator` after it's been used for JSON serialization. This would
not only close it and recycle resources, but also flush the underlyning
buffer to the output.
In a case where the JSON serialization process would throw an exception,
the buffer would be still flushed to the response output. Before the
change introduced in gh-25910, the response body could be still empty at
that point and error handling could write an error body instead.

This commits only closes the `JsonGenerator` when serialization has been
successful.

Note that we're changing this in the spirit of backwards compatibility
in the 5.2.x line, but change this won't be merged forward on the 5.3.x
line, for several reasons:

* this behavior is not consistent. If the JSON output exceeds a
certain size, or if Jackson has been configured to flush after each
write, the response output might still contain an incomplete JSON
payload (just like before this change)

* this behavior is not consistent with the WebFlux and Messaging codecs,
which are flushing or closing the generator

* not closing the generator for error cases prevents resources from
being recycled as expected by Jackson

Fixes gh-26246
2020-12-08 20:50:26 +01:00
Juergen Hoeller 396fdf125f Remove duplicate "property" in PropertyCacheKey.toString()
Closes gh-26237
2020-12-08 15:03:33 +01:00
Stephane Nicoll 085825fbc0 Upgrade to Reactor Dysprosium-SR15
Closes gh-26175
2020-12-08 05:50:48 +01:00
Juergen Hoeller b0c6e5e322 Polishing
(cherry picked from commit c970c318f4)
2020-12-07 22:09:43 +01:00
Juergen Hoeller a7efa9659a Clarify intended advice execution behavior (includes related polishing)
Closes gh-26202

(cherry picked from commit 834032df1f)
2020-12-07 22:09:30 +01:00
Juergen Hoeller f7605eaf58 Upgrade to Hibernate ORM 5.4.25 and Checkstyle 8.38 2020-12-02 17:37:59 +01:00
Juergen Hoeller ce67b89861 Support for multi-threaded addConverter calls
Closes gh-26183

(cherry picked from commit 396fb0cd51)
2020-12-02 12:35:21 +01:00
Rossen Stoyanchev 736af46fc0 ContentCachingResponseWrapper skips contentLength for chunked responses
Closes gh-26182
2020-12-01 17:50:07 +00:00
Stephane Nicoll a495bd6679 Start building against Reactor Dysprosium-SR15 snapshots
See gh-26175
2020-11-30 07:23:19 +01:00
Juergen Hoeller 7c33c70742 Polishing 2020-11-27 21:58:55 +01:00
Juergen Hoeller 81c1b60f19 Register @Bean definitions as dependent on containing configuration class
Closes gh-26167
2020-11-27 21:57:55 +01:00
Rossen Stoyanchev 3703be5aaf MessageHeaderAccessor handle self-copy correctly
1. Revert changes in setHeader from 5.2.9 that caused regression on self-copy.
2. Update copyHeaders/IfAbsent to ensure a copy of native headers.
3. Exit if source and target are the same instance, as an optimization.

Closes gh-26155
2020-11-26 21:56:17 +00:00
Сергей Цыпанов b929edb221 Remove unused package-private class o.s.w.u.p.SubSequence
(cherry picked from commit 42216b77df)
2020-11-26 15:38:14 +01:00
Juergen Hoeller 50803ce142 Remove misleading default note on ISO.DATE_TIME
Closes gh-26134

(cherry picked from commit 86f9716fef)
2020-11-26 15:38:05 +01:00
Sam Brannen 346445ee7e Fix broken links to XSD schemas in ref docs
Closes gh-26129
2020-11-23 14:52:47 +01:00
Juergen Hoeller e238c8a87c Declare resolvedCharset as transient (restoring serializability)
Closes gh-26127
2020-11-20 18:57:30 +01:00
Juergen Hoeller 9e99fd5df2 Upgrade to Hibernate ORM 5.4.24
(cherry picked from commit 135682e073)
2020-11-19 16:03:03 +01:00
Juergen Hoeller 990c74b1e2 Upgrade to Apache HttpClient 4.5.13 (and consistent Caffeine declarations) 2020-11-19 15:19:29 +01:00
Rossen Stoyanchev 37bda566eb Allow "*" for Access-Control-Expose-Headers
Closes gh-26113
2020-11-19 09:48:41 +00:00
Juergen Hoeller 322babc04a Document that @Transactional does not propagate to new threads
Closes gh-25439
2020-11-17 16:16:24 +01:00
Juergen Hoeller 4e720e8104 Encode hash symbol in jar file path (for compatibility with JDK 11+)
Closes gh-26104

(cherry picked from commit b29723623b)
2020-11-17 16:11:07 +01:00
Juergen Hoeller fdab75a1d8 Upgrade to Reactor Dysprosium-SR14, OpenPDF 1.3.23, AssertJ 3.18.1, MockK 1.10.2, Checkstyle 8.37 2020-11-16 21:08:24 +01:00
Juergen Hoeller 240cfb1224 Polishing 2020-11-16 17:50:15 +01:00
Rossen Stoyanchev db2786264b UrlPathHelper.removeJsessionid correctly appends remainder
Closes gh-26079
2020-11-16 11:18:20 +00:00
Juergen Hoeller 763fa98bdf Early log entry for async EntityManagerFactory initialization failure
Closes gh-26093
2020-11-13 17:59:14 +01:00
Sam Brannen 25cbc263f0 Assert same instance returned for cached merged BeanDefinition 2020-11-12 14:58:58 +01:00
Juergen Hoeller 9949a91048 Fix javadoc and assertion glitches 2020-11-12 14:19:12 +01:00
Juergen Hoeller d8b7a593c5 Individually apply the SQL type from each SqlParameterSource argument
Closes gh-26071
2020-11-12 14:18:23 +01:00
Rossen Stoyanchev d3d8f1a487 LimitedDataBufferList adds or raises error
Closes gh-26060
2020-11-10 20:12:38 +00:00
Spring Buildmaster a637f6a27c Next Development Version 2020-11-10 07:09:28 +00:00
Juergen Hoeller e9416b369e Polishing 2020-11-09 18:23:45 +01:00
Juergen Hoeller cde95e1446 Polishing 2020-11-09 13:52:34 +01:00
Sam Brannen da4e37dc1d Do not create intermediate list in MergedAnnotationCollectors.toAnnotationSet()
Prior to this commit, MergedAnnotationCollectors.toAnnotationSet()
created an intermediate ArrayList for storing the results prior to
creating a LinkedHashSet in the finishing step.

Since the creation of the intermediate list is unnecessary, this commit
simplifies the implementation of toAnnotationSet() by using the
Collector.of() factory method that does not accept a `finisher` argument.
The resulting Collector internally uses a `castingIdentity()` function
as the `finisher`.

Closes gh-26031
2020-11-08 15:20:18 +01:00
Rossen Stoyanchev 010d0947c7 Refine logging in StompErrorHandler
Avoid a full stacktrace at ERROR level for a client message that could
not be sent to a MessageChannel.

See gh-26026
2020-11-05 21:51:16 +00:00
Juergen Hoeller 5b06c23a1b Consistent javadoc within SpelCompiler 2020-11-05 18:33:12 +01:00
Juergen Hoeller 7881329cf7 Polishing 2020-11-05 18:18:38 +01:00
Juergen Hoeller 58aa0659cc Suppress NotWritablePropertyException in case of ignoreUnknown=true
Closes gh-25986
2020-11-05 18:15:59 +01:00
Juergen Hoeller dbbedc6c86 Add FullyQualifiedAnnotationBeanNameGenerator.INSTANCE
Closes gh-26025
2020-11-05 18:15:29 +01:00
Juergen Hoeller 09c1e986b9 Upgrade to Hibernate ORM 5.4.23
(cherry picked from commit b815accca9)
2020-11-05 17:54:18 +01:00
Juergen Hoeller 10bff054a9 Reliably refresh metadata for dynamically changing prototype bean class
Closes gh-26019

(cherry picked from commit 412aa06d86)
2020-11-05 17:50:24 +01:00
Sam Brannen e713e0d6d5 Preserve registration order in @ActiveProfiles
With this commit, bean definition profiles declared via @ActiveProfiles
are once again stored in registration order, in order to support use
cases in Spring Boot and other frameworks that depend on the
registration order.

This effectively reverts the changes made in conjunction with gh-25973.

Closes gh-26004
2020-11-02 22:40:59 +01:00
Rossen Stoyanchev b4f8fc8177 Use static accessors in DefaultSimpUserRegistry
Closes gh-26010
2020-11-02 17:32:12 +00:00
Rossen Stoyanchev 8c3cdc6118 Remove unused import 2020-10-30 18:43:19 +00:00
Rossen Stoyanchev 01827fd8d2 Ensure response not closed by MappingJackson2HttpMessageConverter
Closes gh-25987
2020-10-30 18:42:11 +00:00
Stephane Nicoll af1d721aa3 Fix a broken Asciidoctor syntax in core-resources.adoc
Closes gh-26000
2020-10-30 09:33:36 +01:00
Arjen Poutsma 0acb1e5513 Copy default headers, cookies in WebClient builder
This commit makes copies of the default headers and cookies when a
WebClient is built, so that subsequent changes to these do not affect
previously built clients.

Closes: gh-25992
2020-10-29 13:36:26 +01:00
137 changed files with 2044 additions and 1113 deletions
+14 -14
View File
@@ -1,6 +1,6 @@
plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
id 'io.spring.gradle-enterprise-conventions' version '0.0.2'
id 'io.spring.ge.conventions' version '0.0.7'
id 'io.spring.nohttp' version '0.0.5.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
id 'org.jetbrains.dokka' version '0.10.1' apply false
@@ -8,9 +8,9 @@ plugins {
id 'org.asciidoctor.jvm.pdf' version '2.4.0'
id 'de.undercouch.download' version '4.1.1'
id "io.freefair.aspectj" version '4.1.6' apply false
id "com.github.ben-manes.versions" version '0.28.0'
id 'com.gradle.build-scan' version '3.2'
id "com.jfrog.artifactory" version '4.12.0' apply false
id "com.github.ben-manes.versions" version '0.24.0'
}
apply from: "$rootDir/gradle/build-scan-user-data.gradle"
@@ -29,7 +29,7 @@ configure(allprojects) { project ->
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.10.5"
mavenBom "io.netty:netty-bom:4.1.51.Final"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR13"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR17"
mavenBom "io.rsocket:rsocket-bom:1.0.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72"
@@ -61,7 +61,7 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.19"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "com.caucho:hessian:4.0.63"
@@ -76,7 +76,7 @@ configure(allprojects) { project ->
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
}
dependency "org.apache.johnzon:johnzon-jsonb:1.2.8"
dependency "org.apache.johnzon:johnzon-jsonb:1.2.10"
dependency("org.codehaus.jettison:jettison:1.3.8") {
exclude group: "stax", name: "stax-api"
}
@@ -88,8 +88,8 @@ configure(allprojects) { project ->
dependency "org.yaml:snakeyaml:1.27"
dependency "com.h2database:h2:1.4.200"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.6"
dependency "com.github.librepdf:openpdf:1.3.22"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
dependency "com.github.librepdf:openpdf:1.3.25"
dependency "com.rometools:rome:1.12.2"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.3"
@@ -116,8 +116,8 @@ configure(allprojects) { project ->
dependency "net.sf.ehcache:ehcache:2.10.6"
dependency "org.ehcache:jcache:1.0.1"
dependency "org.ehcache:ehcache:3.4.0"
dependency "org.hibernate:hibernate-core:5.4.22.Final"
dependency "org.hibernate:hibernate-validator:6.1.6.Final"
dependency "org.hibernate:hibernate-core:5.4.28.Final"
dependency "org.hibernate:hibernate-validator:6.1.7.Final"
dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
@@ -147,7 +147,7 @@ configure(allprojects) { project ->
entry 'okhttp'
entry 'mockwebserver'
}
dependency("org.apache.httpcomponents:httpclient:4.5.12") {
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.apache.httpcomponents:httpasyncclient:4.1.4") {
@@ -180,7 +180,7 @@ configure(allprojects) { project ->
dependency "org.testng:testng:6.14.3"
dependency "org.hamcrest:hamcrest:2.1"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.17.2"
dependency "org.assertj:assertj-core:3.18.1"
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
@@ -193,7 +193,7 @@ configure(allprojects) { project ->
}
entry 'mockito-junit-jupiter'
}
dependency "io.mockk:mockk:1.10.0"
dependency "io.mockk:mockk:1.10.2"
dependency("net.sourceforge.htmlunit:htmlunit:2.43.0") {
exclude group: "commons-logging", name: "commons-logging"
@@ -225,7 +225,7 @@ configure(allprojects) { project ->
dependency "com.ibm.websphere:uow:6.0.2.17"
dependency "com.jamonapi:jamon:2.82"
dependency "joda-time:joda-time:2.10.6"
dependency "joda-time:joda-time:2.10.10"
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7"
dependency "org.javamoney:moneta:1.3"
@@ -326,7 +326,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "8.36.2"
toolVersion = "8.38"
configDir = rootProject.file("src/checkstyle")
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.2.11.BUILD-SNAPSHOT
version=5.2.13.RELEASE
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
+1 -2
View File
@@ -29,12 +29,11 @@ eclipse.classpath.file.whenMerged { classpath ->
classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
}
// Use separate main/test outputs (prevents WTP from packaging test classes)
eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse")
eclipse.classpath.file.beforeMerged { classpath ->
classpath.entries.findAll{ it instanceof SourceFolder }.each {
if(it.output.startsWith("bin/")) {
if (it.output.startsWith("bin/")) {
it.output = null
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ include "spring-aop"
include "spring-aspects"
include "spring-beans"
include "spring-context"
include "spring-context-support"
include "spring-context-indexer"
include "spring-context-support"
include "spring-core"
include "kotlin-coroutines"
project(':kotlin-coroutines').projectDir = file('spring-core/kotlin-coroutines')
@@ -26,8 +26,8 @@ include "spring-oxm"
include "spring-test"
include "spring-tx"
include "spring-web"
include "spring-webmvc"
include "spring-webflux"
include "spring-webmvc"
include "spring-websocket"
include "framework-bom"
include "integration-tests"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -219,10 +219,12 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint,
@Override
@Nullable
public String[] getParameterNames() {
if (this.parameterNames == null) {
this.parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
String[] parameterNames = this.parameterNames;
if (parameterNames == null) {
parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
this.parameterNames = parameterNames;
}
return this.parameterNames;
return parameterNames;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 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.
@@ -59,7 +59,8 @@ public abstract aspect AbstractTransactionAspect extends TransactionAspectSuppor
@Override
public void destroy() {
clearTransactionManagerCache(); // An aspect is basically a singleton
// An aspect is basically a singleton -> cleanup on destruction
clearTransactionManagerCache();
}
@SuppressAjWarnings("adviceDidNotMatch")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -422,9 +422,12 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
return;
}
else {
throw createNotWritablePropertyException(tokens.canonicalName);
if (this.suppressNotWritablePropertyException) {
// Optimization for common ignoreUnknown=true scenario since the
// exception would be caught and swallowed higher up anyway...
return;
}
throw createNotWritablePropertyException(tokens.canonicalName);
}
Object oldValue = null;
@@ -806,7 +809,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
* @param propertyPath property path, which may be nested
* @return a property accessor for the target bean
*/
@SuppressWarnings("unchecked") // avoid nested generic
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
// Handle nested properties recursively.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -40,6 +40,8 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
private boolean autoGrowNestedPaths = false;
boolean suppressNotWritablePropertyException = false;
@Override
public void setExtractOldValueForEditor(boolean extractOldValueForEditor) {
@@ -89,30 +91,41 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
for (PropertyValue pv : propertyValues) {
try {
// This method may throw any BeansException, which won't be caught
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = true;
}
try {
for (PropertyValue pv : propertyValues) {
// setPropertyValue may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
setPropertyValue(pv);
}
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
try {
setPropertyValue(pv);
}
// Otherwise, just ignore it and continue...
}
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
// Otherwise, just ignore it and continue...
}
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
propertyAccessExceptions.add(ex);
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
}
propertyAccessExceptions.add(ex);
}
}
}
finally {
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = false;
}
}
@@ -506,6 +506,7 @@ public abstract class BeanUtils {
if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
return null;
}
ClassLoader cl = targetType.getClassLoader();
if (cl == null) {
try {
@@ -522,28 +523,34 @@ public abstract class BeanUtils {
return null;
}
}
String targetTypeName = targetType.getName();
String editorName = targetTypeName + "Editor";
try {
Class<?> editorClass = cl.loadClass(editorName);
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
if (logger.isInfoEnabled()) {
logger.info("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
if (editorClass != null) {
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
if (logger.isInfoEnabled()) {
logger.info("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
}
unknownEditorTypes.add(targetType);
return null;
}
unknownEditorTypes.add(targetType);
return null;
return (PropertyEditor) instantiateClass(editorClass);
}
return (PropertyEditor) instantiateClass(editorClass);
// Misbehaving ClassLoader returned null instead of ClassNotFoundException
// - fall back to unknown editor type registration below
}
catch (ClassNotFoundException ex) {
if (logger.isTraceEnabled()) {
logger.trace("No property editor [" + editorName + "] found for type " +
targetTypeName + " according to 'Editor' suffix convention");
}
unknownEditorTypes.add(targetType);
return null;
// Ignore - fall back to unknown editor type registration below
}
if (logger.isTraceEnabled()) {
logger.trace("No property editor [" + editorName + "] found for type " +
targetTypeName + " according to 'Editor' suffix convention");
}
unknownEditorTypes.add(targetType);
return null;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,8 +92,7 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
@Override
protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
PropertyMatches matches = PropertyMatches.forField(propertyName, getRootClass());
throw new NotWritablePropertyException(
getRootClass(), getNestedPath() + propertyName,
throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
matches.buildErrorMessage(), matches.getPossibleMatches());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,10 +41,10 @@ public class TypeMismatchException extends PropertyAccessException {
private String propertyName;
@Nullable
private transient Object value;
private final transient Object value;
@Nullable
private Class<?> requiredType;
private final Class<?> requiredType;
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -613,7 +613,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
private final boolean required;
private volatile boolean cached = false;
private volatile boolean cached;
@Nullable
private volatile Object cachedFieldValue;
@@ -628,46 +628,58 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
value = resolveFieldValue(field, bean, beanName);
}
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
if (value != null || this.required) {
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
}
return value;
}
}
@@ -678,7 +690,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
private final boolean required;
private volatile boolean cached = false;
private volatile boolean cached;
@Nullable
private volatile Object[] cachedMethodArguments;
@@ -696,58 +708,16 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
Method method = (Method) this.member;
Object[] arguments;
if (this.cached) {
// Shortcut for avoiding synchronization...
arguments = resolveCachedArguments(beanName);
try {
arguments = resolveCachedArguments(beanName);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
arguments = resolveMethodArguments(method, bean, beanName);
}
}
else {
int argumentCount = method.getParameterCount();
arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
}
else {
this.cachedMethodArguments = null;
}
this.cached = true;
}
}
arguments = resolveMethodArguments(method, bean, beanName);
}
if (arguments != null) {
try {
@@ -772,6 +742,59 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
}
return arguments;
}
@Nullable
private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) {
int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
}
else {
this.cachedMethodArguments = null;
}
this.cached = true;
}
}
return arguments;
}
}
@@ -26,9 +26,6 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -69,8 +66,6 @@ public class InjectionMetadata {
};
private static final Log logger = LogFactory.getLog(InjectionMetadata.class);
private final Class<?> targetClass;
private final Collection<InjectedElement> injectedElements;
@@ -110,9 +105,6 @@ public class InjectionMetadata {
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
}
}
}
this.checkedElements = checkedElements;
@@ -124,9 +116,6 @@ public class InjectionMetadata {
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
@@ -152,12 +141,12 @@ public class InjectionMetadata {
* Return an {@code InjectionMetadata} instance, possibly for empty elements.
* @param elements the elements to inject (possibly empty)
* @param clazz the target class
* @return a new {@link #InjectionMetadata(Class, Collection)} instance,
* or {@link #EMPTY} in case of no elements
* @return a new {@link #InjectionMetadata(Class, Collection)} instance
* @since 5.2
*/
public static InjectionMetadata forElements(Collection<InjectedElement> elements, Class<?> clazz) {
return (elements.isEmpty() ? InjectionMetadata.EMPTY : new InjectionMetadata(clazz, elements));
return (elements.isEmpty() ? new InjectionMetadata(clazz, Collections.emptyList()) :
new InjectionMetadata(clazz, elements));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1013,6 +1013,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
throw ex;
}
catch (BeanCreationException ex) {
// Don't swallow a linkage error since it contains a full stacktrace on
// first occurrence... and just a plain NoClassDefFoundError afterwards.
if (ex.contains(LinkageError.class)) {
throw ex;
}
// Instantiation failure, maybe too early...
if (logger.isDebugEnabled()) {
logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1194,8 +1194,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
this.primary == that.primary &&
this.nonPublicAccessAllowed == that.nonPublicAccessAllowed &&
this.lenientConstructorResolution == that.lenientConstructorResolution &&
ObjectUtils.nullSafeEquals(this.constructorArgumentValues, that.constructorArgumentValues) &&
ObjectUtils.nullSafeEquals(this.propertyValues, that.propertyValues) &&
equalsConstructorArgumentValues(that) &&
equalsPropertyValues(that) &&
ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) &&
ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) &&
ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) &&
@@ -1208,12 +1208,30 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
super.equals(other));
}
private boolean equalsConstructorArgumentValues(AbstractBeanDefinition other) {
if (!hasConstructorArgumentValues()) {
return !other.hasConstructorArgumentValues();
}
return ObjectUtils.nullSafeEquals(this.constructorArgumentValues, other.constructorArgumentValues);
}
private boolean equalsPropertyValues(AbstractBeanDefinition other) {
if (!hasPropertyValues()) {
return !other.hasPropertyValues();
}
return ObjectUtils.nullSafeEquals(this.propertyValues, other.propertyValues);
}
@Override
public int hashCode() {
int hashCode = ObjectUtils.nullSafeHashCode(getBeanClassName());
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.scope);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
if (hasConstructorArgumentValues()) {
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
}
if (hasPropertyValues()) {
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
}
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryBeanName);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryMethodName);
hashCode = 29 * hashCode + super.hashCode();
@@ -66,6 +66,7 @@ import org.springframework.util.StringUtils;
/**
* Delegate for resolving constructors and factory methods.
*
* <p>Performs constructor resolution through argument matching.
*
* @author Juergen Hoeller
@@ -84,7 +85,7 @@ class ConstructorResolver {
private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Marker for autowired arguments in a cached argument array, to be later replaced
* Marker for autowired arguments in a cached argument array, to be replaced
* by a {@linkplain #resolveAutowiredArgument resolved autowired argument}.
*/
private static final Object autowiredArgumentMarker = new Object();
@@ -148,7 +149,7 @@ class ConstructorResolver {
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
}
}
@@ -409,6 +410,7 @@ class ConstructorResolver {
if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
throw new ImplicitlyAppearedSingletonException();
}
this.beanFactory.registerDependentBean(factoryBeanName, beanName);
factoryClass = factoryBean.getClass();
isStatic = false;
}
@@ -443,7 +445,7 @@ class ConstructorResolver {
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true);
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
}
}
@@ -815,7 +817,7 @@ class ConstructorResolver {
* Resolve the prepared arguments stored in the given bean definition.
*/
private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
Executable executable, Object[] argsToResolve, boolean fallback) {
Executable executable, Object[] argsToResolve) {
TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
TypeConverter converter = (customConverter != null ? customConverter : bw);
@@ -828,7 +830,7 @@ class ConstructorResolver {
Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
if (argValue == autowiredArgumentMarker) {
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, fallback);
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true);
}
else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,7 +87,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private transient Method destroyMethod;
@Nullable
private List<DestructionAwareBeanPostProcessor> beanPostProcessors;
private final List<DestructionAwareBeanPostProcessor> beanPostProcessors;
/**
@@ -120,14 +120,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
else {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
if (destroyMethod.getParameterCount() > 0) {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
}
}
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod);
}
@@ -169,67 +171,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
/**
* If the current value of the given beanDefinition's "destroyMethodName" property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named "close" or
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
@Nullable
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
if (!(bean instanceof DisposableBean)) {
try {
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
return null;
}
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
/**
* Search for all DestructionAwareBeanPostProcessors in the List.
* @param processors the List to search
* @return the filtered List of DestructionAwareBeanPostProcessors
*/
@Nullable
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
if (!CollectionUtils.isEmpty(processors)) {
filteredPostProcessors = new ArrayList<>(processors.size());
for (BeanPostProcessor processor : processors) {
if (processor instanceof DestructionAwareBeanPostProcessor) {
DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
if (dabpp.requiresDestruction(bean)) {
filteredPostProcessors.add(dabpp);
}
}
}
}
return filteredPostProcessors;
}
@Override
public void run() {
destroy();
@@ -384,12 +325,50 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
return inferDestroyMethodIfNecessary(bean, beanDefinition) != null;
}
/**
* If the current value of the given beanDefinition's "destroyMethodName" property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named "close" or
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
@Nullable
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
destroyMethodName = null;
if (!(bean instanceof DisposableBean)) {
try {
destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
}
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
}
return StringUtils.hasLength(destroyMethodName);
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
/**
@@ -411,4 +390,26 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
return false;
}
/**
* Search for all DestructionAwareBeanPostProcessors in the List.
* @param processors the List to search
* @return the filtered List of DestructionAwareBeanPostProcessors
*/
@Nullable
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
if (!CollectionUtils.isEmpty(processors)) {
filteredPostProcessors = new ArrayList<>(processors.size());
for (BeanPostProcessor processor : processors) {
if (processor instanceof DestructionAwareBeanPostProcessor) {
DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
if (dabpp.requiresDestruction(bean)) {
filteredPostProcessors.add(dabpp);
}
}
}
}
return filteredPostProcessors;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
boolean allowCaching = true;
boolean isFactoryMethodUnique = false;
boolean isFactoryMethodUnique;
@Nullable
volatile ResolvableType targetType;
@@ -86,6 +86,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
@Nullable
volatile Method factoryMethodToIntrospect;
/** Package-visible field for caching a resolved destroy method name (also for inferred). */
@Nullable
volatile String resolvedDestroyMethodName;
/** Common lock for the four constructor fields below. */
final Object constructorArgumentLock = new Object();
@@ -63,9 +63,8 @@ public class BeanFactoryUtilsTests {
@BeforeEach
public void setUp() {
public void setup() {
// Interesting hierarchical factory to test counts.
// Slow to read so we cache it.
DefaultListableBeanFactory grandParent = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(grandParent).loadBeanDefinitions(ROOT_CONTEXT);
@@ -93,7 +92,7 @@ public class BeanFactoryUtilsTests {
* Check that override doesn't count as two separate beans.
*/
@Test
public void testHierarchicalCountBeansWithOverride() throws Exception {
public void testHierarchicalCountBeansWithOverride() {
// Leaf count
assertThat(this.listableBeanFactory.getBeanDefinitionCount() == 1).isTrue();
// Count minus duplicate
@@ -101,14 +100,14 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalNamesWithNoMatch() throws Exception {
public void testHierarchicalNamesWithNoMatch() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, NoOp.class));
assertThat(names.size()).isEqualTo(0);
}
@Test
public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception {
public void testHierarchicalNamesWithMatchOnlyInRoot() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, IndexedTestBean.class));
assertThat(names.size()).isEqualTo(1);
@@ -118,7 +117,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testGetBeanNamesForTypeWithOverride() throws Exception {
public void testGetBeanNamesForTypeWithOverride() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class));
// includes 2 TestBeans from FactoryBeans (DummyFactory definitions)
@@ -236,7 +235,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalResolutionWithOverride() throws Exception {
public void testHierarchicalResolutionWithOverride() {
Object test3 = this.listableBeanFactory.getBean("test3");
Object test = this.listableBeanFactory.getBean("test");
@@ -276,14 +275,14 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalNamesForAnnotationWithNoMatch() throws Exception {
public void testHierarchicalNamesForAnnotationWithNoMatch() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, Override.class));
assertThat(names.size()).isEqualTo(0);
}
@Test
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() throws Exception {
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class));
assertThat(names.size()).isEqualTo(1);
@@ -293,7 +292,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testGetBeanNamesForAnnotationWithOverride() throws Exception {
public void testGetBeanNamesForAnnotationWithOverride() {
AnnotatedBean annotatedBean = new AnnotatedBean();
this.listableBeanFactory.registerSingleton("anotherAnnotatedBean", annotatedBean);
List<String> names = Arrays.asList(
@@ -433,6 +432,7 @@ public class BeanFactoryUtilsTests {
String basePackage() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@ControllerAdvice
@interface RestControllerAdvice {
@@ -444,18 +444,23 @@ public class BeanFactoryUtilsTests {
String basePackage() default "";
}
@ControllerAdvice("com.example")
static class ControllerAdviceClass {
}
@RestControllerAdvice("com.example")
static class RestControllerAdviceClass {
}
static class TestBeanSmartFactoryBean implements SmartFactoryBean<TestBean> {
private final TestBean testBean = new TestBean("enigma", 42);
private final boolean singleton;
private final boolean prototype;
TestBeanSmartFactoryBean(boolean singleton, boolean prototype) {
@@ -478,7 +483,7 @@ public class BeanFactoryUtilsTests {
return TestBean.class;
}
public TestBean getObject() throws Exception {
public TestBean getObject() {
// We don't really care if the actual instance is a singleton or prototype
// for the tests that use this factory.
return this.testBean;
@@ -796,12 +796,13 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", childDefinition);
factory.registerAlias("parent", "alias");
TestBean child = (TestBean) factory.getBean("child");
TestBean child = factory.getBean("child", TestBean.class);
assertThat(child.getName()).isEqualTo(EXPECTED_NAME);
assertThat(child.getAge()).isEqualTo(EXPECTED_AGE);
Object mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
BeanDefinition mergedBeanDefinition1 = factory.getMergedBeanDefinition("child");
BeanDefinition mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
assertThat(mergedBeanDefinition2).as("Use cached merged bean definition").isEqualTo(mergedBeanDefinition2);
assertThat(mergedBeanDefinition1).as("Use cached merged bean definition").isSameAs(mergedBeanDefinition2);
}
@Test
@@ -1849,8 +1850,7 @@ class DefaultListableBeanFactoryTests {
assertThat(factoryBean).as("The FactoryBean should have been registered.").isNotNull();
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) lbf.autowire(FactoryBeanDependentBean.class,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
Object mergedBeanDefinition2 = bean.getFactoryBean();
assertThat(mergedBeanDefinition2).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(mergedBeanDefinition2);
assertThat(bean.getFactoryBean()).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(factoryBean);
}
@Test
@@ -2556,8 +2556,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
}
@Test
@@ -2571,8 +2570,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(2);
}
@Test
@@ -2587,8 +2585,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
}
@Test
@@ -2710,14 +2707,15 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", child);
AbstractBeanDefinition def = (AbstractBeanDefinition) factory.getBeanDefinition("child");
Object mergedBeanDefinition2 = def.getScope();
assertThat(mergedBeanDefinition2).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(mergedBeanDefinition2);
assertThat(def.getScope()).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(theChildScope);
}
@Test
void scopeInheritanceForChildBeanDefinitions() {
String theParentScope = "bonanza!";
RootBeanDefinition parent = new RootBeanDefinition();
parent.setScope("bonanza!");
parent.setScope(theParentScope);
AbstractBeanDefinition child = new ChildBeanDefinition("parent");
child.setBeanClass(TestBean.class);
@@ -2727,8 +2725,7 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", child);
BeanDefinition def = factory.getMergedBeanDefinition("child");
Object mergedBeanDefinition2 = def.getScope();
assertThat(mergedBeanDefinition2).as("Child 'scope' not inherited").isEqualTo(mergedBeanDefinition2);
assertThat(def.getScope()).as("Child 'scope' not inherited").isEqualTo(theParentScope);
}
@Test
@@ -2764,15 +2761,12 @@ class DefaultListableBeanFactoryTests {
});
lbf.preInstantiateSingletons();
TestBean tb = (TestBean) lbf.getBean("test");
Object mergedBeanDefinition2 = tb.getName();
assertThat(mergedBeanDefinition2).as("Name was set on field by IAPP").isEqualTo(mergedBeanDefinition2);
assertThat(tb.getName()).as("Name was set on field by IAPP").isEqualTo(nameSetOnField);
if (!skipPropertyPopulation) {
Object mergedBeanDefinition21 = tb.getAge();
assertThat(mergedBeanDefinition21).as("Property value still set").isEqualTo(mergedBeanDefinition21);
assertThat(tb.getAge()).as("Property value still set").isEqualTo(ageSetByPropertyValue);
}
else {
Object mergedBeanDefinition21 = tb.getAge();
assertThat(mergedBeanDefinition21).as("Property value was NOT set and still has default value").isEqualTo(mergedBeanDefinition21);
assertThat(tb.getAge()).as("Property value was NOT set and still has default value").isEqualTo(0);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -297,6 +297,121 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalResourceInjectionWithSingletonRemoval() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", rbd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
IndexedTestBean itb = new IndexedTestBean();
bf.registerSingleton("indexedTestBean", itb);
NestedTestBean ntb1 = new NestedTestBean();
bf.registerSingleton("nestedTestBean1", ntb1);
NestedTestBean ntb2 = new NestedTestBean();
bf.registerSingleton("nestedTestBean2", ntb2);
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.destroySingleton("testBean");
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bean.getTestBean4()).isNull();
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.registerSingleton("testBean", tb);
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalResourceInjectionWithBeanDefinitionRemoval() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", rbd);
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
IndexedTestBean itb = new IndexedTestBean();
bf.registerSingleton("indexedTestBean", itb);
NestedTestBean ntb1 = new NestedTestBean();
bf.registerSingleton("nestedTestBean1", ntb1);
NestedTestBean ntb2 = new NestedTestBean();
bf.registerSingleton("nestedTestBean2", ntb2);
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.removeBeanDefinition("testBean");
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bean.getTestBean4()).isNull();
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalCollectionResourceInjection() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalCollectionResourceInjectionBean.class);
@@ -533,6 +648,83 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithSingletonRemoval() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
NestedTestBean ntb = new NestedTestBean();
bf.registerSingleton("nestedTestBean", ntb);
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.destroySingleton("nestedTestBean");
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isNull();
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.registerSingleton("nestedTestBean", ntb);
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithBeanDefinitionRemoval() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.removeBeanDefinition("nestedTestBean");
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isNull();
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithNullFromFactoryBean() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,6 +133,16 @@ public class BeanDefinitionTests {
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
bd.getPropertyValues();
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
bd.getConstructorArgumentValues();
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor;
*
* <p>In contrast to the {@link Cacheable @Cacheable} annotation, this annotation
* does not cause the advised method to be skipped. Rather, it always causes the
* method to be invoked and its result to be stored in the associated cache. Note
* method to be invoked and its result to be stored in the associated cache if the
* {@link #condition()} and {@link #unless()} expressions match accordingly. Note
* that Java8's {@code Optional} return types are automatically handled and its
* content is stored in the cache if present.
*
@@ -118,10 +119,15 @@ public @interface CachePut {
/**
* Spring Expression Language (SpEL) expression used for making the cache
* put operation conditional.
* <p>This expression is evaluated after the method has been called due to the
* nature of the put operation and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning the method result is always cached.
* <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation. For
* supported wrappers such as {@code Optional}, {@code #result} refers to the actual
* object, not the wrapper</li>
* <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
* references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
@@ -136,8 +142,6 @@ public @interface CachePut {
/**
* Spring Expression Language (SpEL) expression used to veto the cache put operation.
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning that caching is never vetoed.
* <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
@@ -43,6 +43,15 @@ import org.springframework.util.Assert;
*/
public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
/**
* A convenient constant for a default {@code FullyQualifiedAnnotationBeanNameGenerator}
* instance, as used for configuration-level import purposes.
* @since 5.2.11
*/
public static final FullyQualifiedAnnotationBeanNameGenerator INSTANCE =
new FullyQualifiedAnnotationBeanNameGenerator();
@Override
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -106,7 +106,6 @@ public @interface DateTimeFormat {
/**
* The most common ISO DateTime Format {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX},
* e.g. "2000-10-31T01:30:00.000-05:00".
* <p>This is the default if no annotation value is specified.
*/
DATE_TIME,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
package org.springframework.validation.beanvalidation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -291,12 +293,17 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
configureParameterNameProvider(this.parameterNameDiscoverer, configuration);
}
List<InputStream> mappingStreams = null;
if (this.mappingLocations != null) {
mappingStreams = new ArrayList<>(this.mappingLocations.length);
for (Resource location : this.mappingLocations) {
try {
configuration.addMapping(location.getInputStream());
InputStream stream = location.getInputStream();
mappingStreams.add(stream);
configuration.addMapping(stream);
}
catch (IOException ex) {
closeMappingStreams(mappingStreams);
throw new IllegalStateException("Cannot read mapping resource: " + location);
}
}
@@ -307,8 +314,13 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
// Allow for custom post-processing before we actually build the ValidatorFactory.
postProcessConfiguration(configuration);
this.validatorFactory = configuration.buildValidatorFactory();
setTargetValidator(this.validatorFactory.getValidator());
try {
this.validatorFactory = configuration.buildValidatorFactory();
setTargetValidator(this.validatorFactory.getValidator());
}
finally {
closeMappingStreams(mappingStreams);
}
}
private void configureParameterNameProvider(ParameterNameDiscoverer discoverer, Configuration<?> configuration) {
@@ -329,6 +341,18 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
});
}
private void closeMappingStreams(@Nullable List<InputStream> mappingStreams){
if (!CollectionUtils.isEmpty(mappingStreams)) {
for (InputStream stream : mappingStreams) {
try {
stream.close();
}
catch (IOException ignored) {
}
}
}
}
/**
* Post-process the given Bean Validation configuration,
* adding to or overriding any of its settings.
@@ -397,7 +421,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return super.unwrap(type);
}
catch (ValidationException ex) {
// ignore - we'll try ValidatorFactory unwrapping next
// Ignore - we'll try ValidatorFactory unwrapping next
}
}
if (this.validatorFactory != null) {
@@ -405,7 +429,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return this.validatorFactory.unwrap(type);
}
catch (ValidationException ex) {
// ignore if just being asked for ValidatorFactory
// Ignore if just being asked for ValidatorFactory
if (ValidatorFactory.class == type) {
return (T) this.validatorFactory;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -179,7 +179,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
}
}
else {
// got no BindingResult - can only do standard rejectValue call
// Got no BindingResult - can only do standard rejectValue call
// with automatic extraction of the current field value
errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
}
@@ -386,7 +386,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
}
catch (ValidationException ex) {
// ignore if just being asked for plain Validator
// Ignore if just being asked for plain JSR-303 Validator
if (javax.validation.Validator.class == type) {
return (T) this.targetValidator;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,24 +36,26 @@ import static org.assertj.core.api.Assertions.assertThat;
public class AtAspectJAfterThrowingTests {
@Test
public void testAccessThrowable() throws Exception {
public void testAccessThrowable() {
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
ITestBean bean = (ITestBean) ctx.getBean("testBean");
ExceptionHandlingAspect aspect = (ExceptionHandlingAspect) ctx.getBean("aspect");
assertThat(AopUtils.isAopProxy(bean)).isTrue();
IOException exceptionThrown = null;
try {
bean.unreliableFileOperation();
}
catch (IOException e) {
//
catch (IOException ex) {
exceptionThrown = ex;
}
assertThat(aspect.handled).isEqualTo(1);
assertThat(aspect.lastException).isNotNull();
assertThat(aspect.lastException).isSameAs(exceptionThrown);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class AtAspectJAnnotationBindingTests {
private AnnotatedTestBean testBean;
private ClassPathXmlApplicationContext ctx;
@@ -70,8 +71,7 @@ public class AtAspectJAnnotationBindingTests {
class AtAspectJAnnotationBindingTestAspect {
@Around("execution(* *(..)) && @annotation(testAnn)")
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn)
throws Throwable {
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn) throws Throwable {
String annValue = testAnn.value();
Object result = pjp.proceed();
return (result instanceof String ? annValue + " " + result : result);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,23 @@
package org.springframework.context.annotation;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* @author Andy Wilkinson
* @author Liu Dongmiao
*/
public class AggressiveFactoryBeanInstantiationTests {
@@ -49,17 +58,66 @@ public class AggressiveFactoryBeanInstantiationTests {
}
}
@Test
public void checkLinkageError() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(BeanMethodConfigurationWithExceptionInInitializer.class);
context.refresh();
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(baos);
ex.printStackTrace(pw);
pw.flush();
String stackTrace = baos.toString();
assertThat(stackTrace.contains(".<clinit>")).isTrue();
assertThat(stackTrace.contains("java.lang.NoClassDefFoundError")).isFalse();
}
}
@Configuration
static class BeanMethodConfiguration {
@Bean
public String foo() {
return "foo";
}
@Bean
public AutowiredBean autowiredBean() {
return new AutowiredBean();
}
@Bean
@DependsOn("autowiredBean")
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
return new SimpleFactoryBean(applicationContext);
}
}
@Configuration
static class BeanMethodConfigurationWithExceptionInInitializer extends BeanMethodConfiguration {
@Bean
@DependsOn("autowiredBean")
@Override
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
new ExceptionInInitializer();
return new SimpleFactoryBean(applicationContext);
}
}
static class AutowiredBean {
@Autowired
String foo;
}
static class SimpleFactoryBean implements FactoryBean<Object> {
public SimpleFactoryBean(ApplicationContext applicationContext) {
@@ -76,4 +134,14 @@ public class AggressiveFactoryBeanInstantiationTests {
}
}
static class ExceptionInInitializer {
private static final int ERROR = callInClinit();
private static int callInClinit() {
throw new UnsupportedOperationException();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -105,7 +104,9 @@ public class ConfigurationClassPostProcessorTests {
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("foo"), "bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "foo")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "bar")).isTrue();
}
@Test
@@ -117,7 +118,9 @@ public class ConfigurationClassPostProcessorTests {
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("foo"), "bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "foo")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "bar")).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ public class EnableLoadTimeWeavingTests {
@Test
public void control() {
GenericXmlApplicationContext ctx =
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
ctx.getBean("loadTimeWeaver", LoadTimeWeaver.class);
}
@@ -73,9 +73,11 @@ public class EnableLoadTimeWeavingTests {
verify(loadTimeWeaver).addTransformer(isA(ClassFileTransformer.class));
}
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.DISABLED)
static class EnableLTWConfig_withAjWeavingDisabled implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
@@ -85,6 +87,7 @@ public class EnableLoadTimeWeavingTests {
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.AUTODETECT)
static class EnableLTWConfig_withAjWeavingAutodetect implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
@@ -94,9 +97,11 @@ public class EnableLoadTimeWeavingTests {
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
static class EnableLTWConfig_withAjWeavingEnabled implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
}
}
}
@@ -307,6 +307,23 @@ public class ConfigurationClassProcessingTests {
assertThat(tb.getLawyer()).isEqualTo(ctx.getBean(NestedTestBean.class));
}
@Test // gh-26019
public void autowiringWithDynamicPrototypeBeanClass() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
ConfigWithDynamicPrototype.class, PrototypeDependency.class);
PrototypeInterface p1 = ctx.getBean(PrototypeInterface.class, 1);
assertThat(p1).isInstanceOf(PrototypeOne.class);
assertThat(((PrototypeOne) p1).prototypeDependency).isNotNull();
PrototypeInterface p2 = ctx.getBean(PrototypeInterface.class, 2);
assertThat(p2).isInstanceOf(PrototypeTwo.class);
PrototypeInterface p3 = ctx.getBean(PrototypeInterface.class, 1);
assertThat(p3).isInstanceOf(PrototypeOne.class);
assertThat(((PrototypeOne) p3).prototypeDependency).isNotNull();
}
/**
* Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition}
@@ -632,4 +649,42 @@ public class ConfigurationClassProcessingTests {
}
}
static class PrototypeDependency {
}
interface PrototypeInterface {
}
static class PrototypeOne extends AbstractPrototype {
@Autowired
PrototypeDependency prototypeDependency;
}
static class PrototypeTwo extends AbstractPrototype {
// no autowired dependency here, in contrast to above
}
static class AbstractPrototype implements PrototypeInterface {
}
@Configuration
static class ConfigWithDynamicPrototype {
@Bean
@Scope(value = "prototype")
public PrototypeInterface getDemoBean( int i) {
switch ( i) {
case 1: return new PrototypeOne();
case 2:
default:
return new PrototypeTwo();
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,10 +66,10 @@ internal fun isSuspendingFunction(method: Method) = method.kotlinFunction!!.isSu
* @since 5.2
*/
@Suppress("UNCHECKED_CAST")
internal fun invokeSuspendingFunction(method: Method, bean: Any, vararg args: Any?): Publisher<*> {
internal fun invokeSuspendingFunction(method: Method, target: Any, vararg args: Any?): Publisher<*> {
val function = method.kotlinFunction!!
val mono = mono(Dispatchers.Unconfined) {
function.callSuspend(bean, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
}.onErrorMap(InvocationTargetException::class.java) { it.targetException }
return if (function.returnType.classifier == Flow::class) {
mono.flatMapMany { (it as Flow<Any>).asFlux() }
@@ -76,7 +76,7 @@ public abstract class AnnotationVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.av = annotationVisitor;
}
@@ -74,7 +74,7 @@ public abstract class ClassVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.cv = classVisitor;
}
@@ -72,7 +72,7 @@ public abstract class FieldVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.fv = fieldVisitor;
}
@@ -88,7 +88,7 @@ public abstract class MethodVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.mv = methodVisitor;
}
@@ -74,7 +74,7 @@ public abstract class ModuleVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.mv = moduleVisitor;
}
@@ -80,7 +80,7 @@ public abstract class RecordComponentVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.delegate = recordComponentVisitor;
}
@@ -1,6 +1,6 @@
/**
* Spring's repackaging of
* <a href="https://gitlab.ow2.org/asm/asm">ASM 7.0</a>
* <a href="https://gitlab.ow2.org/asm/asm">ASM 7.x</a>
* (with Spring-specific patches; for internal use only).
*
* <p>This repackaging technique avoids any potential conflicts with
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.IntFunction;
@@ -31,10 +31,11 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Collector implementations that provide various reduction operations for
* {@link Collector} implementations that provide various reduction operations for
* {@link MergedAnnotation} instances.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
*/
public abstract class MergedAnnotationCollectors {
@@ -52,13 +53,16 @@ public abstract class MergedAnnotationCollectors {
* Create a new {@link Collector} that accumulates merged annotations to a
* {@link LinkedHashSet} containing {@linkplain MergedAnnotation#synthesize()
* synthesized} versions.
* <p>The collector returned by this method is effectively equivalent to
* {@code Collectors.mapping(MergedAnnotation::synthesize, Collectors.toCollection(LinkedHashSet::new))}
* but avoids the creation of a composite collector.
* @param <A> the annotation type
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link Set}
*/
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Set<A>> toAnnotationSet() {
return Collector.of(ArrayList<A>::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::addAll, LinkedHashSet::new);
return Collector.of(LinkedHashSet::new, (set, annotation) -> set.add(annotation.synthesize()),
MergedAnnotationCollectors::combiner);
}
/**
@@ -90,14 +94,14 @@ public abstract class MergedAnnotationCollectors {
IntFunction<R[]> generator) {
return Collector.of(ArrayList::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::addAll, list -> list.toArray(generator.apply(list.size())));
MergedAnnotationCollectors::combiner, list -> list.toArray(generator.apply(list.size())));
}
/**
* Create a new {@link Collector} that accumulates merged annotations to an
* Create a new {@link Collector} that accumulates merged annotations to a
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
* @param <A> the annotation type
* @param adaptations the adaptations that should be applied to the annotation values
* @return a {@link Collector} which collects and synthesizes the
@@ -111,13 +115,13 @@ public abstract class MergedAnnotationCollectors {
}
/**
* Create a new {@link Collector} that accumulates merged annotations to an
* Create a new {@link Collector} that accumulates merged annotations to a
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
* @param <A> the annotation type
* @param adaptations the adaptations that should be applied to the annotation values
* @param finisher the finisher function for the new {@link MultiValueMap}
* @param adaptations the adaptations that should be applied to the annotation values
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link LinkedMultiValueMap}
* @see #toMultiValueMap(MergedAnnotation.Adapt...)
@@ -130,7 +134,7 @@ public abstract class MergedAnnotationCollectors {
IDENTITY_FINISH_CHARACTERISTICS : NO_CHARACTERISTICS);
return Collector.of(LinkedMultiValueMap::new,
(map, annotation) -> annotation.asMap(adaptations).forEach(map::add),
MergedAnnotationCollectors::merge, finisher, characteristics);
MergedAnnotationCollectors::combiner, finisher, characteristics);
}
@@ -138,13 +142,22 @@ public abstract class MergedAnnotationCollectors {
return instance == candidate;
}
private static <E, L extends List<E>> L addAll(L list, L additions) {
list.addAll(additions);
return list;
/**
* {@link Collector#combiner() Combiner} for collections.
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
*/
private static <E, C extends Collection<E>> C combiner(C collection, C additions) {
collection.addAll(additions);
return collection;
}
private static <K, V> MultiValueMap<K, V> merge(MultiValueMap<K, V> map,
MultiValueMap<K, V> additions) {
/**
* {@link Collector#combiner() Combiner} for multi-value maps.
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
*/
private static <K, V> MultiValueMap<K, V> combiner(MultiValueMap<K, V> map, MultiValueMap<K, V> additions) {
map.addAll(additions);
return map;
}
@@ -157,16 +157,20 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
int startIndex = buffer.readPosition();
int length = (endIndex - startIndex + 1);
DataBuffer slice = buffer.retainedSlice(startIndex, length);
if (this.stripDelimiter) {
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
}
result = (result != null ? result : new ArrayList<>());
if (chunks.isEmpty()) {
if (this.stripDelimiter) {
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
}
result.add(slice);
}
else {
chunks.add(slice);
result.add(buffer.factory().join(chunks));
DataBuffer joined = buffer.factory().join(chunks);
if (this.stripDelimiter) {
joined.writePosition(joined.writePosition() - matcher.delimiter().length);
}
result.add(joined);
chunks.clear();
}
buffer.readPosition(endIndex + 1);
@@ -19,14 +19,15 @@ package org.springframework.core.convert.support;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.ResolvableType;
@@ -499,9 +500,9 @@ public class GenericConversionService implements ConfigurableConversionService {
*/
private static class Converters {
private final Set<GenericConverter> globalConverters = new LinkedHashSet<>();
private final Set<GenericConverter> globalConverters = new CopyOnWriteArraySet<>();
private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<>(36);
private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);
public void add(GenericConverter converter) {
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
@@ -512,8 +513,7 @@ public class GenericConversionService implements ConfigurableConversionService {
}
else {
for (ConvertiblePair convertiblePair : convertibleTypes) {
ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
convertersForPair.add(converter);
getMatchableConverters(convertiblePair).add(converter);
}
}
}
@@ -651,7 +651,7 @@ public class GenericConversionService implements ConfigurableConversionService {
*/
private static class ConvertersForPair {
private final LinkedList<GenericConverter> converters = new LinkedList<>();
private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
public void add(GenericConverter converter) {
this.converters.addFirst(converter);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -54,11 +54,8 @@ public class LimitedDataBufferList extends ArrayList<DataBuffer> {
@Override
public boolean add(DataBuffer buffer) {
boolean result = super.add(buffer);
if (result) {
updateCount(buffer.readableByteCount());
}
return result;
updateCount(buffer.readableByteCount());
return super.add(buffer);
}
@Override
@@ -432,6 +432,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
filePath = StringUtils.capitalize(filePath);
}
// # can appear in directories/filenames, java.net.URL should not treat it as a fragment
filePath = StringUtils.replace(filePath, "#", "%23");
// Build URL that points to the root of the jar file
UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
ResourceUtils.FILE_URL_PREFIX + filePath + ResourceUtils.JAR_URL_SEPARATOR);
// Potentially overlapping with URLClassLoader.getURLs() result above!
@@ -16,6 +16,8 @@
package org.springframework.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.BitSet;
@@ -104,7 +106,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
private final Map<String, String> parameters;
@Nullable
private Charset resolvedCharset;
private transient Charset resolvedCharset;
@Nullable
private volatile String toStringValue;
@@ -184,9 +186,9 @@ public class MimeType implements Comparable<MimeType>, Serializable {
this.subtype = subtype.toLowerCase(Locale.ENGLISH);
if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH);
parameters.forEach((attribute, value) -> {
checkParameters(attribute, value);
map.put(attribute, value);
parameters.forEach((parameter, value) -> {
checkParameters(parameter, value);
map.put(parameter, value);
});
this.parameters = Collections.unmodifiableMap(map);
}
@@ -210,11 +212,11 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
}
protected void checkParameters(String attribute, String value) {
Assert.hasLength(attribute, "'attribute' must not be empty");
protected void checkParameters(String parameter, String value) {
Assert.hasLength(parameter, "'parameter' must not be empty");
Assert.hasLength(value, "'value' must not be empty");
checkToken(attribute);
if (PARAM_CHARSET.equals(attribute)) {
checkToken(parameter);
if (PARAM_CHARSET.equals(parameter)) {
if (this.resolvedCharset == null) {
this.resolvedCharset = Charset.forName(unquote(value));
}
@@ -569,6 +571,17 @@ public class MimeType implements Comparable<MimeType>, Serializable {
return 0;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization, just initialize state after deserialization.
ois.defaultReadObject();
// Initialize transient fields.
String charsetName = getParameter(PARAM_CHARSET);
if (charsetName != null) {
this.resolvedCharset = Charset.forName(unquote(charsetName));
}
}
/**
* Parse the given String value into a {@code MimeType} object,
@@ -125,10 +125,10 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
);
testDecode(input, String.class, step -> step
.expectNext("")
.expectNext("").as("1st")
.expectNext("abc")
.expectNext("defghi")
.expectNext("")
.expectNext("").as("2nd")
.expectNext("jklmno")
.expectNext("pqr")
.expectNext("stuvwxyz")
@@ -136,6 +136,21 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
.verify());
}
@Test
void decodeNewlinesAcrossBuffers() {
Flux<DataBuffer> input = Flux.just(
stringBuffer("\r"),
stringBuffer("\n"),
stringBuffer("xyz")
);
testDecode(input, String.class, step -> step
.expectNext("")
.expectNext("xyz")
.expectComplete()
.verify());
}
@Test
void maxInMemoryLimit() {
Flux<DataBuffer> input = Flux.just(
@@ -35,6 +35,8 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import org.junit.jupiter.api.Test;
import org.mockito.stubbing.Answer;
import org.reactivestreams.Subscription;
import reactor.core.publisher.BaseSubscriber;
@@ -834,6 +836,22 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
.verifyError(DataBufferLimitException.class);
}
@Test // gh-26060
void joinWithLimitDoesNotOverRelease() {
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
byte[] bytes = "foo-bar-baz".getBytes(StandardCharsets.UTF_8);
NettyDataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
buffer.getNativeBuffer().retain(); // should be at 2 now
buffer.write(bytes);
Mono<DataBuffer> result = DataBufferUtils.join(Flux.just(buffer), 8);
StepVerifier.create(result).verifyError(DataBufferLimitException.class);
assertThat(buffer.getNativeBuffer().refCnt()).isEqualTo(1);
buffer.release();
}
@ParameterizedDataBufferAllocatingTest
void joinErrors(String displayName, DataBufferFactory bufferFactory) {
super.bufferFactory = bufferFactory;
@@ -17,9 +17,11 @@ package org.springframework.core.io.buffer;
import java.nio.charset.StandardCharsets;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Unit tests for {@link LimitedDataBufferList}.
* @author Rossen Stoyanchev
@@ -32,8 +34,10 @@ public class LimitedDataBufferListTests {
@Test
void limitEnforced() {
Assertions.assertThatThrownBy(() -> new LimitedDataBufferList(5).add(toDataBuffer("123456")))
.isInstanceOf(DataBufferLimitException.class);
LimitedDataBufferList list = new LimitedDataBufferList(5);
assertThatThrownBy(() -> list.add(toDataBuffer("123456"))).isInstanceOf(DataBufferLimitException.class);
assertThat(list).isEmpty();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
@@ -267,13 +268,13 @@ class MimeTypeTests {
assertThat(mimeType.getParameter("attr")).isEqualTo("'v>alue'");
}
@Test // SPR-16630
@Test // SPR-16630
void parseMimeTypeWithSpacesAroundEquals() {
MimeType mimeType = MimeTypeUtils.parseMimeType("multipart/x-mixed-replace;boundary = --myboundary");
assertThat(mimeType.getParameter("boundary")).isEqualTo("--myboundary");
}
@Test // SPR-16630
@Test // SPR-16630
void parseMimeTypeWithSpacesAroundEqualsAndQuotedValue() {
MimeType mimeType = MimeTypeUtils.parseMimeType("text/plain; foo = \" bar \" ");
assertThat(mimeType.getParameter("foo")).isEqualTo("\" bar \"");
@@ -303,14 +304,14 @@ class MimeTypeTests {
assertThat(mimeTypes.size()).as("Invalid amount of mime types").isEqualTo(0);
}
@Test // gh-23241
@Test // gh-23241
void parseMimeTypesWithTrailingComma() {
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes("text/plain, text/html,");
assertThat(mimeTypes).as("No mime types returned").isNotNull();
assertThat(mimeTypes.size()).as("Incorrect number of mime types").isEqualTo(2);
}
@Test // SPR-17459
@Test // SPR-17459
void parseMimeTypesWithQuotedParameters() {
testWithQuotedParameters("foo/bar;param=\",\"");
testWithQuotedParameters("foo/bar;param=\"s,a,\"");
@@ -323,8 +324,9 @@ class MimeTypeTests {
private void testWithQuotedParameters(String... mimeTypes) {
String s = String.join(",", mimeTypes);
List<MimeType> actual = MimeTypeUtils.parseMimeTypes(s);
assertThat(actual.size()).isEqualTo(mimeTypes.length);
for (int i=0; i < mimeTypes.length; i++) {
for (int i = 0; i < mimeTypes.length; i++) {
assertThat(actual.get(i).toString()).isEqualTo(mimeTypes[i]);
}
}
@@ -351,6 +353,7 @@ class MimeTypeTests {
List<MimeType> result = new ArrayList<>(expected);
Random rnd = new Random();
// shuffle & sort 10 times
for (int i = 0; i < 10; i++) {
Collections.shuffle(result, rnd);
@@ -380,11 +383,7 @@ class MimeTypeTests {
assertThat(m2.compareTo(m1) != 0).as("Invalid comparison result").isTrue();
}
/**
* SPR-13157
* @since 4.2
*/
@Test
@Test // SPR-13157
void equalsIsCaseInsensitiveForCharsets() {
MimeType m1 = new MimeType("text", "plain", singletonMap("charset", "UTF-8"));
MimeType m2 = new MimeType("text", "plain", singletonMap("charset", "utf-8"));
@@ -394,4 +393,12 @@ class MimeTypeTests {
assertThat(m2.compareTo(m1)).isEqualTo(0);
}
@Test // gh-26127
void serialize() throws Exception {
MimeType original = new MimeType("text", "plain", StandardCharsets.UTF_8);
MimeType deserialized = (MimeType) SerializationTestUtils.serializeAndDeserialize(original);
assertThat(deserialized).isEqualTo(original);
assertThat(original).isEqualTo(deserialized);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -54,10 +54,9 @@ public abstract class AstUtils {
}
else {
if (targetType != null) {
int pos = 0;
for (Class<?> clazz : targets) {
if (clazz == targetType) { // put exact matches on the front to be tried first?
specificAccessors.add(pos++, resolver);
specificAccessors.add(resolver);
}
else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the
// specificAccessor list
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -63,18 +63,20 @@ import org.springframework.util.StringUtils;
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 4.1
*/
public final class SpelCompiler implements Opcodes {
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
private static final int CLASSES_DEFINED_LIMIT = 100;
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
// A compiler is created for each classloader, it manages a child class loader of that
// classloader and the child is used to load the compiled expressions.
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
// The child ClassLoader used to load the compiled expression classes
private ChildClassLoader ccl;
@@ -90,7 +92,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Attempt compilation of the supplied expression. A check is made to see
* if it is compilable before compilation proceeds. The check involves
* visiting all the nodes in the expression Ast and ensuring enough state
* visiting all the nodes in the expression AST and ensuring enough state
* is known about them that bytecode can be generated for them.
* @param expression the expression to compile
* @return an instance of the class implementing the compiled expression,
@@ -125,7 +127,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Generate the class that encapsulates the compiled expression and define it.
* The generated class will be a subtype of CompiledExpression.
* The generated class will be a subtype of CompiledExpression.
* @param expressionToCompile the expression to be compiled
* @return the expression call, or {@code null} if the decision was to opt out of
* compilation during code generation
@@ -150,7 +152,7 @@ public final class SpelCompiler implements Opcodes {
// Create getValue() method
mv = cw.visitMethod(ACC_PUBLIC, "getValue",
"(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
new String[ ]{"org/springframework/expression/EvaluationException"});
new String[] {"org/springframework/expression/EvaluationException"});
mv.visitCode();
CodeFlow cf = new CodeFlow(className, cw);
@@ -187,7 +189,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Load a compiled expression class. Makes sure the classloaders aren't used too much
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* continually recompiling over time then by replacing the classloader periodically
* at least some of the older variants can be garbage collected.
* @param name the name of the class
@@ -202,6 +204,7 @@ public final class SpelCompiler implements Opcodes {
return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
}
/**
* Factory method for compiler instances. The returned SpelCompiler will
* attach a class loader as the child of the given class loader and this
@@ -222,10 +225,12 @@ public final class SpelCompiler implements Opcodes {
}
/**
* Request that an attempt is made to compile the specified expression. It may fail if
* components of the expression are not suitable for compilation or the data types
* involved are not suitable for compilation. Used for testing.
* @return true if the expression was successfully compiled
* Request that an attempt is made to compile the specified expression.
* It may fail if components of the expression are not suitable for compilation
* or the data types involved are not suitable for compilation. Used for testing.
* @param expression the expression to compile
* @return {@code true} if the expression was successfully compiled,
* {@code false} otherwise
*/
public static boolean compile(Expression expression) {
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
@@ -256,18 +261,21 @@ public final class SpelCompiler implements Opcodes {
super(NO_URLS, classLoader);
}
int getClassesDefinedCount() {
return this.classesDefinedCount;
}
public Class<?> defineClass(String name, byte[] bytes) {
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
this.classesDefinedCount++;
return clazz;
}
public int getClassesDefinedCount() {
return this.classesDefinedCount;
}
}
/**
* An ASM ClassWriter extension bound to the SpelCompiler's ClassLoader.
*/
private class ExpressionClassWriter extends ClassWriter {
public ExpressionClassWriter() {
@@ -47,7 +47,7 @@ import org.springframework.util.StringUtils;
/**
* A powerful {@link PropertyAccessor} that uses reflection to access properties
* for reading and possibly also for writing.
* for reading and possibly also for writing on a target instance.
*
* <p>A property can be referenced through a public getter method (when being read)
* or a public setter method (when being written), and also as a public field.
@@ -98,8 +98,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
/**
* Create a new property accessor for reading and possibly writing.
* @param allowWrite whether to also allow for write operations
* Create a new property accessor for reading and possibly also writing.
* @param allowWrite whether to allow write operations on a target instance
* @since 4.3.15
* @see #canWrite
*/
@@ -623,8 +623,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
@Override
public String toString() {
return "CacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property + ", " +
this.property + ", targetIsClass=" + this.targetIsClass + "]";
return "PropertyCacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property +
", targetIsClass=" + this.targetIsClass + "]";
}
@Override
@@ -76,7 +76,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
public class SpelReproTests extends AbstractExpressionTests {
@Test
public void NPE_SPR5661() {
evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,10 @@
package org.springframework.expression.spel.testresources;
///CLOVER:OFF
public class Person {
private String privateName;
Company company;
public Person(String name) {
@@ -41,4 +42,5 @@ public class Person {
public Company getCompany() {
return company;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,19 +19,25 @@ package org.springframework.expression.spel.testresources;
import java.util.List;
public class TestAddress{
private String street;
private List<String> crossStreets;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public List<String> getCrossStreets() {
return crossStreets;
}
public void setCrossStreets(List<String> crossStreets) {
this.crossStreets = crossStreets;
}
private String street;
private List<String> crossStreets;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public List<String> getCrossStreets() {
return crossStreets;
}
public void setCrossStreets(List<String> crossStreets) {
this.crossStreets = crossStreets;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,19 +17,25 @@
package org.springframework.expression.spel.testresources;
public class TestPerson {
private String name;
private TestAddress address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TestAddress getAddress() {
return address;
}
public void setAddress(TestAddress address) {
this.address = address;
}
private String name;
private TestAddress address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TestAddress getAddress() {
return address;
}
public void setAddress(TestAddress address) {
this.address = address;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -30,7 +30,6 @@ import java.util.Set;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Helper class that efficiently creates multiple {@link PreparedStatementCreator}
@@ -200,9 +199,8 @@ public class PreparedStatementCreatorFactory {
public PreparedStatementCreatorImpl(String actualSql, List<?> parameters) {
this.actualSql = actualSql;
Assert.notNull(parameters, "Parameters List must not be null");
this.parameters = parameters;
if (this.parameters.size() != declaredParameters.size()) {
if (parameters.size() != declaredParameters.size()) {
// Account for named parameters being used multiple times
Set<String> names = new HashSet<>();
for (int i = 0; i < parameters.size(); i++) {
@@ -637,21 +637,23 @@ public class CallMetaDataContext {
schemaNameToUse = this.metaDataProvider.schemaNameToUse(getSchemaName());
}
String procedureNameToUse = this.metaDataProvider.procedureNameToUse(getProcedureName());
if (isFunction() || isReturnValueRequired()) {
callString = new StringBuilder().append("{? = call ").
append(StringUtils.hasLength(catalogNameToUse) ? catalogNameToUse + "." : "").
append(StringUtils.hasLength(schemaNameToUse) ? schemaNameToUse + "." : "").
append(procedureNameToUse).append("(");
callString = new StringBuilder("{? = call ");
parameterCount = -1;
}
else {
callString = new StringBuilder().append("{call ").
append(StringUtils.hasLength(catalogNameToUse) ? catalogNameToUse + "." : "").
append(StringUtils.hasLength(schemaNameToUse) ? schemaNameToUse + "." : "").
append(procedureNameToUse).append("(");
callString = new StringBuilder("{call ");
}
if (StringUtils.hasLength(catalogNameToUse)) {
callString.append(catalogNameToUse).append(".");
}
if (StringUtils.hasLength(schemaNameToUse)) {
callString.append(schemaNameToUse).append(".");
}
callString.append(this.metaDataProvider.procedureNameToUse(getProcedureName()));
callString.append("(");
for (SqlParameter parameter : this.callParameters) {
if (!parameter.isResultsParameter()) {
if (parameterCount > 0) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
*/
public class GenericCallMetaDataProvider implements CallMetaDataProvider {
@@ -414,8 +415,15 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
}
catch (SQLException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Error while retrieving meta-data for procedure columns: " + ex);
logger.warn("Error while retrieving meta-data for procedure columns. " +
"Consider declaring explicit parameters -- for example, via SimpleJdbcCall#addDeclaredParameter().",
ex);
}
// Although we could invoke `this.callParameterMetaData.clear()` so that
// we don't retain a partial list of column names (like we do in
// GenericTableMetaDataProvider.processTableColumns(...)), we choose
// not to do that here, since invocation of the stored procedure will
// likely fail anyway with an incorrect argument list.
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import org.springframework.lang.Nullable;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
*/
public class GenericTableMetaDataProvider implements TableMetaDataProvider {
@@ -422,8 +423,12 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
}
catch (SQLException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Error while retrieving meta-data for table columns: " + ex.getMessage());
logger.warn("Error while retrieving meta-data for table columns. " +
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().",
ex);
}
// Clear the metadata so that we don't retain a partial list of column names
this.tableParameterMetaData.clear();
}
finally {
JdbcUtils.closeResultSet(tableColumns);
@@ -432,7 +437,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
/**
* Inner class representing table meta-data.
* Class representing table meta-data.
*/
private static class TableMetaData {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@ import org.springframework.util.Assert;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
*/
public class TableMetaDataContext {
@@ -302,8 +303,12 @@ public class TableMetaDataContext {
}
}
else {
throw new InvalidDataAccessApiUsageException("Unable to locate columns for table '" +
getTableName() + "' so an insert statement can't be generated");
String message = "Unable to locate columns for table '" + getTableName()
+ "' so an insert statement can't be generated.";
if (isAccessTableColumnMetaData()) {
message += " Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().";
}
throw new InvalidDataAccessApiUsageException(message);
}
}
String params = String.join(", ", Collections.nCopies(columnCount, "?"));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,6 @@ public final class TableMetaDataProviderFactory {
try {
return JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();
TableMetaDataProvider provider;
if ("Oracle".equals(databaseProductName)) {
@@ -70,15 +69,17 @@ public final class TableMetaDataProviderFactory {
else {
provider = new GenericTableMetaDataProvider(databaseMetaData);
}
if (logger.isDebugEnabled()) {
logger.debug("Using " + provider.getClass().getSimpleName());
}
provider.initializeWithMetaData(databaseMetaData);
if (accessTableColumnMetaData) {
if (context.isAccessTableColumnMetaData()) {
provider.initializeWithTableColumnMetaData(databaseMetaData,
context.getCatalogName(), context.getSchemaName(), context.getTableName());
}
return provider;
});
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Yanming Zhou
* @since 2.0
*/
public abstract class NamedParameterUtils {
@@ -83,7 +84,7 @@ public abstract class NamedParameterUtils {
Assert.notNull(sql, "SQL must not be null");
Set<String> namedParameters = new HashSet<>();
String sqlToUse = sql;
StringBuilder sqlToUse = new StringBuilder(sql);
List<ParameterHolder> parameterList = new ArrayList<>();
char[] statement = sql.toCharArray();
@@ -155,7 +156,7 @@ public abstract class NamedParameterUtils {
int j = i + 1;
if (j < statement.length && statement[j] == ':') {
// escaped ":" should be skipped
sqlToUse = sqlToUse.substring(0, i - escapes) + sqlToUse.substring(i - escapes + 1);
sqlToUse.deleteCharAt(i - escapes);
escapes++;
i = i + 2;
continue;
@@ -174,7 +175,7 @@ public abstract class NamedParameterUtils {
}
i++;
}
ParsedSql parsedSql = new ParsedSql(sqlToUse);
ParsedSql parsedSql = new ParsedSql(sqlToUse.toString());
for (ParameterHolder ph : parameterList) {
parsedSql.addNamedParameter(ph.getParameterName(), ph.getStartIndex(), ph.getEndIndex());
}
@@ -345,9 +346,15 @@ public abstract class NamedParameterUtils {
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
try {
Object value = paramSource.getValue(paramName);
SqlParameter param = findParameter(declaredParams, paramName, i);
paramArray[i] = (param != null ? new SqlParameterValue(param, value) : value);
Object paramValue = paramSource.getValue(paramName);
if (paramValue instanceof SqlParameterValue) {
paramArray[i] = paramValue;
}
else {
paramArray[i] = (param != null ? new SqlParameterValue(param, paramValue) :
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
}
}
catch (IllegalArgumentException ex) {
throw new InvalidDataAccessApiUsageException(
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -44,7 +44,6 @@ public abstract class SqlParameterSourceUtils {
* @see BeanPropertySqlParameterSource
* @see NamedParameterJdbcTemplate#batchUpdate(String, SqlParameterSource[])
*/
@SuppressWarnings("unchecked")
public static SqlParameterSource[] createBatch(Object... candidates) {
return createBatch(Arrays.asList(candidates));
}
@@ -93,17 +92,13 @@ public abstract class SqlParameterSourceUtils {
* @param source the source of parameter values and type information
* @param parameterName the name of the parameter
* @return the value object
* @see SqlParameterValue
*/
@Nullable
public static Object getTypedValue(SqlParameterSource source, String parameterName) {
int sqlType = source.getSqlType(parameterName);
if (sqlType != SqlParameterSource.TYPE_UNKNOWN) {
if (source.getTypeName(parameterName) != null) {
return new SqlParameterValue(sqlType, source.getTypeName(parameterName), source.getValue(parameterName));
}
else {
return new SqlParameterValue(sqlType, source.getValue(parameterName));
}
return new SqlParameterValue(sqlType, source.getTypeName(parameterName), source.getValue(parameterName));
}
else {
return source.getValue(parameterName);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,7 +70,7 @@ public abstract class AbstractJdbcCall {
* Has this operation been compiled? Compilation means at least checking
* that a DataSource or JdbcTemplate has been provided.
*/
private volatile boolean compiled = false;
private volatile boolean compiled;
/** The generated string used for call statement. */
@Nullable
@@ -433,7 +433,7 @@ public abstract class AbstractJdbcCall {
/**
* Match the provided in parameter values with registered parameters and
* parameters defined via meta-data processing.
* @param parameterSource the parameter vakues provided as a {@link SqlParameterSource}
* @param parameterSource the parameter values provided as a {@link SqlParameterSource}
* @return a Map with parameter names and values
*/
protected Map<String, Object> matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
@@ -70,7 +70,7 @@ public abstract class AbstractJdbcInsert {
/** Context used to retrieve and manage database meta-data. */
private final TableMetaDataContext tableMetaDataContext = new TableMetaDataContext();
/** List of columns objects to be used in insert statement. */
/** List of column names to be used in insert statement. */
private final List<String> declaredColumns = new ArrayList<>();
/** The names of the columns holding the generated key. */
@@ -526,10 +526,11 @@ public class NamedParameterJdbcTemplateTests {
@Test
public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception {
SqlParameterSource[] ids = new SqlParameterSource[2];
ids[0] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC);
ids[1] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC);
final int[] rowsAffected = new int[] {1, 2};
SqlParameterSource[] ids = new SqlParameterSource[3];
ids[0] = new MapSqlParameterSource().addValue("id", null, Types.NULL);
ids[1] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC);
ids[2] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC);
final int[] rowsAffected = new int[] {1, 2, 3};
given(preparedStatement.executeBatch()).willReturn(rowsAffected);
given(connection.getMetaData()).willReturn(databaseMetaData);
@@ -537,13 +538,15 @@ public class NamedParameterJdbcTemplateTests {
int[] actualRowsAffected = namedParameterTemplate.batchUpdate(
"UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids);
assertThat(actualRowsAffected.length == 2).as("executed 2 updates").isTrue();
assertThat(actualRowsAffected.length == 3).as("executed 3 updates").isTrue();
assertThat(actualRowsAffected[0]).isEqualTo(rowsAffected[0]);
assertThat(actualRowsAffected[1]).isEqualTo(rowsAffected[1]);
assertThat(actualRowsAffected[2]).isEqualTo(rowsAffected[2]);
verify(connection).prepareStatement("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?");
verify(preparedStatement).setNull(1, Types.NULL);
verify(preparedStatement).setObject(1, 100, Types.NUMERIC);
verify(preparedStatement).setObject(1, 200, Types.NUMERIC);
verify(preparedStatement, times(2)).addBatch();
verify(preparedStatement, times(3)).addBatch();
verify(preparedStatement, atLeastOnce()).close();
verify(connection, atLeastOnce()).close();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.SqlParameterValue;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Juergen Hoeller
* @author Rick Evans
* @author Artur Geraschenko
* @author Yanming Zhou
*/
public class NamedParameterUtilsTests {
@@ -96,6 +98,20 @@ public class NamedParameterUtilsTests {
.buildSqlTypeArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams)[4]).isEqualTo(2);
}
@Test
public void convertSqlParameterValueToArray() {
SqlParameterValue sqlParameterValue = new SqlParameterValue(2, "b");
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("a", "a");
paramMap.put("b", sqlParameterValue);
paramMap.put("c", "c");
assertThat(NamedParameterUtils.buildValueArray("xxx :a :b :c xx :a :b", paramMap)[4]).isSameAs(sqlParameterValue);
MapSqlParameterSource namedParams = new MapSqlParameterSource();
namedParams.addValue("a", "a", 1).addValue("b", sqlParameterValue).addValue("c", "c", 3);
assertThat(NamedParameterUtils
.buildValueArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams, null)[4]).isSameAs(sqlParameterValue);
}
@Test
public void convertTypeMapToSqlParameterList() {
MapSqlParameterSource namedParams = new MapSqlParameterSource();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,31 +46,28 @@ import static org.mockito.Mockito.verify;
*
* @author Thomas Risberg
* @author Kiril Nugmanov
* @author Sam Brannen
*/
public class SimpleJdbcCallTests {
class SimpleJdbcCallTests {
private Connection connection;
private final Connection connection = mock(Connection.class);
private DatabaseMetaData databaseMetaData;
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
private DataSource dataSource;
private final DataSource dataSource = mock(DataSource.class);
private CallableStatement callableStatement;
private final CallableStatement callableStatement = mock(CallableStatement.class);
@BeforeEach
public void setUp() throws Exception {
connection = mock(Connection.class);
databaseMetaData = mock(DatabaseMetaData.class);
dataSource = mock(DataSource.class);
callableStatement = mock(CallableStatement.class);
void setUp() throws Exception {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(dataSource.getConnection()).willReturn(connection);
}
@Test
public void testNoSuchStoredProcedure() throws Exception {
void noSuchStoredProcedure() throws Exception {
final String NO_SUCH_PROC = "x";
SQLException sqlException = new SQLException("Syntax error or access violation exception", "42000");
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
@@ -81,8 +78,8 @@ public class SimpleJdbcCallTests {
given(connection.prepareCall("{call " + NO_SUCH_PROC + "()}")).willReturn(callableStatement);
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(NO_SUCH_PROC);
try {
assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(() ->
sproc.execute())
assertThatExceptionOfType(BadSqlGrammarException.class)
.isThrownBy(() -> sproc.execute())
.withCause(sqlException);
}
finally {
@@ -92,7 +89,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testUnnamedParameterHandling() throws Exception {
void unnamedParameterHandling() throws Exception {
final String MY_PROC = "my_proc";
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(MY_PROC);
// Shouldn't succeed in adding unnamed parameter
@@ -101,7 +98,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
void addInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithoutMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
adder.declareParameters(
@@ -117,7 +114,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
void addInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithoutMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
adder.declareParameters(
@@ -131,7 +128,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
void addInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
Number newId = adder.executeObject(Number.class, new MapSqlParameterSource()
@@ -143,7 +140,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
void addInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
Number newId = adder.executeObject(Number.class, 1103, 3);
@@ -153,7 +150,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
void addInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithoutMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
adder.declareParameters(
@@ -169,7 +166,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
void addInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithoutMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
adder.declareParameters(
@@ -183,7 +180,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
void addInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
Number newId = adder.executeFunction(Number.class, new MapSqlParameterSource()
@@ -192,22 +189,20 @@ public class SimpleJdbcCallTests {
assertThat(newId.intValue()).isEqualTo(4);
verifyAddInvoiceWithMetaData(true);
verify(connection, atLeastOnce()).close();
}
@Test
public void testAddInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
void addInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
Number newId = adder.executeFunction(Number.class, 1103, 3);
assertThat(newId.intValue()).isEqualTo(4);
verifyAddInvoiceWithMetaData(true);
verify(connection, atLeastOnce()).close();
}
@Test
public void testCorrectFunctionStatement() throws Exception {
void correctFunctionStatement() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
adder.compile();
@@ -215,7 +210,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testCorrectFunctionStatementNamed() throws Exception {
void correctFunctionStatementNamed() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withFunctionName("add_invoice");
adder.compile();
@@ -223,13 +218,51 @@ public class SimpleJdbcCallTests {
}
@Test
public void testCorrectProcedureStatementNamed() throws Exception {
void correctProcedureStatementNamed() throws Exception {
initializeAddInvoiceWithMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
adder.compile();
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}");
}
/**
* This test demonstrates that a CALL statement will still be generated if
* an exception occurs while retrieving metadata, potentially resulting in
* missing metadata and consequently a failure while invoking the stored
* procedure.
*/
@Test // gh-26486
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
ResultSet proceduresResultSet = mock(ResultSet.class);
ResultSet procedureColumnsResultSet = mock(ResultSet.class);
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
given(databaseMetaData.getUserName()).willReturn("ME");
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
given(proceduresResultSet.next()).willReturn(true, false);
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
given(procedureColumnsResultSet.next()).willReturn(true, true, true, false);
given(procedureColumnsResultSet.getString("COLUMN_NAME")).willReturn("amount", "custid", "newid");
given(procedureColumnsResultSet.getInt("DATA_TYPE"))
// Return a valid data type for the first 2 columns.
.willReturn(Types.INTEGER, Types.INTEGER)
// 3rd time, simulate an error while retrieving metadata.
.willThrow(new SQLException("error with DATA_TYPE for column 3"));
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
adder.compile();
// If an exception were not thrown for column 3, we would expect:
// {call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?)}");
verify(proceduresResultSet).close();
verify(procedureColumnsResultSet).close();
}
private void verifyStatement(SimpleJdbcCall adder, String expected) {
assertThat(adder.getCallString()).as("Incorrect call statement").isEqualTo(expected);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,9 @@ package org.springframework.jdbc.core.simple;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.HashMap;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collections;
import javax.sql.DataSource;
@@ -29,45 +31,44 @@ import org.junit.jupiter.api.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Mock object based tests for SimpleJdbcInsert.
* Mock object based tests for {@link SimpleJdbcInsert}.
*
* @author Thomas Risberg
* @author Sam Brannen
*/
public class SimpleJdbcInsertTests {
class SimpleJdbcInsertTests {
private Connection connection;
private final Connection connection = mock(Connection.class);
private DatabaseMetaData databaseMetaData;
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
private DataSource dataSource;
private final DataSource dataSource = mock(DataSource.class);
@BeforeEach
public void setUp() throws Exception {
connection = mock(Connection.class);
databaseMetaData = mock(DatabaseMetaData.class);
dataSource = mock(DataSource.class);
void setUp() throws Exception {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(dataSource.getConnection()).willReturn(connection);
}
@AfterEach
public void verifyClosed() throws Exception {
void verifyClosed() throws Exception {
verify(connection).close();
}
@Test
public void testNoSuchTable() throws Exception {
void noSuchTable() throws Exception {
ResultSet resultSet = mock(ResultSet.class);
given(resultSet.next()).willReturn(false);
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
given(databaseMetaData.getDatabaseProductVersion()).willReturn("1.0");
given(databaseMetaData.getUserName()).willReturn("me");
@@ -76,9 +77,65 @@ public class SimpleJdbcInsertTests {
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("x");
// Shouldn't succeed in inserting into table which doesn't exist
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
insert.execute(new HashMap<>()));
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(() -> insert.execute(Collections.emptyMap()))
.withMessageStartingWith("Unable to locate columns for table 'x' so an insert statement can't be generated");
verify(resultSet).close();
}
@Test // gh-26486
void retrieveColumnNamesFromMetadata() throws Exception {
ResultSet tableResultSet = mock(ResultSet.class);
given(tableResultSet.next()).willReturn(true, false);
given(databaseMetaData.getUserName()).willReturn("me");
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
ResultSet columnResultSet = mock(ResultSet.class);
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
given(columnResultSet.next()).willReturn(true, true, false);
given(columnResultSet.getString("COLUMN_NAME")).willReturn("col1", "col2");
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO me (col1, col2) VALUES(?, ?)");
verify(columnResultSet).close();
verify(tableResultSet).close();
}
@Test // gh-26486
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
ResultSet tableResultSet = mock(ResultSet.class);
given(tableResultSet.next()).willReturn(true, false);
given(databaseMetaData.getUserName()).willReturn("me");
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
ResultSet columnResultSet = mock(ResultSet.class);
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
// true, true, false --> simulates processing of two columns
given(columnResultSet.next()).willReturn(true, true, false);
given(columnResultSet.getString("COLUMN_NAME"))
// Return a column name the first time.
.willReturn("col1")
// Second time, simulate an error while retrieving metadata.
.willThrow(new SQLException("error with col2"));
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(insert::compile)
.withMessage("Unable to locate columns for table 'me' so an insert statement can't be generated. " +
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().");
verify(columnResultSet).close();
verify(tableResultSet).close();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -233,7 +233,6 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
@Override
protected StompBrokerRelayMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
StompBrokerRelayMessageHandler handler = new StompBrokerRelayMessageHandler(
getClientInboundChannel(), getClientOutboundChannel(),
brokerChannel, getDestinationPrefixes());
@@ -378,13 +378,14 @@ public class MessageHeaderAccessor {
* {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values.
*/
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy != null) {
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeader(key, value);
}
});
if (headersToCopy == null || this.headers == headersToCopy) {
return;
}
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeader(key, value);
}
});
}
/**
@@ -392,13 +393,14 @@ public class MessageHeaderAccessor {
* <p>This operation will <em>not</em> overwrite any existing values.
*/
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy != null) {
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeaderIfAbsent(key, value);
}
});
if (headersToCopy == null || this.headers == headersToCopy) {
return;
}
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeaderIfAbsent(key, value);
}
});
}
protected boolean isReadOnly(String headerName) {
@@ -16,6 +16,7 @@
package org.springframework.messaging.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -75,6 +76,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
@SuppressWarnings("unchecked")
Map<String, List<String>> map = (Map<String, List<String>>) getHeader(NATIVE_HEADERS);
if (map != null) {
// setHeader checks for equality but we need copy of native headers
setHeader(NATIVE_HEADERS, null);
setHeader(NATIVE_HEADERS, new LinkedMultiValueMap<>(map));
}
}
@@ -103,6 +106,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
if (isMutable()) {
Map<String, List<String>> map = getNativeHeaders();
if (map != null) {
// setHeader checks for equality but we need immutable wrapper
setHeader(NATIVE_HEADERS, null);
setHeader(NATIVE_HEADERS, Collections.unmodifiableMap(map));
}
super.setImmutable();
@@ -110,31 +115,34 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
}
@Override
public void setHeader(String name, @Nullable Object value) {
if (name.equalsIgnoreCase(NATIVE_HEADERS)) {
// Force removal since setHeader checks for equality
super.setHeader(NATIVE_HEADERS, null);
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy == null) {
return;
}
super.setHeader(name, value);
@SuppressWarnings("unchecked")
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
if (map != null && map != getNativeHeaders()) {
map.forEach(this::setNativeHeaderValues);
}
// setHeader checks for equality, native headers should be equal by now
super.copyHeaders(headersToCopy);
}
@Override
@SuppressWarnings("unchecked")
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy != null) {
Map<String, List<String>> nativeHeaders = getNativeHeaders();
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
if (map != null) {
if (nativeHeaders != null) {
nativeHeaders.putAll(map);
}
else {
nativeHeaders = new LinkedMultiValueMap<>(map);
}
}
super.copyHeaders(headersToCopy);
setHeader(NATIVE_HEADERS, nativeHeaders);
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy == null) {
return;
}
@SuppressWarnings("unchecked")
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
if (map != null && getNativeHeaders() == null) {
map.forEach(this::setNativeHeaderValues);
}
super.copyHeadersIfAbsent(headersToCopy);
}
/**
@@ -201,6 +209,30 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
}
}
/**
* Variant of {@link #addNativeHeader(String, String)} for all values.
* @since 5.2.12
*/
public void setNativeHeaderValues(String name, @Nullable List<String> values) {
Assert.state(isMutable(), "Already immutable");
Map<String, List<String>> map = getNativeHeaders();
if (values == null) {
if (map != null && map.get(name) != null) {
setModified(true);
map.remove(name);
}
return;
}
if (map == null) {
map = new LinkedMultiValueMap<>(3);
setHeader(NATIVE_HEADERS, map);
}
if (!ObjectUtils.nullSafeEquals(values, getHeader(name))) {
setModified(true);
map.put(name, new ArrayList<>(values));
}
}
/**
* Add the specified native header value to existing values.
* <p>In order for this to work, the accessor must be {@link #isMutable()
@@ -226,19 +226,46 @@ public class NativeMessageHeaderAccessorTests {
@Test // gh-25821
void copyImmutableToMutable() {
NativeMessageHeaderAccessor source = new NativeMessageHeaderAccessor();
source.addNativeHeader("foo", "bar");
Message<String> message = MessageBuilder.createMessage("payload", source.getMessageHeaders());
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
sourceAccessor.addNativeHeader("foo", "bar");
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
NativeMessageHeaderAccessor target = new NativeMessageHeaderAccessor();
target.copyHeaders(message.getHeaders());
target.setLeaveMutable(true);
message = MessageBuilder.createMessage(message.getPayload(), target.getMessageHeaders());
NativeMessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
targetAccessor.copyHeaders(source.getHeaders());
targetAccessor.setLeaveMutable(true);
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(message);
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
assertThat(accessor.isMutable());
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
}
@Test // gh-25821
void copyIfAbsentImmutableToMutable() {
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
sourceAccessor.addNativeHeader("foo", "bar");
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
MessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
targetAccessor.copyHeadersIfAbsent(source.getHeaders());
targetAccessor.setLeaveMutable(true);
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
assertThat(accessor.isMutable());
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
}
@Test // gh-26155
void copySelf() {
NativeMessageHeaderAccessor accessor = new NativeMessageHeaderAccessor();
accessor.addNativeHeader("foo", "bar");
accessor.setHeader("otherHeader", "otherHeaderValue");
accessor.setLeaveMutable(true);
// Does not fail with ConcurrentModificationException
accessor.copyHeaders(accessor.getMessageHeaders());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -193,8 +193,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
* {@code Persistence.createEntityManagerFactory} (if any).
* <p>Can be populated with a String "value" (parsed via PropertiesEditor) or a
* "props" element in XML bean definitions.
* @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
* @see javax.persistence.Persistence#createEntityManagerFactory(String, Map)
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo, Map)
*/
public void setJpaProperties(Properties jpaProperties) {
CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
@@ -204,8 +204,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
* Specify JPA properties as a Map, to be passed into
* {@code Persistence.createEntityManagerFactory} (if any).
* <p>Can be populated with a "map" or "props" element in XML bean definitions.
* @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
* @see javax.persistence.Persistence#createEntityManagerFactory(String, Map)
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo, Map)
*/
public void setJpaPropertyMap(@Nullable Map<String, ?> jpaProperties) {
if (jpaProperties != null) {
@@ -400,10 +400,13 @@ public abstract class AbstractEntityManagerFactoryBean implements
String message = ex.getMessage();
String causeString = cause.toString();
if (!message.endsWith(causeString)) {
throw new PersistenceException(message + "; nested exception is " + causeString, cause);
ex = new PersistenceException(message + "; nested exception is " + causeString, cause);
}
}
}
if (logger.isErrorEnabled()) {
logger.error("Failed to initialize JPA EntityManagerFactory: " + ex.getMessage());
}
throw ex;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
@@ -42,10 +43,10 @@ public class MockMultipartFile implements MultipartFile {
private final String name;
private String originalFilename;
private final String originalFilename;
@Nullable
private String contentType;
private final String contentType;
private final byte[] content;
@@ -79,7 +80,7 @@ public class MockMultipartFile implements MultipartFile {
public MockMultipartFile(
String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {
Assert.hasLength(name, "Name must not be null");
Assert.hasLength(name, "Name must not be empty");
this.name = name;
this.originalFilename = (originalFilename != null ? originalFilename : "");
this.contentType = contentType;
@@ -108,6 +109,7 @@ public class MockMultipartFile implements MultipartFile {
}
@Override
@NonNull
public String getOriginalFilename() {
return this.originalFilename;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -79,7 +79,7 @@ public @interface ActiveProfiles {
* <p>The default value is {@code true}, which means that a test
* class will <em>inherit</em> bean definition profiles defined by a
* test superclass. Specifically, the bean definition profiles for a test
* class will be added to the list of bean definition profiles
* class will be appended to the list of bean definition profiles
* defined by a test superclass. Thus, subclasses have the option of
* <em>extending</em> the list of bean definition profiles.
* <p>If {@code inheritProfiles} is set to {@code false}, the bean
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,8 @@ package org.springframework.test.context;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
@@ -533,8 +533,8 @@ public class MergedContextConfiguration implements Serializable {
return EMPTY_STRING_ARRAY;
}
// Active profiles must be unique and sorted
Set<String> profilesSet = new TreeSet<>(Arrays.asList(activeProfiles));
// Active profiles must be unique
Set<String> profilesSet = new LinkedHashSet<>(Arrays.asList(activeProfiles));
return StringUtils.toStringArray(profilesSet);
}
@@ -16,8 +16,11 @@
package org.springframework.test.context.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -67,7 +70,7 @@ abstract class ActiveProfilesUtils {
static String[] resolveActiveProfiles(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
Set<String> activeProfiles = new TreeSet<>();
List<String[]> profileArrays = new ArrayList<>();
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
AnnotationDescriptor<ActiveProfiles> descriptor =
@@ -106,17 +109,25 @@ abstract class ActiveProfilesUtils {
String[] profiles = resolver.resolve(rootDeclaringClass);
if (!ObjectUtils.isEmpty(profiles)) {
for (String profile : profiles) {
if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim());
}
}
profileArrays.add(profiles);
}
descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor(
rootDeclaringClass.getSuperclass(), annotationType) : null);
}
// Reverse the list so that we can traverse "down" the hierarchy.
Collections.reverse(profileArrays);
Set<String> activeProfiles = new LinkedHashSet<>();
for (String[] profiles : profileArrays) {
for (String profile : profiles) {
if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim());
}
}
}
return StringUtils.toStringArray(activeProfiles);
}
@@ -16,9 +16,6 @@
package org.springframework.test.context.support;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -26,7 +23,6 @@ import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ActiveProfilesResolver;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor;
@@ -43,6 +39,8 @@ import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDe
*/
public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Log logger = LogFactory.getLog(DefaultActiveProfilesResolver.class);
@@ -58,36 +56,24 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
Set<String> activeProfiles = new TreeSet<>();
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, annotationType);
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, ActiveProfiles.class);
if (descriptor == null) {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
annotationType.getName(), testClass.getName()));
ActiveProfiles.class.getName(), testClass.getName()));
}
return EMPTY_STRING_ARRAY;
}
else {
Class<?> declaringClass = descriptor.getDeclaringClass();
ActiveProfiles annotation = descriptor.synthesizeAnnotation();
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
declaringClass.getName()));
}
for (String profile : annotation.profiles()) {
if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim());
}
descriptor.getDeclaringClass().getName()));
}
return annotation.profiles();
}
return StringUtils.toStringArray(activeProfiles);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -143,7 +143,7 @@ class MergedContextConfigurationTests {
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertThat(mergedConfig2).hasSameHashCodeAs(mergedConfig1);
assertThat(mergedConfig2.hashCode()).isNotEqualTo(mergedConfig1.hashCode());
}
@Test
@@ -339,7 +339,7 @@ class MergedContextConfigurationTests {
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertThat(mergedConfig2).isEqualTo(mergedConfig1);
assertThat(mergedConfig2).isNotEqualTo(mergedConfig1);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -90,8 +90,8 @@ class ContextCacheTests {
int size = 0, hit = 0, miss = 0;
loadCtxAndAssertStats(FooBarProfilesTestCase.class, ++size, hit, ++miss);
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
// Profiles {foo, bar} MUST hash to the same as {bar, foo}
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);
// Profiles {foo, bar} should not hash to the same as {bar,foo}
loadCtxAndAssertStats(BarFooProfilesTestCase.class, ++size, hit, ++miss);
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);
@@ -67,12 +67,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
@Test
void resolveActiveProfilesWithDuplicatedProfiles() {
assertResolvedProfiles(DuplicatedProfiles.class, "bar", "baz", "foo");
assertResolvedProfiles(DuplicatedProfiles.class, "foo", "bar", "baz");
}
@Test
void resolveActiveProfilesWithLocalAndInheritedDuplicatedProfiles() {
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "bar", "baz", "cat", "dog", "foo");
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "foo", "bar", "baz", "cat", "dog");
}
@Test
@@ -92,12 +92,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
@Test
void resolveActiveProfilesWithLocalAndInheritedAnnotations() {
assertResolvedProfiles(LocationsBar.class, "bar", "foo");
assertResolvedProfiles(LocationsBar.class, "foo", "bar");
}
@Test
void resolveActiveProfilesWithOverriddenAnnotation() {
assertResolvedProfiles(Animals.class, "cat", "dog");
assertResolvedProfiles(Animals.class, "dog", "cat");
}
/**
@@ -129,7 +129,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
*/
@Test
void resolveActiveProfilesWithLocalAndInheritedMetaAnnotations() {
assertResolvedProfiles(MetaLocationsBar.class, "bar", "foo");
assertResolvedProfiles(MetaLocationsBar.class, "foo", "bar");
}
/**
@@ -137,7 +137,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
*/
@Test
void resolveActiveProfilesWithOverriddenMetaAnnotation() {
assertResolvedProfiles(MetaAnimals.class, "cat", "dog");
assertResolvedProfiles(MetaAnimals.class, "dog", "cat");
}
/**
@@ -161,7 +161,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
*/
@Test
void resolveActiveProfilesWithMergedInheritedResolver() {
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "bar", "foo");
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "foo", "bar");
}
/**
@@ -38,16 +38,25 @@ import org.springframework.transaction.TransactionDefinition;
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
* class, and in fact {@link AnnotationTransactionAttributeSource} will directly
* convert the data to the latter class, so that Spring's transaction support code
* does not have to know about annotations. If no rules are relevant to the exception,
* it will be treated like
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
* (rolling back on {@link RuntimeException} and {@link Error} but not on checked
* exceptions).
* does not have to know about annotations. If no custom rollback rules apply,
* the transaction will roll back on {@link RuntimeException} and {@link Error}
* but not on checked exceptions.
*
* <p>For specific information about the semantics of this annotation's attributes,
* consult the {@link org.springframework.transaction.TransactionDefinition} and
* {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
*
* <p>This annotation commonly works with thread-bound transactions managed by
* {@link org.springframework.transaction.PlatformTransactionManager}, exposing a
* transaction to all data access operations within the current execution thread.
* <b>Note: This does NOT propagate to newly started threads within the method.</b>
*
* <p>Alternatively, this annotation may demarcate a reactive transaction managed
* by {@link org.springframework.transaction.ReactiveTransactionManager} which
* uses the Reactor context instead of thread-local attributes. As a consequence,
* all participating data access operations need to execute within the same
* Reactor context in the same reactive pipeline.
*
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @author Sam Brannen
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
/**
* TransactionAttribute implementation that works out whether a given exception
* should cause transaction rollback by applying a number of rollback rules,
* both positive and negative. If no rules are relevant to the exception, it
* both positive and negative. If no custom rollback rules apply, this attribute
* behaves like DefaultTransactionAttribute (rolling back on runtime exceptions).
*
* <p>{@link TransactionAttributeEditor} creates objects of this class.
@@ -480,9 +480,9 @@ public class MediaType extends MimeType implements Serializable {
@Override
protected void checkParameters(String attribute, String value) {
super.checkParameters(attribute, value);
if (PARAM_QUALITY_FACTOR.equals(attribute)) {
protected void checkParameters(String parameter, String value) {
super.checkParameters(parameter, value);
if (PARAM_QUALITY_FACTOR.equals(parameter)) {
value = unquote(value);
double d = Double.parseDouble(value);
Assert.isTrue(d >= 0D && d <= 1D,
@@ -128,7 +128,8 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
message.getHeaders().setContentLength(buffer.readableByteCount());
return message.writeWith(Mono.just(buffer)
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
});
})
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
}
if (isStreamingMediaType(contentType)) {
@@ -18,6 +18,7 @@ package org.springframework.http.converter.json;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
@@ -55,6 +56,7 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.springframework.util.TypeUtils;
/**
@@ -308,7 +310,9 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding)) {
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding);
try {
writePrefix(generator, object);
Object value = object;
@@ -343,6 +347,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
writeSuffix(generator, object);
generator.flush();
generator.close();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
@@ -203,10 +203,28 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
// We must resolve value first however, for a chance to handle potential error.
if (body instanceof Mono) {
return ((Mono<? extends DataBuffer>) body)
.flatMap(buffer -> doCommit(() ->
writeWithInternal(Mono.fromCallable(() -> buffer)
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release))))
.doOnError(t -> getHeaders().clearContentHeaders());
.flatMap(buffer -> {
AtomicReference<Boolean> subscribed = new AtomicReference<>(false);
return doCommit(
() -> {
try {
return writeWithInternal(Mono.fromCallable(() -> buffer)
.doOnSubscribe(s -> subscribed.set(true))
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
}
catch (Throwable ex) {
return Mono.error(ex);
}
})
.doOnError(ex -> DataBufferUtils.release(buffer))
.doOnCancel(() -> {
if (!subscribed.get()) {
DataBufferUtils.release(buffer);
}
});
})
.doOnError(t -> getHeaders().clearContentHeaders())
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
}
else {
return new ChannelSendOperator<>(body, inner -> doCommit(() -> writeWithInternal(inner)))
@@ -54,7 +54,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
@Override
public void add(String key, @Nullable String value) {
this.headers.add(key, value);
if (value != null) {
this.headers.add(key, value);
}
}
@Override
@@ -69,7 +71,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
@Override
public void set(String key, @Nullable String value) {
this.headers.set(key, value);
if (value != null) {
this.headers.set(key, value);
}
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -112,6 +112,8 @@ public @interface CrossOrigin {
* {@code Expires}, {@code Last-Modified}, or {@code Pragma},
* <p>Exposed headers are listed in the {@code Access-Control-Expose-Headers}
* response header of actual CORS requests.
* <p>The special value {@code "*"} allows all headers to be exposed for
* non-credentialed requests.
* <p>By default no headers are listed as exposed.
*/
String[] exposedHeaders() default {};
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -254,13 +254,11 @@ public class CorsConfiguration {
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}) that an
* actual response might have and can be exposed.
* <p>Note that {@code "*"} is not a valid exposed header value.
* <p>The special value {@code "*"} allows all headers to be exposed for
* non-credentialed requests.
* <p>By default this is not set.
*/
public void setExposedHeaders(@Nullable List<String> exposedHeaders) {
if (exposedHeaders != null && exposedHeaders.contains(ALL)) {
throw new IllegalArgumentException("'*' is not a valid exposed header value");
}
this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null);
}
@@ -276,12 +274,10 @@ public class CorsConfiguration {
/**
* Add a response header to expose.
* <p>Note that {@code "*"} is not a valid exposed header value.
* <p>The special value {@code "*"} allows all headers to be exposed for
* non-credentialed requests.
*/
public void addExposedHeader(String exposedHeader) {
if (ALL.equals(exposedHeader)) {
throw new IllegalArgumentException("'*' is not a valid exposed header value");
}
if (this.exposedHeaders == null) {
this.exposedHeaders = new ArrayList<>(4);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,8 +42,8 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0;
return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0);
}
@Override
@@ -70,8 +70,8 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle
}
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
throw new UnsupportedOperationException("Unexpected return type [" +
returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,8 +70,8 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
}
else {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
throw new UnsupportedOperationException("Unexpected return type [" +
returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -224,7 +224,16 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
if (this.forwardedHeaderTransformer != null) {
request = this.forwardedHeaderTransformer.apply(request);
try {
request = this.forwardedHeaderTransformer.apply(request);
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to apply forwarded headers to " + formatRequest(request), ex);
}
response.setStatusCode(HttpStatus.BAD_REQUEST);
return response.setComplete();
}
}
ServerWebExchange exchange = createExchange(request, response);
@@ -27,6 +27,7 @@ import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.FastByteArrayOutputStream;
@@ -209,7 +210,9 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
if (this.content.size() > 0) {
HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
if ((complete || this.contentLength != null) && !rawResponse.isCommitted()) {
rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
if (rawResponse.getHeader(HttpHeaders.TRANSFER_ENCODING) == null) {
rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
}
this.contentLength = null;
}
this.content.writeTo(rawResponse.getOutputStream());
@@ -578,7 +578,7 @@ public class UrlPathHelper {
return requestUri;
}
String start = requestUri.substring(0, index);
for (int i = key.length(); i < requestUri.length(); i++) {
for (int i = index + key.length(); i < requestUri.length(); i++) {
char c = requestUri.charAt(i);
if (c == ';' || c == '/') {
return start + requestUri.substring(i);
@@ -1,64 +0,0 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.util.pattern;
/**
* Used to represent a subsection of an array, useful when wanting to pass that subset of data
* to another method (e.g. a java regex matcher) but not wanting to create a new string object
* to hold all that data.
*
* @author Andy Clement
* @since 5.0
*/
class SubSequence implements CharSequence {
private final char[] chars;
private final int start;
private final int end;
SubSequence(char[] chars, int start, int end) {
this.chars = chars;
this.start = start;
this.end = end;
}
@Override
public int length() {
return (this.end - this.start);
}
@Override
public char charAt(int index) {
return this.chars[this.start + index];
}
@Override
public CharSequence subSequence(int start, int end) {
return new SubSequence(this.chars, this.start + start, this.start + end);
}
@Override
public String toString() {
return new String(this.chars, this.start, this.end - this.start);
}
}

Some files were not shown because too many files have changed in this diff Show More