Compare commits

...

130 Commits

Author SHA1 Message Date
Spring Builds 8f4c172734 Release v5.2.22.RELEASE 2022-05-11 07:20:17 +00:00
Juergen Hoeller 9f238c997d Polishing 2022-05-11 08:47:46 +02:00
Juergen Hoeller 50177b1ad3 Refine CachedIntrospectionResults property introspection
Closes gh-28445
2022-05-11 08:42:43 +02:00
rstoyanchev 159a99bbaf Ignore invalid STOMP frame
Closes gh-28444
2022-05-11 07:26:26 +01:00
Spring Builds 41e158c93d Next development version (v5.2.22.BUILD-SNAPSHOT) 2022-04-13 10:32:09 +00:00
Sam Brannen 833e750175 Improve documentation and matching algorithm in data binders 2022-04-13 10:20:41 +02:00
Juergen Hoeller d70054d686 Upgrade to Log4j2 2.17.2 2022-04-08 14:00:06 +02:00
Juergen Hoeller 36e4951b5e Polishing 2022-04-08 13:58:10 +02:00
Juergen Hoeller 87b5080586 Consistent use of getLocalAddr() without DNS lookups in request adapters
Closes gh-28280
2022-04-08 13:57:09 +02:00
Juergen Hoeller 5cbf85a6f8 Avoid return value reference in potentially cached MethodParameter instance
Closes gh-28232

(cherry picked from commit eefdd2c768)
2022-04-08 13:32:12 +02:00
Juergen Hoeller 69c7eb9418 Restore ability to configure setClassLoader methods
Closes gh-28269

(cherry picked from commit 9f91168396)
2022-04-08 13:31:34 +02:00
Juergen Hoeller fb763dd0f4 Consistent fallback in case of fast-class generation failure
Closes gh-28138

(cherry picked from commit 7aed6279a2)
2022-04-08 13:29:07 +02:00
Spring Builds 1b75f393a9 Next development version (v5.2.21.BUILD-SNAPSHOT) 2022-03-31 10:01:20 +00:00
Brian Clozel 996f701a19 Refine PropertyDescriptor filtering
Restrict property paths under `Class` and properties of types
`ClassLoader` or `ProtectionDomain`.
2022-03-31 10:32:41 +02:00
Sam Brannen 90cfde985e Improve diagnostics in SpEL for large array creation
Attempting to create a large array in a SpEL expression can result in
an OutOfMemoryError. Although the JVM recovers from that, the error
message is not very helpful to the user.

This commit improves the diagnostics in SpEL for large array creation
by throwing a SpelEvaluationException with a meaningful error message
in order to improve diagnostics for the user.

Closes gh-28257
2022-03-31 10:25:27 +02:00
Stephane Nicoll 94f52bc94f Upgrade to Artifactory Resource 0.0.17 2022-03-30 13:13:31 +02:00
Stephane Nicoll d4478bafa4 Upgrade Java versions in CI image 2022-03-30 13:12:08 +02:00
Stephane Nicoll 136e6db822 Upgrade Ubuntu version in CI images 2022-03-30 13:11:18 +02:00
Stephane Nicoll 8f1f68377d Upgrade Java versions in CI image 2022-01-27 09:54:40 +01:00
Sam Brannen ce2367a76f Upgrade to Log4j2 2.17.1 2022-01-03 11:33:22 +01:00
Spring Builds acf782347f Next development version (v5.2.20.BUILD-SNAPSHOT) 2021-12-16 09:38:42 +00:00
Stephane Nicoll 1a03ffef62 Upgrade to Log4j2 2.16.0 2021-12-15 09:49:30 +01:00
Juergen Hoeller e954d7fc20 Upgrade to AspectJ 1.9.7, Groovy 2.5.15, BlockHound 1.0.6, SLF4J 1.7.32 2021-12-14 18:19:49 +01:00
Juergen Hoeller ec13a3f1ba Backported clarifications for SchedulerFactoryBean and cache annotations
See gh-27709
See gh-27726
2021-12-14 18:19:17 +01:00
Juergen Hoeller 842f5b354d Declare serialVersionUID on DefaultAopProxyFactory
Closes gh-27784
2021-12-14 18:17:03 +01:00
Juergen Hoeller 67a4b63d42 Avoid NPE against null value from toString call
Closes gh-27782
2021-12-14 18:16:52 +01:00
Juergen Hoeller 354dad615b Avoid early initialization of empty interceptor names
Closes gh-12238
2021-12-14 18:16:35 +01:00
Stephane Nicoll 0668323176 Upgrade to Log4j2 2.15.0 2021-12-10 15:12:57 +01:00
Sam Brannen e03e62c76a Test status quo for null in varargs in SpEL expressions
This commit also points out that `null` supplied as a single value for
a varargs array of type Optional will be kept as `null` instead of being
converted to Optional.empty(); whereas, if more than one value is passed
to such a varargs array a null value will be properly converted to
Optional.empty().

See gh-27719
2021-12-10 11:18:10 +01:00
Rossen Stoyanchev 96a7fc693b Apply LogFormatUtils in more places 2021-12-09 16:07:20 +00:00
Rossen Stoyanchev 50f6db2704 Replace both EOL and control characters 2021-12-08 13:41:45 +00:00
Arjen Poutsma 24ebb5ecd5 Use ByteArrayDecoder in DefaultClientResponse::createException
This commit changes DefaultClientResponse::createException to use
the ByteArrayDecoder, instead of converting to DataBuffers and
turning these into a byte array.

Closes gh-27666
2021-11-10 17:09:55 +01:00
Juergen Hoeller a439a83f34 Polishing 2021-11-09 23:01:46 +01:00
Juergen Hoeller f0de3a9924 Recommend ObjectProvider as alternative to @Lazy for optional dependencies
Closes gh-27649
2021-11-09 23:01:31 +01:00
Stephane Nicoll 17c3976bca Upgrade to Reactor Dysprosium-SR25
Closes gh-27635
2021-11-09 19:40:59 +01:00
Sam Brannen 9c4a33e64c Increase the likelihood that timed tests pass on CI server 2021-11-05 13:29:06 +01:00
Stephane Nicoll dd3ae4e63d Start building against Reactor Dysprosium-SR25 snapshots
See gh-27635
2021-11-04 14:51:52 +01:00
Sam Brannen dfc435d045 Return previous value in UndertowHeadersAdapter's remove() method
Prior to this commit, UndertowHeadersAdapter's remove() method violated
the java.util.Map contract by always returning null.

This commit fixes this by returning the previous list stored under the
specified key, and otherwise returning null if no previous value was
present.

Closes gh-27592
2021-10-22 14:59:57 +02:00
Sam Brannen 78a24c0dd1 Update "Reporting a Vulnerability" link 2021-10-20 14:50:19 +02:00
Sam Brannen 2bec54e7b3 Fix Javadoc in [NamedParameter]JdbcOperations.queryForObject methods
This commit fixes the Javadoc in all queryForObject(...) methods in
JdbcOperations and NamedParameterJdbcOperations regarding what kinds of
exceptions are thrown under which conditions.

Closes gh-27559

(cherry picked from commit 0853baaa3f)
2021-10-19 18:38:17 +02:00
Rossen Stoyanchev 52e2953de9 Fix checkstyle warning
See gh-27569
2021-10-18 17:09:46 +01:00
Rossen Stoyanchev 7b149a68c6 Fix assertion message in DefaultDataBuffer
Closes gh-27567
2021-10-18 16:57:12 +01:00
Smile d1b3b8eaf6 Update Javadoc in DefaultResponseErrorHandler
Closes gh-27569
2021-10-18 16:28:19 +01:00
Spring Builds f94794e5c0 Next development version (v5.2.19.BUILD-SNAPSHOT) 2021-10-14 10:25:19 +00:00
Rossen Stoyanchev 26af506441 DefaultResponseErrorHandler shows full error details
Closes gh-27552
2021-10-13 21:21:38 +01:00
Juergen Hoeller b14a940716 Polishing 2021-10-13 13:00:53 +02:00
Juergen Hoeller 4ecbf6511d Remove dead fallback code 2021-10-13 12:52:38 +02:00
Juergen Hoeller 5b6a32d1b9 Polishing 2021-10-12 16:18:41 +02:00
Juergen Hoeller 263244f637 Avoid expensive isReadable() check during classpath scan
Closes gh-27541
See gh-21372
2021-10-12 16:18:21 +02:00
Juergen Hoeller 3a166ea742 Add efficient existence check to ClassPathResource.isReadable()
Includes reduced isReadable() check in PathResourceLookupFunction, aligned with PathResourceResolver.

Closes gh-27538
See gh-21372
2021-10-12 16:18:09 +02:00
Sam Brannen 6d4dfed772 Introduce test for gh-27499 and polish contribution 2021-10-12 13:19:13 +02:00
Nick 09ef597836 Avoid duplicate JCacheOperationSource bean registration in <cache:annotation-driven />
In our application we use XML context and <cache:annotation-driven />
declaration. Also we disable bean definition duplication by setting
GenericApplicationContext.setAllowBeanDefinitionOverriding(false) in an
ApplicationContextInitializer. This combination leads to a
BeanDefinitionOverrideException because the
DefaultJCacheOperationSource bean is registered twice.

 - once for: parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
 - once for: parserContext.registerBeanComponent(new BeanComponentDefinition(sourceDef, sourceName));

This commit refactors JCacheCachingConfigurer.registerCacheAspect(...)
so that the JCacheOperationSource bean is registered only once.

Closes gh-27499
2021-10-12 13:19:13 +02:00
Sam Brannen 50920e1a96 Upgrade to Reactor Dysprosium-SR24
Closes gh-27526
2021-10-12 13:19:13 +02:00
Stephane Nicoll 8c4d1d4d29 Upgrade CI to github-release resource 1.5.5
Closes gh-27460
2021-10-11 16:20:35 +02:00
Rossen Stoyanchev 82f8d0dfd7 Apply value formatting to resolved exceptions 2021-10-11 11:15:59 +01:00
Stephane Nicoll 38f616a8e8 Start building against Reactor Dysprosium-SR23 snapshots
See gh-27526
2021-10-07 08:52:40 +02:00
Rossen Stoyanchev 441c4d5d5c Generalize formatValue
Provide an overload for additional control and compact output.
2021-10-06 21:31:02 +01:00
Sam Brannen 66f4d20fd4 Comment out unused fudgeFactor 2021-10-05 14:22:51 +02:00
Rossen Stoyanchev 2fa44d62b9 ResponseStatusExceptionHandler logs exception message only
No need to log the full exception for a resolved exception, and as the
comment suggests that it mirrors Spring MVC equivalent, which also
logs the message only.
2021-10-05 12:13:39 +01:00
Stephane Nicoll c86ac97267 Upgrade to GitHub Changelog Generator 0.0.7 in CI
Closes gh-27513
2021-10-03 07:10:06 +02:00
Juergen Hoeller fd916baf79 Skip all flaky StopWatch time assertions 2021-10-02 12:09:01 +02:00
Juergen Hoeller 790d56dd18 Backported tests for bean-derived (Auto)Closeable.close() method
See gh-27504
2021-10-01 13:47:58 +02:00
Sam Brannen 5a5f118efc Escape closing curly braces in regular expressions for Android support
PR gh-24470 introduced a regression for Android users by no longer
escaping closing curly braces in regular expressions.

This commit therefore partially reverts the changes made in 273812f9c5
for closing curly braces (`}`).

Closes gh27467
2021-10-01 10:56:00 +02:00
Juergen Hoeller edf664c1cf Defensively handle fast class generation failure for individual methods
Includes rethrowing of last actual defineClass exception encountered.

Closes gh-27490
2021-09-30 18:00:10 +02:00
Juergen Hoeller 137bca129f Skip flaky StopWatch time assertions 2021-09-27 17:43:25 +02:00
Juergen Hoeller b01a3aeb6b Fix invalid characters in source files
Closes gh-27475
2021-09-27 17:42:27 +02:00
Juergen Hoeller 195d375964 Polishing 2021-09-24 13:49:48 +02:00
Juergen Hoeller ebd351753f Fix contract violations in ConcurrentReferenceHashMap's EntrySet/Iterator
Closes gh-27454

(cherry picked from commit 208fafa4a3)
2021-09-24 13:41:35 +02:00
Juergen Hoeller 9524ecd5b0 Log rejected listener container tasks at warn level
Closes gh-27451
2021-09-24 13:30:50 +02:00
Spring Builds 9053613a65 Next development version (v5.2.18.BUILD-SNAPSHOT) 2021-09-15 08:10:53 +00:00
Stephane Nicoll 0aa7ba80e1 Migrate to Spring Builds account 2021-09-15 09:07:14 +02:00
Stephane Nicoll 9a05a8846b Fix GitHub credentials to use token rather than password
Closes gh-27403
2021-09-15 08:42:30 +02:00
Juergen Hoeller fb97a126a5 Defensive handling of dimensions nullability 2021-09-14 21:54:42 +02:00
Sam Brannen 1ad5b4fbd6 Support char primitive default values in BeanUtils.instantiateClass()
Closes gh-27390
2021-09-14 16:17:46 +02:00
Sam Brannen 4d1cdf6379 Introduce test for gh-27390 2021-09-14 16:17:36 +02:00
takeaction21 e1ba3e7dbb Support float and double primitive default values in BeanUtils.instantiateClass()
See gh-27390
2021-09-14 16:13:36 +02:00
Rossen Stoyanchev 2b55b9ccd5 Do not log request parameters for multipart requests
Closes gh-27350
2021-09-14 13:41:15 +01:00
Brian Clozel 90bcb2e375 Upgrade to Reactor Dysprosium-SR23
Closes gh-27378
2021-09-14 13:49:51 +02:00
Juergen Hoeller 9ad3464c80 Reduce log statement for non-unique JavaBean property to debug level
Closes gh-27372

(cherry picked from commit 6c17e9375b)
2021-09-13 18:47:27 +02:00
Stephane Nicoll 1954a22c23 Fix bug fix icon in changelog configuration
Closes gh-27385
2021-09-11 00:01:46 +02:00
Brian Clozel bd90c4d0f0 Switch to Reactor Dysprosium SNAPSHOTs
See gh-27378
2021-09-09 19:06:25 +02:00
Juergen Hoeller e31d66cd2b Polishing (backported from 5.3.x) 2021-09-02 23:18:36 +02:00
Juergen Hoeller eb349be1fc Fix Kotlin example for filtering handler functions
Closes gh-27337
2021-09-02 23:08:27 +02:00
Juergen Hoeller 4b3e8619ec Polishing 2021-09-02 22:53:30 +02:00
Juergen Hoeller 50d597eb41 Avoid unnecessary cause initialization in ResponseStatusException
Closes gh-27196
2021-09-02 22:52:14 +02:00
Juergen Hoeller 9c931f669c Convenient configuration of type permissions for XStream 1.4.18
Closes gh-27343

(cherry picked from commit 837301fdb3)
2021-09-02 22:49:09 +02:00
Sam Brannen b7e6169e12 Update copyright date 2021-08-31 09:46:29 +02:00
Inmord 9c204dc475 Polish AbstractAspectJAdvisorFactory
Closes gh-27340
2021-08-31 09:40:28 +02:00
Sam Brannen 21c0e50b33 Document when prepareTestInstance() is invoked by the SpringMethodRule
Closes gh-27305
2021-08-22 16:02:57 +02:00
Sam Brannen cecd2ee2e6 Polishing 2021-08-22 14:40:55 +02:00
Sam Brannen 97a89d6db9 Polish contribution
See gh-27303
2021-08-22 14:39:24 +02:00
Erik van Paassen 10a90d3204 Fix UrlPathHelper#shouldRemoveSemicolonContent() (#27303)
The checkReadOnly() method should only be called from
methods that modify properties to prevent modification
of read-only instances.

Fixes #27256
2021-08-22 14:30:46 +02:00
Sam Brannen ab2a861163 Fix and document CompositeUriComponentsContributor#hasContributors()
Prior to this commit, the hasContributors() method incorrectly returned
false if contributors had been configured.

This commit fixes the logic in hasContributors() and documents it.

Closes #27271
2021-08-18 17:35:50 +02:00
Sam Brannen f56d6ea1fb Avoid StringIndexOutOfBoundsException in WebSocketMessageBrokerStats
Prior to this commit, if the TaskExecutor configured in
WebSocketMessageBrokerStats for the inboundChannelExecutor or
outboundChannelExecutor was not a ThreadPoolTaskExecutor, a
StringIndexOutOfBoundsException was thrown when attempting to parse the
results of invoking toString() on the executor.

The reason is that ThreadPoolTaskExecutor delegates to a
ThreadPoolExecutor whose toString() implementation generates text
containing "pool size = ...", and WebSocketMessageBrokerStats'
getExecutorStatsInfo() method relied on the presence of "pool" in the
text returned from toString().

This commit fixes this bug by ensuring that the text returned from
toString() contains "pool" before parsing the text. If "pool" is not
present in the text, getExecutorStatsInfo() now returns "unknown"
instead of throwing a StringIndexOutOfBoundsException.

Closes gh-27209
2021-07-26 11:50:27 +02:00
Spring Buildmaster 420aa9bef0 Next development version (v5.2.17.BUILD-SNAPSHOT) 2021-07-14 08:00:09 +00:00
Stephane Nicoll 14adefa867 Remove coroutines links until the javadoc publication is fixed
See gh-27169
2021-07-14 07:48:42 +02:00
Juergen Hoeller fe3357d6b9 Polishing (backported) 2021-07-13 19:07:15 +02:00
Juergen Hoeller 11c51d8633 Upgrade to Reactor Dysprosium-SR21
Closes gh-27163
2021-07-13 19:06:22 +02:00
Juergen Hoeller bdbd999619 Make proxyTargetClass=true with introduction advice work for JDK proxy targets
Closes gh-27044

(cherry picked from commit c45c46dad7)
2021-07-13 18:34:49 +02:00
Juergen Hoeller bc4af15e9e BeanCreationException message includes declaring class of constructor/factory method
Closes gh-27139

(cherry picked from commit 74f91339e2)
2021-07-13 18:34:42 +02:00
Stephane Nicoll 348dc826f9 Fix CI build badge in README 2021-07-12 09:51:51 +02:00
Sam Brannen 43901b2c6a Fix bug in SimpleMethodMetadataReadingVisitor.Source.toString()
Prior to this commit, the toString() implementation did not separate
method argument types with a comma or any form of separator, leading
to results such as:

    org.example.MyClass.myMethod(java.lang.Stringjava.lang.Integer)

instead of:

    org.example.MyClass.myMethod(java.lang.String,java.lang.Integer)

Closes gh-27095
2021-06-24 16:46:52 +02:00
Arjen Poutsma 7b34bf2a6c Synchronoss should create temp directory lazily
The SynchronossPartHttpMessageReader should only create temp directory
when needed, not at startup.

Closes gh-27092
2021-06-23 16:04:08 +02:00
Sam Brannen c5a138a167 Ensure DefaultPathSegment does not allow parameters to be mutated
Prior to this commit, if a PathContainer was created using
Options.MESSAGE_ROUTE, DefaultPathSegment#parameters() returned a
mutable map which would allow the user to modify the contents of the
static, shared EMPTY_PARAMS map in DefaultPathContainer.

This commit prevents corruption of the shared EMPTY_PARAMS map by
ensuring that parameters stored in DefaultPathSegment are always
immutable.

Closes gh-27064
2021-06-15 15:57:35 +02:00
Sam Brannen 1f098e1f22 Polish DefaultPathContainerTests 2021-06-15 15:51:21 +02:00
Juergen Hoeller 67fccc3c7e Nullability refinements and related polishing (backported) 2021-06-08 14:58:58 +02:00
Sam Brannen 70e6606f0f Improve @Transactional docs regarding method visibility
Closes gh-27003
2021-05-30 17:16:08 +02:00
Sviatoslav Hryb 5b55cefd64 Fix @Transactional docs regarding method visibility
Closes gh-27001
2021-05-30 17:13:14 +02:00
Sam Brannen 911aca17ea Doc tx semantics for @TransactionalEventListener after completion phases
This commit improves the Javadoc regarding transactional semantics for
@TransactionalEventListener methods invoked in the AFTER_COMMIT,
AFTER_ROLLBACK, or AFTER_COMPLETION phase. Specifically, the
documentation now points out that interactions with the underlying
transactional resource will not be committed in those phases.

Closes gh-26974
2021-05-26 14:27:16 +02:00
Sam Brannen 90183568ba Deprecate internal APIs in ScriptUtils
Many of the utility methods in ScriptUtils are public only because they
were once invoked from JdbdTestUtils in spring-test, which is no longer
the case. Consequently, there should no longer be a need for any
external clients to invoke such methods.

To address, this commit formally deprecates the following methods in
ScriptUtils in spring-jdbc.

- readScript(...)
- containsSqlScriptDelimiters(...)
- splitSqlScript(...)

Closes gh-26947
2021-05-18 15:59:57 +02:00
Sam Brannen b5ce514b27 Logically reorganize methods in ScriptUtils
See gh-26947
2021-05-18 15:47:39 +02:00
Sam Brannen 9ae6268a1a Polish ScriptUtils implementation 2021-05-14 15:18:26 +02:00
Sam Brannen e4d843e41e Ignore comments when searching for statement delimiter in ScriptUtils
Prior to this commit, the implementation of
ScriptUtils.containsSqlScriptDelimiters() did not ignore comments when
searching for the statement delimiter within an SQL script. This
resulted in subtle bugs if a comment contained a single single-quote or
single double-quote, since the absence of the closing single-quote or
double-quote led the algorithm to believe that it was still "within a
text literal". Similar issues could arise if a comment contained the
sought statement delimiter but the rest of the script did not contain
the sought statement delimiter. In such cases, the algorithms in
ScriptUtils could erroneously choose an incorrect statement delimiter
-- for example, using the fallback statement delimiter instead of the
delimiter specified by the user.

This commit avoids such bugs by ignoring single-line comments and block
comments when searching for the statement delimiter within an SQL
script.

Closes gh-26911
2021-05-14 14:33:07 +02:00
Sam Brannen 22256966fb Ignore delimiter enclosed in double quotes in ScriptUtils
Prior to this commit, the containsSqlScriptDelimiters() method in
ScriptUtils ignored delimiters enclosed in single quotes but not those
enclosed within double quotes, which contradicts the algorithm in
splitSqlScript() and therefore constitutes a bug.

This commit fixes this bug in the ScriptUtils implementation in
spring-jdbc.

Closes gh-26935
2021-05-12 13:06:00 +02:00
Spring Buildmaster 7c74459cb0 Next development version (v5.2.16.BUILD-SNAPSHOT) 2021-05-12 06:16:21 +00:00
Arjen Poutsma 85ad63bd35 Ensure Synchronoss temp directories do not collide
This commit makes sure that Synchronoss uses a random temporary
directory to store uploaded files, so that two instances do not collide.

Closes gh-26931
2021-05-11 21:19:27 +02:00
Juergen Hoeller a2ff769bc1 Polishing (backported from master) 2021-05-11 17:32:23 +02:00
Juergen Hoeller 9523f1ffd6 Correct clientInboundChannel assertion (includes constructor javadoc)
Closes gh-26896
2021-05-11 17:31:04 +02:00
Juergen Hoeller cd0570ee39 Log resource path resolution failure at debug level (instead of warn)
Closes gh-26828
2021-05-11 17:30:57 +02:00
Juergen Hoeller 05d767b1cf Always propagate checked exceptions from Kotlin code behind CGLIB proxies
Closes gh-23844
2021-05-11 17:29:40 +02:00
Stephane Nicoll 30655555bd Remove .RELEASE suffix before querying GitHub
This commit removes the `.RELEASE` suffix of the released version, if
necessary. Such commit is not present on GitHub and the changelog
generation process fails to find the milestone to use to generate the
changelog.

Closes gh-26796
2021-05-11 16:20:28 +02:00
Stephane Nicoll 0ee8790a9a Upgrade to Reactor Dysprosium-SR20
Closes gh-26891
2021-05-11 15:35:50 +02:00
Sam Brannen bcd8896689 Polish contribution
See gh-25200
2021-05-10 12:50:13 +02:00
Vlad Kisel 53b8d73078 Support single-value request param resolution for data class constructors
Prior to this commit, when Web MVC attempted to resolve a constructor
argument for a data class constructor with @ModelAttribute binding,
ModelAttributeMethodProcessor failed to unwrap the array returned by
WebRequest.getParameter(String).

According to the Javadoc for WebRequest.getParameter(String), "a
single-value parameter will be exposed as an array with a single
element."

This commit fixes this issue by extracting the single value from such
an array and using that as the constructor argument (potentially
converted by the WebDataBinder).

Closes gh-25200
2021-05-10 12:47:47 +02:00
ShindongLee 3ab270e0f1 Fix parameter bug of handler inside the filterFunction DSL
Co-authored-by: hojongs <hojong.jjh@gmail.com>
Co-authored-by: bjh970913 <bjh970913@gmail.com>

Closes gh-26921
2021-05-10 10:22:13 +02:00
Rossen Stoyanchev ddb727bee8 Switch to Reactor Dysprosium snapshots
See gh-26891
2021-05-04 17:12:19 +01:00
Sam Brannen a33adac6c8 Support @ModelAttribute(binding=false) with WebFlux
Prior to this commit, @ModelAttribute(binding=false) was honored with
Spring Web MVC but not with WebFlux.

This commit adds support for disabling binding via @ModelAttribute with
WebFlux by adding a check to resolveArgument(...) in
ModelAttributeMethodArgumentResolver.

Closes gh-26856
2021-05-03 19:13:16 +02:00
Sam Brannen 08663d4792 Polish Javadoc 2021-05-03 18:58:12 +02:00
Stephane Nicoll 30b5a7b5c3 Backport "Remove leftover Javadoc from WebClient"
Closes gh-26809
2021-04-15 09:57:21 +02:00
Spring Buildmaster 620023cbc3 Next development version (v5.2.15.BUILD-SNAPSHOT) 2021-04-13 11:46:36 +00:00
213 changed files with 3368 additions and 1961 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# <img src="src/docs/asciidoc/images/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://build.spring.io/plugins/servlet/wittified/build-status/SPR-PUBM)](https://build.spring.io/browse/SPR)
# <img src="src/docs/asciidoc/images/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.2.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.2.x?groups=Build")
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
+1 -1
View File
@@ -8,4 +8,4 @@ wiki page.
## Reporting a Vulnerability
Please see https://pivotal.io/security.
Please see https://spring.io/security-policy.
+9 -9
View File
@@ -2,14 +2,14 @@ plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
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 "io.freefair.aspectj" version '4.1.6' apply false
id 'org.jetbrains.dokka' version '0.10.1' apply false
id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
id 'org.asciidoctor.jvm.convert' version '2.4.0'
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 'de.undercouch.download' version '4.1.1'
}
apply from: "$rootDir/gradle/build-scan-user-data.gradle"
@@ -28,7 +28,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-SR19"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR25"
mavenBom "io.rsocket:rsocket-bom:1.0.4"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72"
@@ -36,21 +36,21 @@ configure(allprojects) { project ->
mavenBom "org.junit:junit-bom:5.6.3"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.13.3') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.17.2') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
entry 'log4j-slf4j-impl'
}
dependency "org.slf4j:slf4j-api:1.7.30"
dependency "org.slf4j:slf4j-api:1.7.32"
dependency "com.google.code.findbugs:jsr305:3.0.2"
dependencySet(group: 'org.aspectj', version: '1.9.6') {
dependencySet(group: 'org.aspectj', version: '1.9.7') {
entry 'aspectjrt'
entry 'aspectjtools'
entry 'aspectjweaver'
}
dependencySet(group: 'org.codehaus.groovy', version: '2.5.13') {
dependencySet(group: 'org.codehaus.groovy', version: '2.5.15') {
entry 'groovy'
entry 'groovy-jsr223'
entry 'groovy-templates'
@@ -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.21"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
dependency "com.caucho:hessian:4.0.63"
dependency "com.fasterxml:aalto-xml:1.2.2"
+1 -1
View File
@@ -4,7 +4,7 @@ changelog:
- title: ":star: New Features"
labels:
- "type: enhancement"
- title: ":beetle: Bug Fixes"
- title: ":lady_beetle: Bug Fixes"
labels:
- "type: bug"
- "type: regression"
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20210119
FROM ubuntu:focal-20220302
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20210119
FROM ubuntu:focal-20220302
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+2 -2
View File
@@ -3,10 +3,10 @@ set -e
case "$1" in
java8)
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u282-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u282b08.tar.gz"
echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz"
;;
java11)
echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz"
echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz"
;;
*)
echo $"Unknown java version"
+7 -2
View File
@@ -2,7 +2,7 @@ anchors:
git-repo-resource-source: &git-repo-resource-source
uri: ((github-repo))
username: ((github-username))
password: ((github-password))
password: ((github-ci-release-token))
branch: ((branch))
gradle-enterprise-task-params: &gradle-enterprise-task-params
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
@@ -48,7 +48,12 @@ resource_types:
type: registry-image
source:
repository: springio/artifactory-resource
tag: 0.0.13
tag: 0.0.17
- name: github-release
type: registry-image
source:
repository: concourse/github-release-resource
tag: 1.5.5
- name: github-status-resource
type: registry-image
source:
+6 -1
View File
@@ -4,9 +4,14 @@ set -e
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
milestone=${version}
if [[ $RELEASE_TYPE = "RELEASE" ]]; then
milestone=${version%.RELEASE}
fi
java -jar /github-changelog-generator.jar \
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
${version} generated-changelog/changelog.md
${milestone} generated-changelog/changelog.md
echo ${version} > generated-changelog/version
echo v${version} > generated-changelog/tag
+2 -2
View File
@@ -29,8 +29,8 @@ fi
echo "Staging $stageVersion (next version will be $nextVersion)"
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
git config user.name "Spring Buildmaster" > /dev/null
git config user.email "buildmaster@springframework.org" > /dev/null
git config user.name "Spring Builds" > /dev/null
git config user.email "spring-builds@users.noreply.github.com" > /dev/null
git add gradle.properties > /dev/null
git commit -m"Release v$stageVersion" > /dev/null
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.6'
tag: '0.0.7'
inputs:
- name: git-repo
- name: artifactory-repo
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.2.14.BUILD-SNAPSHOT
version=5.2.22.RELEASE
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
-3
View File
@@ -103,9 +103,6 @@ dokka {
externalDocumentationLink {
url = new URL("https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/")
}
externalDocumentationLink {
url = new URL("https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/")
}
}
}
@@ -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.
@@ -23,7 +23,7 @@ import java.lang.reflect.AccessibleObject;
* terminology).
*
* <p>A runtime joinpoint is an <i>event</i> that occurs on a static
* joinpoint (i.e. a location in a the program). For instance, an
* joinpoint (i.e. a location in a program). For instance, an
* invocation is the runtime joinpoint on a method (static joinpoint).
* The static part of a given joinpoint can be generically retrieved
* using the {@link #getStaticPart()} method.
@@ -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.
@@ -103,10 +103,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
@Override
public void validate(Class<?> aspectClass) throws AopConfigException {
// If the parent has the annotation and isn't abstract it's an error
if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null &&
!Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) {
Class<?> superclass = aspectClass.getSuperclass();
if (superclass.getAnnotation(Aspect.class) != null &&
!Modifier.isAbstract(superclass.getModifiers())) {
throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" +
aspectClass.getSuperclass().getName() + "]");
superclass.getName() + "]");
}
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,6 +49,7 @@ import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.core.KotlinDetector;
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -236,7 +237,7 @@ class CglibAopProxy implements AopProxy, Serializable {
* validates it if not.
*/
private void validateClassIfNecessary(Class<?> proxySuperClass, @Nullable ClassLoader proxyClassLoader) {
if (logger.isWarnEnabled()) {
if (logger.isInfoEnabled()) {
synchronized (validatedClasses) {
if (!validatedClasses.containsKey(proxySuperClass)) {
doValidateClass(proxySuperClass, proxyClassLoader,
@@ -374,6 +375,22 @@ class CglibAopProxy implements AopProxy, Serializable {
return false;
}
/**
* Invoke the given method with a CGLIB MethodProxy if possible, falling back
* to a plain reflection invocation in case of a fast-class generation failure.
*/
@Nullable
private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
try {
return methodProxy.invoke(target, args);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
}
/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
@@ -424,7 +441,7 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object retVal = methodProxy.invoke(this.target, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
}
@@ -449,7 +466,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object oldProxy = null;
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(this.target, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
finally {
@@ -477,7 +494,7 @@ class CglibAopProxy implements AopProxy, Serializable {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object target = this.targetSource.getTarget();
try {
Object retVal = methodProxy.invoke(target, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -507,7 +524,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object target = this.targetSource.getTarget();
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(target, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -678,13 +695,13 @@ class CglibAopProxy implements AopProxy, Serializable {
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
retVal = invokeMethod(target, method, argsToUse, methodProxy);
}
else {
// We need to create a method invocation...
@@ -736,10 +753,7 @@ class CglibAopProxy implements AopProxy, Serializable {
super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
// Only use method proxy for public methods not derived from java.lang.Object
this.methodProxy = (Modifier.isPublic(method.getModifiers()) &&
method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
!AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method) ?
methodProxy : null);
this.methodProxy = (isMethodProxyCompatible(method) ? methodProxy : null);
}
@Override
@@ -752,10 +766,17 @@ class CglibAopProxy implements AopProxy, Serializable {
throw ex;
}
catch (Exception ex) {
if (ReflectionUtils.declaresException(getMethod(), ex.getClass())) {
if (ReflectionUtils.declaresException(getMethod(), ex.getClass()) ||
KotlinDetector.isKotlinType(getMethod().getDeclaringClass())) {
// Propagate original exception if declared on the target method
// (with callers expecting it). Always propagate it for Kotlin code
// since checked exceptions do not have to be explicitly declared there.
throw ex;
}
else {
// Checked exception thrown in the interceptor but not declared on the
// target method signature -> apply an UndeclaredThrowableException,
// aligned with standard JDK dynamic proxy behavior.
throw new UndeclaredThrowableException(ex);
}
}
@@ -768,10 +789,25 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
protected Object invokeJoinpoint() throws Throwable {
if (this.methodProxy != null) {
return this.methodProxy.invoke(this.target, this.arguments);
try {
return this.methodProxy.invoke(this.target, this.arguments);
}
catch (CodeGenerationException ex) {
logFastClassGenerationFailure(this.method);
}
}
else {
return super.invokeJoinpoint();
return super.invokeJoinpoint();
}
static boolean isMethodProxyCompatible(Method method) {
return (Modifier.isPublic(method.getModifiers()) &&
method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
!AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method));
}
static void logFastClassGenerationFailure(Method method) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to generate CGLIB fast class for method: " + method);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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,9 +43,11 @@ import org.springframework.aop.SpringProxy;
* @see AdvisedSupport#setProxyTargetClass
* @see AdvisedSupport#setInterfaces
*/
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
private static final long serialVersionUID = 7930414337282325166L;
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
@@ -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.
@@ -421,11 +421,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
* are unaffected by such changes.
*/
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
if (this.advisorChainInitialized) {
return;
}
if (!ObjectUtils.isEmpty(this.interceptorNames)) {
if (!this.advisorChainInitialized && !ObjectUtils.isEmpty(this.interceptorNames)) {
if (this.beanFactory == null) {
throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
"- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
@@ -464,9 +460,9 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
addAdvisorOnChainCreation(advice);
}
}
}
this.advisorChainInitialized = true;
this.advisorChainInitialized = true;
}
}
@@ -17,6 +17,7 @@
package org.springframework.aop.framework.autoproxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -449,7 +450,17 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (proxyFactory.isProxyTargetClass()) {
// Explicit handling of JDK proxy targets (for introduction advice scenarios)
if (Proxy.isProxyClass(beanClass)) {
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
for (Class<?> ifc : beanClass.getInterfaces()) {
proxyFactory.addInterface(ifc);
}
}
}
else {
// No proxyTargetClass flag enforced, let's apply our default checks...
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
@@ -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.
@@ -71,7 +71,8 @@ public class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFil
if (introductionInfo != null) {
Class<?>[] introducedInterfaces = introductionInfo.getInterfaces();
if (introducedInterfaces.length == 0) {
throw new IllegalArgumentException("IntroductionAdviceSupport implements no interfaces");
throw new IllegalArgumentException(
"IntroductionInfo defines no interfaces to introduce: " + introductionInfo);
}
for (Class<?> ifc : introducedInterfaces) {
addInterface(ifc);
@@ -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.
@@ -183,7 +183,7 @@ public class ProxyFactoryTests {
}
@Test
public void testGetsAllInterfaces() throws Exception {
public void testGetsAllInterfaces() {
// Extend to get new interface
class TestBeanSubclass extends TestBean implements Comparable<Object> {
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 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.
@@ -22,13 +22,18 @@ import org.springframework.contextsupport.testfixture.jcache.AbstractJCacheAnnot
/**
* @author Stephane Nicoll
* @author Sam Brannen
*/
public class JCacheAspectJNamespaceConfigTests extends AbstractJCacheAnnotationTests {
@Override
protected ApplicationContext getApplicationContext() {
return new GenericXmlApplicationContext(
"/org/springframework/cache/config/annotation-jcache-aspectj.xml");
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
// Disallow bean definition overriding to test https://github.com/spring-projects/spring-framework/pull/27499
context.setAllowBeanDefinitionOverriding(false);
context.load("/org/springframework/cache/config/annotation-jcache-aspectj.xml");
context.refresh();
return context;
}
}
@@ -83,7 +83,10 @@ public abstract class BeanUtils {
values.put(byte.class, (byte) 0);
values.put(short.class, (short) 0);
values.put(int.class, 0);
values.put(long.class, (long) 0);
values.put(long.class, 0L);
values.put(float.class, 0F);
values.put(double.class, 0D);
values.put(char.class, '\0');
DEFAULT_TYPE_VALUES = Collections.unmodifiableMap(values);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -281,9 +283,17 @@ public final class CachedIntrospectionResults {
// This call is slow so we do it once.
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (Class.class == beanClass &&
("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
// Ignore Class.getClassLoader() and getProtectionDomain() methods - nobody needs to bind to those
if (Class.class == beanClass && !("name".equals(pd.getName()) ||
(pd.getName().endsWith("Name") && String.class == pd.getPropertyType()))) {
// Only allow all name variants of Class properties
continue;
}
if (URL.class == beanClass && "content".equals(pd.getName())) {
// Only allow URL attribute introspection, not content resolution
continue;
}
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
// Ignore read-only properties such as ClassLoader - no need to bind to those
continue;
}
if (logger.isTraceEnabled()) {
@@ -321,6 +331,10 @@ public final class CachedIntrospectionResults {
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
// against a declared read method, so we prefer read method descriptors here.
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
// Ignore read-only properties such as ClassLoader - no need to bind to those
continue;
}
this.propertyDescriptors.put(pd.getName(), pd);
}
}
@@ -329,6 +343,12 @@ public final class CachedIntrospectionResults {
}
}
private boolean isInvalidReadOnlyPropertyType(@Nullable Class<?> returnType) {
return (returnType != null && (AutoCloseable.class.isAssignableFrom(returnType) ||
ClassLoader.class.isAssignableFrom(returnType) ||
ProtectionDomain.class.isAssignableFrom(returnType)));
}
BeanInfo getBeanInfo() {
return this.beanInfo;
@@ -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.
@@ -138,7 +138,7 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
Set<Method> ambiguousCandidates = this.ambiguousWriteMethods;
if (ambiguousCandidates != null) {
this.ambiguousWriteMethods = null;
LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).warn("Invalid JavaBean property '" +
LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).debug("Non-unique JavaBean property '" +
getName() + "' being accessed! Ambiguous write methods found next to actually used [" +
this.writeMethod + "]: " + ambiguousCandidates);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,9 @@ import org.springframework.lang.Nullable;
/**
* Common interface for classes that can access named properties
* (such as bean properties of an object or fields in an object)
* Serves as base interface for {@link BeanWrapper}.
* (such as bean properties of an object or fields in an object).
*
* <p>Serves as base interface for {@link BeanWrapper}.
*
* @author Juergen Hoeller
* @since 1.1
@@ -459,7 +459,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
@@ -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.
@@ -351,7 +351,9 @@ public class ConstructorArgumentValues {
* @return the ValueHolder for the argument, or {@code null} if none set
*/
@Nullable
public ValueHolder getArgumentValue(int index, @Nullable Class<?> requiredType, @Nullable String requiredName, @Nullable Set<ValueHolder> usedValueHolders) {
public ValueHolder getArgumentValue(int index, @Nullable Class<?> requiredType,
@Nullable String requiredName, @Nullable Set<ValueHolder> usedValueHolders) {
Assert.isTrue(index >= 0, "Index must not be negative");
ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName);
if (valueHolder == null) {
@@ -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.
@@ -350,7 +350,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
@@ -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.
@@ -276,12 +276,12 @@ class ConstructorResolver {
throw ex;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Could not resolve matching constructor " +
"Could not resolve matching constructor on bean class [" + mbd.getBeanClassName() + "] " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
}
else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous constructor matches found in bean '" + beanName + "' " +
"Ambiguous constructor matches found on bean class [" + mbd.getBeanClassName() + "] " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
ambiguousConstructors);
}
@@ -607,7 +607,7 @@ class ConstructorResolver {
}
String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes);
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"No matching factory method found: " +
"No matching factory method found on class [" + factoryClass.getName() + "]: " +
(mbd.getFactoryBeanName() != null ?
"factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
"factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " +
@@ -618,12 +618,12 @@ class ConstructorResolver {
}
else if (void.class == factoryMethodToUse.getReturnType()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Invalid factory method '" + mbd.getFactoryMethodName() +
"': needs to have a non-void return type!");
"Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
factoryClass.getName() + "]: needs to have a non-void return type!");
}
else if (ambiguousFactoryMethods != null) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous factory method matches found in bean '" + beanName + "' " +
"Ambiguous factory method matches found on class [" + factoryClass.getName() + "] " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
ambiguousFactoryMethods);
}
@@ -62,6 +62,8 @@ import org.springframework.util.StringUtils;
@SuppressWarnings("serial")
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private static final String DESTROY_METHOD_NAME = "destroy";
private static final String CLOSE_METHOD_NAME = "close";
private static final String SHUTDOWN_METHOD_NAME = "shutdown";
@@ -104,13 +106,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
Assert.notNull(bean, "Disposable bean must not be null");
this.bean = bean;
this.beanName = beanName;
this.invokeDisposableBean =
(this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
this.invokeDisposableBean = (bean instanceof DisposableBean &&
!beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME));
this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
this.acc = acc;
String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
if (destroyMethodName != null &&
!(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodName)) &&
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
this.destroyMethodName = destroyMethodName;
Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod == null) {
@@ -135,6 +140,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
this.destroyMethod = destroyMethod;
}
this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}
@@ -349,8 +355,8 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
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
// Only perform destroy method inference in case of the bean
// not explicitly implementing the DisposableBean interface
destroyMethodName = null;
if (!(bean instanceof DisposableBean)) {
try {
@@ -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.
@@ -64,24 +64,24 @@ public final class ParserContext {
}
public final XmlReaderContext getReaderContext() {
public XmlReaderContext getReaderContext() {
return this.readerContext;
}
public final BeanDefinitionRegistry getRegistry() {
public BeanDefinitionRegistry getRegistry() {
return this.readerContext.getRegistry();
}
public final BeanDefinitionParserDelegate getDelegate() {
public BeanDefinitionParserDelegate getDelegate() {
return this.delegate;
}
@Nullable
public final BeanDefinition getContainingBeanDefinition() {
public BeanDefinition getContainingBeanDefinition() {
return this.containingBeanDefinition;
}
public final boolean isNested() {
public boolean isNested() {
return (this.containingBeanDefinition != null);
}
@@ -44,6 +44,7 @@ import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
/**
* Unit tests for {@link BeanUtils}.
@@ -80,19 +81,43 @@ class BeanUtilsTests {
}
@Test // gh-22531
void testInstantiateClassWithOptionalPrimitiveType() throws NoSuchMethodException {
Constructor<BeanWithPrimitiveTypes> ctor = BeanWithPrimitiveTypes.class.getDeclaredConstructor(int.class, boolean.class, String.class);
BeanWithPrimitiveTypes bean = BeanUtils.instantiateClass(ctor, null, null, "foo");
assertThat(bean.getCounter()).isEqualTo(0);
assertThat(bean.isFlag()).isEqualTo(false);
assertThat(bean.getValue()).isEqualTo("foo");
void instantiateClassWithFewerArgsThanParameters() throws NoSuchMethodException {
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor();
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() ->
BeanUtils.instantiateClass(constructor, null, null, "foo"));
}
@Test // gh-22531
void testInstantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException {
Constructor<BeanWithPrimitiveTypes> ctor = BeanWithPrimitiveTypes.class.getDeclaredConstructor(int.class, boolean.class, String.class);
@Test // gh-22531
void instantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException {
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor();
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() ->
BeanUtils.instantiateClass(ctor, null, null, "foo", null));
BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null));
}
@Test // gh-22531, gh-27390
void instantiateClassWithOptionalPrimitiveTypes() throws NoSuchMethodException {
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor();
BeanWithPrimitiveTypes bean = BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo");
assertSoftly(softly -> {
softly.assertThat(bean.isFlag()).isFalse();
softly.assertThat(bean.getByteCount()).isEqualTo((byte) 0);
softly.assertThat(bean.getShortCount()).isEqualTo((short) 0);
softly.assertThat(bean.getIntCount()).isEqualTo(0);
softly.assertThat(bean.getLongCount()).isEqualTo(0L);
softly.assertThat(bean.getFloatCount()).isEqualTo(0F);
softly.assertThat(bean.getDoubleCount()).isEqualTo(0D);
softly.assertThat(bean.getCharacter()).isEqualTo('\0');
softly.assertThat(bean.getText()).isEqualTo("foo");
});
}
private Constructor<BeanWithPrimitiveTypes> getBeanWithPrimitiveTypesConstructor() throws NoSuchMethodException {
return BeanWithPrimitiveTypes.class.getConstructor(boolean.class, byte.class, short.class, int.class,
long.class, float.class, double.class, char.class, String.class);
}
@Test
@@ -535,30 +560,68 @@ class BeanUtilsTests {
private static class BeanWithPrimitiveTypes {
private int counter;
private boolean flag;
private byte byteCount;
private short shortCount;
private int intCount;
private long longCount;
private float floatCount;
private double doubleCount;
private char character;
private String text;
private String value;
@SuppressWarnings("unused")
public BeanWithPrimitiveTypes(int counter, boolean flag, String value) {
this.counter = counter;
this.flag = flag;
this.value = value;
}
public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount,
float floatCount, double doubleCount, char character, String text) {
public int getCounter() {
return counter;
this.flag = flag;
this.byteCount = byteCount;
this.shortCount = shortCount;
this.intCount = intCount;
this.longCount = longCount;
this.floatCount = floatCount;
this.doubleCount = doubleCount;
this.character = character;
this.text = text;
}
public boolean isFlag() {
return flag;
}
public String getValue() {
return value;
public byte getByteCount() {
return byteCount;
}
public short getShortCount() {
return shortCount;
}
public int getIntCount() {
return intCount;
}
public long getLongCount() {
return longCount;
}
public float getFloatCount() {
return floatCount;
}
public double getDoubleCount() {
return doubleCount;
}
public char getCharacter() {
return character;
}
public String getText() {
return text;
}
}
private static class PrivateBeanWithPrivateConstructor {
@@ -38,7 +38,7 @@ public class BeanWrapperAutoGrowingTests {
@BeforeEach
public void setUp() {
public void setup() {
wrapper.setAutoGrowNestedPaths(true);
}
@@ -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.
@@ -16,7 +16,6 @@
package org.springframework.beans;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -86,7 +85,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericList() throws MalformedURLException {
public void testGenericList() throws Exception {
GenericBean<?> gb = new GenericBean<>();
BeanWrapper bw = new BeanWrapperImpl(gb);
List<String> input = new ArrayList<>();
@@ -98,7 +97,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericListElement() throws MalformedURLException {
public void testGenericListElement() throws Exception {
GenericBean<?> gb = new GenericBean<>();
gb.setResourceList(new ArrayList<>());
BeanWrapper bw = new BeanWrapperImpl(gb);
@@ -163,10 +162,8 @@ public class BeanWrapperGenericsTests {
value2.add(Boolean.TRUE);
input.put("2", value2);
bw.setPropertyValue("collectionMap", input);
boolean condition1 = gb.getCollectionMap().get(1) instanceof HashSet;
assertThat(condition1).isTrue();
boolean condition = gb.getCollectionMap().get(2) instanceof ArrayList;
assertThat(condition).isTrue();
assertThat(gb.getCollectionMap().get(1) instanceof HashSet).isTrue();
assertThat(gb.getCollectionMap().get(2) instanceof ArrayList).isTrue();
}
@Test
@@ -178,8 +175,7 @@ public class BeanWrapperGenericsTests {
HashSet<Integer> value1 = new HashSet<>();
value1.add(1);
bw.setPropertyValue("collectionMap[1]", value1);
boolean condition = gb.getCollectionMap().get(1) instanceof HashSet;
assertThat(condition).isTrue();
assertThat(gb.getCollectionMap().get(1) instanceof HashSet).isTrue();
}
@Test
@@ -195,7 +191,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericListOfLists() throws MalformedURLException {
public void testGenericListOfLists() {
GenericBean<String> gb = new GenericBean<>();
List<List<Integer>> list = new LinkedList<>();
list.add(new LinkedList<>());
@@ -207,7 +203,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericListOfListsWithElementConversion() throws MalformedURLException {
public void testGenericListOfListsWithElementConversion() {
GenericBean<String> gb = new GenericBean<>();
List<List<Integer>> list = new LinkedList<>();
list.add(new LinkedList<>());
@@ -219,7 +215,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericListOfArrays() throws MalformedURLException {
public void testGenericListOfArrays() {
GenericBean<String> gb = new GenericBean<>();
ArrayList<String[]> list = new ArrayList<>();
list.add(new String[] {"str1", "str2"});
@@ -231,7 +227,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericListOfArraysWithElementConversion() throws MalformedURLException {
public void testGenericListOfArraysWithElementConversion() {
GenericBean<String> gb = new GenericBean<>();
ArrayList<String[]> list = new ArrayList<>();
list.add(new String[] {"str1", "str2"});
@@ -244,7 +240,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericListOfMaps() throws MalformedURLException {
public void testGenericListOfMaps() {
GenericBean<String> gb = new GenericBean<>();
List<Map<Integer, Long>> list = new LinkedList<>();
list.add(new HashMap<>());
@@ -256,7 +252,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericListOfMapsWithElementConversion() throws MalformedURLException {
public void testGenericListOfMapsWithElementConversion() {
GenericBean<String> gb = new GenericBean<>();
List<Map<Integer, Long>> list = new LinkedList<>();
list.add(new HashMap<>());
@@ -268,7 +264,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericMapOfMaps() throws MalformedURLException {
public void testGenericMapOfMaps() {
GenericBean<String> gb = new GenericBean<>();
Map<String, Map<Integer, Long>> map = new HashMap<>();
map.put("mykey", new HashMap<>());
@@ -280,7 +276,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericMapOfMapsWithElementConversion() throws MalformedURLException {
public void testGenericMapOfMapsWithElementConversion() {
GenericBean<String> gb = new GenericBean<>();
Map<String, Map<Integer, Long>> map = new HashMap<>();
map.put("mykey", new HashMap<>());
@@ -292,7 +288,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericMapOfLists() throws MalformedURLException {
public void testGenericMapOfLists() {
GenericBean<String> gb = new GenericBean<>();
Map<Integer, List<Integer>> map = new HashMap<>();
map.put(1, new LinkedList<>());
@@ -304,7 +300,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericMapOfListsWithElementConversion() throws MalformedURLException {
public void testGenericMapOfListsWithElementConversion() {
GenericBean<String> gb = new GenericBean<>();
Map<Integer, List<Integer>> map = new HashMap<>();
map.put(1, new LinkedList<>());
@@ -316,7 +312,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericTypeNestingMapOfInteger() throws Exception {
public void testGenericTypeNestingMapOfInteger() {
Map<String, String> map = new HashMap<>();
map.put("testKey", "100");
@@ -325,14 +321,13 @@ public class BeanWrapperGenericsTests {
bw.setPropertyValue("mapOfInteger", map);
Object obj = gb.getMapOfInteger().get("testKey");
boolean condition = obj instanceof Integer;
assertThat(condition).isTrue();
assertThat(obj instanceof Integer).isTrue();
}
@Test
public void testGenericTypeNestingMapOfListOfInteger() throws Exception {
public void testGenericTypeNestingMapOfListOfInteger() {
Map<String, List<String>> map = new HashMap<>();
List<String> list = Arrays.asList(new String[] {"1", "2", "3"});
List<String> list = Arrays.asList("1", "2", "3");
map.put("testKey", list);
NestedGenericCollectionBean gb = new NestedGenericCollectionBean();
@@ -340,13 +335,12 @@ public class BeanWrapperGenericsTests {
bw.setPropertyValue("mapOfListOfInteger", map);
Object obj = gb.getMapOfListOfInteger().get("testKey").get(0);
boolean condition = obj instanceof Integer;
assertThat(condition).isTrue();
assertThat(obj instanceof Integer).isTrue();
assertThat(((Integer) obj).intValue()).isEqualTo(1);
}
@Test
public void testGenericTypeNestingListOfMapOfInteger() throws Exception {
public void testGenericTypeNestingListOfMapOfInteger() {
List<Map<String, String>> list = new LinkedList<>();
Map<String, String> map = new HashMap<>();
map.put("testKey", "5");
@@ -357,15 +351,14 @@ public class BeanWrapperGenericsTests {
bw.setPropertyValue("listOfMapOfInteger", list);
Object obj = gb.getListOfMapOfInteger().get(0).get("testKey");
boolean condition = obj instanceof Integer;
assertThat(condition).isTrue();
assertThat(obj instanceof Integer).isTrue();
assertThat(((Integer) obj).intValue()).isEqualTo(5);
}
@Test
public void testGenericTypeNestingMapOfListOfListOfInteger() throws Exception {
public void testGenericTypeNestingMapOfListOfListOfInteger() {
Map<String, List<List<String>>> map = new HashMap<>();
List<String> list = Arrays.asList(new String[] {"1", "2", "3"});
List<String> list = Arrays.asList("1", "2", "3");
map.put("testKey", Collections.singletonList(list));
NestedGenericCollectionBean gb = new NestedGenericCollectionBean();
@@ -373,8 +366,7 @@ public class BeanWrapperGenericsTests {
bw.setPropertyValue("mapOfListOfListOfInteger", map);
Object obj = gb.getMapOfListOfListOfInteger().get("testKey").get(0).get(0);
boolean condition = obj instanceof Integer;
assertThat(condition).isTrue();
assertThat(obj instanceof Integer).isTrue();
assertThat(((Integer) obj).intValue()).isEqualTo(1);
}
@@ -465,7 +457,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericallyTypedIntegerBean() throws Exception {
public void testGenericallyTypedIntegerBean() {
GenericIntegerBean gb = new GenericIntegerBean();
BeanWrapper bw = new BeanWrapperImpl(gb);
bw.setPropertyValue("genericProperty", "10");
@@ -476,7 +468,7 @@ public class BeanWrapperGenericsTests {
}
@Test
public void testGenericallyTypedSetOfIntegerBean() throws Exception {
public void testGenericallyTypedSetOfIntegerBean() {
GenericSetOfIntegerBean gb = new GenericSetOfIntegerBean();
BeanWrapper bw = new BeanWrapperImpl(gb);
bw.setPropertyValue("genericProperty", "10");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,9 @@ import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.UrlResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -104,7 +107,7 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests {
.satisfies(ex -> assertThat(ex.getPossibleMatches()).containsExactly("age"));
}
@Test // Can't be shared; there is no such thing as a read-only field
@Test // Can't be shared; there is no such thing as a read-only field
public void setReadOnlyMapProperty() {
TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean()));
TypedReadOnlyMapClient target = new TypedReadOnlyMapClient();
@@ -150,18 +153,58 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests {
}
@Test
public void propertyDescriptors() {
public void propertyDescriptors() throws Exception {
TestBean target = new TestBean();
target.setSpouse(new TestBean());
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("name", "a");
accessor.setPropertyValue("spouse.name", "b");
assertThat(target.getName()).isEqualTo("a");
assertThat(target.getSpouse().getName()).isEqualTo("b");
assertThat(accessor.getPropertyValue("name")).isEqualTo("a");
assertThat(accessor.getPropertyValue("spouse.name")).isEqualTo("b");
assertThat(accessor.getPropertyDescriptor("name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.getPropertyDescriptor("spouse.name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("class.name")).isTrue();
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
assertThat(accessor.getPropertyValue("class.name")).isEqualTo(TestBean.class.getName());
assertThat(accessor.getPropertyValue("class.simpleName")).isEqualTo(TestBean.class.getSimpleName());
assertThat(accessor.getPropertyDescriptor("class.name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.getPropertyDescriptor("class.simpleName").getPropertyType()).isEqualTo(String.class);
accessor = createAccessor(new DefaultResourceLoader());
assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("class.name")).isTrue();
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
assertThat(accessor.isReadableProperty("classLoader")).isTrue();
assertThat(accessor.isWritableProperty("classLoader")).isTrue();
OverridingClassLoader ocl = new OverridingClassLoader(getClass().getClassLoader());
accessor.setPropertyValue("classLoader", ocl);
assertThat(accessor.getPropertyValue("classLoader")).isSameAs(ocl);
accessor = createAccessor(new UrlResource("https://spring.io"));
assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("class.name")).isTrue();
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
assertThat(accessor.isReadableProperty("URL.protocol")).isTrue();
assertThat(accessor.isReadableProperty("URL.host")).isTrue();
assertThat(accessor.isReadableProperty("URL.port")).isTrue();
assertThat(accessor.isReadableProperty("URL.file")).isTrue();
assertThat(accessor.isReadableProperty("URL.content")).isFalse();
assertThat(accessor.isReadableProperty("inputStream")).isFalse();
assertThat(accessor.isReadableProperty("filename")).isTrue();
assertThat(accessor.isReadableProperty("description")).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.
@@ -31,6 +31,7 @@ import static org.springframework.beans.factory.support.BeanDefinitionBuilder.ro
* invoking a factory method is not instructive to the user and rather misleading.
*
* @author Chris Beams
* @author Juergen Hoeller
*/
public class Spr5475Tests {
@@ -40,7 +41,8 @@ public class Spr5475Tests {
rootBeanDefinition(Foo.class)
.setFactoryMethod("noArgFactory")
.addConstructorArgValue("bogusArg").getBeanDefinition(),
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(String)'. " +
"Error creating bean with name 'foo': No matching factory method found on class " +
"[org.springframework.beans.factory.Spr5475Tests$Foo]: factory method 'noArgFactory(String)'. " +
"Check that a method with the specified name and arguments exists and that it is static.");
}
@@ -51,7 +53,8 @@ public class Spr5475Tests {
.setFactoryMethod("noArgFactory")
.addConstructorArgValue("bogusArg1")
.addConstructorArgValue("bogusArg2".getBytes()).getBeanDefinition(),
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(String,byte[])'. " +
"Error creating bean with name 'foo': No matching factory method found on class " +
"[org.springframework.beans.factory.Spr5475Tests$Foo]: factory method 'noArgFactory(String,byte[])'. " +
"Check that a method with the specified name and arguments exists and that it is static.");
}
@@ -65,7 +68,8 @@ public class Spr5475Tests {
def.setConstructorArgumentValues(cav);
assertExceptionMessageForMisconfiguredFactoryMethod(def,
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(CharSequence,byte[])'. " +
"Error creating bean with name 'foo': No matching factory method found on class " +
"[org.springframework.beans.factory.Spr5475Tests$Foo]: factory method 'noArgFactory(CharSequence,byte[])'. " +
"Check that a method with the specified name and arguments exists and that it is static.");
}
@@ -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.
@@ -244,7 +244,7 @@ public class QualifierAnnotationAutowireBeanFactoryTests {
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
private static @interface TestQualifier {
private @interface TestQualifier {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,8 @@ import javax.lang.model.element.Element;
/**
* Provide the list of stereotypes that match an {@link Element}.
* If an element has one more stereotypes, it is referenced in the index
*
* <p>If an element has one or more stereotypes, it is referenced in the index
* of candidate components and each stereotype can be queried individually.
*
* @author Stephane Nicoll
@@ -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.
@@ -58,6 +58,8 @@ import org.springframework.lang.Nullable;
* @since 1.1
* @see SchedulerFactoryBean#setDataSource
* @see SchedulerFactoryBean#setNonTransactionalDataSource
* @see SchedulerFactoryBean#getConfigTimeDataSource()
* @see SchedulerFactoryBean#getConfigTimeNonTransactionalDataSource()
* @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
* @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
*/
@@ -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.
@@ -313,6 +313,7 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
* If set, this will override corresponding settings in Quartz properties.
* <p>Note: If this is set, the Quartz settings should not define
* a job store "dataSource" to avoid meaningless double configuration.
* Also, do not define a "org.quartz.jobStore.class" property at all.
* <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
* It is therefore strongly recommended to perform all operations on
* the Scheduler within Spring-managed (or plain JTA) transactions.
+1 -1
View File
@@ -34,8 +34,8 @@ dependencies {
testCompile("org.codehaus.groovy:groovy-test")
testCompile("org.codehaus.groovy:groovy-xml")
testCompile("org.apache.commons:commons-pool2")
testCompile("javax.inject:javax.inject-tck")
testCompile("org.awaitility:awaitility")
testCompile("javax.inject:javax.inject-tck")
testRuntime("javax.xml.bind:jaxb-api")
testRuntime("org.glassfish:javax.el")
// Substitute for javax.management:jmxremote_optional:1.0.1_04 (not available on Maven Central)
@@ -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.
@@ -244,17 +244,21 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private static void registerCacheAspect(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME)) {
Object eleSource = parserContext.extractSource(element);
RootBeanDefinition def = new RootBeanDefinition();
def.setBeanClassName(JCACHE_ASPECT_CLASS_NAME);
def.setFactoryMethodName("aspectOf");
BeanDefinition sourceDef = createJCacheOperationSourceBeanDefinition(element, eleSource);
String sourceName =
parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
def.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
Object source = parserContext.extractSource(element);
parserContext.registerBeanComponent(new BeanComponentDefinition(sourceDef, sourceName));
parserContext.registerBeanComponent(new BeanComponentDefinition(def, CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME));
BeanDefinition cacheOperationSourceDef = createJCacheOperationSourceBeanDefinition(element, source);
String cacheOperationSourceName = parserContext.getReaderContext().registerWithGeneratedName(cacheOperationSourceDef);
RootBeanDefinition jcacheAspectDef = new RootBeanDefinition();
jcacheAspectDef.setBeanClassName(JCACHE_ASPECT_CLASS_NAME);
jcacheAspectDef.setFactoryMethodName("aspectOf");
jcacheAspectDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(cacheOperationSourceName));
parserContext.getRegistry().registerBeanDefinition(CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME, jcacheAspectDef);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
compositeDef.addNestedComponent(new BeanComponentDefinition(cacheOperationSourceDef, cacheOperationSourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(jcacheAspectDef, CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME));
parserContext.registerComponent(compositeDef);
}
}
@@ -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.
@@ -16,11 +16,12 @@
package org.springframework.context.annotation;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -93,9 +94,9 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> includeFilters = new ArrayList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new ArrayList<>();
@Nullable
private Environment environment;
@@ -424,40 +425,38 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
else {
catch (FileNotFoundException ex) {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
catch (IOException 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.
@@ -332,7 +332,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
@@ -352,7 +352,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
return metadata;
}
private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {
private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
@@ -464,6 +464,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
public void releaseTarget(Object target) {
}
};
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
if (element.lookupType.isInterface()) {
@@ -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.
@@ -73,7 +73,7 @@ class ComponentScanAnnotationParser {
}
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
@@ -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.
@@ -453,8 +453,8 @@ class ConfigurationClassEnhancer {
* instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
* it will not be proxied. This too is aligned with the way XML configuration works.
*/
private Object enhanceFactoryBean(final Object factoryBean, Class<?> exposedType,
final ConfigurableBeanFactory beanFactory, final String beanName) {
private Object enhanceFactoryBean(Object factoryBean, Class<?> exposedType,
ConfigurableBeanFactory beanFactory, String beanName) {
try {
Class<?> clazz = factoryBean.getClass();
@@ -489,8 +489,8 @@ class ConfigurationClassEnhancer {
return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);
}
private Object createInterfaceProxyForFactoryBean(final Object factoryBean, Class<?> interfaceType,
final ConfigurableBeanFactory beanFactory, final String beanName) {
private Object createInterfaceProxyForFactoryBean(Object factoryBean, Class<?> interfaceType,
ConfigurableBeanFactory beanFactory, String beanName) {
return Proxy.newProxyInstance(
factoryBean.getClass().getClassLoader(), new Class<?>[] {interfaceType},
@@ -502,8 +502,8 @@ class ConfigurationClassEnhancer {
});
}
private Object createCglibProxyForFactoryBean(final Object factoryBean,
final ConfigurableBeanFactory beanFactory, final String beanName) {
private Object createCglibProxyForFactoryBean(Object factoryBean,
ConfigurableBeanFactory beanFactory, String beanName) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(factoryBean.getClass());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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.
@@ -47,6 +47,11 @@ import java.lang.annotation.Target;
* or {@link javax.inject.Inject}: In that context, it leads to the creation of a
* lazy-resolution proxy for all affected dependencies, as an alternative to using
* {@link org.springframework.beans.factory.ObjectFactory} or {@link javax.inject.Provider}.
* Please note that such a lazy-resolution proxy will always be injected; if the target
* dependency does not exist, you will only be able to find out through an exception on
* invocation. As a consequence, such an injection point results in unintuitive behavior
* for optional dependencies. For a programmatic equivalent, allowing for lazy references
* with more sophistication, consider {@link org.springframework.beans.factory.ObjectProvider}.
*
* @author Chris Beams
* @author Juergen Hoeller
@@ -83,6 +83,7 @@ import org.springframework.core.annotation.AliasFor;
* @author Sam Brannen
* @since 4.2
* @see EventListenerMethodProcessor
* @see org.springframework.transaction.event.TransactionalEventListener
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@@ -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.
@@ -66,7 +66,8 @@ public class ConcurrentModel extends ConcurrentHashMap<String, Object> implement
@Override
public Object put(String key, Object value) {
@Nullable
public Object put(String key, @Nullable Object value) {
if (value != null) {
return super.put(key, value);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,18 +51,20 @@ import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* Binder that allows for setting property values onto a target object,
* including support for validation and binding result analysis.
* The binding process can be customized through specifying allowed fields,
* Binder that allows for setting property values on a target object, including
* support for validation and binding result analysis.
*
* <p>The binding process can be customized by specifying allowed field patterns,
* required fields, custom editors, etc.
*
* <p>Note that there are potential security implications in failing to set an array
* of allowed fields. In the case of HTTP form POST data for example, malicious clients
* can attempt to subvert an application by supplying values for fields or properties
* that do not exist on the form. In some cases this could lead to illegal data being
* set on command objects <i>or their nested objects</i>. For this reason, it is
* <b>highly recommended to specify the {@link #setAllowedFields allowedFields} property</b>
* on the DataBinder.
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>The binding results can be examined via the {@link BindingResult} interface,
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
@@ -96,6 +98,7 @@ import org.springframework.util.StringUtils;
* @author Rob Harrop
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Sam Brannen
* @see #setAllowedFields
* @see #setRequiredFields
* @see #registerCustomEditor
@@ -415,13 +418,21 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Register fields that should be allowed for binding. Default is all
* fields. Restrict this for example to avoid unwanted modifications
* by malicious users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
* can be implemented by overriding the {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> fields.
* @param allowedFields array of field names
* Register field patterns that should be allowed for binding.
* <p>Default is all fields.
* <p>Restrict this for example to avoid unwanted modifications by malicious
* users when binding HTTP request parameters.
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores allowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. Subclasses which override this method must therefore take this into
* account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> field patterns.
* @param allowedFields array of allowed field patterns
* @see #setDisallowedFields
* @see #isAllowed(String)
*/
@@ -430,8 +441,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Return the fields that should be allowed for binding.
* @return array of field names
* Return the field patterns that should be allowed for binding.
* @return array of allowed field patterns
* @see #setAllowedFields(String...)
*/
@Nullable
public String[] getAllowedFields() {
@@ -439,23 +451,44 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Register fields that should <i>not</i> be allowed for binding. Default is none.
* Mark fields as disallowed for example to avoid unwanted modifications
* by malicious users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
* can be implemented by overriding the {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> fields.
* @param disallowedFields array of field names
* Register field patterns that should <i>not</i> be allowed for binding.
* <p>Default is none.
* <p>Mark fields as disallowed, for example to avoid unwanted
* modifications by malicious users when binding HTTP request parameters.
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores disallowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. As of Spring Framework 5.2.21, the default implementation also transforms
* disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
* support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
* which override this method must therefore take both of these transformations
* into account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
* @param disallowedFields array of disallowed field patterns
* @see #setAllowedFields
* @see #isAllowed(String)
*/
public void setDisallowedFields(@Nullable String... disallowedFields) {
this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
if (disallowedFields == null) {
this.disallowedFields = null;
}
else {
String[] fieldPatterns = new String[disallowedFields.length];
for (int i = 0; i < fieldPatterns.length; i++) {
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]).toLowerCase();
}
this.disallowedFields = fieldPatterns;
}
}
/**
* Return the fields that should <i>not</i> be allowed for binding.
* @return array of field names
* Return the field patterns that should <i>not</i> be allowed for binding.
* @return array of disallowed field patterns
* @see #setDisallowedFields(String...)
*/
@Nullable
public String[] getDisallowedFields() {
@@ -767,15 +800,20 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Return if the given field is allowed for binding.
* Invoked for each passed-in property value.
* <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,
* as well as direct equality, in the specified lists of allowed fields and
* disallowed fields. A field matching a disallowed pattern will not be accepted
* even if it also happens to match a pattern in the allowed list.
* <p>Can be overridden in subclasses.
* Determine if the given field is allowed for binding.
* <p>Invoked for each passed-in property value.
* <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality, in the configured lists of allowed field patterns
* and disallowed field patterns.
* <p>Matching against allowed field patterns is case-sensitive; whereas,
* matching against disallowed field patterns is case-insensitive.
* <p>A field matching a disallowed pattern will not be accepted even if it
* also happens to match a pattern in the allowed list.
* <p>Can be overridden in subclasses, but care must be taken to honor the
* aforementioned contract.
* @param field the field to check
* @return if the field is allowed
* @return {@code true} if the field is allowed
* @see #setAllowedFields
* @see #setDisallowedFields
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
@@ -784,7 +822,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
String[] allowed = getAllowedFields();
String[] disallowed = getDisallowedFields();
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase())));
}
/**
@@ -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.
@@ -18,6 +18,7 @@ package org.springframework.validation.support;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.ui.ConcurrentModel;
import org.springframework.validation.BindingResult;
@@ -36,17 +37,19 @@ import org.springframework.validation.BindingResult;
* @author Rossen Stoyanchev
* @since 5.0
* @see BindingResult
* @see BindingAwareModelMap
*/
@SuppressWarnings("serial")
public class BindingAwareConcurrentModel extends ConcurrentModel {
@Override
public Object put(String key, Object value) {
@Nullable
public Object put(String key, @Nullable Object value) {
removeBindingResultIfNecessary(key, value);
return super.put(key, value);
}
private void removeBindingResultIfNecessary(String key, Object value) {
private void removeBindingResultIfNecessary(String key, @Nullable Object value) {
if (!key.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
String resultKey = BindingResult.MODEL_KEY_PREFIX + key;
BindingResult result = (BindingResult) get(resultKey);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -18,6 +18,7 @@ package org.springframework.validation.support;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.validation.BindingResult;
@@ -39,7 +40,7 @@ import org.springframework.validation.BindingResult;
public class BindingAwareModelMap extends ExtendedModelMap {
@Override
public Object put(String key, Object value) {
public Object put(String key, @Nullable Object value) {
removeBindingResultIfNecessary(key, value);
return super.put(key, value);
}
@@ -50,7 +51,7 @@ public class BindingAwareModelMap extends ExtendedModelMap {
super.putAll(map);
}
private void removeBindingResultIfNecessary(Object key, Object value) {
private void removeBindingResultIfNecessary(Object key, @Nullable Object value) {
if (key instanceof String) {
String attributeName = (String) key;
if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
@@ -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.
@@ -65,10 +65,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
/**
* @since 13.03.2003
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 13.03.2003
*/
public class ProxyFactoryBeanTests {
@@ -631,20 +631,50 @@ public class ProxyFactoryBeanTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(FROZEN_CONTEXT, CLASS));
Advised advised = (Advised)bf.getBean("frozen");
Advised advised = (Advised) bf.getBean("frozen");
assertThat(advised.isFrozen()).as("The proxy should be frozen").isTrue();
}
@Test
public void testDetectsInterfaces() throws Exception {
public void testDetectsInterfaces() {
ProxyFactoryBean fb = new ProxyFactoryBean();
fb.setTarget(new TestBean());
fb.addAdvice(new DebugInterceptor());
fb.setBeanFactory(new DefaultListableBeanFactory());
ITestBean proxy = (ITestBean) fb.getObject();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
}
@Test
public void testWithInterceptorNames() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerSingleton("debug", new DebugInterceptor());
ProxyFactoryBean fb = new ProxyFactoryBean();
fb.setTarget(new TestBean());
fb.setInterceptorNames("debug");
fb.setBeanFactory(bf);
Advised proxy = (Advised) fb.getObject();
assertThat(proxy.getAdvisors().length).isEqualTo(1);
}
@Test
public void testWithLateInterceptorNames() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerSingleton("debug", new DebugInterceptor());
ProxyFactoryBean fb = new ProxyFactoryBean();
fb.setTarget(new TestBean());
fb.setBeanFactory(bf);
fb.getObject();
fb.setInterceptorNames("debug");
Advised proxy = (Advised) fb.getObject();
assertThat(proxy.getAdvisors().length).isEqualTo(1);
}
/**
* Fires only on void methods. Saves list of methods intercepted.
@@ -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.
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanFactory;
@@ -219,7 +220,7 @@ public class AutoProxyCreatorTests {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("proxyFactoryBean", "false");
sac.registerSingleton("testAutoProxyCreator", TestAutoProxyCreator.class, pvs);
sac.registerSingleton("testAutoProxyCreator", IntroductionTestAutoProxyCreator.class, pvs);
sac.registerSingleton("noInterfaces", NoInterfaces.class);
sac.registerSingleton("containerCallbackInterfacesOnly", ContainerCallbackInterfacesOnly.class);
@@ -248,9 +249,9 @@ public class AutoProxyCreatorTests {
singletonNoInterceptor.getName();
assertThat(tapc.testInterceptor.nrOfInvocations).isEqualTo(0);
singletonToBeProxied.getAge();
assertThat(tapc.testInterceptor.nrOfInvocations).isEqualTo(1);
prototypeToBeProxied.getSpouse();
assertThat(tapc.testInterceptor.nrOfInvocations).isEqualTo(2);
prototypeToBeProxied.getSpouse();
assertThat(tapc.testInterceptor.nrOfInvocations).isEqualTo(4);
}
@Test
@@ -404,7 +405,7 @@ public class AutoProxyCreatorTests {
else if (name.endsWith("ToBeProxied")) {
boolean isFactoryBean = FactoryBean.class.isAssignableFrom(beanClass);
if ((this.proxyFactoryBean && isFactoryBean) || (this.proxyObject && !isFactoryBean)) {
return new Object[] {this.testInterceptor};
return getAdvicesAndAdvisors();
}
else {
return DO_NOT_PROXY;
@@ -414,6 +415,10 @@ public class AutoProxyCreatorTests {
return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
}
}
protected Object[] getAdvicesAndAdvisors() {
return new Object[] {this.testInterceptor};
}
}
@@ -426,6 +431,17 @@ public class AutoProxyCreatorTests {
}
@SuppressWarnings("serial")
public static class IntroductionTestAutoProxyCreator extends TestAutoProxyCreator {
protected Object[] getAdvicesAndAdvisors() {
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(this.testInterceptor);
advisor.addInterface(Serializable.class);
return new Object[] {this.testInterceptor, advisor};
}
}
/**
* Interceptor that counts the number of non-finalize method calls.
*/
@@ -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.
@@ -47,8 +47,9 @@ import static org.springframework.util.ClassUtils.convertClassNameToResourcePath
public class QualifierAnnotationTests {
private static final String CLASSNAME = QualifierAnnotationTests.class.getName();
private static final String CONFIG_LOCATION =
format("classpath:%s-context.xml", convertClassNameToResourcePath(CLASSNAME));
format("classpath:%s-context.xml", convertClassNameToResourcePath(CLASSNAME));
@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.
@@ -25,7 +25,6 @@ import org.springframework.beans.testfixture.beans.TestBean;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests cornering the issue reported in SPR-8080. If the product of a @Bean method
* was @Autowired into a configuration class while at the same time the declaring
@@ -34,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* 'currently in creation' status of the autowired bean and result in creating multiple
* instances of the given @Bean, violating container scoping / singleton semantics.
*
* This is resolved through no longer relying on 'currently in creation' status, but
* <p>This is resolved through no longer relying on 'currently in creation' status, but
* rather on a thread local that informs the enhanced bean method implementation whether
* the factory is the caller or not.
*
@@ -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.
@@ -26,7 +26,6 @@ import org.springframework.context.support.GenericXmlApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Chris Beams
* @author Juergen Hoeller
@@ -47,6 +46,7 @@ public class DestroyMethodInferenceTests {
WithLocalShutdownMethod c7 = ctx.getBean("c7", WithLocalShutdownMethod.class);
WithInheritedCloseMethod c8 = ctx.getBean("c8", WithInheritedCloseMethod.class);
WithDisposableBean c9 = ctx.getBean("c9", WithDisposableBean.class);
WithAutoCloseable c10 = ctx.getBean("c10", WithAutoCloseable.class);
assertThat(c0.closed).as("c0").isFalse();
assertThat(c1.closed).as("c1").isFalse();
@@ -58,6 +58,8 @@ public class DestroyMethodInferenceTests {
assertThat(c7.closed).as("c7").isFalse();
assertThat(c8.closed).as("c8").isFalse();
assertThat(c9.closed).as("c9").isFalse();
assertThat(c10.closed).as("c10").isFalse();
ctx.close();
assertThat(c0.closed).as("c0").isTrue();
assertThat(c1.closed).as("c1").isTrue();
@@ -69,6 +71,7 @@ public class DestroyMethodInferenceTests {
assertThat(c7.closed).as("c7").isTrue();
assertThat(c8.closed).as("c8").isFalse();
assertThat(c9.closed).as("c9").isTrue();
assertThat(c10.closed).as("c10").isTrue();
}
@Test
@@ -80,21 +83,29 @@ public class DestroyMethodInferenceTests {
WithLocalCloseMethod x3 = ctx.getBean("x3", WithLocalCloseMethod.class);
WithNoCloseMethod x4 = ctx.getBean("x4", WithNoCloseMethod.class);
WithInheritedCloseMethod x8 = ctx.getBean("x8", WithInheritedCloseMethod.class);
WithDisposableBean x9 = ctx.getBean("x9", WithDisposableBean.class);
WithAutoCloseable x10 = ctx.getBean("x10", WithAutoCloseable.class);
assertThat(x1.closed).isFalse();
assertThat(x2.closed).isFalse();
assertThat(x3.closed).isFalse();
assertThat(x4.closed).isFalse();
assertThat(x8.closed).isFalse();
assertThat(x9.closed).isFalse();
assertThat(x10.closed).isFalse();
ctx.close();
assertThat(x1.closed).isFalse();
assertThat(x2.closed).isTrue();
assertThat(x3.closed).isTrue();
assertThat(x4.closed).isFalse();
assertThat(x8.closed).isFalse();
assertThat(x9.closed).isTrue();
assertThat(x10.closed).isTrue();
}
@Configuration
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean(destroyMethod = "explicitClose")
@@ -155,6 +166,11 @@ public class DestroyMethodInferenceTests {
public WithDisposableBean c9() {
return new WithDisposableBean();
}
@Bean
public WithAutoCloseable c10() {
return new WithAutoCloseable();
}
}
@@ -189,17 +205,6 @@ public class DestroyMethodInferenceTests {
}
static class WithDisposableBean implements DisposableBean {
boolean closed = false;
@Override
public void destroy() {
closed = true;
}
}
static class WithNoCloseMethod {
boolean closed = false;
@@ -215,4 +220,26 @@ public class DestroyMethodInferenceTests {
}
}
static class WithDisposableBean implements DisposableBean {
boolean closed = false;
@Override
public void destroy() {
closed = true;
}
}
static class WithAutoCloseable implements AutoCloseable {
boolean closed = false;
@Override
public void close() {
closed = true;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,23 +64,26 @@ import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.tests.sample.beans.BeanWithObjectProperty;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.entry;
/**
* Unit tests for {@link DataBinder}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Kazuki Shimizu
* @author Sam Brannen
*/
public class DataBinderTests {
class DataBinderTests {
@Test
public void testBindingNoErrors() throws BindException {
void bindingNoErrors() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -110,12 +113,11 @@ public class DataBinderTests {
assertThat(ex).isEqualTo(binder.getBindingResult());
other.reject("xxx");
boolean condition = !other.equals(binder.getBindingResult());
assertThat(condition).isTrue();
assertThat(other).isNotEqualTo(binder.getBindingResult());
}
@Test
public void testBindingWithDefaultConversionNoErrors() throws BindException {
void bindingWithDefaultConversionNoErrors() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -131,7 +133,7 @@ public class DataBinderTests {
}
@Test
public void testNestedBindingWithDefaultConversionNoErrors() throws BindException {
void nestedBindingWithDefaultConversionNoErrors() throws BindException {
TestBean rod = new TestBean(new TestBean());
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -147,7 +149,7 @@ public class DataBinderTests {
}
@Test
public void testBindingNoErrorsNotIgnoreUnknown() {
void bindingNoErrorsNotIgnoreUnknown() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setIgnoreUnknownFields(false);
@@ -160,7 +162,7 @@ public class DataBinderTests {
}
@Test
public void testBindingNoErrorsWithInvalidField() {
void bindingNoErrorsWithInvalidField() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -171,7 +173,7 @@ public class DataBinderTests {
}
@Test
public void testBindingNoErrorsWithIgnoreInvalid() {
void bindingNoErrorsWithIgnoreInvalid() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setIgnoreInvalidFields(true);
@@ -180,10 +182,14 @@ public class DataBinderTests {
pvs.add("spouse.age", 32);
binder.bind(pvs);
binder.close();
assertThat(rod.getName()).isEqualTo("Rod");
assertThat(rod.getSpouse()).isNull();
}
@Test
public void testBindingWithErrors() {
void bindingWithErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -245,7 +251,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithSystemFieldError() {
void bindingWithSystemFieldError() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -257,7 +263,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithErrorsAndCustomEditors() {
void bindingWithErrorsAndCustomEditors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.registerCustomEditor(String.class, "touchy", new PropertyEditorSupport() {
@@ -325,7 +331,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithCustomEditorOnObjectField() {
void bindingWithCustomEditorOnObjectField() {
BeanWithObjectProperty tb = new BeanWithObjectProperty();
DataBinder binder = new DataBinder(tb);
binder.registerCustomEditor(Integer.class, "object", new CustomNumberEditor(Integer.class, true));
@@ -336,7 +342,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithFormatter() {
void bindingWithFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -368,7 +374,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithFormatter() {
void bindingErrorWithFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -391,7 +397,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithParseExceptionFromFormatter() {
void bindingErrorWithParseExceptionFromFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -419,7 +425,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithRuntimeExceptionFromFormatter() {
void bindingErrorWithRuntimeExceptionFromFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -447,7 +453,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithFormatterAgainstList() {
void bindingWithFormatterAgainstList() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -469,7 +475,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithFormatterAgainstList() {
void bindingErrorWithFormatterAgainstList() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -492,7 +498,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithFormatterAgainstFields() {
void bindingWithFormatterAgainstFields() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -525,7 +531,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithFormatterAgainstFields() {
void bindingErrorWithFormatterAgainstFields() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.initDirectFieldAccess();
@@ -549,7 +555,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithCustomFormatter() {
void bindingWithCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.addCustomFormatter(new NumberStyleFormatter(), Float.class);
@@ -578,7 +584,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithCustomFormatter() {
void bindingErrorWithCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.addCustomFormatter(new NumberStyleFormatter());
@@ -599,7 +605,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithParseExceptionFromCustomFormatter() {
void bindingErrorWithParseExceptionFromCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
@@ -624,7 +630,7 @@ public class DataBinderTests {
}
@Test
public void testBindingErrorWithRuntimeExceptionFromCustomFormatter() {
void bindingErrorWithRuntimeExceptionFromCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
@@ -649,7 +655,7 @@ public class DataBinderTests {
}
@Test
public void testConversionWithInappropriateStringEditor() {
void conversionWithInappropriateStringEditor() {
DataBinder dataBinder = new DataBinder(null);
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
dataBinder.setConversionService(conversionService);
@@ -662,7 +668,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithAllowedFields() throws BindException {
void bindingWithAllowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "myparam");
@@ -672,30 +678,32 @@ public class DataBinderTests {
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
}
@Test
public void testBindingWithDisallowedFields() throws BindException {
void bindingWithDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setDisallowedFields("age");
binder.setDisallowedFields(" ", "\t", "favouriteColour", null, "age");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
pvs.add("favouriteColour", "BLUE");
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields.length).isEqualTo(1);
assertThat(disallowedFields[0]).isEqualTo("age");
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
assertThat(rod.getFavouriteColour()).as("did not change favourite colour").isNull();
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactlyInAnyOrder("age", "favouriteColour");
}
@Test
public void testBindingWithAllowedAndDisallowedFields() throws BindException {
void bindingWithAllowedAndDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "myparam");
@@ -706,34 +714,32 @@ public class DataBinderTests {
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields.length).isEqualTo(1);
assertThat(disallowedFields[0]).isEqualTo("age");
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
}
@Test
public void testBindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
void bindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "age");
binder.setDisallowedFields("age");
binder.setDisallowedFields("AGE");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields.length).isEqualTo(1);
assertThat(disallowedFields[0]).isEqualTo("age");
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
}
@Test
public void testBindingWithAllowedFieldsUsingAsterisks() throws BindException {
void bindingWithAllowedFieldsUsingAsterisks() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setAllowedFields("nam*", "*ouchy");
@@ -760,11 +766,11 @@ public class DataBinderTests {
}
@Test
public void testBindingWithAllowedAndDisallowedMapFields() throws BindException {
void bindingWithAllowedAndDisallowedMapFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("someMap[key1]", "someMap[key2]");
binder.setDisallowedFields("someMap['key3']", "someMap[key4]");
binder.setDisallowedFields("someMap['KEY3']", "SomeMap[key4]");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("someMap[key1]", "value1");
@@ -774,21 +780,18 @@ public class DataBinderTests {
binder.bind(pvs);
binder.close();
assertThat(rod.getSomeMap().get("key1")).isEqualTo("value1");
assertThat(rod.getSomeMap().get("key2")).isEqualTo("value2");
assertThat(rod.getSomeMap().get("key3")).isNull();
assertThat(rod.getSomeMap().get("key4")).isNull();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields.length).isEqualTo(2);
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key3]")).isTrue();
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key4]")).isTrue();
@SuppressWarnings("unchecked")
Map<String, String> someMap = (Map<String, String>) rod.getSomeMap();
assertThat(someMap).containsOnly(entry("key1", "value1"), entry("key2", "value2"));
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("someMap[key3]", "someMap[key4]");
}
/**
* Tests for required field, both null, non-existing and empty strings.
*/
@Test
public void testBindingWithRequiredFields() {
void bindingWithRequiredFields() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -819,7 +822,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithRequiredMapFields() {
void bindingWithRequiredMapFields() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -839,7 +842,7 @@ public class DataBinderTests {
}
@Test
public void testBindingWithNestedObjectCreation() {
void bindingWithNestedObjectCreation() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "person");
@@ -860,7 +863,7 @@ public class DataBinderTests {
}
@Test
public void testCustomEditorWithOldValueAccess() {
void customEditorWithOldValueAccess() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -885,7 +888,7 @@ public class DataBinderTests {
}
@Test
public void testCustomEditorForSingleProperty() {
void customEditorForSingleProperty() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
DataBinder binder = new DataBinder(tb, "tb");
@@ -925,7 +928,7 @@ public class DataBinderTests {
}
@Test
public void testCustomEditorForPrimitiveProperty() {
void customEditorForPrimitiveProperty() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -949,7 +952,7 @@ public class DataBinderTests {
}
@Test
public void testCustomEditorForAllStringProperties() {
void customEditorForAllStringProperties() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -981,7 +984,7 @@ public class DataBinderTests {
}
@Test
public void testCustomFormatterForSingleProperty() {
void customFormatterForSingleProperty() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
DataBinder binder = new DataBinder(tb, "tb");
@@ -1021,7 +1024,7 @@ public class DataBinderTests {
}
@Test
public void testCustomFormatterForPrimitiveProperty() {
void customFormatterForPrimitiveProperty() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -1045,7 +1048,7 @@ public class DataBinderTests {
}
@Test
public void testCustomFormatterForAllStringProperties() {
void customFormatterForAllStringProperties() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -1077,7 +1080,7 @@ public class DataBinderTests {
}
@Test
public void testJavaBeanPropertyConventions() {
void javaBeanPropertyConventions() {
Book book = new Book();
DataBinder binder = new DataBinder(book);
@@ -1101,7 +1104,7 @@ public class DataBinderTests {
}
@Test
public void testOptionalProperty() {
void optionalProperty() {
OptionalHolder bean = new OptionalHolder();
DataBinder binder = new DataBinder(bean);
binder.setConversionService(new DefaultConversionService());
@@ -1122,7 +1125,7 @@ public class DataBinderTests {
}
@Test
public void testValidatorNoErrors() throws Exception {
void validatorNoErrors() throws Exception {
TestBean tb = new TestBean();
tb.setAge(33);
tb.setName("Rod");
@@ -1175,15 +1178,13 @@ public class DataBinderTests {
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
assertThat(errors.getErrorCount()).isEqualTo(1);
boolean condition1 = !errors.hasGlobalErrors();
assertThat(condition1).isTrue();
assertThat(errors.hasGlobalErrors()).isFalse();
assertThat(errors.getFieldErrorCount("age")).isEqualTo(1);
boolean condition = !errors.hasFieldErrors("name");
assertThat(condition).isTrue();
assertThat(errors.hasFieldErrors("name")).isFalse();
}
@Test
public void testValidatorWithErrors() {
void validatorWithErrors() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -1252,7 +1253,7 @@ public class DataBinderTests {
}
@Test
public void testValidatorWithErrorsAndCodesPrefix() {
void validatorWithErrorsAndCodesPrefix() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -1324,7 +1325,7 @@ public class DataBinderTests {
}
@Test
public void testValidatorWithNestedObjectNull() {
void validatorWithNestedObjectNull() {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator testValidator = new TestBeanValidator();
@@ -1343,7 +1344,7 @@ public class DataBinderTests {
}
@Test
public void testNestedValidatorWithoutNestedPath() {
void nestedValidatorWithoutNestedPath() {
TestBean tb = new TestBean();
tb.setName("XXX");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
@@ -1357,7 +1358,8 @@ public class DataBinderTests {
}
@Test
public void testBindingStringArrayToIntegerSet() {
@SuppressWarnings("unchecked")
void bindingStringArrayToIntegerSet() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class) {
@@ -1371,12 +1373,8 @@ public class DataBinderTests {
binder.bind(pvs);
assertThat(binder.getBindingResult().getFieldValue("set")).isEqualTo(tb.getSet());
boolean condition = tb.getSet() instanceof TreeSet;
assertThat(condition).isTrue();
assertThat(tb.getSet().size()).isEqualTo(3);
assertThat(tb.getSet().contains(10)).isTrue();
assertThat(tb.getSet().contains(20)).isTrue();
assertThat(tb.getSet().contains(30)).isTrue();
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
assertThat((Set<Integer>) tb.getSet()).containsExactly(10, 20, 30);
pvs = new MutablePropertyValues();
pvs.add("set", null);
@@ -1386,7 +1384,7 @@ public class DataBinderTests {
}
@Test
public void testBindingNullToEmptyCollection() {
void bindingNullToEmptyCollection() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class, true));
@@ -1394,13 +1392,12 @@ public class DataBinderTests {
pvs.add("set", null);
binder.bind(pvs);
boolean condition = tb.getSet() instanceof TreeSet;
assertThat(condition).isTrue();
assertThat(tb.getSet().isEmpty()).isTrue();
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
assertThat(tb.getSet()).isEmpty();
}
@Test
public void testBindingToIndexedField() {
void bindingToIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String.class, "array.name", new PropertyEditorSupport() {
@@ -1439,7 +1436,7 @@ public class DataBinderTests {
}
@Test
public void testBindingToNestedIndexedField() {
void bindingToNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1470,7 +1467,7 @@ public class DataBinderTests {
}
@Test
public void testEditorForNestedIndexedField() {
void editorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1496,7 +1493,7 @@ public class DataBinderTests {
}
@Test
public void testSpecificEditorForNestedIndexedField() {
void specificEditorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1522,7 +1519,7 @@ public class DataBinderTests {
}
@Test
public void testInnerSpecificEditorForNestedIndexedField() {
void innerSpecificEditorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1548,7 +1545,7 @@ public class DataBinderTests {
}
@Test
public void testDirectBindingToIndexedField() {
void directBindingToIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "array", new PropertyEditorSupport() {
@@ -1601,7 +1598,7 @@ public class DataBinderTests {
}
@Test
public void testDirectBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
void directBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "map[key0]", new PropertyEditorSupport() {
@@ -1632,7 +1629,7 @@ public class DataBinderTests {
}
@Test
public void testDirectBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
void directBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() {
@@ -1663,7 +1660,7 @@ public class DataBinderTests {
}
@Test
public void testCustomEditorWithSubclass() {
void customEditorWithSubclass() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, new PropertyEditorSupport() {
@@ -1697,7 +1694,7 @@ public class DataBinderTests {
}
@Test
public void testBindToStringArrayWithArrayEditor() {
void bindToStringArrayWithArrayEditor() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String[].class, "stringArray", new PropertyEditorSupport() {
@@ -1709,15 +1706,12 @@ public class DataBinderTests {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("stringArray", "a1-b2");
binder.bind(pvs);
boolean condition = !binder.getBindingResult().hasErrors();
assertThat(condition).isTrue();
assertThat(tb.getStringArray().length).isEqualTo(2);
assertThat(tb.getStringArray()[0]).isEqualTo("a1");
assertThat(tb.getStringArray()[1]).isEqualTo("b2");
assertThat(binder.getBindingResult().hasErrors()).isFalse();
assertThat(tb.getStringArray()).containsExactly("a1", "b2");
}
@Test
public void testBindToStringArrayWithComponentEditor() {
void bindToStringArrayWithComponentEditor() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() {
@@ -1729,15 +1723,14 @@ public class DataBinderTests {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("stringArray", new String[] {"a1", "b2"});
binder.bind(pvs);
boolean condition = !binder.getBindingResult().hasErrors();
assertThat(condition).isTrue();
assertThat(binder.getBindingResult().hasErrors()).isFalse();
assertThat(tb.getStringArray().length).isEqualTo(2);
assertThat(tb.getStringArray()[0]).isEqualTo("Xa1");
assertThat(tb.getStringArray()[1]).isEqualTo("Xb2");
}
@Test
public void testBindingErrors() {
void bindingErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1764,7 +1757,7 @@ public class DataBinderTests {
}
@Test
public void testAddAllErrors() {
void addAllErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1784,7 +1777,7 @@ public class DataBinderTests {
@Test
@SuppressWarnings("unchecked")
public void testBindingWithResortedList() {
void bindingWithResortedList() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1802,7 +1795,7 @@ public class DataBinderTests {
}
@Test
public void testRejectWithoutDefaultMessage() {
void rejectWithoutDefaultMessage() {
TestBean tb = new TestBean();
tb.setName("myName");
tb.setAge(99);
@@ -1820,7 +1813,7 @@ public class DataBinderTests {
}
@Test
public void testBindExceptionSerializable() throws Exception {
void bindExceptionSerializable() throws Exception {
SerializablePerson tb = new SerializablePerson();
tb.setName("myName");
tb.setAge(99);
@@ -1849,27 +1842,27 @@ public class DataBinderTests {
}
@Test
public void testTrackDisallowedFields() {
void trackDisallowedFields() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAllowedFields("name", "age");
String name = "Rob Harrop";
String beanName = "foobar";
int age = 42;
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("name", name);
mpvs.add("beanName", beanName);
mpvs.add("age", age);
mpvs.add("beanName", "foobar");
binder.bind(mpvs);
assertThat(testBean.getName()).isEqualTo(name);
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields.length).isEqualTo(1);
assertThat(disallowedFields[0]).isEqualTo("beanName");
assertThat(testBean.getAge()).isEqualTo(age);
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("beanName");
}
@Test
public void testAutoGrowWithinDefaultLimit() {
void autoGrowWithinDefaultLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
@@ -1881,7 +1874,7 @@ public class DataBinderTests {
}
@Test
public void testAutoGrowBeyondDefaultLimit() {
void autoGrowBeyondDefaultLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
@@ -1893,7 +1886,7 @@ public class DataBinderTests {
}
@Test
public void testAutoGrowWithinCustomLimit() {
void autoGrowWithinCustomLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
@@ -1906,7 +1899,7 @@ public class DataBinderTests {
}
@Test
public void testAutoGrowBeyondCustomLimit() {
void autoGrowBeyondCustomLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
@@ -1919,7 +1912,7 @@ public class DataBinderTests {
}
@Test
public void testNestedGrowingList() {
void nestedGrowingList() {
Form form = new Form();
DataBinder binder = new DataBinder(form, "form");
MutablePropertyValues mpv = new MutablePropertyValues();
@@ -1935,7 +1928,7 @@ public class DataBinderTests {
}
@Test
public void testFieldErrorAccessVariations() {
void fieldErrorAccessVariations() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
assertThat(binder.getBindingResult().getGlobalError()).isNull();
@@ -1956,7 +1949,7 @@ public class DataBinderTests {
}
@Test // SPR-14888
public void testSetAutoGrowCollectionLimit() {
void setAutoGrowCollectionLimit() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
binder.setAutoGrowCollectionLimit(257);
@@ -1970,7 +1963,7 @@ public class DataBinderTests {
}
@Test // SPR-14888
public void testSetAutoGrowCollectionLimitAfterInitialization() {
void setAutoGrowCollectionLimitAfterInitialization() {
DataBinder binder = new DataBinder(new BeanWithIntegerList());
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
assertThatIllegalStateException().isThrownBy(() ->
@@ -1979,7 +1972,7 @@ public class DataBinderTests {
}
@Test // SPR-15009
public void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
void setCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
@@ -1996,7 +1989,7 @@ public class DataBinderTests {
}
@Test // SPR-15009
public void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
void setCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
@@ -2011,7 +2004,7 @@ public class DataBinderTests {
}
@Test // SPR-15009
public void testSetCustomMessageCodesResolverAfterInitializeBindingResult() {
void setCustomMessageCodesResolverAfterInitializeBindingResult() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.initBeanPropertyAccess();
@@ -2026,7 +2019,7 @@ public class DataBinderTests {
}
@Test // SPR-15009
public void testSetMessageCodesResolverIsNullAfterInitializeBindingResult() {
void setMessageCodesResolverIsNullAfterInitializeBindingResult() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.initBeanPropertyAccess();
@@ -2040,8 +2033,7 @@ public class DataBinderTests {
}
@Test // SPR-15009
public void testCallSetMessageCodesResolverTwice() {
void callSetMessageCodesResolverTwice() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setMessageCodesResolver(new DefaultMessageCodesResolver());
@@ -15,10 +15,16 @@
class="org.springframework.context.annotation.DestroyMethodInferenceTests.WithInheritedCloseMethod"
destroy-method=""/>
<bean id="x9"
class="org.springframework.context.annotation.DestroyMethodInferenceTests.WithDisposableBean"
destroy-method=""/>
<bean id="x10"
class="org.springframework.context.annotation.DestroyMethodInferenceTests.WithAutoCloseable"/>
<beans default-destroy-method="(inferred)">
<bean id="x3"
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"/>
<bean id="x4"
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithNoCloseMethod"/>
</beans>
@@ -5,19 +5,19 @@
<bean id="publisher" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisher"/>
<bean id="publisherMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherMBean"/>
<bean id="publisherMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherMBean"/>
<bean id="publisherStandardMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherStandardMBean"/>
<bean id="publisherStandardMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherStandardMBean"/>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="spring:type=Publisher" value-ref="publisher"/>
<entry key="spring:type=PublisherMBean" value-ref="publisherMBean"/>
<entry key="spring:type=PublisherStandardMBean" value-ref="publisherStandardMBean"/>
<entry key="spring:type=PublisherMBean" value-ref="publisherMBean"/>
<entry key="spring:type=PublisherStandardMBean" value-ref="publisherStandardMBean"/>
</map>
</property>
<property name="server" ref="server"/>
<property name="server" ref="server"/>
</bean>
</beans>
+4 -4
View File
@@ -53,22 +53,22 @@ dependencies {
optional("io.reactivex:rxjava-reactive-streams")
optional("io.reactivex.rxjava2:rxjava")
optional("io.netty:netty-buffer")
testCompile("io.projectreactor:reactor-test")
testCompile("com.google.code.findbugs:jsr305")
testCompile(project(":kotlin-coroutines"))
testCompile("javax.annotation:javax.annotation-api")
testCompile("javax.xml.bind:jaxb-api")
testCompile("com.google.code.findbugs:jsr305")
testCompile("com.fasterxml.woodstox:woodstox-core")
testCompile("org.xmlunit:xmlunit-assertj")
testCompile("org.xmlunit:xmlunit-matchers")
testCompile(project(":kotlin-coroutines"))
testCompile("io.projectreactor:reactor-test")
testCompile("io.projectreactor.tools:blockhound")
testFixturesImplementation("io.projectreactor:reactor-test")
testFixturesImplementation("com.google.code.findbugs:jsr305")
testFixturesImplementation("org.junit.platform:junit-platform-launcher")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params")
testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("org.xmlunit:xmlunit-assertj")
testFixturesImplementation("io.projectreactor:reactor-test")
}
jar {
@@ -499,6 +499,7 @@ public class ReflectUtils {
ProtectionDomain protectionDomain, Class<?> contextClass) throws Exception {
Class c = null;
Throwable t = THROWABLE;
// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
if (contextClass != null && contextClass.getClassLoader() == loader &&
@@ -516,6 +517,7 @@ public class ReflectUtils {
// in case of plain LinkageError (class already defined)
// or IllegalArgumentException (class in different package):
// fall through to traditional ClassLoader.defineClass below
t = target;
}
catch (Throwable ex) {
throw new CodeGenerationException(ex);
@@ -543,6 +545,7 @@ public class ReflectUtils {
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new CodeGenerationException(ex);
}
t = ex;
}
}
@@ -564,7 +567,7 @@ public class ReflectUtils {
// No defineClass variant available at all?
if (c == null) {
throw new CodeGenerationException(THROWABLE);
throw new CodeGenerationException(t);
}
// Force static initializers to run.
@@ -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.
@@ -47,7 +47,7 @@ import org.springframework.util.ReflectionUtils;
*
* <p>By default, depending on classpath availability, adapters are registered
* for Reactor, RxJava 1, RxJava 2 types, {@link CompletableFuture}, Java 9+
* {@code Flow.Publisher} and Kotlin Coroutines {@code Deferred} and {@code Flow}.
* {@code Flow.Publisher}, and Kotlin Coroutines {@code Deferred} and {@code Flow}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
@@ -74,6 +74,10 @@ public class ReactiveAdapterRegistry {
boolean reactorRegistered = false;
if (ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader)) {
new ReactorRegistrar().registerAdapters(this);
if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) {
// Java 9+ Flow.Publisher
new ReactorJdkFlowAdapterRegistrar().registerAdapter(this);
}
reactorRegistered = true;
}
this.reactorPresent = reactorRegistered;
@@ -83,20 +87,12 @@ public class ReactiveAdapterRegistry {
ClassUtils.isPresent("rx.RxReactiveStreams", classLoader)) {
new RxJava1Registrar().registerAdapters(this);
}
// RxJava2
if (ClassUtils.isPresent("io.reactivex.Flowable", classLoader)) {
new RxJava2Registrar().registerAdapters(this);
}
// Java 9+ Flow.Publisher
if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) {
new ReactorJdkFlowAdapterRegistrar().registerAdapter(this);
}
// If not present, do nothing for the time being...
// We can fall back on "reactive-streams-flow-bridge" (once released)
// Coroutines
// Kotlin Coroutines
if (this.reactorPresent && ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader)) {
new CoroutinesRegistrar().registerAdapters(this);
}
@@ -226,6 +222,35 @@ public class ReactiveAdapterRegistry {
}
private static class ReactorJdkFlowAdapterRegistrar {
void registerAdapter(ReactiveAdapterRegistry registry) {
// Reflectively access optional JDK 9+ API (for runtime compatibility with JDK 8)
try {
String publisherName = "java.util.concurrent.Flow.Publisher";
Class<?> publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader());
String adapterName = "reactor.adapter.JdkFlowAdapter";
Class<?> flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader());
Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass);
Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class);
Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty());
registry.registerReactiveType(
ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow),
source -> (Publisher<?>) ReflectionUtils.invokeMethod(toFluxMethod, null, source),
publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher)
);
}
catch (Throwable ex) {
// Ignore
}
}
}
private static class RxJava1Registrar {
void registerAdapters(ReactiveAdapterRegistry registry) {
@@ -280,35 +305,6 @@ public class ReactiveAdapterRegistry {
}
private static class ReactorJdkFlowAdapterRegistrar {
void registerAdapter(ReactiveAdapterRegistry registry) {
// TODO: remove reflection when build requires JDK 9+
try {
String publisherName = "java.util.concurrent.Flow.Publisher";
Class<?> publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader());
String adapterName = "reactor.adapter.JdkFlowAdapter";
Class<?> flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader());
Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass);
Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class);
Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty());
registry.registerReactiveType(
ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow),
source -> (Publisher<?>) ReflectionUtils.invokeMethod(toFluxMethod, null, source),
publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher)
);
}
catch (Throwable ex) {
// Ignore
}
}
}
/**
* ReactiveAdapter variant that wraps adapted Publishers as {@link Flux} or
* {@link Mono} depending on {@link ReactiveTypeDescriptor#isMultiValue()}.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ import org.springframework.util.StringUtils;
*/
public final class Property {
private static Map<Property, Annotation[]> annotationCache = new ConcurrentReferenceHashMap<>();
private static final Map<Property, Annotation[]> annotationCache = new ConcurrentReferenceHashMap<>();
private final Class<?> objectType;
@@ -118,7 +118,7 @@ public final class Property {
}
// package private
// Package private
MethodParameter getMethodParameter() {
return this.methodParameter;
@@ -132,7 +132,7 @@ public final class Property {
}
// internal helpers
// Internal helpers
private String resolveName() {
if (this.readMethod != null) {
@@ -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.
@@ -88,7 +88,15 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
@Override
public boolean isReadable() {
try {
URL url = getURL();
return checkReadable(getURL());
}
catch (IOException ex) {
return false;
}
}
boolean checkReadable(URL url) {
try {
if (ResourceUtils.isFileURL(url)) {
// Proceed with file system resolution
File file = getFile();
@@ -142,6 +142,18 @@ public class ClassPathResource extends AbstractFileResolvingResource {
return (resolveURL() != null);
}
/**
* This implementation checks for the resolution of a resource URL upfront,
* then proceeding with {@link AbstractFileResolvingResource}'s length check.
* @see java.lang.ClassLoader#getResource(String)
* @see java.lang.Class#getResource(String)
*/
@Override
public boolean isReadable() {
URL url = resolveURL();
return (url != null && checkReadable(url));
}
/**
* Resolves a URL for the underlying class path resource.
* @return the resolved URL, or {@code null} if not resolvable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -48,8 +48,9 @@ public interface InputStreamSource {
* creating mail attachments. For such a use case, it is <i>required</i>
* that each {@code getInputStream()} call returns a fresh stream.
* @return the input stream for the underlying resource (must not be {@code null})
* @throws java.io.FileNotFoundException if the underlying resource doesn't exist
* @throws java.io.FileNotFoundException if the underlying resource does not exist
* @throws IOException if the content stream could not be opened
* @see Resource#isReadable()
*/
InputStream getInputStream() throws IOException;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -99,14 +99,14 @@ public interface Resource extends InputStreamSource {
/**
* Return a URL handle for this resource.
* @throws IOException if the resource cannot be resolved as URL,
* i.e. if the resource is not available as descriptor
* i.e. if the resource is not available as a descriptor
*/
URL getURL() throws IOException;
/**
* Return a URI handle for this resource.
* @throws IOException if the resource cannot be resolved as URI,
* i.e. if the resource is not available as descriptor
* i.e. if the resource is not available as a descriptor
* @since 2.5
*/
URI getURI() throws IOException;
@@ -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.
@@ -462,9 +462,9 @@ public class DefaultDataBuffer implements DataBuffer {
private void checkIndex(int index, int length) {
assertIndex(index >= 0, "index %d must be >= 0", index);
assertIndex(length >= 0, "length %d must be >= 0", index);
assertIndex(length >= 0, "length %d must be >= 0", length);
assertIndex(index <= this.capacity, "index %d must be <= %d", index, this.capacity);
assertIndex(length <= this.capacity, "length %d must be <= %d", index, this.capacity);
assertIndex(length <= this.capacity, "length %d must be <= %d", length, this.capacity);
}
private void assertIndex(boolean expression, String format, Object... args) {
@@ -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.
@@ -17,10 +17,12 @@
package org.springframework.core.log;
import java.util.function.Function;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Utility methods for formatting and logging messages.
@@ -35,31 +37,57 @@ import org.springframework.lang.Nullable;
*/
public abstract class LogFormatUtils {
private static final Pattern NEWLINE_PATTERN = Pattern.compile("[\n\r]");
private static final Pattern CONTROL_CHARACTER_PATTERN = Pattern.compile("\\p{Cc}");
/**
* Format the given value via {@code toString()}, quoting it if it is a
* {@link CharSequence}, and possibly truncating at 100 if limitLength is
* set to true.
* Convenience variant of {@link #formatValue(Object, int, boolean)} that
* limits the length of a log message to 100 characters and also replaces
* newline and control characters if {@code limitLength} is set to "true".
* @param value the value to format
* @param limitLength whether to truncate large formatted values (over 100)
* @param limitLength whether to truncate the value at a length of 100
* @return the formatted value
*/
public static String formatValue(@Nullable Object value, boolean limitLength) {
return formatValue(value, (limitLength ? 100 : -1), limitLength);
}
/**
* Format the given value via {@code toString()}, quoting it if it is a
* {@link CharSequence}, truncating at the specified {@code maxLength}, and
* compacting it into a single line when {@code replaceNewLines} is set.
* @param value the value to be formatted
* @param maxLength the max length, after which to truncate, or -1 for unlimited
* @param replaceNewlinesAndControlCharacters whether to replace newline and
* control characters with placeholders
* @return the formatted value
*/
public static String formatValue(
@Nullable Object value, int maxLength, boolean replaceNewlinesAndControlCharacters) {
if (value == null) {
return "";
}
String str;
String result;
try {
result = ObjectUtils.nullSafeToString(value);
}
catch (Throwable ex) {
result = ObjectUtils.nullSafeToString(ex);
}
if (maxLength != -1) {
result = (result.length() > maxLength ? result.substring(0, maxLength) + " (truncated)..." : result);
}
if (replaceNewlinesAndControlCharacters) {
result = NEWLINE_PATTERN.matcher(result).replaceAll("<EOL>");
result = CONTROL_CHARACTER_PATTERN.matcher(result).replaceAll("?");
}
if (value instanceof CharSequence) {
str = "\"" + value + "\"";
result = "\"" + result + "\"";
}
else {
try {
str = value.toString();
}
catch (Throwable ex) {
str = ex.toString();
}
}
return (limitLength && str.length() > 100 ? str.substring(0, 100) + " (truncated)..." : str);
return 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.
@@ -32,6 +32,7 @@ import org.springframework.lang.Nullable;
* ASM method visitor that creates {@link SimpleMethodMetadata}.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
*/
final class SimpleMethodMetadataReadingVisitor extends MethodVisitor {
@@ -144,14 +145,17 @@ final class SimpleMethodMetadataReadingVisitor extends MethodVisitor {
if (value == null) {
StringBuilder builder = new StringBuilder();
builder.append(this.declaringClassName);
builder.append(".");
builder.append('.');
builder.append(this.name);
Type[] argumentTypes = Type.getArgumentTypes(this.descriptor);
builder.append("(");
for (Type type : argumentTypes) {
builder.append(type.getClassName());
builder.append('(');
for (int i = 0; i < argumentTypes.length; i++) {
if (i != 0) {
builder.append(',');
}
builder.append(argumentTypes[i].getClassName());
}
builder.append(")");
builder.append(')');
value = builder.toString();
this.toStringValue = value;
}
@@ -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.
@@ -77,7 +77,7 @@ public class AntPathMatcher implements PathMatcher {
private static final int CACHE_TURNOFF_THRESHOLD = 65536;
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}");
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
private static final char[] WILDCARD_CHARS = { '*', '?', '{' };
@@ -641,7 +641,7 @@ public class AntPathMatcher implements PathMatcher {
*/
protected static class AntPathStringMatcher {
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}");
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
@@ -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.
@@ -88,13 +88,13 @@ public abstract class ClassUtils {
* Map with primitive wrapper type as key and corresponding primitive
* type as value, for example: Integer.class -> int.class.
*/
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap<>(8);
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap<>(9);
/**
* Map with primitive type as key and corresponding wrapper
* type as value, for example: int.class -> Integer.class.
*/
private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(8);
private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(9);
/**
* Map with primitive type name as key and corresponding primitive
@@ -1322,7 +1322,7 @@ public abstract class ClassUtils {
* Note that, despite being synthetic, bridge methods ({@link Method#isBridge()}) are considered
* as user-level methods since they are eventually pointing to a user-declared generic method.
* @param method the method to check
* @return {@code true} if the method can be considered as user-declared; [@code false} otherwise
* @return {@code true} if the method can be considered as user-declared; {@code false} otherwise
*/
public static boolean isUserLevelMethod(Method method) {
Assert.notNull(method, "Method must not be null");
@@ -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.
@@ -299,7 +299,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
public V remove(Object key) {
public V remove(@Nullable Object key) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
@Nullable
@@ -316,7 +316,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
@Override
public boolean remove(Object key, final Object value) {
public boolean remove(@Nullable Object key, final @Nullable Object value) {
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
@@ -333,7 +333,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
@Override
public boolean replace(K key, final V oldValue, final V newValue) {
public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) {
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
@@ -349,7 +349,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
public V replace(K key, final V value) {
public V replace(@Nullable K key, final @Nullable V value) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
@Nullable
@@ -474,7 +474,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
* The total number of references contained in this segment. This includes chained
* references and references that have been garbage collected but not purged.
*/
private final AtomicInteger count = new AtomicInteger(0);
private final AtomicInteger count = new AtomicInteger();
/**
* The threshold when resizing of the references should occur. When {@code count}
@@ -858,7 +858,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
Reference<K, V> ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
Entry<K, V> otherEntry = (ref != null ? ref.get() : null);
if (otherEntry != null) {
return ObjectUtils.nullSafeEquals(otherEntry.getValue(), otherEntry.getValue());
return ObjectUtils.nullSafeEquals(entry.getValue(), otherEntry.getValue());
}
}
return false;
@@ -966,6 +966,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
public void remove() {
Assert.state(this.last != null, "No element to remove");
ConcurrentReferenceHashMap.this.remove(this.last.getKey());
this.last = null;
}
}
@@ -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.
@@ -28,9 +28,11 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
/**
* Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
* {@code ${name}}. Using {@code PropertyPlaceholderHelper} these placeholders can be substituted for
* user-supplied values. <p> Values for substitution can be supplied using a {@link Properties} instance or
* Utility class for working with Strings that have placeholder values in them.
* A placeholder takes the form {@code ${name}}. Using {@code PropertyPlaceholderHelper}
* these placeholders can be substituted for user-supplied values.
*
* <p>Values for substitution can be supplied using a {@link Properties} instance or
* using a {@link PlaceholderResolver}.
*
* @author Juergen Hoeller
@@ -565,7 +565,7 @@ public abstract class StringUtils {
char[] chars = str.toCharArray();
chars[0] = updatedChar;
return new String(chars, 0, chars.length);
return new String(chars);
}
/**
@@ -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.
@@ -41,11 +41,13 @@ import org.springframework.util.comparator.NullSafeComparator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ConcurrentReferenceHashMap}.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
class ConcurrentReferenceHashMapTests {
@@ -466,6 +468,7 @@ class ConcurrentReferenceHashMapTests {
iterator.next();
iterator.next();
iterator.remove();
assertThatIllegalStateException().isThrownBy(iterator::remove);
iterator.next();
assertThat(iterator.hasNext()).isFalse();
assertThat(this.map).hasSize(2);
@@ -486,6 +489,26 @@ class ConcurrentReferenceHashMapTests {
assertThat(this.map.get(2)).isEqualTo("2b");
}
@Test
void containsViaEntrySet() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
Set<Map.Entry<Integer, String>> entrySet = this.map.entrySet();
Set<Map.Entry<Integer, String>> copy = new HashMap<>(this.map).entrySet();
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isTrue());
this.map.put(1, "A");
this.map.put(2, "B");
this.map.put(3, "C");
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isFalse());
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isTrue());
entrySet.clear();
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isFalse());
}
@Test
@Disabled("Intended for use during development only")
void shouldBeFasterThanSynchronizedMap() throws InterruptedException {
@@ -583,7 +606,7 @@ class ConcurrentReferenceHashMapTests {
}
// For testing we want more control of the hash
this.supplementalHash = super.getHash(o);
return o == null ? 0 : o.hashCode();
return (o != null ? o.hashCode() : 0);
}
public int getSupplementalHash() {
@@ -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.
@@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.util.StopWatch.TaskInfo;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -41,7 +40,7 @@ class StopWatchTests {
private static final long duration1 = 200;
private static final long duration2 = 100;
private static final long fudgeFactor = 50;
// private static final long fudgeFactor = 100;
private final StopWatch stopWatch = new StopWatch(ID);
@@ -78,18 +77,21 @@ class StopWatchTests {
assertThat(stopWatch.currentTaskName()).isEqualTo(name1);
stopWatch.stop();
assertThat(stopWatch.isRunning()).isFalse();
/* Flaky StopWatch time assertions...
assertThat(stopWatch.getLastTaskTimeNanos())
.as("last task time in nanoseconds for task #1")
.isGreaterThanOrEqualTo(millisToNanos(duration1 - fudgeFactor))
.isLessThanOrEqualTo(millisToNanos(duration1 + fudgeFactor));
.as("last task time in nanoseconds for task #1")
.isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration1 - fudgeFactor))
.isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration1 + fudgeFactor));
assertThat(stopWatch.getTotalTimeMillis())
.as("total time in milliseconds for task #1")
.isGreaterThanOrEqualTo(duration1 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + fudgeFactor);
.as("total time in milliseconds for task #1")
.isGreaterThanOrEqualTo(duration1 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + fudgeFactor);
assertThat(stopWatch.getTotalTimeSeconds())
.as("total time in seconds for task #1")
.isGreaterThanOrEqualTo((duration1 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + fudgeFactor) / 1000.0);
.as("total time in seconds for task #1")
.isGreaterThanOrEqualTo((duration1 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + fudgeFactor) / 1000.0);
*/
stopWatch.start(name2);
Thread.sleep(duration2);
@@ -97,18 +99,21 @@ class StopWatchTests {
assertThat(stopWatch.currentTaskName()).isEqualTo(name2);
stopWatch.stop();
assertThat(stopWatch.isRunning()).isFalse();
/* Flaky StopWatch time assertions...
assertThat(stopWatch.getLastTaskTimeNanos())
.as("last task time in nanoseconds for task #2")
.isGreaterThanOrEqualTo(millisToNanos(duration2))
.isLessThanOrEqualTo(millisToNanos(duration2 + fudgeFactor));
.as("last task time in nanoseconds for task #2")
.isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration2))
.isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration2 + fudgeFactor));
assertThat(stopWatch.getTotalTimeMillis())
.as("total time in milliseconds for tasks #1 and #2")
.isGreaterThanOrEqualTo(duration1 + duration2 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + duration2 + fudgeFactor);
.as("total time in milliseconds for tasks #1 and #2")
.isGreaterThanOrEqualTo(duration1 + duration2 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + duration2 + fudgeFactor);
assertThat(stopWatch.getTotalTimeSeconds())
.as("total time in seconds for task #2")
.isGreaterThanOrEqualTo((duration1 + duration2 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + duration2 + fudgeFactor) / 1000.0);
.as("total time in seconds for task #2")
.isGreaterThanOrEqualTo((duration1 + duration2 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + duration2 + fudgeFactor) / 1000.0);
*/
assertThat(stopWatch.getTaskCount()).isEqualTo(2);
assertThat(stopWatch.prettyPrint()).contains(name1, name2);
@@ -135,12 +140,8 @@ class StopWatchTests {
assertThat(stopWatch.prettyPrint()).contains("No task info kept");
assertThat(stopWatch.toString()).doesNotContain(name1, name2);
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(stopWatch::getTaskInfo)
.withMessage("Task info is not being kept!");
}
private static long millisToNanos(long duration) {
return MILLISECONDS.toNanos(duration);
.isThrownBy(stopWatch::getTaskInfo)
.withMessage("Task info is not being kept!");
}
}
@@ -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.
@@ -19,7 +19,7 @@ package org.springframework.expression;
/**
* A bean resolver can be registered with the evaluation context and will kick in
* for bean references: {@code @myBeanName} and {@code &myBeanName} expressions.
* The <tt>&</tt> variant syntax allows access to the factory bean where relevant.
* The {@code &} variant syntax allows access to the factory bean where relevant.
*
* @author Andy Clement
* @since 3.0.3
@@ -28,7 +28,7 @@ public interface BeanResolver {
/**
* Look up a bean by the given name and return a corresponding instance for it.
* For attempting access to a factory bean, the name needs a <tt>&</tt> prefix.
* For attempting access to a factory bean, the name needs a {@code &} prefix.
* @param context the current evaluation context
* @param beanName the name of the bean to look up
* @return an object representing the bean
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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,10 +40,11 @@ public interface TypeComparator {
* Compare two given objects.
* @param firstObject the first object
* @param secondObject the second object
* @return 0 if they are equal, <0 if the first is smaller than the second,
* or >0 if the first is larger than the second
* @return 0 if they are equal, a negative integer if the first is smaller than
* the second, or a positive integer if the first is larger than the second
* @throws EvaluationException if a problem occurs during comparison
* (or if they are not comparable in the first place)
* @see Comparable#compareTo
*/
int compare(@Nullable Object firstObject, @Nullable Object secondObject) throws EvaluationException;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import java.text.MessageFormat;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public enum SpelMessage {
@@ -255,7 +256,11 @@ public enum SpelMessage {
/** @since 4.3.17 */
FLAWED_PATTERN(Kind.ERROR, 1073,
"Failed to efficiently evaluate pattern ''{0}'': consider redesigning it");
"Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"),
/** @since 5.2.20 */
MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075,
"Array declares too many elements, exceeding the threshold of ''{0}''");
private final Kind kind;
@@ -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.
@@ -25,8 +25,8 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* Represents a bean reference to a type, for example <tt>@foo</tt> or <tt>@'foo.bar'</tt>.
* For a FactoryBean the syntax <tt>&foo</tt> can be used to access the factory itself.
* Represents a bean reference to a type, for example {@code @foo} or {@code @'foo.bar'}.
* For a FactoryBean the syntax {@code &foo} can be used to access the factory itself.
*
* @author Andy Clement
*/
@@ -64,13 +64,13 @@ public class BeanReference extends SpelNodeImpl {
public String toStringAST() {
StringBuilder sb = new StringBuilder();
if (!this.beanName.startsWith(FACTORY_BEAN_PREFIX)) {
sb.append("@");
sb.append('@');
}
if (!this.beanName.contains(".")) {
sb.append(this.beanName);
}
else {
sb.append("'").append(this.beanName).append("'");
sb.append('\'').append(this.beanName).append('\'');
}
return sb.toString();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,11 +53,19 @@ import org.springframework.util.Assert;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class ConstructorReference extends SpelNodeImpl {
private boolean isArrayConstructor = false;
/**
* Maximum number of elements permitted in an array declaration, applying
* to one-dimensional as well as multi-dimensional arrays.
* @since 5.2.20
*/
private static final int MAX_ARRAY_ELEMENTS = 256 * 1024; // 256K
private final boolean isArrayConstructor;
@Nullable
private SpelNodeImpl[] dimensions;
@@ -208,14 +216,14 @@ public class ConstructorReference extends SpelNodeImpl {
StringBuilder sb = new StringBuilder("new ");
int index = 0;
sb.append(getChild(index++).toStringAST());
sb.append("(");
sb.append('(');
for (int i = index; i < getChildCount(); i++) {
if (i > index) {
sb.append(",");
sb.append(',');
}
sb.append(getChild(i).toStringAST());
}
sb.append(")");
sb.append(')');
return sb.toString();
}
@@ -234,6 +242,7 @@ public class ConstructorReference extends SpelNodeImpl {
FormatHelper.formatClassNameForMessage(
intendedArrayType != null ? intendedArrayType.getClass() : null));
}
String type = (String) intendedArrayType;
Class<?> componentType;
TypeCode arrayTypeCode = TypeCode.forName(type);
@@ -243,7 +252,8 @@ public class ConstructorReference extends SpelNodeImpl {
else {
componentType = arrayTypeCode.getType();
}
Object newArray;
Object newArray = null;
if (!hasInitializer()) {
// Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension)
if (this.dimensions != null) {
@@ -252,30 +262,34 @@ public class ConstructorReference extends SpelNodeImpl {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION);
}
}
}
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
// Shortcut for 1 dimensional
if (this.dimensions.length == 1) {
TypedValue o = this.dimensions[0].getTypedValue(state);
int arraySize = ExpressionUtils.toInt(typeConverter, o);
newArray = Array.newInstance(componentType, arraySize);
}
else {
// Multi-dimensional - hold onto your hat!
int[] dims = new int[this.dimensions.length];
for (int d = 0; d < this.dimensions.length; d++) {
TypedValue o = this.dimensions[d].getTypedValue(state);
dims[d] = ExpressionUtils.toInt(typeConverter, o);
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
if (this.dimensions.length == 1) {
// Shortcut for 1-dimensional
TypedValue o = this.dimensions[0].getTypedValue(state);
int arraySize = ExpressionUtils.toInt(typeConverter, o);
checkNumElements(arraySize);
newArray = Array.newInstance(componentType, arraySize);
}
else {
// Multi-dimensional - hold onto your hat!
int[] dims = new int[this.dimensions.length];
long numElements = 1;
for (int d = 0; d < this.dimensions.length; d++) {
TypedValue o = this.dimensions[d].getTypedValue(state);
int arraySize = ExpressionUtils.toInt(typeConverter, o);
dims[d] = arraySize;
numElements *= arraySize;
checkNumElements(numElements);
}
newArray = Array.newInstance(componentType, dims);
}
newArray = Array.newInstance(componentType, dims);
}
}
else {
// There is an initializer
if (this.dimensions == null || this.dimensions.length > 1) {
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
// is not currently supported
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}})
// - this is not currently supported
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
}
@@ -326,6 +340,13 @@ public class ConstructorReference extends SpelNodeImpl {
return new TypedValue(newArray);
}
private void checkNumElements(long numElements) {
if (numElements >= MAX_ARRAY_ELEMENTS) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, MAX_ARRAY_ELEMENTS);
}
}
private void populateReferenceTypeArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer, Class<?> componentType) {
@@ -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,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Andy Clement
* @author Phillip Webb
* @author Sam Brannen
*/
public class MethodInvocationTests extends AbstractExpressionTests {
@@ -232,26 +233,54 @@ public class MethodInvocationTests extends AbstractExpressionTests {
@Test
public void testVarargsInvocation01() {
// Calling 'public int aVarargsMethod(String... strings)'
//evaluate("aVarargsMethod('a','b','c')", 3, Integer.class);
//evaluate("aVarargsMethod('a')", 1, Integer.class);
evaluate("aVarargsMethod()", 0, Integer.class);
evaluate("aVarargsMethod(1,2,3)", 3, Integer.class); // all need converting to strings
evaluate("aVarargsMethod(1)", 1, Integer.class); // needs string conversion
evaluate("aVarargsMethod(1,'a',3.0d)", 3, Integer.class); // first and last need conversion
// evaluate("aVarargsMethod(new String[]{'a','b','c'})", 3, Integer.class);
// Calling 'public String aVarargsMethod(String... strings)'
evaluate("aVarargsMethod('a','b','c')", "[a, b, c]", String.class);
evaluate("aVarargsMethod('a')", "[a]", String.class);
evaluate("aVarargsMethod()", "[]", String.class);
evaluate("aVarargsMethod(1,2,3)", "[1, 2, 3]", String.class); // all need converting to strings
evaluate("aVarargsMethod(1)", "[1]", String.class); // needs string conversion
evaluate("aVarargsMethod(1,'a',3.0d)", "[1, a, 3.0]", String.class); // first and last need conversion
evaluate("aVarargsMethod(new String[]{'a','b','c'})", "[a, b, c]", String.class);
evaluate("aVarargsMethod(new String[]{})", "[]", String.class);
evaluate("aVarargsMethod(null)", "[null]", String.class);
evaluate("aVarargsMethod(null,'a')", "[null, a]", String.class);
evaluate("aVarargsMethod('a',null,'b')", "[a, null, b]", String.class);
}
@Test
public void testVarargsInvocation02() {
// Calling 'public int aVarargsMethod2(int i, String... strings)' - returns int+length_of_strings
evaluate("aVarargsMethod2(5,'a','b','c')", 8, Integer.class);
evaluate("aVarargsMethod2(2,'a')", 3, Integer.class);
evaluate("aVarargsMethod2(4)", 4, Integer.class);
evaluate("aVarargsMethod2(8,2,3)", 10, Integer.class);
evaluate("aVarargsMethod2(9)", 9, Integer.class);
evaluate("aVarargsMethod2(2,'a',3.0d)", 4, Integer.class);
// evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", 11, Integer.class);
// Calling 'public String aVarargsMethod2(int i, String... strings)'
evaluate("aVarargsMethod2(5,'a','b','c')", "5-[a, b, c]", String.class);
evaluate("aVarargsMethod2(2,'a')", "2-[a]", String.class);
evaluate("aVarargsMethod2(4)", "4-[]", String.class);
evaluate("aVarargsMethod2(8,2,3)", "8-[2, 3]", String.class);
evaluate("aVarargsMethod2(2,'a',3.0d)", "2-[a, 3.0]", String.class);
evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", "8-[a, b, c]", String.class);
evaluate("aVarargsMethod2(8,new String[]{})", "8-[]", String.class);
evaluate("aVarargsMethod2(8,null)", "8-[null]", String.class);
evaluate("aVarargsMethod2(8,null,'a')", "8-[null, a]", String.class);
evaluate("aVarargsMethod2(8,'a',null,'b')", "8-[a, null, b]", String.class);
}
@Test
public void testVarargsOptionalInvocation() {
// Calling 'public String optionalVarargsMethod(Optional<String>... values)'
evaluate("optionalVarargsMethod()", "[]", String.class);
evaluate("optionalVarargsMethod(new String[0])", "[]", String.class);
evaluate("optionalVarargsMethod('a')", "[Optional[a]]", String.class);
evaluate("optionalVarargsMethod('a','b','c')", "[Optional[a], Optional[b], Optional[c]]", String.class);
evaluate("optionalVarargsMethod(9)", "[Optional[9]]", String.class);
evaluate("optionalVarargsMethod(2,3)", "[Optional[2], Optional[3]]", String.class);
evaluate("optionalVarargsMethod('a',3.0d)", "[Optional[a], Optional[3.0]]", String.class);
evaluate("optionalVarargsMethod(new String[]{'a','b','c'})", "[Optional[a], Optional[b], Optional[c]]", String.class);
// The following should actually evaluate to [Optional.empty] instead of [null],
// but ReflectionHelper.convertArguments() passes the array type instead of
// the array's component type as the target type to the ConversionService, and
// GenericConversionService.convertNullSource() therefore fails to convert null
// to Optional.empty().
evaluate("optionalVarargsMethod(null)", "[null]", String.class);
evaluate("optionalVarargsMethod(null,'a')", "[Optional.empty, Optional[a]]", String.class);
evaluate("optionalVarargsMethod('a',null,'b')", "[Optional[a], Optional.empty, Optional[b]]", String.class);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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,6 +16,7 @@
package org.springframework.expression.spel;
import java.util.Arrays;
import java.util.GregorianCalendar;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -51,10 +52,10 @@ public class TestScenarioCreator {
TestScenarioCreator.class.getDeclaredMethod("reverseInt", Integer.TYPE, Integer.TYPE, Integer.TYPE));
testContext.registerFunction("reverseString",
TestScenarioCreator.class.getDeclaredMethod("reverseString", String.class));
testContext.registerFunction("varargsFunctionReverseStringsAndMerge",
TestScenarioCreator.class.getDeclaredMethod("varargsFunctionReverseStringsAndMerge", String[].class));
testContext.registerFunction("varargsFunctionReverseStringsAndMerge2",
TestScenarioCreator.class.getDeclaredMethod("varargsFunctionReverseStringsAndMerge2", Integer.TYPE, String[].class));
testContext.registerFunction("varargsFunction",
TestScenarioCreator.class.getDeclaredMethod("varargsFunction", String[].class));
testContext.registerFunction("varargsFunction2",
TestScenarioCreator.class.getDeclaredMethod("varargsFunction2", Integer.TYPE, String[].class));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
@@ -108,25 +109,12 @@ public class TestScenarioCreator {
return backwards.toString();
}
public static String varargsFunctionReverseStringsAndMerge(String... strings) {
StringBuilder sb = new StringBuilder();
if (strings != null) {
for (int i = strings.length - 1; i >= 0; i--) {
sb.append(strings[i]);
}
}
return sb.toString();
public static String varargsFunction(String... strings) {
return Arrays.toString(strings);
}
public static String varargsFunctionReverseStringsAndMerge2(int j, String... strings) {
StringBuilder sb = new StringBuilder();
sb.append(j);
if (strings != null) {
for (int i = strings.length - 1; i >= 0; i--) {
sb.append(strings[i]);
}
}
return sb.toString();
public static String varargsFunction2(int i, String... strings) {
return String.valueOf(i) + "-" + Arrays.toString(strings);
}
}
@@ -58,16 +58,23 @@ public class VariableAndFunctionTests extends AbstractExpressionTests {
@Test
public void testCallVarargsFunction() {
evaluate("#varargsFunctionReverseStringsAndMerge('a','b','c')", "cba", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge('a')", "a", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge()", "", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge('b',25)", "25b", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge(25)", "25", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(1,'a','b','c')", "1cba", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(2,'a')", "2a", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(3)", "3", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(4,'b',25)", "425b", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(5,25)", "525", String.class);
evaluate("#varargsFunction()", "[]", String.class);
evaluate("#varargsFunction(new String[0])", "[]", String.class);
evaluate("#varargsFunction('a','b','c')", "[a, b, c]", String.class);
evaluate("#varargsFunction('a')", "[a]", String.class);
evaluate("#varargsFunction('b',25)", "[b, 25]", String.class);
evaluate("#varargsFunction(25)", "[25]", String.class);
evaluate("#varargsFunction(null)", "[null]", String.class);
evaluate("#varargsFunction('a',null,'b')", "[a, null, b]", String.class);
evaluate("#varargsFunction2(9)", "9-[]", String.class);
evaluate("#varargsFunction2(9, new String[0])", "9-[]", String.class);
evaluate("#varargsFunction2(9,'a','b','c')", "9-[a, b, c]", String.class);
evaluate("#varargsFunction2(9,'a')", "9-[a]", String.class);
evaluate("#varargsFunction2(9,'b',25)", "9-[b, 25]", String.class);
evaluate("#varargsFunction2(9,25)", "9-[25]", String.class);
evaluate("#varargsFunction2(9,null)", "9-[null]", String.class);
evaluate("#varargsFunction2(9,'a',null,'b')", "9-[a, null, b]", String.class);
}
@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.
@@ -17,11 +17,13 @@
package org.springframework.expression.spel.testresources;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.util.ObjectUtils;
@@ -190,16 +192,17 @@ public class Inventor {
return a + b + c;
}
public int aVarargsMethod(String... strings) {
if (strings == null)
return 0;
return strings.length;
public String aVarargsMethod(String... strings) {
return Arrays.toString(strings);
}
public int aVarargsMethod2(int i, String... strings) {
if (strings == null)
return i;
return strings.length + i;
public String aVarargsMethod2(int i, String... strings) {
return String.valueOf(i) + "-" + Arrays.toString(strings);
}
@SuppressWarnings("unchecked")
public String optionalVarargsMethod(Optional<String>... values) {
return Arrays.toString(values);
}
public Inventor(String... strings) {
@@ -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.
@@ -223,10 +223,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) {
if (pd.getWriteMethod() != null) {
this.mappedFields.put(lowerCaseName(pd.getName()), pd);
String underscoredName = underscoreName(pd.getName());
if (!lowerCaseName(pd.getName()).equals(underscoredName)) {
this.mappedFields.put(underscoredName, pd);
String lowerCaseName = lowerCaseName(pd.getName());
this.mappedFields.put(lowerCaseName, pd);
String underscoreName = underscoreName(pd.getName());
if (!lowerCaseName.equals(underscoreName)) {
this.mappedFields.put(underscoreName, pd);
}
this.mappedProperties.add(pd.getName());
}
@@ -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.
@@ -21,22 +21,21 @@ import java.util.List;
import java.util.Map;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.lang.Nullable;
/**
* Interface specifying a basic set of JDBC operations.
* Implemented by {@link JdbcTemplate}. Not often used directly, but a useful
*
* <p>Implemented by {@link JdbcTemplate}. Not often used directly, but a useful
* option to enhance testability, as it can easily be mocked or stubbed.
*
* <p>Alternatively, the standard JDBC infrastructure can be mocked.
* However, mocking this interface constitutes significantly less work.
* As an alternative to a mock objects approach to testing data access code,
* consider the powerful integration testing support provided in the
* {@code org.springframework.test} package, shipped in
* {@code spring-test.jar}.
* consider the powerful integration testing support provided via the <em>Spring
* TestContext Framework</em>, in the {@code spring-test} artifact.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -143,8 +142,8 @@ public interface JdbcOperations {
* @param rowMapper a callback that will map one object per row
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if there is any problem executing the query
* @see #queryForObject(String, RowMapper, Object...)
*/
@@ -163,8 +162,10 @@ public interface JdbcOperations {
* @param sql the SQL query to execute
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if there is any problem executing the query
* @see #queryForObject(String, Class, Object...)
*/
@@ -181,8 +182,8 @@ public interface JdbcOperations {
* mapped to a Map (one entry for each column, using the column name as the key).
* @param sql the SQL query to execute
* @return the result Map (one entry per column, with column name as key)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if there is any problem executing the query
* @see #queryForMap(String, Object...)
* @see ColumnMapRowMapper
@@ -528,8 +529,8 @@ public interface JdbcOperations {
* @param rowMapper a callback that will map one object per row
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -548,8 +549,8 @@ public interface JdbcOperations {
* @param rowMapper a callback that will map one object per row
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -567,8 +568,8 @@ public interface JdbcOperations {
* only the argument value but also the SQL type and optionally the scale
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
* @since 3.0.1
*/
@@ -586,8 +587,10 @@ public interface JdbcOperations {
* (constants from {@code java.sql.Types})
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see #queryForObject(String, Class)
* @see java.sql.Types
@@ -608,8 +611,10 @@ public interface JdbcOperations {
* only the argument value but also the SQL type and optionally the scale
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see #queryForObject(String, Class)
*/
@@ -628,8 +633,10 @@ public interface JdbcOperations {
* may also contain {@link SqlParameterValue} objects which indicate not
* only the argument value but also the SQL type and optionally the scale
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @since 3.0.1
* @see #queryForObject(String, Class)
@@ -647,8 +654,8 @@ public interface JdbcOperations {
* @param argTypes the SQL types of the arguments
* (constants from {@code java.sql.Types})
* @return the result Map (one entry per column, with column name as key)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
* @see #queryForMap(String)
* @see ColumnMapRowMapper
@@ -671,8 +678,8 @@ public interface JdbcOperations {
* only the argument value but also the SQL type and optionally the scale
* @return the result Map (one entry for each column, using the
* column name as the key)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
* @see #queryForMap(String)
* @see ColumnMapRowMapper
@@ -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.
@@ -236,8 +236,7 @@ public interface NamedParameterJdbcOperations {
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -255,8 +254,7 @@ public interface NamedParameterJdbcOperations {
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -273,10 +271,12 @@ public interface NamedParameterJdbcOperations {
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see org.springframework.jdbc.core.JdbcTemplate#queryForObject(String, Class)
* @see org.springframework.jdbc.core.SingleColumnRowMapper
*/
@Nullable
<T> T queryForObject(String sql, SqlParameterSource paramSource, Class<T> requiredType)
@@ -293,8 +293,9 @@ public interface NamedParameterJdbcOperations {
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see org.springframework.jdbc.core.JdbcTemplate#queryForObject(String, 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.
@@ -101,349 +101,6 @@ public abstract class ScriptUtils {
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
/**
* Split an SQL script into separate statements delimited by the provided
* separator character. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator character separating each statement (typically a ';')
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, String, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
*/
public static void splitSqlScript(String script, char separator, List<String> statements) throws ScriptException {
splitSqlScript(script, String.valueOf(separator), statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, char, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
*/
public static void splitSqlScript(String script, String separator, List<String> statements) throws ScriptException {
splitSqlScript(null, script, separator, DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefix} will be honored:
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefix the prefix that identifies SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
*/
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String commentPrefix, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
* any text beginning with one of the comment prefixes and extending to the
* end of the line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefixes the prefixes that identify SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @since 5.2
*/
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(script, "'script' must not be null or empty");
Assert.notNull(separator, "'separator' must not be null");
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
for (String commentPrefix : commentPrefixes) {
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
}
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
StringBuilder sb = new StringBuilder();
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
sb.append(c);
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
sb.append(c);
continue;
}
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(separator, i)) {
// We've reached the end of the current statement
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
i += separator.length() - 1;
continue;
}
else if (startsWithAny(script, commentPrefixes, i)) {
// Skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf('\n', i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// If there's no EOL, we must be at the end of the script, so stop here.
break;
}
}
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// Skip over any block comments
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new ScriptParseException(
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
}
}
else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
// Avoid multiple adjacent whitespace characters
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
c = ' ';
}
else {
continue;
}
}
}
sb.append(c);
}
if (StringUtils.hasText(sb)) {
statements.add(sb.toString());
}
}
/**
* Read a script from the given resource, using "{@code --}" as the comment prefix
* and "{@code ;}" as the statement separator, and build a String containing the lines.
* @param resource the {@code EncodedResource} to be read
* @return {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
static String readScript(EncodedResource resource) throws IOException {
return readScript(resource, DEFAULT_COMMENT_PREFIXES, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
/**
* Read a script from the provided resource, using the supplied comment prefixes
* and statement separator, and build a {@code String} containing the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param commentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
private static String readScript(EncodedResource resource, @Nullable String[] commentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
try (LineNumberReader lnr = new LineNumberReader(resource.getReader())) {
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
}
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param lineCommentPrefix the prefix that identifies comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
public static String readScript(LineNumberReader lineNumberReader, @Nullable String lineCommentPrefix,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String[] lineCommentPrefixes = (lineCommentPrefix != null) ? new String[] { lineCommentPrefix } : null;
return readScript(lineNumberReader, lineCommentPrefixes, separator, blockCommentEndDelimiter);
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefixes and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param lineCommentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @since 5.2
*/
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] lineCommentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
(lineCommentPrefixes != null && !startsWithAny(currentStatement, lineCommentPrefixes, 0))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentStatement);
}
currentStatement = lineNumberReader.readLine();
}
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
return scriptBuilder.toString();
}
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable String separator) {
if (separator == null) {
return;
}
String trimmed = separator.trim();
if (trimmed.length() == separator.length()) {
return;
}
// separator ends in whitespace, so we might want to see if the script is trying
// to end the same way
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
scriptBuilder.append(separator.substring(trimmed.length()));
}
}
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
for (String prefix : prefixes) {
if (script.startsWith(prefix, offset)) {
return true;
}
}
return false;
}
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim the string delimiting each statement - typically a ';' character
*/
public static boolean containsSqlScriptDelimiters(String script, String delim) {
boolean inLiteral = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
continue;
}
if (c == '\'') {
inLiteral = !inLiteral;
}
if (!inLiteral && script.startsWith(delim, i)) {
return true;
}
}
return false;
}
/**
* Execute the given SQL script using default settings for statement
* separators, comment delimiters, and exception handling flags.
@@ -575,7 +232,7 @@ public abstract class ScriptUtils {
String script;
try {
script = readScript(resource, commentPrefixes, separator, blockCommentEndDelimiter);
script = readScript(resource, separator, commentPrefixes, blockCommentEndDelimiter);
}
catch (IOException ex) {
throw new CannotReadScriptException(resource, ex);
@@ -584,7 +241,9 @@ public abstract class ScriptUtils {
if (separator == null) {
separator = DEFAULT_STATEMENT_SEPARATOR;
}
if (!EOF_STATEMENT_SEPARATOR.equals(separator) && !containsSqlScriptDelimiters(script, separator)) {
if (!EOF_STATEMENT_SEPARATOR.equals(separator) &&
!containsStatementSeparator(resource, script, separator, commentPrefixes,
blockCommentStartDelimiter, blockCommentEndDelimiter)) {
separator = FALLBACK_STATEMENT_SEPARATOR;
}
@@ -647,4 +306,424 @@ public abstract class ScriptUtils {
}
}
/**
* Read a script from the provided resource, using the supplied comment prefixes
* and statement separator, and build a {@code String} containing the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param separator the statement separator in the SQL script (typically ";")
* @param commentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
static String readScript(EncodedResource resource, @Nullable String separator,
String[] commentPrefixes, String blockCommentEndDelimiter) throws IOException {
try (LineNumberReader lnr = new LineNumberReader(resource.getReader())) {
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
}
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static String readScript(LineNumberReader lineNumberReader, @Nullable String commentPrefix,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String[] commentPrefixes = (commentPrefix != null) ? new String[] { commentPrefix } : null;
return readScript(lineNumberReader, commentPrefixes, separator, blockCommentEndDelimiter);
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefixes and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param commentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @since 5.2
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] commentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
(commentPrefixes != null && !startsWithAny(currentStatement, commentPrefixes, 0))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentStatement);
}
currentStatement = lineNumberReader.readLine();
}
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
return scriptBuilder.toString();
}
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable String separator) {
if (separator == null) {
return;
}
String trimmed = separator.trim();
if (trimmed.length() == separator.length()) {
return;
}
// separator ends in whitespace, so we might want to see if the script is trying
// to end the same way
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
scriptBuilder.append(separator.substring(trimmed.length()));
}
}
/**
* Determine if the provided SQL script contains the specified delimiter.
* <p>This method is intended to be used to find the string delimiting each
* SQL statement &mdash; for example, a ';' character.
* <p>Any occurrence of the delimiter within the script will be ignored if it
* is within a <em>literal</em> block of text enclosed in single quotes
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
* ({@code \}), or if it is within a single-line comment or block comment.
* @param script the SQL script to search within
* @param delimiter the statement delimiter to search for
* @see #DEFAULT_COMMENT_PREFIXES
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static boolean containsSqlScriptDelimiters(String script, String delimiter) {
return containsStatementSeparator(null, script, delimiter, DEFAULT_COMMENT_PREFIXES,
DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
/**
* Determine if the provided SQL script contains the specified statement separator.
* <p>This method is intended to be used to find the string separating each
* SQL statement &mdash; for example, a ';' character.
* <p>Any occurrence of the separator within the script will be ignored if it
* is within a <em>literal</em> block of text enclosed in single quotes
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
* ({@code \}), or if it is within a single-line comment or block comment.
* @param resource the resource from which the script was read, or {@code null}
* if unknown
* @param script the SQL script to search within
* @param separator the statement separator to search for
* @param commentPrefixes the prefixes that identify single-line comments
* (typically {@code "--"})
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* (typically {@code "/*"})
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* (typically <code>"*&#47;"</code>)
* @since 5.2.16
*/
private static boolean containsStatementSeparator(@Nullable EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws ScriptException {
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
continue;
}
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(separator, i)) {
return true;
}
else if (startsWithAny(script, commentPrefixes, i)) {
// Skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf('\n', i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// If there's no EOL, we must be at the end of the script, so stop here.
break;
}
}
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// Skip over any block comments
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new ScriptParseException(
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
}
}
}
}
return false;
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator character. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator character separating each statement (typically a ';')
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, String, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(String script, char separator, List<String> statements) throws ScriptException {
splitSqlScript(script, String.valueOf(separator), statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, char, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(String script, String separator, List<String> statements) throws ScriptException {
splitSqlScript(null, script, separator, DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefix} will be honored:
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefix the prefix that identifies SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String commentPrefix, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
* any text beginning with one of the comment prefixes and extending to the
* end of the line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefixes the prefixes that identify SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @since 5.2
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(script, "'script' must not be null or empty");
Assert.notNull(separator, "'separator' must not be null");
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
for (String commentPrefix : commentPrefixes) {
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
}
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
StringBuilder sb = new StringBuilder();
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
sb.append(c);
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
sb.append(c);
continue;
}
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(separator, i)) {
// We've reached the end of the current statement
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
i += separator.length() - 1;
continue;
}
else if (startsWithAny(script, commentPrefixes, i)) {
// Skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf('\n', i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// If there's no EOL, we must be at the end of the script, so stop here.
break;
}
}
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// Skip over any block comments
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new ScriptParseException(
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
}
}
else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
// Avoid multiple adjacent whitespace characters
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
c = ' ';
}
else {
continue;
}
}
}
sb.append(c);
}
if (StringUtils.hasText(sb)) {
statements.add(sb.toString());
}
}
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
for (String prefix : prefixes) {
if (script.startsWith(prefix, offset)) {
return true;
}
}
return false;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,7 +53,7 @@ import org.springframework.lang.Nullable;
* @author Juergen Hoeller
* @since 2.5
* @see java.sql.SQLTransientException
* @see java.sql.SQLTransientException
* @see java.sql.SQLNonTransientException
* @see java.sql.SQLRecoverableException
*/
public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLExceptionTranslator {
@@ -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.
@@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
@@ -46,6 +48,7 @@ import static org.springframework.jdbc.datasource.init.ScriptUtils.splitSqlScrip
public class ScriptUtilsUnitTests {
@Test
@SuppressWarnings("deprecation")
public void splitSqlScriptDelimitedWithSemicolon() {
String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
@@ -61,6 +64,7 @@ public class ScriptUtilsUnitTests {
}
@Test
@SuppressWarnings("deprecation")
public void splitSqlScriptDelimitedWithNewLine() {
String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
@@ -73,6 +77,7 @@ public class ScriptUtilsUnitTests {
}
@Test
@SuppressWarnings("deprecation")
public void splitSqlScriptDelimitedWithNewLineButDefaultDelimiterSpecified() {
String statement1 = "do something";
String statement2 = "do something else";
@@ -84,6 +89,7 @@ public class ScriptUtilsUnitTests {
}
@Test // SPR-13218
@SuppressWarnings("deprecation")
public void splitScriptWithSingleQuotesNestedInsideDoubleQuotes() throws Exception {
String statement1 = "select '1' as \"Dogbert's owner's\" from dual";
String statement2 = "select '2' as \"Dilbert's\" from dual";
@@ -95,6 +101,7 @@ public class ScriptUtilsUnitTests {
}
@Test // SPR-11560
@SuppressWarnings("deprecation")
public void readAndSplitScriptWithMultipleNewlinesAsSeparator() throws Exception {
String script = readScript("db-test-data-multi-newline.sql");
List<String> statements = new ArrayList<>();
@@ -122,6 +129,7 @@ public class ScriptUtilsUnitTests {
splitScriptContainingComments(script, "--", "#", "^");
}
@SuppressWarnings("deprecation")
private void splitScriptContainingComments(String script, String... commentPrefixes) throws Exception {
List<String> statements = new ArrayList<>();
splitSqlScript(null, script, ";", commentPrefixes, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
@@ -135,6 +143,7 @@ public class ScriptUtilsUnitTests {
}
@Test // SPR-10330
@SuppressWarnings("deprecation")
public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception {
String script = readScript("test-data-with-comments-and-leading-tabs.sql");
List<String> statements = new ArrayList<>();
@@ -146,6 +155,7 @@ public class ScriptUtilsUnitTests {
}
@Test // SPR-9531
@SuppressWarnings("deprecation")
public void readAndSplitScriptContainingMultiLineComments() throws Exception {
String script = readScript("test-data-with-multi-line-comments.sql");
List<String> statements = new ArrayList<>();
@@ -156,6 +166,7 @@ public class ScriptUtilsUnitTests {
}
@Test
@SuppressWarnings("deprecation")
public void readAndSplitScriptContainingMultiLineNestedComments() throws Exception {
String script = readScript("test-data-with-multi-line-nested-comments.sql");
List<String> statements = new ArrayList<>();
@@ -165,22 +176,48 @@ public class ScriptUtilsUnitTests {
assertThat(statements).containsExactly(statement1, statement2);
}
@Test
public void containsDelimiters() {
assertThat(containsSqlScriptDelimiters("select 1\n select ';'", ";")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1; select 2", ";")).isTrue();
assertThat(containsSqlScriptDelimiters("select 1; select '\\n\n';", "\n")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n")).isTrue();
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n\n")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1\n\n select 2", "\n\n")).isTrue();
// MySQL style escapes '\\'
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('a\\\\', 'b;')", ";")).isFalse();
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('Charles', 'd\\'Artagnan'); select 1;", ";")).isTrue();
@ParameterizedTest
@CsvSource(delimiter = '#', value = {
// semicolon
"'select 1\n select '';''' # ; # false",
"'select 1\n select \";\"' # ; # false",
"'select 1; select 2' # ; # true",
// newline
"'select 1; select ''\n''' # '\n' # false",
"'select 1; select \"\n\"' # '\n' # false",
"'select 1\n select 2' # '\n' # true",
// double newline
"'select 1\n select 2' # '\n\n' # false",
"'select 1\n\n select 2' # '\n\n' # true",
// semicolon with MySQL style escapes '\\'
"'insert into users(first, last)\nvalues(''a\\\\'', ''b;'')' # ; # false",
"'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true",
// semicolon inside comments
"'-- a;b;c\ninsert into colors(color_num) values(42);' # ; # true",
"'/* a;b;c */\ninsert into colors(color_num) values(42);' # ; # true",
"'-- a;b;c\ninsert into colors(color_num) values(42)' # ; # false",
"'/* a;b;c */\ninsert into colors(color_num) values(42)' # ; # false",
// single quotes inside comments
"'-- What\\''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
"'-- What''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
"'/* What\\''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
"'/* What''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
// double quotes inside comments
"'-- double \" quotes\ninsert into colors(color_num) values(42);' # ; # true",
"'-- double \\\" quotes\ninsert into colors(color_num) values(42);' # ; # true",
"'/* double \" quotes */\ninsert into colors(color_num) values(42);' # ; # true",
"'/* double \\\" quotes */\ninsert into colors(color_num) values(42);' # ; # true"
})
@SuppressWarnings("deprecation")
public void containsStatementSeparator(String script, String delimiter, boolean expected) {
// Indirectly tests ScriptUtils.containsStatementSeparator(EncodedResource, String, String, String[], String, String).
assertThat(containsSqlScriptDelimiters(script, delimiter)).isEqualTo(expected);
}
private String readScript(String path) throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource(path, getClass()));
return ScriptUtils.readScript(resource);
return ScriptUtils.readScript(resource, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_COMMENT_PREFIXES,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
}
@@ -5,16 +5,19 @@
* x, y, z...
*/
-- This is a single line comment containing single (') and double quotes (").
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
-- This is also a comment.
/*-------------------------------------------
-- A fancy multi-line comments that puts
-- A fancy multi-line comment that puts
-- single line comments inside of a multi-line
-- comment block.
Moreover, the block comment end delimiter
appears on a line that can potentially also
be a single-line comment if we weren't
already inside a multi-line comment run.
And here's a line containing single and double quotes (").
-------------------------------------------*/
INSERT INTO
users(first_name, last_name) -- This is a single line comment containing the block-end-comment sequence here */ but it's still a single-line comment
@@ -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.
@@ -16,8 +16,8 @@
package org.springframework.jms.listener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.jms.Connection;
@@ -81,9 +81,9 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
private boolean active = false;
private volatile boolean running = false;
private volatile boolean running;
private final List<Object> pausedTasks = new LinkedList<>();
private final List<Object> pausedTasks = new ArrayList<>();
protected final Object lifecycleMonitor = new Object();
@@ -582,13 +582,13 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
/**
* Log a task that has been rejected by {@link #doRescheduleTask}.
* <p>The default implementation simply logs a corresponding message
* at debug level.
* at warn level.
* @param task the rejected task object
* @param ex the exception thrown from {@link #doRescheduleTask}
*/
protected void logRejectedTask(Object task, RuntimeException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Listener container task [" + task + "] has been rejected and paused: " + ex);
if (logger.isWarnEnabled()) {
logger.warn("Listener container task [" + task + "] has been rejected and paused: " + ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -416,21 +416,21 @@ public class HandlerMethod {
private class ReturnValueMethodParameter extends HandlerMethodParameter {
@Nullable
private final Object returnValue;
private final Class<?> returnValueType;
public ReturnValueMethodParameter(@Nullable Object returnValue) {
super(-1);
this.returnValue = returnValue;
this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
}
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
super(original);
this.returnValue = original.returnValue;
this.returnValueType = original.returnValueType;
}
@Override
public Class<?> getParameterType() {
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
}
@Override
@@ -120,8 +120,7 @@ public class DefaultMessageHandlerMethodFactory
* the ones configured by default. This is an advanced option. For most use cases
* it should be sufficient to use {@link #setCustomArgumentResolvers(java.util.List)}.
*/
@SuppressWarnings("ConstantConditions")
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
public void setArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers == null) {
this.argumentResolvers.clear();
return;
@@ -131,7 +131,6 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
* @param message the current message
* @return the frame type or {@code null} if not found
*/
@SuppressWarnings("ConstantConditions")
@Nullable
public static FrameType getFrameType(Message<?> message) {
return (FrameType) message.getHeaders().get(RSocketFrameTypeMessageCondition.FRAME_TYPE_HEADER);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -306,6 +306,12 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
else if (SimpMessageType.CONNECT.equals(messageType)) {
logMessage(message);
if (sessionId != null) {
if (this.sessions.get(sessionId) != null) {
if (logger.isWarnEnabled()) {
logger.warn("Ignoring CONNECT in session " + sessionId + ". Already connected.");
}
return;
}
long[] heartbeatIn = SimpMessageHeaderAccessor.getHeartbeat(headers);
long[] heartbeatOut = getHeartbeatValue();
Principal user = SimpMessageHeaderAccessor.getUser(headers);

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