Compare commits

...

222 Commits

Author SHA1 Message Date
Spring Buildmaster d9a9686b45 Release v5.3.4 2021-02-16 10:43:46 +00:00
Sam Brannen efc335e198 Remove remaining Kotlin "translations" of Java APIs in the reference manual 2021-02-16 11:16:14 +01:00
Sam Brannen 1e57e572dd Link to all built-in Resource implementations
See gh-26447
2021-02-16 10:29:41 +01:00
Sam Brannen 829000d511 Remove Kotlin "translations" of Java APIs in the reference manual
See gh-26447
2021-02-16 10:24:24 +01:00
Sam Brannen a315abfd32 Polish ResourcePatternResolver docuementation in the reference manual
See gh-26447
2021-02-16 10:18:13 +01:00
Sam Brannen 36d133ce53 Escape URLs in literals in reference manual
This prevents the URLs from being rendered as clickable links in the
generated HTML.
2021-02-16 09:59:44 +01:00
Juergen Hoeller 9417975f66 Nullability refinements
See gh-26462
2021-02-16 08:51:42 +01:00
Juergen Hoeller ed9ec58caa Document ASM version as 9.1 in license file 2021-02-16 00:18:14 +01:00
Juergen Hoeller f48c3d9b67 Document ASM version as 9.x (effectively 9.1 at present) 2021-02-16 00:09:10 +01:00
Sam Brannen 6b705443a6 Document ResourcePatternResolver in the reference manual
Closes gh-26447
2021-02-15 22:09:33 +01:00
Sam Brannen c26087e848 Document PathResource as a built-in Resource in the reference manual
See gh-26447
2021-02-15 21:04:37 +01:00
Sam Brannen 7225238ad5 Update Resource interface in the reference manual
See gh-26447
2021-02-15 20:40:40 +01:00
Sam Brannen ff43731347 Polish Resources section of the reference manual
See gh-26447
2021-02-15 20:20:21 +01:00
Taemin Shin 29fb3f27be Fix typo in @Configurable example in reference manual
Closes gh-26551
2021-02-15 19:40:45 +01:00
Rossen Stoyanchev 63db250ebe Upgrade to Reactor 2020.0.4
Closes gh-26548
2021-02-15 17:25:38 +00:00
gaerfield b00c7075a5 Introduce WebClient.ResponseSpec.awaitBodilessEntity()
This commit also allows awaitBody<Unit>() to be used.

Closes gh-26504
2021-02-15 17:46:18 +01:00
Juergen Hoeller 3718c8d0e0 Upgrade to Hibernate ORM 5.4.28 and OpenPDF 1.3.25 2021-02-15 17:25:14 +01:00
Juergen Hoeller ca8261cbf8 Polishing 2021-02-15 17:18:01 +01:00
Carsten Dimmek 8de10e9199 Accept nullable values in MapSqlParameterSource set operators
Closes gh-26288
2021-02-15 17:15:58 +01:00
Stephane Nicoll 7536b7f42a Upgrade to Gradle 6.8.2
Closes gh-26552
2021-02-15 16:57:49 +01:00
Stephane Nicoll bc71cc1a76 Merge branch 'gh-26462'
Closes gh-26462
2021-02-15 16:53:43 +01:00
Stephane Nicoll 582559dd9e Polish "Allow customer resolver and property sources"
See gh-26462
2021-02-15 16:53:18 +01:00
Phillip Webb d4c609f2f0 Allow customer resolver and property sources
Add factory methods to `AbstractEnvironment` that allow a custom
`ConfigurablePropertyResolver` and `MutablePropertySources` instance
to be used.

See gh-26462
2021-02-15 16:53:18 +01:00
Sébastien Deleuze 3524401bf1 Introduce a NativeDetector utility class
With the introduction of the -H:+InlineBeforeAnalysis native image
compiler flag in GraalVM 21.0.0, it is now possible to use an utility method and get code
removal at build time.

This flag will be enabled as of Spring Native 0.9.0.

closes gh-25795
2021-02-15 16:36:23 +01:00
Stephane Nicoll 86423c91f7 Merge branch 'gh-26461'
Closes gh-26461
2021-02-15 16:30:06 +01:00
Stephane Nicoll 8807f94ad8 Polish "Allow bypass of active/default properties"
See gh-26461
2021-02-15 16:28:18 +01:00
Phillip Webb da3ff29e88 Allow bypass of active/default properties
Introduce protected methods that can be used to bypass or change the
way that active and default property values are read.

See gh-26461
2021-02-15 16:24:22 +01:00
Sébastien Deleuze 9c6b1b645d Prevent kotlinx.serialization usage on collection of interfaces
Closes gh-26371
2021-02-15 14:44:54 +01:00
Rossen Stoyanchev 0fc8bf654b Expose DispatcherHandler as PreFlightRequestHandler
Closes gh-26257
2021-02-15 12:59:03 +00:00
Rossen Stoyanchev 729535d36c Ensure presence of cached lookupPath
Closes gh-26546
2021-02-15 12:24:07 +00:00
Juergen Hoeller 634c7710f9 Remove trailing whitespace (Checkstyle)
See gh-26471
2021-02-15 11:34:38 +01:00
Yanming Zhou 2fbfd8a5db Avoid unnecessary wrapping for SqlParameterValue
Fix https://github.com/spring-projects/spring-framework/issues/26467
2021-02-15 11:15:37 +01:00
Sébastien Deleuze 42c15be8d3 Upgrade to Kotlin 1.4.30
Closes gh-26545
2021-02-15 10:47:27 +01:00
Juergen Hoeller 3baa44c5ef Polishing 2021-02-14 18:53:53 +01:00
Juergen Hoeller 4b86a1a755 Upgrade to Netty 4.1.59, Tomcat 9.0.43, Undertow 2.2.4, Mockito 3.7.7, HtmlUnit 2.47.1, Joda-Time 2.10.10, RxJava 2.2.21 and 3.0.10 2021-02-14 17:58:03 +01:00
Juergen Hoeller df977a2fd2 Nullability refinements and related polishing 2021-02-14 17:57:32 +01:00
Juergen Hoeller 99a1388bbd Re-resolve cached arguments in case of NoSuchBeanDefinitionException
Closes gh-26517
2021-02-14 17:57:22 +01:00
Juergen Hoeller 809813dd52 Preserve resolved destroy method name in RootBeanDefinition
Closes gh-26498
2021-02-14 17:57:05 +01:00
Juergen Hoeller d5e5dcb7e1 Consider non-initialized holders as equal to empty holders
Closes gh-26433
2021-02-14 17:56:52 +01:00
Juergen Hoeller defc2466b0 Fail early FactoryBean instantiation for LinkageError
Closes gh-26425
2021-02-14 17:56:38 +01:00
fengyuanwei a991c23db5 Close mapping streams after the ValidatorFactory has been built 2021-02-14 17:29:08 +01:00
Sam Brannen b928ed8f37 Upgrade to JUnit 4.13.2
Closes gh-26543
2021-02-13 19:27:09 +01:00
Sam Brannen 5d70aaacef Improve diagnostics for flaky ParallelApplicationEventsIntegrationTests 2021-02-12 12:02:37 +01:00
Sam Brannen 566ff54782 Polishing
See gh-26540
2021-02-12 11:29:00 +01:00
Oleksandr Kravchuk 77b7e49fb2 Update ref docs for Inner class names
This commit updates the reference manual to point out that one may use
`.` instead of `$` as the nested class separator when providing a fully
qualified class name for a nested class in XML configuration.

This commit also updates the test for ClassUtils#forName to assert
support for `.` instead of `$`.

Closes gh-26540
2021-02-12 10:54:55 +01:00
Rossen Stoyanchev 1c6bab23ea Polishing
See gh-26434
2021-02-11 21:26:37 +00:00
Rossen Stoyanchev 3e502d4739 Document removal of duplicate slashes in UriComponentsBuilder
Closes gh-26457
2021-02-11 20:57:03 +00:00
Sam Brannen 667256adf9 Ignore generics when Proxy is supplied to BeanUtils.copyProperties()
gh-24281 introduced support to honor generic type information in
BeanUtils.copyProperties(), but that introduced a regression.
Specifically, if the supplied source or target object lacked generic
type information for the return type of the read-method or the
parameter type of the write-method for a given property, respectively,
the two properties would be considered a mismatch and ignored. This can
occur if the source or target object is a java.lang.reflect.Proxy since
the dynamically generated class for the proxy loses the generic type
information from interfaces that the proxy implements.

This commit fixes this regression by ignoring generic type information
if either the source or target property is lacking generic type
information.

Closes gh-26531
2021-02-11 18:18:57 +01:00
Rossen Stoyanchev 8791928f61 Update variable detection in UriComponentsBuilder#encode
This commit better aligns how URI variable placeholders are detected
in UriComponentsBuilder#encode (i.e. the pre-encoding of the literal
parts of a URI template) and how they are expanded later on.
The latter relies on a pattern that stops at the first closing '}'
which excludes the possibility for well-formed, nested placeholders
other than variables with regex syntax, e.g. "{year:\d{1,4}}".

UriComponentsBuilder#encode now also stops at the first closing '}' and
further ensures the placeholder is not empty and that it has '{' before
deciding to treat it as a URI variable.

Closes gh-26466
2021-02-11 08:34:06 +00:00
Rossen Stoyanchev c9147c4281 Add RSocket to "Web Reactive" subtext
Closes gh-26534
2021-02-11 08:31:20 +00:00
Stephane Nicoll d6f363c634 Merge pull request #26538 from izeye
* pr/26538:
  Update copyright of change file
  Polish

Closes gh-26538
2021-02-11 08:59:24 +01:00
Stephane Nicoll 87c0e9b48a Update copyright of change file
See gh-26538
2021-02-11 08:58:50 +01:00
izeye 9a4164c4a8 Polish
See gh-26538
2021-02-11 08:57:32 +01:00
Sam Brannen c36b80bd75 Update copyright date in reference manual 2021-02-10 17:56:22 +01:00
Sam Brannen bc90512309 Fix Checkstyle violation 2021-02-10 14:03:33 +01:00
Sam Brannen edd15646af Check available processors in ParallelApplicationEventsIntegrationTests
This commit changes the condition in the if-block to check the number of
available processors instead of currently active threads with the hope
that doing so will prove more reliable on the CI server.
2021-02-10 12:02:32 +01:00
Rossen Stoyanchev 53cafe728c Support to customize rather than replace default codecs
See gh-26212
2021-02-09 18:40:00 +00:00
Brian Clozel 58e9b187fe Enforce standard Java types in YamlProcessor
`spring-beans` ships a `YamlProcessor` that's used as a base class by
`YamlMapFactoryBean` and `YamlPropertiesFactoryBean`. These
implementations have a clear use case: mapping application-internal Yaml
documents for configuration or infrastructure purposes.

Since this use case rarely requires extended types support from the
underlying library, and since we're offering ways to list custom types
(since #25152), we'll restrict to java standard types only by default.
This simplifies the setup and focuses the abstract class on the core
use cases.

Closes gh-26530
2021-02-09 15:05:53 +01:00
Arjen Poutsma 9b0c2cca95 Ensure StringDecoder supports multiline delimiters
This commit makes sure the StringDecoder supports stripping off
multi-line delimiters, such as \r\n. Specifically, we ensure that the
delimiter is stripped from the joined buffer.

Closes gh-26511
2021-02-09 14:31:49 +01:00
Rossen Stoyanchev 26000ee29f Polishing contribution
Closes gh-26502
2021-02-08 17:35:59 +00:00
Christophe Maillard 0c2c66b38b Add HttpHandlerDecoratorFactory
See gh-26502
2021-02-08 15:47:21 +00:00
heqiang bd8e682c51 Simplify BeanUtils.findPrimaryConstructor()
Closes gh-26519
2021-02-08 13:45:40 +01:00
Rossen Stoyanchev f7c952cb3e Polishing contribution
Closes gh-26512
2021-02-05 13:28:19 +00:00
ascopes 6bc7e12bcd Fix typos in ResponseEntity Javadoc
See gh-26512
2021-02-05 13:28:19 +00:00
Rossen Stoyanchev ab94c7c016 Update WebMvcConfigurer Javadoc for message converters
Closes gh-26388
2021-02-05 13:04:44 +00:00
Rossen Stoyanchev 8fb9796d1d Polishing contribution
Closes gh-26463
2021-02-05 12:16:59 +00:00
Yusuke Yamamoto a264013d7a Content-Disposition can parse BASE64 encoded filename
See gh-26463
2021-02-05 12:16:59 +00:00
Rossen Stoyanchev cd80b6b4ac Message broker skips messages with user destination
When a broker message handler is not configured with any prefixes, it will
process all messages by default, but user destination messages should be
pre-processed by the userDestinationMessageHandler first. This change
protects against that.

Closes gh-26474
2021-02-05 11:53:46 +00:00
Rossen Stoyanchev 164b48e25f Polishing STOMP/WebSocket config 2021-02-05 11:53:46 +00:00
Rossen Stoyanchev eb7b206142 Expose ChannelId from ReactorNettyWebSocketSession
Closes gh-26485
2021-02-05 11:53:46 +00:00
Sam Brannen 42061d27bd Upgrade to JUnit 5.7.1
Closes gh-26510
2021-02-04 21:47:21 +01:00
Rossen Stoyanchev 0d16c9100a MIME types by Class for Encoder, Decoder, HttpMessageReader|Writer
Closes gh-26212
2021-02-04 16:12:57 +00:00
Rossen Stoyanchev 7cdaaa22bd Extra ObjectMapper registrations in Jackson2CodecSupport
See gh-26212
2021-02-04 16:12:57 +00:00
Rossen Stoyanchev f4c9f6b860 Media types by Class for HttpMessageConverter
See gh-26212
2021-02-04 16:12:57 +00:00
Rossen Stoyanchev 1721b0b8d7 Extra ObjectMapper registrations in HttpMessageConverter
See gh-26212
2021-02-04 16:12:57 +00:00
Sam Brannen 836976d732 Increase fudge factor 2021-02-04 13:41:47 +01:00
Sam Brannen 59818ceca9 Fix StoredProcedure documentation in reference manual
This commit aligns the documentation in the reference manual with the
actual source code for StoredProcedure with regard to public execute()
methods.

Closes gh-26505
2021-02-04 11:10:50 +01:00
Sam Brannen 08ce6baedf Polishing 2021-02-04 11:08:25 +01:00
Rossen Stoyanchev c52526ad42 Fix in MockMultipartHttpServletRequest#getMultipartHeaders
Previously this method returned headers only when a Content-Type part header
was present. Now it is guaranteed to return headers (possibly empty) as long
as there is a MultipartFile or Part with the given name.

Closes gh-26501
2021-02-03 21:55:42 +00:00
Sam Brannen 7a329eba5b Do not retain partial column metadata in SimpleJdbcInsert
Prior to this commit, if an SQLException was thrown while retrieving
column metadata from the database, SimpleJdbcInsert would generate an
INSERT statement that was syntactically valid but missing columns,
which could lead to data silently missing in the database (for nullable
columns).

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

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

Closes gh-26486
2021-02-03 18:47:19 +01:00
Sam Brannen 91d542e406 Polishing 2021-02-03 16:27:28 +01:00
Rossen Stoyanchev 6fde28be98 Fix checkstyle violations 2021-02-03 12:24:45 +00:00
Rossen Stoyanchev 1e481cd14c Fix checkstyle violation 2021-02-03 12:12:52 +00:00
Rossen Stoyanchev df4ba742cb Apply global CORS settings to static resources in WebFlux
Closes gh-26495
2021-02-03 11:17:59 +00:00
Rossen Stoyanchev 996c86f448 Suppress PREFLIGHT_AMBIGUOUS_MATCH if matches have no CORS config
Closes gh-26490
2021-02-03 10:43:08 +00:00
Toshiaki Maki 0575e6637c Protobuf Codec supports application/vnd.google.protobuf 2021-02-02 16:55:10 +00:00
Sam Brannen ad8775ef95 Link to Objenesis property name constant in SpringProperties 2021-02-02 17:42:45 +01:00
Sam Brannen 8589804012 Link to known property name constants in SpringProperties 2021-02-02 15:20:41 +01:00
Sam Brannen c649b74235 Use api-spring-framework attribute for links to SpringProperties
See gh-26492
2021-02-02 14:01:51 +01:00
Sam Brannen bef5d59e43 Link to SpringProperties Javadoc from the Testing chapter
Closes gh-26492
2021-02-02 13:48:11 +01:00
Sam Brannen 99bd8115dd Clean up deprecation warnings in Gradle build output 2021-02-02 11:37:30 +01:00
Sam Brannen 1a3ff83157 Remove obsolete code in HeaderValueHolder 2021-02-02 11:28:32 +01:00
Sam Brannen c19b2851f1 Ignore null Locale in MockHttpServletResponse
Prior to this commit, calls to setLocale() MockHttpServletResponse
would result in a NullPointerException if the supplied Locale was null.

Although the Javadoc for setLocale(Locale) and addHeader(String, String)
in javax.servlet.ServletResponse does not specify how a null
Locale should be handled, both Tomcat and Jetty simply ignore a null
value.

This commit therefore updates MockHttpServletResponse to silently
ignore a null Locale passed to setLocale().

Closes gh-26493
2021-02-02 11:18:34 +01:00
Sam Brannen 3b4a3431d4 Ignore null header value in MockHttpServletResponse
Prior to this commit, calls to setHeader() and addHeader() in
MockHttpServletResponse would result in an IllegalArgumentException or
NullPointerException if the supplied header value was null.

Although the Javadoc for setHeader(String, String) and
addHeader(String, String) in javax.servlet.http.HttpServletResponse
does not specify how a null header value should be handled, both Tomcat
and Jetty simply ignore a null value. Furthermore,
org.springframework.http.HttpHeaders.add(String, String) declares the
headerValue parameter as @Nullable.

This commit therefore updates MockHttpServletResponse to silently
ignore null header values passed to setHeader() and addHeader().

Closes gh-26488
2021-02-02 11:18:34 +01:00
fengyuanwei c82a445094 Fix Javadoc in StandardServletAsyncWebRequest 2021-02-01 08:27:40 +00:00
Rossen Stoyanchev 95752ef1c9 Improve handling for pre-flight requests
1. Update the HandlerMapping contract to state that CORS checks are expected
to be applied before returning a handler.

2. DispatcherHandler checks explicitly for pre-flight requests or CORS failed
requests and skips handling for both. Technically no change since
AbstractHandlerMapping already returns a NO_OP_HANDLER for those cases.
The purpose however is for the DispatcherHandler to also guarantee more
explicitly that no such handling can take place for such cases.

As one consequence, this makes it possible to invoke the DispatcherHandler from
anywhere in the WebFilter chain in order to "handle" a pre-flight request, and
then skip the rest of the WebFilter chain.

See gh-26257
2021-01-29 22:12:10 +00:00
Rossen Stoyanchev 0ff50d6d9e Improve Javadoc of ServerHttpRequest#getPath
Closes gh-26469
2021-01-29 20:40:13 +00:00
Rossen Stoyanchev 30cdeb23fe Fix order of headers in DefaultHandlerExceptionResolver
Closes gh-26470
2021-01-29 20:30:55 +00:00
Rossen Stoyanchev ecf5113e0e Handle forwarded header parse issues
Closes gh-26459
2021-01-28 20:45:26 +00:00
Gediminas Rimša 80c4e6bb95 Fix ContentCachingResponseWrapper comment typo 2021-01-28 17:53:38 +00:00
Rossen Stoyanchev 5e5d8e4a23 Handle flushingFailed() for Servlet containers
Closes gh-26434
2021-01-28 17:03:35 +00:00
Brian Clozel d7e05aa48f Upgrade CI images to ubuntu:focal-20210119 2021-01-28 11:50:19 +01:00
Rossen Stoyanchev cf52246319 WebSocketExtension#equals matches sub-classes too
Closes gh-26449
2021-01-27 20:39:44 +00:00
Sam Brannen c5284009a1 Fix UriComponentsBuilder examples in ref docs
Closes gh-26453
2021-01-27 15:16:15 +01:00
Sam Brannen 2d29fcd0bb Polish Javadoc for TransactionSynchronizationUtils 2021-01-27 14:03:38 +01:00
Sam Brannen 5e56162394 Fix Checkstyle violation
The @since tag must come before @see tags.

See gh-26450
2021-01-27 11:53:20 +01:00
Johnny Lim 45650f2b27 Add @since tag to TransactionSynchronizationUtils.unwrapResourceIfNecessary()
Closes gh-26450
2021-01-27 11:23:22 +01:00
Rossen Stoyanchev 3e1b1638ef Update link for reporting security issues 2021-01-26 21:39:29 +00:00
Rossen Stoyanchev e80a23d6ad Fix CheckStyle violations 2021-01-26 17:51:52 +00:00
Rossen Stoyanchev 8aeae49f40 Support for servletPath prefix in ServletRequestPathUtils
Closes gh-26445
2021-01-26 17:30:16 +00:00
Rossen Stoyanchev f22e2ac578 Add HttpServletMapping support to MockHttpServletRequest
See gh-26428
2021-01-26 17:30:16 +00:00
izeye 355aca7665 Fix DelegatingWebMvcConfigurationTests.configurePathPatternParser() 2021-01-26 16:15:46 +00:00
Rossen Stoyanchev ec5f99f981 Add mutate() to RequestMappingInfo
Closes gh-26428
2021-01-26 11:44:20 +00:00
Sam Brannen 03f1b65084 Polish Javadoc for ResourceLoader APIs 2021-01-26 12:20:28 +01:00
Juergen Hoeller 0de2833894 Upgrade to Jetty 9.4.36, EclipseLink 2.7.8, XStream 1.4.15, Apache Johnzon 1.2.10 2021-01-25 17:02:27 +01:00
Sam Brannen cd9d562129 Check thread count in ParallelApplicationEventsIntegrationTests 2021-01-25 11:02:36 +01:00
Brian Clozel 080ba5d448 Upgrade JDK versions in CI pipeline 2021-01-25 10:51:47 +01:00
daisuzz d96448fde9 Fix typo in URI Encoding section of reference docs
Closes gh-26436
2021-01-24 14:38:33 +01:00
Sam Brannen 84eed00260 Upgrade to Gradle 6.8.1 2021-01-24 12:48:35 +01:00
Sam Brannen cee51b248a Upgrade to AssertJ 3.19.0 2021-01-24 12:11:17 +01:00
Rossen Stoyanchev 5a640bb635 Expose mvcPatternParser bean in WebMvcConfigurationSupport
See gh-26427
2021-01-22 17:31:59 +00:00
Rossen Stoyanchev 5c1cbb769c Check that PathPatternParser is not set too late
This commit ensures the PathPatternParser cannot be set after request mappings
have been initialized when it is too late.

See gh-26427
2021-01-22 17:31:59 +00:00
Sam Brannen 4a7a2258f9 Unwrap proxied DataSources in SqlScriptsTestExecutionListener
Prior to this commit, if the DataSource in the
DataSourceFromTransactionManager was wrapped in a proxy implementing
InfrastructureProxy, SqlScriptsTestExecutionListener would throw an
exception stating that the DataSource in the ApplicationContext and the
DataSource in the DataSourceFromTransactionManager were not the same.

This commit unwraps both data sources and compares the underlying
target instances to check for equality.

In addition, this commit makes the unwrapResourceIfNecessary() method in
TransactionSynchronizationUtils public.

Closes gh-26422
2021-01-22 18:22:00 +01:00
Rossen Stoyanchev bad8954e65 Append unique number to log prefix for Undertow
Closes gh-26430
2021-01-22 15:26:48 +00:00
Rossen Stoyanchev e4566c11b8 Apply PathPatternParser also to BeanNameUrlHandlerMapping
Closes gh-26414
2021-01-22 15:22:51 +00:00
Sam Brannen 9d8910d869 Improve inline documentation for PostProcessorRegistrationDelegate
This commit introduces warnings in invokeBeanFactoryPostProcessors()
and registerBeanPostProcessors() to deter people from submitting PRs
that result in breaking changes.

Closes gh-26401
2021-01-22 13:34:29 +01:00
Rossen Stoyanchev aa7584d252 Support for Jetty 10
Closes gh-26123
2021-01-21 20:16:21 +00:00
Arjen Poutsma e537844a09 Copy headers from part in MultipartBodyBuilder
This commit makes sure that Part.headers() is copied over when adding a
part in the MultipartBodyBuilder.

Closes gh-26410
2021-01-21 16:24:59 +01:00
Arjen Poutsma daa5465003 Checkstyle fix 2021-01-21 15:55:28 +01:00
Brian Clozel 5a4a677fbd Fix contextPath request matching with PathPatterns
Prior to this commit, `SimpleUrlHandlerMapping` and other
implementations of `AbstractUrlHandlerMapping` would not strip the
Servlet context path from the request path before performing path
matching with PathPattern instances.

This resulted in requests not matching as expected if the application
was configured with a Servlet context path.

This commit ensures that the `RequestPath` "path whithin the
application" is used for matching against path patterns.

Fixes gh-26411
2021-01-21 10:09:49 +01:00
Rossen Stoyanchev 584dabbb39 Add all non-file parts as request parameters
Closes gh-26400
2021-01-19 20:49:59 +00:00
Stephane Nicoll 4779323d33 Clarify behaviour of condition attribute of CachePut
Closes gh-26186
2021-01-19 15:14:26 +01:00
Arjen Poutsma 67112b211a Rollback to midnight in quartz expressions
This commit makes sure that the CronExpression rolls back the time to
midnight when dealing with Quartz expression fields (such as "L", "LW",
etc.).

Closes gh-26390
2021-01-19 15:06:40 +01:00
Juergen Hoeller 799885fd6d Introduce public defineClass variant for SmartClassLoader implementations
Closes gh-26403
2021-01-19 12:03:18 +01:00
Sam Brannen 3c959d3515 Fix grammar in Javadoc 2021-01-18 11:26:29 +01:00
Rossen Stoyanchev cfae40afb4 Return 415 for form data with @RequestBody in WebFlux
Closes gh-26386
2021-01-14 21:23:49 +00:00
Juergen Hoeller 1b1ae33efe Upgrade to Netty 4.1.58, Jackson 2.12.1, Apache Johnzon 1.2.9 2021-01-14 18:14:46 +01:00
Arjen Poutsma d80b08084e Document ordering of RouterFunction beans
Closes gh-26283
2021-01-14 16:18:22 +01:00
Arjen Poutsma d1a7fed16c Clean up content type in ContentNegotiatingViewResolver
This commit removes the quality value of the selected media type before
setting it in the request attribute.

Closes gh-26315
2021-01-14 14:30:07 +01:00
izeye 469eb8146e Add a missing space in exception message
Closes gh-26385
2021-01-14 12:01:04 +00:00
Rossen Stoyanchev 35225e5794 Add verify(Duration) to MockRestServiceServer
Closes gh-22618
2021-01-13 17:50:01 +00:00
Rossen Stoyanchev a7413ea76b Add recommendation for original multipart filename
See gh-26299
2021-01-12 17:28:44 +00:00
Rossen Stoyanchev c3c36ab498 Document choices for async ResponseEntity return values
Closes gh-22614
2021-01-12 14:37:01 +00:00
Nelson Osacky ea4fe7eaf7 Update Gradle Enterprise Plugin versions
Closes gh-26374
2021-01-12 13:46:14 +01:00
Sam Brannen 3112dc5f81 Improve JMH Jar configuration 2021-01-12 12:27:54 +01:00
Sam Brannen b07a6b3283 Upgrade Shadow plugin to version 6.1.0 2021-01-12 12:27:54 +01:00
Sam Brannen 6b9d5698ce Upgrade to JMH 1.25 2021-01-12 12:27:54 +01:00
Sam Brannen 72a9d0ff4b Upgrade JMH plugin to 0.5.2 2021-01-12 11:28:03 +01:00
Sam Brannen 872255a591 Fix ConcurrentBeanFactoryBenchmark 2021-01-12 10:59:57 +01:00
Brian Clozel 671270dae2 Fix optional deployments regex in release CI pipeline
This commit fixes the "optional-deployments" regex for ".zip" artifacts;
"\" do not need to be escaped in YML single-quoted strings.
2021-01-12 10:56:52 +01:00
Sam Brannen 6ea39fd122 Upgrade to Gradle 6.8
Closes gh-26368
2021-01-12 10:42:07 +01:00
Spring Buildmaster dadef9f5bc Next development version (v5.3.4-SNAPSHOT) 2021-01-12 06:34:25 +00:00
Rossen Stoyanchev fceceb480f Validate Resource type in ResourceHttpRequestHandler 2021-01-11 20:54:27 +00:00
Rossen Stoyanchev e5ab67bd71 ResourceHandlerRegistration exposes List<Resource> locations 2021-01-11 18:40:01 +00:00
Sébastien Deleuze 2d53570f4c Prevent kotlinx.serialization usage on interfaces
Remove support for open polymorphic serialization in
kotlinx.serialization web converters and codecs in order
to prevent serialization handling suitable for Jackson
or other general purpose Java JSON libraries.

This will probably need further refinements for collections
for example, and could ultimately be fixed when
kotlinx.serialization will provide a dedicated function to
evaluate upfront if a type can be serialized or not.

Closes gh-26298
2021-01-11 19:07:59 +01:00
Juergen Hoeller be5eb7037f Explicit notes on non-null enforcement and deep cause support in 5.3
Closes gh-26296
See gh-26317
2021-01-11 19:04:12 +01:00
Sam Brannen fff2291f2a Document context lifecycle & logging semantics for the TestContext framework
This commit documents ApplicationContext initialization and shutdown
logging semantics for console output triggered in conjunction with the
Spring TestContext Framework.

Closes gh-25385
2021-01-11 17:48:33 +01:00
Stephane Nicoll 917f3ad101 Upgrade to Reactor 2020.0.3
Closes gh-26367
2021-01-11 16:41:03 +01:00
Sam Brannen 2fd6e6e87c Polish Javadoc for base ExceptionHandlerMethodResolvers 2021-01-11 12:58:19 +01:00
Sam Brannen 570bdbd253 Avoid unnecessary sorting in base ExceptionHandlerMethodResolvers 2021-01-11 12:58:19 +01:00
Juergen Hoeller b587a16d46 Expose all exception causes as provided handler method arguments
Closes gh-26317
2021-01-11 11:10:07 +01:00
Rossen Stoyanchev dd8ea89bea Upgrade to Reactor 2020.0.3 snapshots
See gh-26367
2021-01-11 09:30:47 +00:00
Juergen Hoeller b0dea4d3f6 Upgrade to RxJava 2.2.20, OpenPDF 1.3.24, HtmlUnit 2.46 2021-01-11 09:45:14 +01:00
Juergen Hoeller 4ac27e4dab Suppress ClassCastException for payload type mismatch as well
Closes gh-26349
2021-01-11 09:44:38 +01:00
Nelson Osacky b0abdee012 Add Revved up by Gradle Enterprise badge to Readme
Similar to: https://github.com/spring-projects/spring-boot/pull/24640

Closes gh-26348
2021-01-11 09:40:00 +01:00
Brian Clozel e4dc863ad0 Fix headers keySet in WebFlux adapters
Prior to this commit, WebFlux native headers adapters would delegate the
`httpHeaders.keySet` to underlying implementations that do not honor the
`remove*` methods.

This commit fixes the `Set` implementation backing the
`httpHeaders.keySet` and ensures that headers can be safely removed from
the set.

Fixes gh-26361
2021-01-11 09:18:10 +01:00
Rossen Stoyanchev 689b5566bf Cache "no match" result from ExceptionHandler methods
Closes gh-26339
2021-01-08 19:25:11 +00:00
Arjen Poutsma 234b4719c6 Use default bounded elastic scheduler
Instead of using a new bounded elastic scheduler per
DefaultPartHttpMessageReader instance, which creates daemon threads that
are not shut down, we now use the shared bounded elastic scheduler.

Closes gh-26347
2021-01-08 14:32:49 +01:00
Arjen Poutsma ce1ae2f1b2 Only write non-default charset in FormHttpMessageConverter
This commit only writes the 'charset' parameter in the written headers
if it is non-default (not UTF-8), since RFC7578 states that the only
allowed parameter is 'boundary'.

See gh-25885
Closes gh-26290
2021-01-08 14:03:58 +01:00
Arjen Poutsma 69ce7d33b9 Polishing 2021-01-08 12:02:13 +01:00
Rossen Stoyanchev e36d4162c2 Filter empty buffers PayloadMethodArgumentResolver
An empty buffer for RSocket is an empty message and while some codecs
such as StringDecoder may be able turn that into something such as an
empty String, for other formats such as JSON it is invalid input.

Closes gh-26344
2021-01-07 17:26:00 +00:00
Arjen Poutsma d387d9ae1e Allow quartz expression in cron expression lists
This commit introduces support for lists of quartz cron fields, such
as "1L, LW" or "TUE#1, TUE#3, TUE#5".

Closes gh-26289
2021-01-07 14:27:07 +01:00
Rossen Stoyanchev 138f6bfd84 Update Javadoc of FilePart#filename
See gh-26299
2021-01-07 11:26:00 +00:00
Rossen Stoyanchev a1cf6bbc37 Avoid wrapping of AssertionError in ExchangeResult
Closes gh-26303
2021-01-06 21:56:14 +00:00
Rossen Stoyanchev fb040479eb Update Javadoc of MultipartFile#getOriginalFilename
Closes gh-26299
2021-01-06 21:56:14 +00:00
Rossen Stoyanchev d50375da8e Add more static imports to MockMvc snippets
Closes gh-26311
2021-01-06 21:56:14 +00:00
Rossen Stoyanchev fb2e53276c Assert WebFlux present for WebTestClient
Closes gh-26308
2021-01-06 21:56:14 +00:00
Rossen Stoyanchev c040cd7b05 Parsed RequestPath is recalculated on Forward
Closes gh-26318
2021-01-06 21:56:14 +00:00
Brian Clozel dcc8dcdff8 Set content length on ServletHttpResponse
Prior to this commit, the `ServletServerHttpResponse` would copy headers
from the `HttpHeaders` map and calls methods related to headers exposed
as properties (content-type, character encoding).

Unlike its reactive variant, this would not set the content length.
Depending on the Servlet container implementation, this could cause
duplicate Content-Length response headers in the actual HTTP response.

This commit aligns both implementations and ensures that the
`setContentLengthLong` method is called if necessary so that the Servlet
container can ensure a single header for that.

Fixes gh-26330
2021-01-06 20:38:45 +01:00
Paul Warren efa360cff7 Avoid writing content-length twice in resource handling
Prior to this commit, the `ResourceHttpRequestHandler` would write the
resource size to the "Content-Length" response header.
This is already done by the underlying `ResourceHttpMessageConverter`
and `ResourceRegionHttpMessageConverter`.

This commit avoid this duplicate operation and delegates instead to the
converters.

See gh-26330
Closes gh-26340
2021-01-06 20:38:45 +01:00
Juergen Hoeller 5804db6479 Upgrade to Mockito 3.7, Checkstyle 8.39, RxJava 3.0.9, Joda-Time 2.10.9 2021-01-05 19:33:01 +01:00
Rossen Stoyanchev faa749934b Minor refactoring
- Remove unused clone() code left-over from previous way of cloning.
- Lazy instantiation of ServerCodecConfigurer in
  HttpWebHandlerAdapter since in most cases the configurer instance
  is set externally.

Closes gh-26263
2021-01-05 17:20:26 +00:00
Rossen Stoyanchev e1385090e4 Cached codec instances in DefaultCodecs
Closes gh-26263
2021-01-05 17:20:26 +00:00
Sébastien Deleuze 3113917c55 Update ReactiveAdapterRegistry to do classpath checks during init
To improve GraalVM native image footprint and compatibility.

Closes gh-26295
2021-01-05 09:17:34 +01:00
Rossen Stoyanchev 994a35d691 Mutated ServerHttpRequest returns native request correctly
Closes gh-26304
2021-01-04 21:40:25 +00:00
Juergen Hoeller 8095ba4cd2 Upgrade to Hibernate Validator 6.2 (and Hibernate ORM 5.4.27)
Closes gh-26255
2021-01-04 19:10:46 +01:00
Mattison chao bd2edf26df Fix implementation of isOpen() in ReactorNettyWebSocketSession
See gh-26332 for details.

Closes gh-26341
2021-01-04 17:13:39 +01:00
Arjen Poutsma 4f6d26b25c Parse list with range delta in CronExpression
Closes gh-26313
2021-01-04 16:02:28 +01:00
Sébastien Deleuze 6d85a8a0cc Fix JdbcOperationsExtensions
This commit reverts some changes done in 2d5f9723fa
in order to fix JdbcOperations.queryForObject and
JdbcOperations.queryForList.

It is not possible to provide a vararg variant in Kotlin
for now due to a signature clash, but that will probably
make sense in Spring Framework 6 timeframe along to array
based variant removal for both Java and Kotlin.

Closes gh-26312
2021-01-04 14:57:31 +01:00
Sam Brannen 44b29b6ecf Remove obsolete commandName attribute in spring-form.tld
Since support for the commandName attribute was removed from the
implementation of FormTag in Spring Framework 5.0, the presence of the
commandName attribute in the spring-form.tld file is no longer valid
and can lead one to assume that the commandName attribute is still
supported -- for example when using code completion in a JSP editor.

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

Closes gh-26337
2021-01-03 17:46:20 +01:00
Sam Brannen 91a6254187 Test status quo for repeatable @PropertySource on composed annotation
This commit introduces tests that verify support for using
@PropertySource as a repeatable annotation without the
@PropertySources container, both locally on an @Configuration class
and on a custom composed annotation.

See gh-26329
2021-01-02 16:21:53 +01:00
Sam Brannen c8ad762b40 Polishing 2021-01-02 16:21:53 +01:00
Sam Brannen 5c1176786c Test status quo for list support in CronExpression
See gh-26289
2020-12-30 14:38:58 +01:00
diguage eab61f692f Merge source and substitution configuration in reference docs
Closes gh-25545
2020-12-28 20:49:36 +01:00
Sam Brannen ee41ebc1ab Polish MockMvcReuseTests 2020-12-28 16:03:16 +01:00
Sam Brannen 885f6dbab9 Ensure ParallelApplicationEventsIntegrationTests passes on CI server
See gh-25616
2020-12-23 10:16:13 +01:00
Juergen Hoeller b0f404722e Upgrade to ASM master (including early support for Java 17 bytecode)
Closes gh-26307
2020-12-21 19:33:33 +01:00
Juergen Hoeller cd7b0b11f3 Upgrade to Hibernate ORM 5.4.26, Netty 4.1.56, XMLUnit 2.8.2 2020-12-21 19:10:17 +01:00
Sam Brannen 1565f4b83e Introduce ApplicationEvents to assert events published during tests
This commit introduces a new feature in the Spring TestContext
Framework (TCF) that provides support for recording application events
published in the ApplicationContext so that assertions can be performed
against those events within tests. All events published during the
execution of a single test are made available via the ApplicationEvents
API which allows one to process the events as a java.util.Stream.

The following example demonstrates usage of this new feature.

@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents
class OrderServiceTests {

    @Autowired
    OrderService orderService;

    @Autowired
    ApplicationEvents events;

    @Test
    void submitOrder() {
        // Invoke method in OrderService that publishes an event
        orderService.submitOrder(new Order(/* ... */));
        // Verify that an OrderSubmitted event was published
        int numEvents = events.stream(OrderSubmitted.class).count();
        assertThat(numEvents).isEqualTo(1);
    }
}

To enable the feature, a test class must be annotated or meta-annotated
with @RecordApplicationEvents. Behind the scenes, a new
ApplicationEventsTestExecutionListener manages the registration of
ApplicationEvents for the current thread at various points within the
test execution lifecycle and makes the current instance of
ApplicationEvents available to tests via an @Autowired field in the
test class. The latter is made possible by a custom ObjectFactory that
is registered as a "resolvable dependency". Thanks to the magic of
ObjectFactoryDelegatingInvocationHandler in the spring-beans module,
the ApplicationEvents instance injected into test classes is
effectively a scoped-proxy that always accesses the ApplicationEvents
managed for the current test.

The current ApplicationEvents instance is stored in a ThreadLocal
variable which is made available in the ApplicationEventsHolder.
Although this class is public, it is only intended for use within the
TCF or in the implementation of third-party extensions.

ApplicationEventsApplicationListener is responsible for listening to
all application events and recording them in the current
ApplicationEvents instance. A single
ApplicationEventsApplicationListener is registered with the test's
ApplicationContext by the ApplicationEventsTestExecutionListener.

The SpringExtension has also been updated to support parameters of type
ApplicationEvents via the JUnit Jupiter ParameterResolver extension
API. This allows JUnit Jupiter based tests to receive access to the
current ApplicationEvents via test and lifecycle method parameters as
an alternative to @Autowired fields in the test class.

Closes gh-25616
2020-12-20 23:46:19 +01:00
Sam Brannen 65a395ef0e Fix syntax in Kotlin example 2020-12-19 15:29:40 +01:00
Juergen Hoeller 947255e377 Always propagate checked exceptions from Kotlin code behind CGLIB proxies
Closes gh-23844
2020-12-18 11:55:20 +01:00
Rossen Stoyanchev 499be70a71 Update async dispatch check in OncePerRequestFilter
We no longer need to rely on an indirect check since Servlet 3.0 is expected
so we can just check the DispatcherType of the request.

Closes gh-26282
2020-12-17 17:33:39 +00:00
Rossen Stoyanchev 0cf5005a3d Apply abortOnCancel in JettyClientHttpConnector
This new option allows a cancel signal to abort the request, which is
how we expect a connection to be aborted in a reactive chain that
involves the WebClient.

Closes gh-26287
2020-12-17 17:18:38 +00:00
Rossen Stoyanchev f07fc76cf3 Limit scheme/host check in fromUriString to HTTP URLs
Closes gh-26258
2020-12-17 14:49:57 +00:00
Juergen Hoeller d3e1c54354 Upgrade to Hibernate Validator 6.1.7, Jetty Reactive HttpClient 1.1.5, Joda-Time 2.10.8, XMLUnit 2.8.1 2020-12-17 10:46:13 +01:00
Juergen Hoeller a109b4c31a Translate PostgreSQL code 21000 (cardinality_violation)
Closes gh-26276
2020-12-16 22:27:57 +01:00
Juergen Hoeller 00b56c026a Consistent handling of NullBean instances in resolveNamedBean
Closes gh-26271
2020-12-16 22:27:41 +01:00
Juergen Hoeller fbd2ffdd23 Consistent declarations and assertions in MockMultipartFile
See gh-26261
2020-12-16 22:27:33 +01:00
Sam Brannen b3a47c76f8 Introduce TestContextAnnotationUtils.hasAnnotation() 2020-12-15 22:48:33 +01:00
Sam Brannen 1292947f78 Introduce computeAttribute() in AttributeAccessor
This commit introduces computeAttribute() as an interface default method
in the AttributeAccessor API. This serves as a convenience analogous to
the computeIfAbsent() method in java.util.Map.

Closes gh-26281
2020-12-15 22:47:02 +01:00
Sam Brannen ef6a582c78 Clean up warnings in Gradle build 2020-12-15 22:47:02 +01:00
Rossen Stoyanchev a11d1c8510 Enrich WebSocketHandler context
Closes gh-26210
2020-12-15 21:34:16 +00:00
Brian Clozel 83c19cd60e Fix NPE when calling NettyHeadersAdapter.add()
Prior to this commit, the `NettyHeadersAdapter` would directly delegate
the `add()` and `set()` calls to the adapted
`io.netty.handler.codec.http.HttpHeaders`. This implementation rejects
`null` values with exceptions.

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

Fixes gh-26274
2020-12-15 13:23:53 +01:00
Rossen Stoyanchev bcfbde9848 Parse parts in MockMultipartHttpServletRequestBuilder
Closes gh-26261
2020-12-14 21:13:29 +00:00
izeye 17e6cf1cc1 Replace AtomicReference<Boolean> with AtomicBoolean in AbstractServerHttpResponse.writeWith() 2020-12-12 10:40:05 +01:00
Juergen Hoeller ec33b4241a Upgrade to Netty 4.1.55, Tomcat 9.0.41, Caffeine 2.8.8 2020-12-11 11:53:53 +01:00
Brian Clozel 657641ebaa Remove JDK14 CI variant from build pipeline 2020-12-11 11:08:33 +01:00
Brian Clozel 4597e9b547 Upgrade CI container images to Ubuntu Focal 2020-12-11 09:46:00 +01:00
Brian Clozel 721dacca5a Upgrade JDK8, JDK11 and JDK15 versions in CI build 2020-12-11 09:45:21 +01:00
alexscari 9d124fffcb Fix typo in Javadoc for AbstractJdbcCall
Closes gh-26254
2020-12-10 17:32:58 +01:00
Juergen Hoeller 2a47751fcd Defensively handle loadClass null result in BeanUtils.findEditorByConvention
Closes gh-26252
2020-12-10 16:24:32 +01:00
Spring Buildmaster 06e352822a Next development version (v5.3.3-SNAPSHOT) 2020-12-09 06:11:23 +00:00
345 changed files with 10082 additions and 3386 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
<!--
!!! For Security Vulnerabilities, please go to https://pivotal.io/security !!!
!!! For Security Vulnerabilities, please go to https://spring.io/security-policy !!!
-->
**Affects:** \<Spring Framework version>
@@ -14,4 +14,4 @@ Thanks for taking the time to create an issue. Please read the following:
Issue or Pull Request? Create only one, not both. GitHub treats them as the same.
If unsure, start with an issue, and if you submit a pull request later, the
issue will be closed as superseded.
-->
-->
+1 -1
View File
@@ -1,4 +1,4 @@
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build")
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
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".
+33 -32
View File
@@ -1,15 +1,16 @@
plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
id 'io.spring.nohttp' version '0.0.5.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.4.21' apply false
id 'org.jetbrains.kotlin.jvm' version '1.4.30' apply false
id 'org.jetbrains.dokka' version '0.10.1' apply false
id 'org.asciidoctor.jvm.convert' version '3.1.0'
id 'org.asciidoctor.jvm.pdf' version '3.1.0'
id 'de.undercouch.download' version '4.1.1'
id "io.freefair.aspectj" version '5.1.1' apply false
id "com.github.ben-manes.versions" version '0.28.0'
id "me.champeau.gradle.jmh" version "0.5.0" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.21" apply false
id "com.github.johnrengelman.shadow" version "6.1.0" apply false
id "me.champeau.gradle.jmh" version "0.5.2" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.30" apply false
}
ext {
@@ -25,15 +26,15 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.0"
mavenBom "io.netty:netty-bom:4.1.54.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.2"
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.1"
mavenBom "io.netty:netty-bom:4.1.59.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.4"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR8"
mavenBom "io.rsocket:rsocket-bom:1.1.0"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.35.v20201120"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.21"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.36.v20210114"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.30"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.2"
mavenBom "org.junit:junit-bom:5.7.0"
mavenBom "org.junit:junit-bom:5.7.1"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.14.0') {
@@ -63,8 +64,8 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.19"
dependency "io.reactivex.rxjava3:rxjava:3.0.8"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.0.10"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "com.caucho:hessian:4.0.63"
@@ -75,11 +76,11 @@ configure(allprojects) { project ->
dependency "com.google.code.gson:gson:2.8.6"
dependency "com.google.protobuf:protobuf-java-util:3.14.0"
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
dependency("com.thoughtworks.xstream:xstream:1.4.14") {
dependency("com.thoughtworks.xstream:xstream:1.4.15") {
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
}
dependency "org.apache.johnzon:johnzon-jsonb:1.2.8"
dependency "org.apache.johnzon:johnzon-jsonb:1.2.10"
dependency("org.codehaus.jettison:jettison:1.3.8") {
exclude group: "stax", name: "stax-api"
}
@@ -95,8 +96,8 @@ configure(allprojects) { project ->
}
dependency "com.h2database:h2:1.4.200"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.6"
dependency "com.github.librepdf:openpdf:1.3.23"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
dependency "com.github.librepdf:openpdf:1.3.25"
dependency "com.rometools:rome:1.15.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.3"
@@ -123,23 +124,23 @@ configure(allprojects) { project ->
dependency "net.sf.ehcache:ehcache:2.10.6"
dependency "org.ehcache:jcache:1.0.1"
dependency "org.ehcache:ehcache:3.4.0"
dependency "org.hibernate:hibernate-core:5.4.25.Final"
dependency "org.hibernate:hibernate-validator:6.1.6.Final"
dependency "org.hibernate:hibernate-core:5.4.28.Final"
dependency "org.hibernate:hibernate-validator:6.2.0.Final"
dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.40') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.43') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
}
}
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.40') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.43') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.3.Final') {
dependencySet(group: 'io.undertow', version: '2.2.4.Final') {
entry 'undertow-core'
entry('undertow-websockets-jsr') {
exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec"
@@ -162,7 +163,7 @@ configure(allprojects) { project ->
}
dependency 'org.apache.httpcomponents.client5:httpclient5:5.0.3'
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.0.3'
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.4"
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.5"
dependency "org.jruby:jruby:9.2.13.0"
dependency "org.python:jython-standalone:2.7.1"
@@ -181,7 +182,7 @@ configure(allprojects) { project ->
exclude group: "dom4j", name: "dom4j"
}
dependency("junit:junit:4.13.1") {
dependency("junit:junit:4.13.2") {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
dependency("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1") {
@@ -190,14 +191,14 @@ configure(allprojects) { project ->
dependency "org.testng:testng:7.3.0"
dependency "org.hamcrest:hamcrest:2.1"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.18.1"
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
dependency "org.assertj:assertj-core:3.19.0"
dependencySet(group: 'org.xmlunit', version: '2.8.2') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '3.6.28') {
dependencySet(group: 'org.mockito', version: '3.7.7') {
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
@@ -205,10 +206,10 @@ configure(allprojects) { project ->
}
dependency "io.mockk:mockk:1.10.2"
dependency("net.sourceforge.htmlunit:htmlunit:2.45.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.47.1") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.45.0") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.47.1") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -236,8 +237,8 @@ configure(allprojects) { project ->
dependency "com.ibm.websphere:uow:6.0.2.17"
dependency "com.jamonapi:jamon:2.82"
dependency "joda-time:joda-time:2.10.6"
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7"
dependency "joda-time:joda-time:2.10.10"
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.8"
dependency "org.javamoney:moneta:1.3"
dependency "com.sun.activation:javax.activation:1.2.0"
@@ -339,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "8.38"
toolVersion = "8.39"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -380,8 +381,8 @@ configure([rootProject] + javaProjects) { project ->
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
"https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.1/",
"https://junit.org/junit5/docs/5.7.0/api/"
"https://junit.org/junit4/javadoc/4.13.2/",
"https://junit.org/junit5/docs/5.7.1/api/"
] as String[]
}
+2 -2
View File
@@ -3,7 +3,7 @@ logging:
io.spring.concourse: DEBUG
distribute:
optional-deployments:
- '.*\\.zip'
- '.*\.zip'
spring:
main:
banner-mode: off
banner-mode: off
+3 -6
View File
@@ -3,16 +3,13 @@ set -e
case "$1" in
java8)
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u265-b01/OpenJDK8U-jdk_x64_linux_hotspot_8u265b01.tar.gz"
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u282-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u282b08.tar.gz"
;;
java11)
echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.8%2B10/OpenJDK11U-jdk_x64_linux_hotspot_11.0.8_10.tar.gz"
;;
java14)
echo "https://github.com/AdoptOpenJDK/openjdk14-binaries/releases/download/jdk-14.0.2%2B12/OpenJDK14U-jdk_x64_linux_hotspot_14.0.2_12.tar.gz"
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"
;;
java15)
echo "https://github.com/AdoptOpenJDK/openjdk15-binaries/releases/download/jdk-15%2B36/OpenJDK15U-jdk_x64_linux_hotspot_15_36.tar.gz"
echo "https://github.com/AdoptOpenJDK/openjdk15-binaries/releases/download/jdk-15.0.2%2B7/OpenJDK15U-jdk_x64_linux_hotspot_15.0.2_7.tar.gz"
;;
*)
echo $"Unknown java version"
+4 -1
View File
@@ -5,8 +5,11 @@ set -ex
# UTILS
###########################################################
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --no-install-recommends -y ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
dpkg-reconfigure --frontend noninteractive tzdata
rm -rf /var/lib/apt/lists/*
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.3/concourse-java.sh > /opt/concourse-java.sh
@@ -1,4 +1,4 @@
FROM ubuntu:bionic-20200713
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
@@ -1,4 +1,4 @@
FROM ubuntu:bionic-20200713
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
@@ -1,8 +0,0 @@
FROM ubuntu:bionic-20200713
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java14
ENV JAVA_HOME /opt/openjdk
ENV PATH $JAVA_HOME/bin:$PATH
@@ -1,4 +1,4 @@
FROM ubuntu:bionic-20200713
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+1 -47
View File
@@ -87,12 +87,6 @@ resources:
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-jdk11-ci-image
- name: spring-framework-jdk14-ci-image
type: docker-image
icon: docker
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-jdk14-ci-image
- name: spring-framework-jdk15-ci-image
type: docker-image
icon: docker
@@ -123,14 +117,6 @@ resources:
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk11-build
- name: repo-status-jdk14-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk14-build
- name: repo-status-jdk15-build
type: github-status-resource
icon: eye-check-outline
@@ -176,10 +162,6 @@ jobs:
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk11-ci-image/Dockerfile
- put: spring-framework-jdk14-ci-image
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk14-ci-image/Dockerfile
- put: spring-framework-jdk15-ci-image
params:
build: ci-images-git-repo/ci/images
@@ -268,34 +250,6 @@ jobs:
<<: *slack-fail-params
- put: repo-status-jdk11-build
params: { state: "success", commit: "git-repo" }
- name: jdk14-build
serial: true
public: true
plan:
- get: spring-framework-jdk14-ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk14-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
privileged: true
timeout: ((task-timeout))
image: spring-framework-jdk14-ci-image
file: git-repo/ci/tasks/check-project.yml
params:
BRANCH: ((branch))
<<: *gradle-enterprise-task-params
on_failure:
do:
- put: repo-status-jdk14-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk14-build
params: { state: "success", commit: "git-repo" }
- name: jdk15-build
serial: true
public: true
@@ -480,7 +434,7 @@ jobs:
groups:
- name: "builds"
jobs: ["build", "jdk11-build", "jdk14-build", "jdk15-build"]
jobs: ["build", "jdk11-build", "jdk15-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone","promote-rc", "promote-release", "sync-to-maven-central"]
- name: "ci-images"
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.2-SNAPSHOT
version=5.3.4
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
-8
View File
@@ -55,14 +55,6 @@ eclipse.classpath.file.whenMerged { classpath ->
}
}
// Ensure that test fixture dependencies are handled properly in Gradle 6.7.
// Bug fixed in Gradle 6.8: https://github.com/gradle/gradle/issues/14932
eclipse.classpath.file.whenMerged {
entries.findAll { it instanceof ProjectDependency }.each {
it.entryAttributes.remove('without_test_code')
}
}
// Ensure that JMH sources and resources are treated as test classpath entries
// so that they can see test fixtures.
// https://github.com/melix/jmh-gradle-plugin/issues/157
+22 -2
View File
@@ -1,17 +1,37 @@
apply plugin: 'org.springframework.build.compile'
apply plugin: 'org.springframework.build.optional-dependencies'
// Uncomment the following for Shadow support in the jmhJar block.
// Currently commented out due to ZipException: archive is not a ZIP archive
// apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'me.champeau.gradle.jmh'
apply from: "$rootDir/gradle/publications.gradle"
dependencies {
jmh 'org.openjdk.jmh:jmh-core:1.23'
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.23'
jmh 'org.openjdk.jmh:jmh-core:1.25'
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.25'
jmh 'net.sf.jopt-simple:jopt-simple:4.6'
}
jmh {
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
}
jmhJar {
// Uncomment the following for Shadow's Transformer support.
// mergeServiceFiles()
// append('META-INF/spring.handlers')
// append('META-INF/spring.schemas')
// append('META-INF/spring.tooling')
exclude 'LICENSE'
exclude 'THIRD-PARTY'
exclude 'META-INF/license.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE*'
exclude 'META-INF/NOTICE'
exclude 'META-INF/THIRD-PARTY'
}
jar {
manifest.attributes["Implementation-Title"] = project.name
manifest.attributes["Implementation-Version"] = project.version
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+2 -2
View File
@@ -6,8 +6,8 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.2"
id "io.spring.gradle-enterprise-conventions" version "0.0.2"
id "com.gradle.enterprise" version "3.5.1"
id "io.spring.ge.conventions" version "0.0.7"
}
include "spring-aop"
@@ -48,6 +48,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;
@@ -752,10 +753,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);
}
}
@@ -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.
@@ -20,6 +20,7 @@ import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
import org.springframework.core.NativeDetector;
/**
* Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
@@ -47,17 +48,10 @@ import org.springframework.aop.SpringProxy;
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
/**
* Whether this environment lives within a native image.
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
*/
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!IN_NATIVE_IMAGE &&
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == 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.
@@ -116,7 +116,7 @@ public abstract class AbstractPoolingTargetSource extends AbstractPrototypeBased
/**
* Return an IntroductionAdvisor that providing a mixin
* Return an IntroductionAdvisor that provides a mixin
* exposing statistics about the pool maintained by this object.
*/
public DefaultIntroductionAdvisor getPoolingConfigMixin() {
@@ -1,14 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="bean1" class="org.springframework.beans.factory.ConcurrentBeanFactoryTests$ConcurrentBean"
<bean id="bean1" class="org.springframework.beans.factory.ConcurrentBeanFactoryBenchmark$ConcurrentBean"
scope="prototype">
<property name="date" value="2004/08/08"/>
</bean>
<bean id="bean2" class="org.springframework.beans.factory.ConcurrentBeanFactoryTests$ConcurrentBean"
<bean id="bean2" class="org.springframework.beans.factory.ConcurrentBeanFactoryBenchmark$ConcurrentBean"
scope="prototype">
<property name="date" value="2000/02/02"/>
</bean>
@@ -268,10 +268,7 @@ public abstract class BeanUtils {
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
Assert.notNull(clazz, "Class must not be null");
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
Constructor<T> kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz);
if (kotlinPrimaryConstructor != null) {
return kotlinPrimaryConstructor;
}
return KotlinDelegate.findPrimaryConstructor(clazz);
}
return null;
}
@@ -543,6 +540,7 @@ public abstract class BeanUtils {
if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
return null;
}
ClassLoader cl = targetType.getClassLoader();
if (cl == null) {
try {
@@ -559,28 +557,34 @@ public abstract class BeanUtils {
return null;
}
}
String targetTypeName = targetType.getName();
String editorName = targetTypeName + "Editor";
try {
Class<?> editorClass = cl.loadClass(editorName);
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
if (logger.isInfoEnabled()) {
logger.info("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
if (editorClass != null) {
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
if (logger.isInfoEnabled()) {
logger.info("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
}
unknownEditorTypes.add(targetType);
return null;
}
unknownEditorTypes.add(targetType);
return null;
return (PropertyEditor) instantiateClass(editorClass);
}
return (PropertyEditor) instantiateClass(editorClass);
// Misbehaving ClassLoader returned null instead of ClassNotFoundException
// - fall back to unknown editor type registration below
}
catch (ClassNotFoundException ex) {
if (logger.isTraceEnabled()) {
logger.trace("No property editor [" + editorName + "] found for type " +
targetTypeName + " according to 'Editor' suffix convention");
}
unknownEditorTypes.add(targetType);
return null;
// Ignore - fall back to unknown editor type registration below
}
if (logger.isTraceEnabled()) {
logger.trace("No property editor [" + editorName + "] found for type " +
targetTypeName + " according to 'Editor' suffix convention");
}
unknownEditorTypes.add(targetType);
return null;
}
/**
@@ -772,7 +776,14 @@ public abstract class BeanUtils {
if (readMethod != null) {
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
if (targetResolvableType.isAssignableFrom(sourceResolvableType)) {
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
boolean isAssignable =
(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetResolvableType.isAssignableFrom(sourceResolvableType));
if (isAssignable) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,10 +41,10 @@ public class TypeMismatchException extends PropertyAccessException {
private String propertyName;
@Nullable
private transient Object value;
private final transient Object value;
@Nullable
private Class<?> requiredType;
private final Class<?> requiredType;
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -628,45 +628,58 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
value = resolveFieldValue(field, bean, beanName);
}
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
}
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
}
return value;
}
}
@@ -695,58 +708,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
Method method = (Method) this.member;
Object[] arguments;
if (this.cached) {
// Shortcut for avoiding synchronization...
arguments = resolveCachedArguments(beanName);
try {
arguments = resolveCachedArguments(beanName);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
arguments = resolveMethodArguments(method, bean, beanName);
}
}
else {
int argumentCount = method.getParameterCount();
arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
}
else {
this.cachedMethodArguments = null;
}
this.cached = true;
}
}
arguments = resolveMethodArguments(method, bean, beanName);
}
if (arguments != null) {
try {
@@ -771,6 +742,59 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
return arguments;
}
@Nullable
private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) {
int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
}
else {
this.cachedMethodArguments = null;
}
this.cached = true;
}
}
return arguments;
}
}
@@ -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.
@@ -52,6 +52,7 @@ import org.springframework.util.StringUtils;
* @author Dave Syer
* @author Juergen Hoeller
* @author Sam Brannen
* @author Brian Clozel
* @since 4.1
*/
public abstract class YamlProcessor {
@@ -128,10 +129,11 @@ public abstract class YamlProcessor {
/**
* Set the supported types that can be loaded from YAML documents.
* <p>If no supported types are configured, all types encountered in YAML
* documents will be supported. If an unsupported type is encountered, an
* {@link IllegalStateException} will be thrown when the corresponding YAML
* node is processed.
* <p>If no supported types are configured, only Java standard classes
* (as defined in {@link org.yaml.snakeyaml.constructor.SafeConstructor})
* encountered in YAML documents will be supported.
* If an unsupported type is encountered, an {@link IllegalStateException}
* will be thrown when the corresponding YAML node is processed.
* @param supportedTypes the supported types, or an empty array to clear the
* supported types
* @since 5.1.16
@@ -182,12 +184,8 @@ public abstract class YamlProcessor {
protected Yaml createYaml() {
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setAllowDuplicateKeys(false);
if (!this.supportedTypes.isEmpty()) {
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
new DumperOptions(), loaderOptions);
}
return new Yaml(loaderOptions);
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
new DumperOptions(), loaderOptions);
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
@@ -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.
@@ -73,6 +73,7 @@ import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.NativeDetector;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.ResolvableType;
@@ -123,14 +124,6 @@ import org.springframework.util.StringUtils;
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
/**
* Whether this environment lives within a native image.
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
*/
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
/** Strategy for creating bean instances. */
private InstantiationStrategy instantiationStrategy;
@@ -184,7 +177,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
if (IN_NATIVE_IMAGE) {
if (NativeDetector.inNativeImage()) {
this.instantiationStrategy = new SimpleInstantiationStrategy();
}
else {
@@ -1022,6 +1015,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
throw ex;
}
catch (BeanCreationException ex) {
// Don't swallow a linkage error since it contains a full stacktrace on
// first occurrence... and just a plain NoClassDefFoundError afterwards.
if (ex.contains(LinkageError.class)) {
throw ex;
}
// Instantiation failure, maybe too early...
if (logger.isDebugEnabled()) {
logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1194,8 +1194,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
this.primary == that.primary &&
this.nonPublicAccessAllowed == that.nonPublicAccessAllowed &&
this.lenientConstructorResolution == that.lenientConstructorResolution &&
ObjectUtils.nullSafeEquals(this.constructorArgumentValues, that.constructorArgumentValues) &&
ObjectUtils.nullSafeEquals(this.propertyValues, that.propertyValues) &&
equalsConstructorArgumentValues(that) &&
equalsPropertyValues(that) &&
ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) &&
ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) &&
ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) &&
@@ -1208,12 +1208,30 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
super.equals(other));
}
private boolean equalsConstructorArgumentValues(AbstractBeanDefinition other) {
if (!hasConstructorArgumentValues()) {
return !other.hasConstructorArgumentValues();
}
return ObjectUtils.nullSafeEquals(this.constructorArgumentValues, other.constructorArgumentValues);
}
private boolean equalsPropertyValues(AbstractBeanDefinition other) {
if (!hasPropertyValues()) {
return !other.hasPropertyValues();
}
return ObjectUtils.nullSafeEquals(this.propertyValues, other.propertyValues);
}
@Override
public int hashCode() {
int hashCode = ObjectUtils.nullSafeHashCode(getBeanClassName());
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.scope);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
if (hasConstructorArgumentValues()) {
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
}
if (hasPropertyValues()) {
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
}
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryBeanName);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryMethodName);
hashCode = 29 * hashCode + super.hashCode();
@@ -250,7 +250,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
@@ -264,7 +264,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
@@ -342,7 +342,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
@@ -355,7 +355,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
@@ -377,7 +377,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
@@ -395,14 +395,19 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
@SuppressWarnings("unchecked")
<T> T adaptBeanInstance(String name, Object bean, @Nullable Class<?> requiredType) {
// Check if required type matches the type of the actual bean instance.
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
Object convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return convertedBean;
return (T) convertedBean;
}
catch (TypeMismatchException ex) {
if (logger.isTraceEnabled()) {
@@ -1231,8 +1231,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
if (candidateNames.length == 1) {
String beanName = candidateNames[0];
return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args));
return resolveNamedBean(candidateNames[0], requiredType, args);
}
else if (candidateNames.length > 1) {
Map<String, Object> candidates = CollectionUtils.newLinkedHashMap(candidateNames.length);
@@ -1251,8 +1250,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
if (candidateName != null) {
Object beanInstance = candidates.get(candidateName);
if (beanInstance == null || beanInstance instanceof Class) {
beanInstance = getBean(candidateName, requiredType.toClass(), args);
if (beanInstance == null) {
return null;
}
if (beanInstance instanceof Class) {
return resolveNamedBean(candidateName, requiredType, args);
}
return new NamedBeanHolder<>(candidateName, (T) beanInstance);
}
@@ -1264,6 +1266,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return null;
}
@Nullable
private <T> NamedBeanHolder<T> resolveNamedBean(
String beanName, ResolvableType requiredType, @Nullable Object[] args) throws BeansException {
Object bean = getBean(beanName, null, args);
if (bean instanceof NullBean) {
return null;
}
return new NamedBeanHolder<T>(beanName, adaptBeanInstance(beanName, bean, requiredType.toClass()));
}
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@@ -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.
@@ -119,14 +119,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
else {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
if (destroyMethod.getParameterCount() > 0) {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
}
}
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod);
}
@@ -170,66 +172,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
/**
* If the current value of the given beanDefinition's "destroyMethodName" property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named "close" or
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
@Nullable
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
if (!(bean instanceof DisposableBean)) {
try {
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
return null;
}
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
/**
* Search for all DestructionAwareBeanPostProcessors in the List.
* @param processors the List to search
* @return the filtered List of DestructionAwareBeanPostProcessors
*/
@Nullable
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(
List<DestructionAwareBeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
if (!CollectionUtils.isEmpty(processors)) {
filteredPostProcessors = new ArrayList<>(processors.size());
for (DestructionAwareBeanPostProcessor processor : processors) {
if (processor.requiresDestruction(bean)) {
filteredPostProcessors.add(processor);
}
}
}
return filteredPostProcessors;
}
@Override
public void run() {
destroy();
@@ -384,12 +326,50 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
return inferDestroyMethodIfNecessary(bean, beanDefinition) != null;
}
/**
* If the current value of the given beanDefinition's "destroyMethodName" property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named "close" or
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
@Nullable
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
destroyMethodName = null;
if (!(bean instanceof DisposableBean)) {
try {
destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
}
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
}
return StringUtils.hasLength(destroyMethodName);
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
/**
@@ -408,4 +388,25 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
return false;
}
/**
* Search for all DestructionAwareBeanPostProcessors in the List.
* @param processors the List to search
* @return the filtered List of DestructionAwareBeanPostProcessors
*/
@Nullable
private static List<DestructionAwareBeanPostProcessor> filterPostProcessors(
List<DestructionAwareBeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
if (!CollectionUtils.isEmpty(processors)) {
filteredPostProcessors = new ArrayList<>(processors.size());
for (DestructionAwareBeanPostProcessor processor : processors) {
if (processor.requiresDestruction(bean)) {
filteredPostProcessors.add(processor);
}
}
}
return filteredPostProcessors;
}
}
@@ -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.
@@ -86,6 +86,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
@Nullable
volatile Method factoryMethodToIntrospect;
/** Package-visible field for caching a resolved destroy method name (also for inferred). */
@Nullable
volatile String resolvedDestroyMethodName;
/** Common lock for the four constructor fields below. */
final Object constructorArgumentLock = new Object();
@@ -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.
@@ -19,12 +19,16 @@ package org.springframework.beans;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.net.URL;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -196,6 +200,29 @@ class BeanUtilsTests {
assertThat(longListHolder.getList()).isEmpty();
}
@Test // gh-26531
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception {
Order original = new Order("test", Arrays.asList("foo", "bar"));
// Create a Proxy that loses the generic type information for the getLineItems() method.
OrderSummary proxy = proxyOrder(original);
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List<java.lang.String>");
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
// Ensure that our custom Proxy works as expected.
assertThat(proxy.getId()).isEqualTo("test");
assertThat(proxy.getLineItems()).containsExactly("foo", "bar");
// Copy from proxy to target.
Order target = new Order();
BeanUtils.copyProperties(proxy, target);
assertThat(target.getId()).isEqualTo("test");
assertThat(target.getLineItems()).containsExactly("foo", "bar");
}
@Test
void copyPropertiesWithEditable() throws Exception {
TestBean tb = new TestBean();
@@ -633,4 +660,77 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class Order {
private String id;
private List<String> lineItems;
Order() {
}
Order(String id, List<String> lineItems) {
this.id = id;
this.lineItems = lineItems;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getLineItems() {
return this.lineItems;
}
public void setLineItems(List<String> lineItems) {
this.lineItems = lineItems;
}
@Override
public String toString() {
return "Order [id=" + this.id + ", lineItems=" + this.lineItems + "]";
}
}
private interface OrderSummary {
String getId();
List<String> getLineItems();
}
private OrderSummary proxyOrder(Order order) {
return (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] { OrderSummary.class }, new OrderInvocationHandler(order));
}
private static class OrderInvocationHandler implements InvocationHandler {
private final Order order;
OrderInvocationHandler(Order order) {
this.order = order;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// Ignore args since OrderSummary doesn't declare any methods with arguments,
// and we're not supporting equals(Object), etc.
return Order.class.getDeclaredMethod(method.getName()).invoke(this.order);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
}
@@ -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.
@@ -297,6 +297,121 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalResourceInjectionWithSingletonRemoval() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", rbd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
IndexedTestBean itb = new IndexedTestBean();
bf.registerSingleton("indexedTestBean", itb);
NestedTestBean ntb1 = new NestedTestBean();
bf.registerSingleton("nestedTestBean1", ntb1);
NestedTestBean ntb2 = new NestedTestBean();
bf.registerSingleton("nestedTestBean2", ntb2);
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.destroySingleton("testBean");
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bean.getTestBean4()).isNull();
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.registerSingleton("testBean", tb);
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalResourceInjectionWithBeanDefinitionRemoval() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", rbd);
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
IndexedTestBean itb = new IndexedTestBean();
bf.registerSingleton("indexedTestBean", itb);
NestedTestBean ntb1 = new NestedTestBean();
bf.registerSingleton("nestedTestBean1", ntb1);
NestedTestBean ntb2 = new NestedTestBean();
bf.registerSingleton("nestedTestBean2", ntb2);
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.removeBeanDefinition("testBean");
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bean.getTestBean4()).isNull();
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalCollectionResourceInjection() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalCollectionResourceInjectionBean.class);
@@ -533,6 +648,83 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithSingletonRemoval() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
NestedTestBean ntb = new NestedTestBean();
bf.registerSingleton("nestedTestBean", ntb);
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.destroySingleton("nestedTestBean");
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isNull();
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.registerSingleton("nestedTestBean", ntb);
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithBeanDefinitionRemoval() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.removeBeanDefinition("nestedTestBean");
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isNull();
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithNullFromFactoryBean() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,9 +17,11 @@
package org.springframework.beans.factory.config;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.yaml.snakeyaml.constructor.ConstructorException;
@@ -28,7 +30,6 @@ import org.yaml.snakeyaml.scanner.ScannerException;
import org.springframework.core.io.ByteArrayResource;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.entry;
@@ -39,10 +40,12 @@ import static org.assertj.core.api.Assertions.entry;
* @author Dave Syer
* @author Juergen Hoeller
* @author Sam Brannen
* @author Brian Clozel
*/
class YamlProcessorTests {
private final YamlProcessor processor = new YamlProcessor() {};
private final YamlProcessor processor = new YamlProcessor() {
};
@Test
@@ -79,8 +82,8 @@ class YamlProcessorTests {
void badResource() {
setYaml("foo: bar\ncd\nspam:\n foo: baz");
assertThatExceptionOfType(ScannerException.class)
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("line 3, column 1");
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("line 3, column 1");
}
@Test
@@ -127,8 +130,8 @@ class YamlProcessorTests {
Map<String, Object> bar = (Map<String, Object>) map.get("bar");
assertThat(bar.get("spam")).isEqualTo("bucket");
List<Object> keysFromProperties = properties.keySet().stream().collect(toList());
List<String> keysFromFlattenedMap = flattenedMap.keySet().stream().collect(toList());
List<Object> keysFromProperties = new ArrayList<>(properties.keySet());
List<String> keysFromFlattenedMap = new ArrayList<>(flattenedMap.keySet());
assertThat(keysFromProperties).containsExactlyInAnyOrderElementsOf(keysFromFlattenedMap);
// Keys in the Properties object are sorted.
assertThat(keysFromProperties).containsExactly("bar.spam", "cat", "foo");
@@ -138,14 +141,23 @@ class YamlProcessorTests {
}
@Test
void customTypeSupportedByDefault() throws Exception {
void standardTypesSupportedByDefault() throws Exception {
setYaml("value: !!set\n ? first\n ? second");
this.processor.process((properties, map) -> {
assertThat(properties).containsExactly(entry("value[0]", "first"), entry("value[1]", "second"));
assertThat(map.get("value")).isInstanceOf(Set.class);
Set<String> set = (Set<String>) map.get("value");
assertThat(set).containsExactly("first", "second");
});
}
@Test
void customTypeNotSupportedByDefault() throws Exception {
URL url = new URL("https://localhost:9000/");
setYaml("value: !!java.net.URL [\"" + url + "\"]");
this.processor.process((properties, map) -> {
assertThat(properties).containsExactly(entry("value", url));
assertThat(map).containsExactly(entry("value", url));
});
assertThatExceptionOfType(ConstructorException.class)
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
}
@Test
@@ -168,8 +180,8 @@ class YamlProcessorTests {
setYaml("value: !!java.net.URL [\"https://localhost:9000/\"]");
assertThatExceptionOfType(ConstructorException.class)
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
}
private void setYaml(String yaml) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,6 +133,16 @@ public class BeanDefinitionTests {
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
bd.getPropertyValues();
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
bd.getConstructorArgumentValues();
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor;
*
* <p>In contrast to the {@link Cacheable @Cacheable} annotation, this annotation
* does not cause the advised method to be skipped. Rather, it always causes the
* method to be invoked and its result to be stored in the associated cache. Note
* method to be invoked and its result to be stored in the associated cache if the
* {@link #condition()} and {@link #unless()} expressions match accordingly. Note
* that Java8's {@code Optional} return types are automatically handled and its
* content is stored in the cache if present.
*
@@ -118,10 +119,15 @@ public @interface CachePut {
/**
* Spring Expression Language (SpEL) expression used for making the cache
* put operation conditional.
* <p>This expression is evaluated after the method has been called due to the
* nature of the put operation and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning the method result is always cached.
* <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation. For
* supported wrappers such as {@code Optional}, {@code #result} refers to the actual
* object, not the wrapper</li>
* <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
* references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
@@ -136,8 +142,6 @@ public @interface CachePut {
/**
* Spring Expression Language (SpEL) expression used to veto the cache put operation.
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning that caching is never vetoed.
* <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
@@ -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,8 @@
package org.springframework.context;
import java.util.function.Consumer;
import org.springframework.core.ResolvableType;
import org.springframework.core.ResolvableTypeProvider;
import org.springframework.util.Assert;
@@ -23,11 +25,12 @@ import org.springframework.util.Assert;
/**
* An {@link ApplicationEvent} that carries an arbitrary payload.
*
* <p>Mainly intended for internal use within the framework.
*
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 4.2
* @param <T> the payload type of the event
* @see ApplicationEventPublisher#publishEvent(Object)
* @see ApplicationListener#forPayload(Consumer)
*/
@SuppressWarnings("serial")
public class PayloadApplicationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
@@ -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,10 +26,10 @@ import org.springframework.core.io.ResourceLoader;
* the {@link org.springframework.context.ApplicationContextAware} interface.
*
* <p>Note that {@link org.springframework.core.io.Resource} dependencies can also
* be exposed as bean properties of type {@code Resource}, populated via Strings
* with automatic type conversion by the bean factory. This removes the need for
* implementing any callback interface just for the purpose of accessing a
* specific file resource.
* be exposed as bean properties of type {@code Resource} or {@code Resource[]},
* populated via Strings with automatic type conversion by the bean factory. This
* removes the need for implementing any callback interface just for the purpose
* of accessing specific file resources.
*
* <p>You typically need a {@link ResourceLoader} when your application object has to
* access a variety of file resources whose names are calculated. A good strategy is
@@ -48,7 +48,7 @@ import org.springframework.core.io.ResourceLoader;
* default; see also the {@code ResourcePatternUtils.getResourcePatternResolver} method.
*
* <p>As an alternative to a {@code ResourcePatternResolver} dependency, consider
* exposing bean properties of type {@code Resource} array, populated via pattern
* exposing bean properties of type {@code Resource[]} array, populated via pattern
* Strings with automatic type conversion by the bean factory at binding time.
*
* @author Juergen Hoeller
@@ -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.
@@ -52,6 +52,7 @@ import org.springframework.context.ApplicationStartupAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
import org.springframework.core.NativeDetector;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.Environment;
@@ -104,13 +105,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
private static final String IMPORT_REGISTRY_BEAN_NAME =
ConfigurationClassPostProcessor.class.getName() + ".importRegistry";
/**
* Whether this environment lives within a native image.
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
*/
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
private final Log logger = LogFactory.getLog(getClass());
@@ -427,7 +421,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty() || IN_NATIVE_IMAGE) {
if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
// nothing to enhance -> return immediately
enhanceConfigClasses.end();
return;
@@ -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.
@@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.ErrorHandler;
@@ -55,6 +56,9 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
@Nullable
private ErrorHandler errorHandler;
@Nullable
private volatile Log lazyLogger;
/**
* Create a new SimpleApplicationEventMulticaster.
@@ -173,12 +177,18 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
(event instanceof PayloadApplicationEvent &&
matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, ex);
// -> let's suppress the exception.
Log loggerToUse = this.lazyLogger;
if (loggerToUse == null) {
loggerToUse = LogFactory.getLog(getClass());
this.lazyLogger = loggerToUse;
}
if (loggerToUse.isTraceEnabled()) {
loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
@@ -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.
@@ -66,6 +66,7 @@ import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor;
import org.springframework.core.NativeDetector;
import org.springframework.core.ResolvableType;
import org.springframework.core.SpringProperties;
import org.springframework.core.annotation.AnnotationUtils;
@@ -164,13 +165,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
private static final boolean shouldIgnoreSpel = SpringProperties.getFlag("spring.spel.ignore");
/**
* Whether this environment lives within a native image.
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
*/
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
static {
// Eagerly load the ContextClosedEvent class to avoid weird classloader issues
@@ -711,7 +705,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// Detect a LoadTimeWeaver and prepare for weaving, if found.
if (!IN_NATIVE_IMAGE && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
if (!NativeDetector.inNativeImage() && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
@@ -752,7 +746,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (!IN_NATIVE_IMAGE && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
@@ -943,7 +937,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
if (!IN_NATIVE_IMAGE) {
if (!NativeDetector.inNativeImage()) {
LiveBeansView.registerApplicationContext(this);
}
}
@@ -1054,7 +1048,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
logger.debug("Closing " + this);
}
if (!IN_NATIVE_IMAGE) {
if (!NativeDetector.inNativeImage()) {
LiveBeansView.unregisterApplicationContext(this);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.context.support;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -74,6 +75,11 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
return (clazz.getClassLoader() instanceof ContextOverridingClassLoader);
}
@Override
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
return defineClass(name, b, 0, b.length, protectionDomain);
}
/**
* ClassLoader to be created for each loaded 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.
@@ -59,6 +59,19 @@ final class PostProcessorRegistrationDelegate {
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// WARNING: Although it may appear that the body of this method can be easily
// refactored to avoid the use of multiple loops and multiple lists, the use
// of multiple lists and multiple passes over the names of processors is
// intentional. We must ensure that we honor the contracts for PriorityOrdered
// and Ordered processors. Specifically, we must NOT cause processors to be
// instantiated (via getBean() invocations) or registered in the ApplicationContext
// in the wrong order.
//
// Before submitting a pull request (PR) to change this method, please review the
// list of all declined PRs involving changes to PostProcessorRegistrationDelegate
// to ensure that your proposal does not result in a breaking change:
// https://github.com/spring-projects/spring-framework/issues?q=PostProcessorRegistrationDelegate+is%3Aclosed+label%3A%22status%3A+declined%22
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<>();
@@ -192,6 +205,19 @@ final class PostProcessorRegistrationDelegate {
public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
// WARNING: Although it may appear that the body of this method can be easily
// refactored to avoid the use of multiple loops and multiple lists, the use
// of multiple lists and multiple passes over the names of processors is
// intentional. We must ensure that we honor the contracts for PriorityOrdered
// and Ordered processors. Specifically, we must NOT cause processors to be
// instantiated (via getBean() invocations) or registered in the ApplicationContext
// in the wrong order.
//
// Before submitting a pull request (PR) to change this method, please review the
// list of all declined PRs involving changes to PostProcessorRegistrationDelegate
// to ensure that your proposal does not result in a breaking change:
// https://github.com/spring-projects/spring-framework/issues?q=PostProcessorRegistrationDelegate+is%3Aclosed+label%3A%22status%3A+declined%22
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
// Register BeanPostProcessorChecker that logs an info message when
@@ -19,14 +19,13 @@ package org.springframework.scheduling.support;
import java.time.DateTimeException;
import java.time.temporal.Temporal;
import java.time.temporal.ValueRange;
import java.util.BitSet;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Efficient {@link BitSet}-based extension of {@link CronField}.
* Efficient bitwise-operator extension of {@link CronField}.
* Created using the {@code parse*} methods.
*
* @author Arjen Poutsma
@@ -130,8 +129,8 @@ final class BitsCronField extends CronField {
result.setBits(range);
}
else {
String rangeStr = value.substring(0, slashPos);
String deltaStr = value.substring(slashPos + 1);
String rangeStr = field.substring(0, slashPos);
String deltaStr = field.substring(slashPos + 1);
ValueRange range = parseRange(rangeStr, type);
if (rangeStr.indexOf('-') == -1) {
range = ValueRange.of(range.getMinimum(), type.range().getMaximum());
@@ -0,0 +1,97 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.support;
import java.time.temporal.Temporal;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Extension of {@link CronField} that wraps an array of cron fields.
*
* @author Arjen Poutsma
* @since 5.3.3
*/
final class CompositeCronField extends CronField {
private final CronField[] fields;
private final String value;
private CompositeCronField(Type type, CronField[] fields, String value) {
super(type);
this.fields = fields;
this.value = value;
}
/**
* Composes the given fields into a {@link CronField}.
*/
public static CronField compose(CronField[] fields, Type type, String value) {
Assert.notEmpty(fields, "Fields must not be empty");
Assert.hasLength(value, "Value must not be empty");
if (fields.length == 1) {
return fields[0];
}
else {
return new CompositeCronField(type, fields, value);
}
}
@Nullable
@Override
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
T result = null;
for (CronField field : this.fields) {
T candidate = field.nextOrSame(temporal);
if (result == null ||
candidate != null && candidate.compareTo(result) < 0) {
result = candidate;
}
}
return result;
}
@Override
public int hashCode() {
return this.value.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CompositeCronField)) {
return false;
}
CompositeCronField other = (CompositeCronField) o;
return type() == other.type() &&
this.value.equals(other.value);
}
@Override
public String toString() {
return type() + " '" + this.value + "'";
}
}
@@ -20,8 +20,10 @@ import java.time.DateTimeException;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.time.temporal.ValueRange;
import java.util.function.BiFunction;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -77,11 +79,18 @@ abstract class CronField {
* Parse the given value into a days of months {@code CronField}, the fourth entry of a cron expression.
*/
public static CronField parseDaysOfMonth(String value) {
if (value.contains("L") || value.contains("W")) {
return QuartzCronField.parseDaysOfMonth(value);
if (!QuartzCronField.isQuartzDaysOfMonthField(value)) {
return BitsCronField.parseDaysOfMonth(value);
}
else {
return BitsCronField.parseDaysOfMonth(value);
return parseList(value, Type.DAY_OF_MONTH, (field, type) -> {
if (QuartzCronField.isQuartzDaysOfMonthField(field)) {
return QuartzCronField.parseDaysOfMonth(field);
}
else {
return BitsCronField.parseDaysOfMonth(field);
}
});
}
}
@@ -98,15 +107,32 @@ abstract class CronField {
*/
public static CronField parseDaysOfWeek(String value) {
value = replaceOrdinals(value, DAYS);
if (value.contains("L") || value.contains("#")) {
return QuartzCronField.parseDaysOfWeek(value);
if (!QuartzCronField.isQuartzDaysOfWeekField(value)) {
return BitsCronField.parseDaysOfWeek(value);
}
else {
return BitsCronField.parseDaysOfWeek(value);
return parseList(value, Type.DAY_OF_WEEK, (field, type) -> {
if (QuartzCronField.isQuartzDaysOfWeekField(field)) {
return QuartzCronField.parseDaysOfWeek(field);
}
else {
return BitsCronField.parseDaysOfWeek(field);
}
});
}
}
private static CronField parseList(String value, Type type, BiFunction<String, Type, CronField> parseFieldFunction) {
Assert.hasLength(value, "Value must not be empty");
String[] fields = StringUtils.delimitedListToStringArray(value, ",");
CronField[] cronFields = new CronField[fields.length];
for (int i = 0; i < fields.length; i++) {
cronFields[i] = parseFieldFunction.apply(fields[i], type);
}
return CompositeCronField.compose(cronFields, type, value);
}
private static String replaceOrdinals(String value, String[] list) {
value = value.toUpperCase();
for (int i = 0; i < list.length; i++) {
@@ -38,24 +38,6 @@ import org.springframework.util.Assert;
*/
final class QuartzCronField extends CronField {
/**
* Temporal adjuster that returns the last weekday of the month.
*/
private static final TemporalAdjuster lastWeekdayOfMonth = temporal -> {
Temporal lastDayOfMonth = TemporalAdjusters.lastDayOfMonth().adjustInto(temporal);
int dayOfWeek = lastDayOfMonth.get(ChronoField.DAY_OF_WEEK);
if (dayOfWeek == 6) { // Saturday
return lastDayOfMonth.minus(1, ChronoUnit.DAYS);
}
else if (dayOfWeek == 7) { // Sunday
return lastDayOfMonth.minus(2, ChronoUnit.DAYS);
}
else {
return lastDayOfMonth;
}
};
private final Type rollForwardType;
private final TemporalAdjuster adjuster;
@@ -78,6 +60,12 @@ final class QuartzCronField extends CronField {
this.rollForwardType = rollForwardType;
}
/**
* Returns whether the given value is a Quartz day-of-month field.
*/
public static boolean isQuartzDaysOfMonthField(String value) {
return value.contains("L") || value.contains("W");
}
/**
* Parse the given value into a days of months {@code QuartzCronField}, the fourth entry of a cron expression.
@@ -91,11 +79,11 @@ final class QuartzCronField extends CronField {
throw new IllegalArgumentException("Unrecognized characters before 'L' in '" + value + "'");
}
else if (value.length() == 2 && value.charAt(1) == 'W') { // "LW"
adjuster = lastWeekdayOfMonth;
adjuster = lastWeekdayOfMonth();
}
else {
if (value.length() == 1) { // "L"
adjuster = TemporalAdjusters.lastDayOfMonth();
adjuster = lastDayOfMonth();
}
else { // "L-[0-9]+"
int offset = Integer.parseInt(value.substring(idx + 1));
@@ -125,6 +113,13 @@ final class QuartzCronField extends CronField {
throw new IllegalArgumentException("No 'L' or 'W' found in '" + value + "'");
}
/**
* Returns whether the given value is a Quartz day-of-week field.
*/
public static boolean isQuartzDaysOfWeekField(String value) {
return value.contains("L") || value.contains("#");
}
/**
* Parse the given value into a days of week {@code QuartzCronField}, the sixth entry of a cron expression.
* Expects a "L" or "#" in the given value.
@@ -142,7 +137,7 @@ final class QuartzCronField extends CronField {
}
else { // "[0-7]L"
DayOfWeek dayOfWeek = parseDayOfWeek(value.substring(0, idx));
adjuster = TemporalAdjusters.lastInMonth(dayOfWeek);
adjuster = lastInMonth(dayOfWeek);
}
return new QuartzCronField(Type.DAY_OF_WEEK, Type.DAY_OF_MONTH, adjuster, value);
}
@@ -158,14 +153,17 @@ final class QuartzCronField extends CronField {
// "[0-7]#[0-9]+"
DayOfWeek dayOfWeek = parseDayOfWeek(value.substring(0, idx));
int ordinal = Integer.parseInt(value.substring(idx + 1));
if (ordinal <= 0) {
throw new IllegalArgumentException("Ordinal '" + ordinal + "' in '" + value +
"' must be positive number ");
}
TemporalAdjuster adjuster = TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek);
TemporalAdjuster adjuster = dayOfWeekInMonth(ordinal, dayOfWeek);
return new QuartzCronField(Type.DAY_OF_WEEK, Type.DAY_OF_MONTH, adjuster, value);
}
throw new IllegalArgumentException("No 'L' or '#' found in '" + value + "'");
}
private static DayOfWeek parseDayOfWeek(String value) {
int dayOfWeek = Integer.parseInt(value);
if (dayOfWeek == 0) {
@@ -180,6 +178,54 @@ final class QuartzCronField extends CronField {
}
}
/**
* Returns an adjuster that resets to midnight.
*/
private static TemporalAdjuster atMidnight() {
return temporal -> {
if (temporal.isSupported(ChronoField.NANO_OF_DAY)) {
return temporal.with(ChronoField.NANO_OF_DAY, 0);
}
else {
return temporal;
}
};
}
/**
* Returns an adjuster that returns a new temporal set to the last
* day of the current month at midnight.
*/
private static TemporalAdjuster lastDayOfMonth() {
TemporalAdjuster adjuster = TemporalAdjusters.lastDayOfMonth();
return temporal -> {
Temporal result = adjuster.adjustInto(temporal);
return rollbackToMidnight(temporal, result);
};
}
/**
* Returns an adjuster that returns the last weekday of the month.
*/
private static TemporalAdjuster lastWeekdayOfMonth() {
TemporalAdjuster adjuster = TemporalAdjusters.lastDayOfMonth();
return temporal -> {
Temporal lastDom = adjuster.adjustInto(temporal);
Temporal result;
int dow = lastDom.get(ChronoField.DAY_OF_WEEK);
if (dow == 6) { // Saturday
result = lastDom.minus(1, ChronoUnit.DAYS);
}
else if (dow == 7) { // Sunday
result = lastDom.minus(2, ChronoUnit.DAYS);
}
else {
result = lastDom;
}
return rollbackToMidnight(temporal, result);
};
}
/**
* Return a temporal adjuster that finds the nth-to-last day of the month.
* @param offset the negative offset, i.e. -3 means third-to-last
@@ -187,9 +233,10 @@ final class QuartzCronField extends CronField {
*/
private static TemporalAdjuster lastDayWithOffset(int offset) {
Assert.isTrue(offset < 0, "Offset should be < 0");
TemporalAdjuster adjuster = TemporalAdjusters.lastDayOfMonth();
return temporal -> {
Temporal lastDayOfMonth = TemporalAdjusters.lastDayOfMonth().adjustInto(temporal);
return lastDayOfMonth.plus(offset, ChronoUnit.DAYS);
Temporal result = adjuster.adjustInto(temporal).plus(offset, ChronoUnit.DAYS);
return rollbackToMidnight(temporal, result);
};
}
@@ -215,6 +262,7 @@ final class QuartzCronField extends CronField {
int count = 0;
while (count++ < CronExpression.MAX_ATTEMPTS) {
temporal = Type.DAY_OF_MONTH.elapseUntil(cast(temporal), dayOfMonth);
temporal = atMidnight().adjustInto(temporal);
current = Type.DAY_OF_MONTH.get(temporal);
if (current == dayOfMonth) {
dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
@@ -240,6 +288,44 @@ final class QuartzCronField extends CronField {
};
}
/**
* Return a temporal adjuster that finds the last of the given doy-of-week
* in a month.
*/
private static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) {
TemporalAdjuster adjuster = TemporalAdjusters.lastInMonth(dayOfWeek);
return temporal -> {
Temporal result = adjuster.adjustInto(temporal);
return rollbackToMidnight(temporal, result);
};
}
/**
* Returns a temporal adjuster that finds {@code ordinal}-th occurrence of
* the given day-of-week in a month.
*/
private static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) {
TemporalAdjuster adjuster = TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek);
return temporal -> {
Temporal result = adjuster.adjustInto(temporal);
return rollbackToMidnight(temporal, result);
};
}
/**
* Rolls back the given {@code result} to midnight. When
* {@code current} has the same day of month as {@code result}, the former
* is returned, to make sure that we don't end up before where we started.
*/
private static Temporal rollbackToMidnight(Temporal current, Temporal result) {
if (result.get(ChronoField.DAY_OF_MONTH) == current.get(ChronoField.DAY_OF_MONTH)) {
return current;
}
else {
return atMidnight().adjustInto(result);
}
}
@SuppressWarnings("unchecked")
private static <T extends Temporal & Comparable<? super T>> T cast(Temporal temporal) {
return (T) temporal;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
package org.springframework.validation.beanvalidation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -291,12 +293,17 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
configureParameterNameProvider(this.parameterNameDiscoverer, configuration);
}
List<InputStream> mappingStreams = null;
if (this.mappingLocations != null) {
mappingStreams = new ArrayList<>(this.mappingLocations.length);
for (Resource location : this.mappingLocations) {
try {
configuration.addMapping(location.getInputStream());
InputStream stream = location.getInputStream();
mappingStreams.add(stream);
configuration.addMapping(stream);
}
catch (IOException ex) {
closeMappingStreams(mappingStreams);
throw new IllegalStateException("Cannot read mapping resource: " + location);
}
}
@@ -307,8 +314,13 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
// Allow for custom post-processing before we actually build the ValidatorFactory.
postProcessConfiguration(configuration);
this.validatorFactory = configuration.buildValidatorFactory();
setTargetValidator(this.validatorFactory.getValidator());
try {
this.validatorFactory = configuration.buildValidatorFactory();
setTargetValidator(this.validatorFactory.getValidator());
}
finally {
closeMappingStreams(mappingStreams);
}
}
private void configureParameterNameProvider(ParameterNameDiscoverer discoverer, Configuration<?> configuration) {
@@ -329,6 +341,18 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
});
}
private void closeMappingStreams(@Nullable List<InputStream> mappingStreams){
if (!CollectionUtils.isEmpty(mappingStreams)) {
for (InputStream stream : mappingStreams) {
try {
stream.close();
}
catch (IOException ignored) {
}
}
}
}
/**
* Post-process the given Bean Validation configuration,
* adding to or overriding any of its settings.
@@ -397,7 +421,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return super.unwrap(type);
}
catch (ValidationException ex) {
// ignore - we'll try ValidatorFactory unwrapping next
// Ignore - we'll try ValidatorFactory unwrapping next
}
}
if (this.validatorFactory != null) {
@@ -405,7 +429,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return this.validatorFactory.unwrap(type);
}
catch (ValidationException ex) {
// ignore if just being asked for ValidatorFactory
// Ignore if just being asked for ValidatorFactory
if (ValidatorFactory.class == type) {
return (T) this.validatorFactory;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -179,7 +179,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
}
}
else {
// got no BindingResult - can only do standard rejectValue call
// Got no BindingResult - can only do standard rejectValue call
// with automatic extraction of the current field value
errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
}
@@ -386,7 +386,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
}
catch (ValidationException ex) {
// ignore if just being asked for plain Validator
// Ignore if just being asked for plain JSR-303 Validator
if (javax.validation.Validator.class == type) {
return (T) this.targetValidator;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,23 @@
package org.springframework.context.annotation;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* @author Andy Wilkinson
* @author Liu Dongmiao
*/
public class AggressiveFactoryBeanInstantiationTests {
@@ -49,17 +58,66 @@ public class AggressiveFactoryBeanInstantiationTests {
}
}
@Test
public void checkLinkageError() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(BeanMethodConfigurationWithExceptionInInitializer.class);
context.refresh();
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(baos);
ex.printStackTrace(pw);
pw.flush();
String stackTrace = baos.toString();
assertThat(stackTrace.contains(".<clinit>")).isTrue();
assertThat(stackTrace.contains("java.lang.NoClassDefFoundError")).isFalse();
}
}
@Configuration
static class BeanMethodConfiguration {
@Bean
public String foo() {
return "foo";
}
@Bean
public AutowiredBean autowiredBean() {
return new AutowiredBean();
}
@Bean
@DependsOn("autowiredBean")
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
return new SimpleFactoryBean(applicationContext);
}
}
@Configuration
static class BeanMethodConfigurationWithExceptionInInitializer extends BeanMethodConfiguration {
@Bean
@DependsOn("autowiredBean")
@Override
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
new ExceptionInInitializer();
return new SimpleFactoryBean(applicationContext);
}
}
static class AutowiredBean {
@Autowired
String foo;
}
static class SimpleFactoryBean implements FactoryBean<Object> {
public SimpleFactoryBean(ApplicationContext applicationContext) {
@@ -76,4 +134,14 @@ public class AggressiveFactoryBeanInstantiationTests {
}
}
static class ExceptionInInitializer {
private static final int ERROR = callInClinit();
private static int callInClinit() {
throw new UnsupportedOperationException();
}
}
}
@@ -271,9 +271,15 @@ class AnnotationConfigApplicationContextTests {
assertThat(ObjectUtils.containsElement(context.getBeanNamesForType(BeanA.class), "a")).isTrue();
assertThat(ObjectUtils.containsElement(context.getBeanNamesForType(BeanB.class), "b")).isTrue();
assertThat(ObjectUtils.containsElement(context.getBeanNamesForType(BeanC.class), "c")).isTrue();
assertThat(context.getBeansOfType(BeanA.class)).isEmpty();
assertThat(context.getBeansOfType(BeanB.class).values().iterator().next()).isSameAs(context.getBean(BeanB.class));
assertThat(context.getBeansOfType(BeanC.class).values().iterator().next()).isSameAs(context.getBean(BeanC.class));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() ->
context.getBeanFactory().resolveNamedBean(BeanA.class));
assertThat(context.getBeanFactory().resolveNamedBean(BeanB.class).getBeanInstance()).isSameAs(context.getBean(BeanB.class));
assertThat(context.getBeanFactory().resolveNamedBean(BeanC.class).getBeanInstance()).isSameAs(context.getBean(BeanC.class));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ public class EnableLoadTimeWeavingTests {
@Test
public void control() {
GenericXmlApplicationContext ctx =
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
ctx.getBean("loadTimeWeaver", LoadTimeWeaver.class);
}
@@ -73,9 +73,11 @@ public class EnableLoadTimeWeavingTests {
verify(loadTimeWeaver).addTransformer(isA(ClassFileTransformer.class));
}
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.DISABLED)
static class EnableLTWConfig_withAjWeavingDisabled implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
@@ -85,6 +87,7 @@ public class EnableLoadTimeWeavingTests {
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.AUTODETECT)
static class EnableLTWConfig_withAjWeavingAutodetect implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
@@ -94,9 +97,11 @@ public class EnableLoadTimeWeavingTests {
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
static class EnableLTWConfig_withAjWeavingEnabled implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
}
}
}
@@ -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.
@@ -18,8 +18,10 @@ package org.springframework.context.annotation;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.Iterator;
import java.util.Properties;
@@ -31,6 +33,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
@@ -47,13 +50,13 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Chris Beams
* @author Phillip Webb
* @author Sam Brannen
* @since 3.1
*/
public class PropertySourceAnnotationTests {
class PropertySourceAnnotationTests {
@Test
public void withExplicitName() {
void withExplicitName() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithExplicitName.class);
ctx.refresh();
@@ -73,29 +76,25 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withImplicitName() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithImplicitName.class);
ctx.refresh();
void withImplicitName() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithImplicitName.class);
assertThat(ctx.getEnvironment().getPropertySources().contains("class path resource [org/springframework/context/annotation/p1.properties]")).as("property source p1 was not added").isTrue();
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
}
@Test
public void withTestProfileBeans() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithTestProfileBeans.class);
ctx.refresh();
void withTestProfileBeans() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithTestProfileBeans.class);
assertThat(ctx.containsBean("testBean")).isTrue();
assertThat(ctx.containsBean("testProfileBean")).isTrue();
}
/**
* Tests the LIFO behavior of @PropertySource annotaitons.
* The last one registered should 'win'.
* Tests the LIFO behavior of @PropertySource annotations.
* <p>The last one registered should 'win'.
*/
@Test
public void orderingIsLifo() {
void orderingIsLifo() {
{
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithImplicitName.class, P2Config.class);
@@ -114,7 +113,7 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withCustomFactory() {
void withCustomFactory() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithImplicitName.class, WithCustomFactory.class);
ctx.refresh();
@@ -122,7 +121,7 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withCustomFactoryAsMeta() {
void withCustomFactoryAsMeta() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithImplicitName.class, WithCustomFactoryAsMeta.class);
ctx.refresh();
@@ -130,59 +129,43 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withUnresolvablePlaceholder() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithUnresolvablePlaceholder.class);
try {
ctx.refresh();
}
catch (BeanDefinitionStoreException ex) {
assertThat(ex.getCause() instanceof IllegalArgumentException).isTrue();
}
void withUnresolvablePlaceholder() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithUnresolvablePlaceholder.class))
.withCauseInstanceOf(IllegalArgumentException.class);
}
@Test
public void withUnresolvablePlaceholderAndDefault() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithUnresolvablePlaceholderAndDefault.class);
ctx.refresh();
void withUnresolvablePlaceholderAndDefault() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithUnresolvablePlaceholderAndDefault.class);
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
}
@Test
public void withResolvablePlaceholder() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithResolvablePlaceholder.class);
void withResolvablePlaceholder() {
System.setProperty("path.to.properties", "org/springframework/context/annotation");
ctx.refresh();
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithResolvablePlaceholder.class);
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
System.clearProperty("path.to.properties");
}
@Test
public void withResolvablePlaceholderAndFactoryBean() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithResolvablePlaceholderAndFactoryBean.class);
void withResolvablePlaceholderAndFactoryBean() {
System.setProperty("path.to.properties", "org/springframework/context/annotation");
ctx.refresh();
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithResolvablePlaceholderAndFactoryBean.class);
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
System.clearProperty("path.to.properties");
}
@Test
public void withEmptyResourceLocations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithEmptyResourceLocations.class);
try {
ctx.refresh();
}
catch (BeanDefinitionStoreException ex) {
assertThat(ex.getCause() instanceof IllegalArgumentException).isTrue();
}
void withEmptyResourceLocations() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithEmptyResourceLocations.class))
.withCauseInstanceOf(IllegalArgumentException.class);
}
@Test
public void withNameAndMultipleResourceLocations() {
void withNameAndMultipleResourceLocations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithNameAndMultipleResourceLocations.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
@@ -191,7 +174,7 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withMultipleResourceLocations() {
void withMultipleResourceLocations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithMultipleResourceLocations.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
@@ -200,7 +183,7 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withPropertySources() {
void withRepeatedPropertySourcesInContainerAnnotation() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithPropertySources.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
@@ -209,7 +192,43 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withNamedPropertySources() {
void withRepeatedPropertySources() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithRepeatedPropertySourceAnnotations.class)) {
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
// p2 should 'win' as it was registered last
assertThat(ctx.getEnvironment().getProperty("testbean.name")).isEqualTo("p2TestBean");
}
}
@Test
void withRepeatedPropertySourcesOnComposedAnnotation() {
Class<?> configClass = ConfigWithRepeatedPropertySourceAnnotationsOnComposedAnnotation.class;
String key = "custom.config.package";
System.clearProperty(key);
try (ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(configClass)) {
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
// p2 should 'win' as it was registered last
assertThat(ctx.getEnvironment().getProperty("testbean.name")).isEqualTo("p2TestBean");
}
System.setProperty(key, "org/springframework/context/annotation");
try (ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(configClass)) {
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p3")).isTrue();
// p3 should 'win' as it was registered last
assertThat(ctx.getEnvironment().getProperty("testbean.name")).isEqualTo("p3TestBean");
}
finally {
System.clearProperty(key);
}
}
@Test
void withNamedPropertySources() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithNamedPropertySources.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
@@ -218,21 +237,21 @@ public class PropertySourceAnnotationTests {
}
@Test
public void withMissingPropertySource() {
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() ->
new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class))
void withMissingPropertySource() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class))
.withCauseInstanceOf(FileNotFoundException.class);
}
@Test
public void withIgnoredPropertySource() {
void withIgnoredPropertySource() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithIgnoredPropertySource.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
}
@Test
public void withSameSourceImportedInDifferentOrder() {
void withSameSourceImportedInDifferentOrder() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithSameSourceImportedInDifferentOrder.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
@@ -240,7 +259,7 @@ public class PropertySourceAnnotationTests {
}
@Test
public void orderingWithAndWithoutNameAndMultipleResourceLocations() {
void orderingWithAndWithoutNameAndMultipleResourceLocations() {
// SPR-10820: p2 should 'win' as it was registered last
AnnotationConfigApplicationContext ctxWithName = new AnnotationConfigApplicationContext(ConfigWithNameAndMultipleResourceLocations.class);
AnnotationConfigApplicationContext ctxWithoutName = new AnnotationConfigApplicationContext(ConfigWithMultipleResourceLocations.class);
@@ -249,14 +268,14 @@ public class PropertySourceAnnotationTests {
}
@Test
public void orderingWithAndWithoutNameAndFourResourceLocations() {
void orderingWithAndWithoutNameAndFourResourceLocations() {
// SPR-12198: p4 should 'win' as it was registered last
AnnotationConfigApplicationContext ctxWithoutName = new AnnotationConfigApplicationContext(ConfigWithFourResourceLocations.class);
assertThat(ctxWithoutName.getEnvironment().getProperty("testbean.name")).isEqualTo("p4TestBean");
}
@Test
public void orderingDoesntReplaceExisting() throws Exception {
void orderingDoesntReplaceExisting() throws Exception {
// SPR-12198: mySource should 'win' as it was registered manually
AnnotationConfigApplicationContext ctxWithoutName = new AnnotationConfigApplicationContext();
MapPropertySource mySource = new MapPropertySource("mine", Collections.singletonMap("testbean.name", "myTestBean"));
@@ -267,51 +286,51 @@ public class PropertySourceAnnotationTests {
}
@Configuration
@PropertySource(value="classpath:${unresolvable}/p1.properties")
@PropertySource("classpath:${unresolvable}/p1.properties")
static class ConfigWithUnresolvablePlaceholder {
}
@Configuration
@PropertySource(value="classpath:${unresolvable:org/springframework/context/annotation}/p1.properties")
@PropertySource("classpath:${unresolvable:org/springframework/context/annotation}/p1.properties")
static class ConfigWithUnresolvablePlaceholderAndDefault {
@Inject Environment env;
@Bean
public TestBean testBean() {
TestBean testBean() {
return new TestBean(env.getProperty("testbean.name"));
}
}
@Configuration
@PropertySource(value="classpath:${path.to.properties}/p1.properties")
@PropertySource("classpath:${path.to.properties}/p1.properties")
static class ConfigWithResolvablePlaceholder {
@Inject Environment env;
@Bean
public TestBean testBean() {
TestBean testBean() {
return new TestBean(env.getProperty("testbean.name"));
}
}
@Configuration
@PropertySource(value="classpath:${path.to.properties}/p1.properties")
@PropertySource("classpath:${path.to.properties}/p1.properties")
static class ConfigWithResolvablePlaceholderAndFactoryBean {
@Inject Environment env;
@SuppressWarnings("rawtypes")
@Bean
public FactoryBean testBean() {
FactoryBean<TestBean> testBean() {
final String name = env.getProperty("testbean.name");
return new FactoryBean() {
return new FactoryBean<TestBean>() {
@Override
public Object getObject() {
public TestBean getObject() {
return new TestBean(name);
}
@Override
@@ -334,7 +353,7 @@ public class PropertySourceAnnotationTests {
@Inject Environment env;
@Bean
public TestBean testBean() {
TestBean testBean() {
return new TestBean(env.getProperty("testbean.name"));
}
}
@@ -347,7 +366,7 @@ public class PropertySourceAnnotationTests {
@Inject Environment env;
@Bean
public TestBean testBean() {
TestBean testBean() {
return new TestBean(env.getProperty("testbean.name"));
}
}
@@ -361,7 +380,7 @@ public class PropertySourceAnnotationTests {
@Inject Environment env;
@Bean @Profile("test")
public TestBean testBean() {
TestBean testBean() {
return new TestBean(env.getProperty("testbean.name"));
}
}
@@ -380,21 +399,21 @@ public class PropertySourceAnnotationTests {
@Configuration
@MyPropertySource(value = "classpath:org/springframework/context/annotation/p2.properties")
@MyPropertySource("classpath:org/springframework/context/annotation/p2.properties")
static class WithCustomFactoryAsMeta {
}
@Retention(RetentionPolicy.RUNTIME)
@PropertySource(value = {}, factory = MyCustomFactory.class)
public @interface MyPropertySource {
@interface MyPropertySource {
@AliasFor(annotation = PropertySource.class)
String value();
}
public static class MyCustomFactory implements PropertySourceFactory {
static class MyCustomFactory implements PropertySourceFactory {
@Override
public org.springframework.core.env.PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
@@ -422,11 +441,10 @@ public class PropertySourceAnnotationTests {
@Configuration
@PropertySource(
value = {
"classpath:org/springframework/context/annotation/p1.properties",
"classpath:org/springframework/context/annotation/p2.properties"
})
@PropertySource({
"classpath:org/springframework/context/annotation/p1.properties",
"classpath:org/springframework/context/annotation/p2.properties"
})
static class ConfigWithMultipleResourceLocations {
}
@@ -434,12 +452,33 @@ public class PropertySourceAnnotationTests {
@Configuration
@PropertySources({
@PropertySource("classpath:org/springframework/context/annotation/p1.properties"),
@PropertySource("classpath:${base.package}/p2.properties"),
@PropertySource("classpath:${base.package}/p2.properties")
})
static class ConfigWithPropertySources {
}
@Configuration
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
@PropertySource(value = "classpath:${base.package}/p2.properties", ignoreResourceNotFound = true)
static class ConfigWithRepeatedPropertySourceAnnotations {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
@PropertySource(value = "classpath:${base.package}/p2.properties", ignoreResourceNotFound = true)
@PropertySource(value = "classpath:${custom.config.package:bogus/config}/p3.properties", ignoreResourceNotFound = true)
@interface ComposedConfiguration {
}
@ComposedConfiguration
static class ConfigWithRepeatedPropertySourceAnnotationsOnComposedAnnotation {
}
@Configuration
@PropertySources({
@PropertySource(name = "psName", value = "classpath:org/springframework/context/annotation/p1.properties"),
@@ -471,7 +510,7 @@ public class PropertySourceAnnotationTests {
@Configuration
@PropertySource(value = {})
@PropertySource({})
static class ConfigWithEmptyResourceLocations {
}
@@ -482,7 +521,7 @@ public class PropertySourceAnnotationTests {
@PropertySource("classpath:org/springframework/context/annotation/p2.properties")
})
@Configuration
public static class ConfigWithSameSourceImportedInDifferentOrder {
static class ConfigWithSameSourceImportedInDifferentOrder {
}
@@ -492,18 +531,17 @@ public class PropertySourceAnnotationTests {
@PropertySource("classpath:org/springframework/context/annotation/p2.properties"),
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
})
public static class ConfigImportedWithSameSourceImportedInDifferentOrder {
static class ConfigImportedWithSameSourceImportedInDifferentOrder {
}
@Configuration
@PropertySource(
value = {
"classpath:org/springframework/context/annotation/p1.properties",
"classpath:org/springframework/context/annotation/p2.properties",
"classpath:org/springframework/context/annotation/p3.properties",
"classpath:org/springframework/context/annotation/p4.properties"
})
@PropertySource({
"classpath:org/springframework/context/annotation/p1.properties",
"classpath:org/springframework/context/annotation/p2.properties",
"classpath:org/springframework/context/annotation/p3.properties",
"classpath:org/springframework/context/annotation/p4.properties"
})
static class ConfigWithFourResourceLocations {
}
@@ -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.
@@ -49,9 +49,11 @@ public class PayloadApplicationEventTests {
public void testProgrammaticEventListener() {
List<Auditable> events = new ArrayList<>();
ApplicationListener<AuditablePayloadEvent<String>> listener = events::add;
ApplicationListener<AuditablePayloadEvent<Integer>> mismatch = (event -> event.getPayload().intValue());
ConfigurableApplicationContext ac = new GenericApplicationContext();
ac.addApplicationListener(listener);
ac.addApplicationListener(mismatch);
ac.refresh();
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
@@ -63,9 +65,11 @@ public class PayloadApplicationEventTests {
public void testProgrammaticPayloadListener() {
List<String> events = new ArrayList<>();
ApplicationListener<PayloadApplicationEvent<String>> listener = ApplicationListener.forPayload(events::add);
ApplicationListener<PayloadApplicationEvent<Integer>> mismatch = ApplicationListener.forPayload(payload -> payload.intValue());
ConfigurableApplicationContext ac = new GenericApplicationContext();
ac.addApplicationListener(listener);
ac.addApplicationListener(mismatch);
ac.refresh();
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
@@ -25,18 +25,47 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link BitsCronField}.
*
* @author Arjen Poutsma
* @author Sam Brannen
*/
public class BitsCronFieldTests {
class BitsCronFieldTests {
@Test
void parse() {
assertThat(BitsCronField.parseSeconds("42")).has(clearRange(0, 41)).has(set(42)).has(clearRange(43, 59));
assertThat(BitsCronField.parseMinutes("1,2,5,9")).has(clear(0)).has(set(1, 2)).has(clearRange(3,4)).has(set(5)).has(clearRange(6,8)).has(set(9)).has(clearRange(10,59));
assertThat(BitsCronField.parseSeconds("0-4,8-12")).has(setRange(0, 4)).has(clearRange(5,7)).has(setRange(8, 12)).has(clearRange(13,59));
assertThat(BitsCronField.parseHours("0-23/2")).has(set(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22)).has(clear(1,3,5,7,9,11,13,15,17,19,21,23));
assertThat(BitsCronField.parseDaysOfWeek("0")).has(clearRange(0, 6)).has(set(7, 7));
assertThat(BitsCronField.parseSeconds("57/2")).has(clearRange(0, 56)).has(set(57)).has(clear(58)).has(set(59));
assertThat(BitsCronField.parseMinutes("30")).has(set(30)).has(clearRange(1, 29)).has(clearRange(31, 59));
assertThat(BitsCronField.parseHours("23")).has(set(23)).has(clearRange(0, 23));
assertThat(BitsCronField.parseHours("0-23/2")).has(set(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22)).has(clear(1,3,5,7,9,11,13,15,17,19,21,23));
assertThat(BitsCronField.parseDaysOfMonth("1")).has(set(1)).has(clearRange(2, 31));
assertThat(BitsCronField.parseMonth("1")).has(set(1)).has(clearRange(2, 12));
assertThat(BitsCronField.parseDaysOfWeek("0")).has(set(7, 7)).has(clearRange(0, 6));
}
@Test
void parseLists() {
assertThat(BitsCronField.parseSeconds("15,30")).has(set(15, 30)).has(clearRange(1, 15)).has(clearRange(31, 59));
assertThat(BitsCronField.parseMinutes("1,2,5,9")).has(set(1, 2, 5, 9)).has(clear(0)).has(clearRange(3, 4)).has(clearRange(6, 8)).has(clearRange(10, 59));
assertThat(BitsCronField.parseHours("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 23));
assertThat(BitsCronField.parseDaysOfMonth("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 31));
assertThat(BitsCronField.parseMonth("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 12));
assertThat(BitsCronField.parseDaysOfWeek("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 7));
assertThat(BitsCronField.parseMinutes("5,10-30/2"))
.has(clearRange(0, 5))
.has(set(5))
.has(clearRange(6,10))
.has(set(10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30))
.has(clear(11, 13, 15, 17, 19, 21, 23, 25, 27, 29))
.has(clearRange(31, 60));
}
@Test
@@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
import static java.time.DayOfWeek.FRIDAY;
import static java.time.DayOfWeek.MONDAY;
import static java.time.DayOfWeek.SUNDAY;
import static java.time.DayOfWeek.THURSDAY;
import static java.time.DayOfWeek.TUESDAY;
import static java.time.DayOfWeek.WEDNESDAY;
import static java.time.temporal.TemporalAdjusters.next;
@@ -116,6 +117,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withMinute(10);
LocalDateTime expected = last.plusMinutes(1).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -149,6 +151,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.of(year, 10, 30, 11, 1);
LocalDateTime expected = last.withHour(12).withMinute(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -173,6 +176,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withDayOfMonth(1);
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -207,6 +211,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withMonth(9).withDayOfMonth(30);
LocalDateTime expected = LocalDateTime.of(last.getYear(), 10, 1, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -222,6 +227,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withMonth(8).withDayOfMonth(30);
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -237,6 +243,7 @@ class CronExpressionTests {
ZonedDateTime last = ZonedDateTime.now(ZoneId.of("CET")).withMonth(10).withDayOfMonth(30);
ZonedDateTime expected = last.withDayOfMonth(31).withHour(0).withMinute(0).withSecond(0).withNano(0);
ZonedDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -251,6 +258,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withMonth(10).withDayOfMonth(30);
LocalDateTime expected = LocalDateTime.of(last.getYear(), 11, 1, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -265,6 +273,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withYear(2010).withMonth(12).withDayOfMonth(31);
LocalDateTime expected = LocalDateTime.of(2011, 1, 1, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -297,6 +306,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().with(next(MONDAY));
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(TUESDAY);
}
@@ -308,6 +318,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().with(next(WEDNESDAY));
LocalDateTime expected = last.plusDays(6).withHour(0).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(TUESDAY);
}
@@ -319,6 +330,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withMinute(4).withSecond(54);
LocalDateTime expected = last.plusMinutes(1).withSecond(55).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -333,6 +345,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withHour(9).withSecond(54);
LocalDateTime expected = last.plusHours(1).withMinute(0).withSecond(55).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -347,6 +360,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withHour(9).withMinute(4);
LocalDateTime expected = last.plusHours(1).plusMinutes(1).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -362,6 +376,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withDayOfMonth(2).withSecond(54);
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(55).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -376,6 +391,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withMonth(10).withDayOfMonth(2);
LocalDateTime expected = LocalDateTime.of(last.getYear(), 11, 3, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -398,6 +414,7 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.now().withYear(2007).withMonth(2).withDayOfMonth(10);
LocalDateTime expected = LocalDateTime.of(2008, 2, 29, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -413,12 +430,14 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.of(LocalDate.of(2009, 9, 26), LocalTime.now());
LocalDateTime expected = last.plusDays(2).withHour(7).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
// Next day is a week day so add one
last = actual;
expected = expected.plusDays(1);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -433,12 +452,14 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.of(LocalDate.of(2010, 12, 30), LocalTime.now());
LocalDateTime expected = last.plusMonths(1).withHour(23).withMinute(30).withSecond(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
// Next trigger is 3 months later
last = actual;
expected = expected.plusMonths(3);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -485,11 +506,13 @@ class CronExpressionTests {
LocalDateTime expected = LocalDateTime.of(last.getYear() + 1, 1, 1, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = expected.plusYears(1);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -513,11 +536,13 @@ class CronExpressionTests {
LocalDateTime expected = LocalDateTime.of(last.getYear(), 11, 1, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = expected.plusMonths(1);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -534,11 +559,13 @@ class CronExpressionTests {
LocalDateTime expected = last.with(next(SUNDAY)).withHour(0).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = expected.plusWeeks(1);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -555,11 +582,13 @@ class CronExpressionTests {
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = expected.plusDays(1);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -583,11 +612,13 @@ class CronExpressionTests {
LocalDateTime expected = last.plusHours(1).withMinute(0).withSecond(0).withNano(0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = expected.plusHours(1);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -603,16 +634,19 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.of(LocalDate.of(2008, 1, 4), LocalTime.now());
LocalDateTime expected = LocalDateTime.of(2008, 1, 31, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = LocalDateTime.of(2008, 2, 29, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = LocalDateTime.of(2008, 3, 31, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -628,16 +662,19 @@ class CronExpressionTests {
LocalDateTime last = LocalDateTime.of(LocalDate.of(2008, 1, 4), LocalTime.now());
LocalDateTime expected = LocalDateTime.of(2008, 1, 28, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = LocalDateTime.of(2008, 2, 26, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = LocalDateTime.of(2008, 3, 28, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
@@ -1042,4 +1079,168 @@ class CronExpressionTests {
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(WEDNESDAY);
}
@Test
void dayOfMonthListWithQuartz() {
CronExpression expression = CronExpression.parse("0 0 0 1W,15,LW * ?");
LocalDateTime last = LocalDateTime.of(2019, 12, 30, 0, 0);
LocalDateTime expected = LocalDateTime.of(2019, 12, 31, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual).is(weekday);
last = actual;
expected = LocalDateTime.of(2020, 1, 1, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual).is(weekday);
last = actual;
expected = LocalDateTime.of(2020, 1, 15, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = LocalDateTime.of(2020, 1, 31, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual).is(weekday);
}
@Test
void dayOfWeekListWithQuartz() {
CronExpression expression = CronExpression.parse("0 0 0 ? * THU#1,THU#3,THU#5");
LocalDateTime last = LocalDateTime.of(2019, 12, 31, 0, 0);
LocalDateTime expected = LocalDateTime.of(2020, 1, 2, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(THURSDAY);
last = actual;
expected = LocalDateTime.of(2020, 1, 16, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(THURSDAY);
last = actual;
expected = LocalDateTime.of(2020, 1, 30, 0, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(THURSDAY);
}
@Test
void quartzLastDayOfMonthEveryHour() {
CronExpression expression = CronExpression.parse("0 0 * L * *");
LocalDateTime last = LocalDateTime.of(2021, 1, 30, 0, 1);
LocalDateTime expected = LocalDateTime.of(2021, 1, 31, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = LocalDateTime.of(2021, 1, 31, 1, 0);
expected = LocalDateTime.of(2021, 1, 31, 2, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
void quartzLastDayOfMonthOffsetEveryHour() {
CronExpression expression = CronExpression.parse("0 0 * L-1 * *");
LocalDateTime last = LocalDateTime.of(2021, 1, 29, 0, 1);
LocalDateTime expected = LocalDateTime.of(2021, 1, 30, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = LocalDateTime.of(2021, 1, 30, 1, 0);
expected = LocalDateTime.of(2021, 1, 30, 2, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
void quartzFirstWeekdayOfMonthEveryHour() {
CronExpression expression = CronExpression.parse("0 0 * 1W * *");
LocalDateTime last = LocalDateTime.of(2021, 1, 31, 0, 1);
LocalDateTime expected = LocalDateTime.of(2021, 2, 1, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = LocalDateTime.of(2021, 2, 1, 1, 0);
expected = LocalDateTime.of(2021, 2, 1, 2, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
void quartzLastWeekdayOfMonthEveryHour() {
CronExpression expression = CronExpression.parse("0 0 * LW * *");
LocalDateTime last = LocalDateTime.of(2021, 1, 28, 0, 1);
LocalDateTime expected = LocalDateTime.of(2021, 1, 29, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = LocalDateTime.of(2021, 1, 29, 1, 0);
expected = LocalDateTime.of(2021, 1, 29, 2, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
void quartz5thFridayOfTheMonthEveryHour() {
CronExpression expression = CronExpression.parse("0 0 * ? * FRI#5");
LocalDateTime last = LocalDateTime.of(2021, 1, 28, 0, 1);
LocalDateTime expected = LocalDateTime.of(2021, 1, 29, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(FRIDAY);
last = LocalDateTime.of(2021, 1, 29, 1, 0);
expected = LocalDateTime.of(2021, 1, 29, 2, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
void quartzLastFridayOfTheMonthEveryHour() {
CronExpression expression = CronExpression.parse("0 0 * ? * FRIL");
LocalDateTime last = LocalDateTime.of(2021, 1, 28, 0, 1);
LocalDateTime expected = LocalDateTime.of(2021, 1, 29, 0, 0);
LocalDateTime actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
assertThat(actual.getDayOfWeek()).isEqualTo(FRIDAY);
last = LocalDateTime.of(2021, 1, 29, 1, 0);
expected = LocalDateTime.of(2021, 1, 29, 2, 0);
actual = expression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
}
@@ -25,6 +25,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link QuartzCronField}.
*
* @author Arjen Poutsma
*/
class QuartzCronFieldTests {
@@ -95,6 +97,8 @@ class QuartzCronFieldTests {
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("1#L"));
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("L#1"));
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("8#1"));
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("2#1,2#3,2#5"));
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("FRI#-1"));
}
}
@@ -1 +1,2 @@
testbean.name=p3TestBean
from.p3=p3Value
+1 -5
View File
@@ -1,15 +1,11 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id "com.github.johnrengelman.shadow" version "5.2.0"
}
description = "Spring Core"
apply plugin: "kotlin"
// spring-core includes asm and repackages cglib, inlining both into the spring-core jar.
// cglib itself depends on asm and is therefore further transformed by the JarJar task to
// cglib itself depends on asm and is therefore further transformed by the ShadowJar task to
// depend on org.springframework.asm; this avoids including two different copies of asm.
def cglibVersion = "3.3.0"
def objenesisVersion = "3.1"
@@ -77,7 +77,7 @@ public abstract class AnnotationVisitor {
&& api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 9 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.av = annotationVisitor;
}
@@ -129,9 +129,9 @@ public abstract class AnnotationVisitor {
}
/**
* Visits an array value of the annotation. Note that arrays of primitive types (such as byte,
* Visits an array value of the annotation. Note that arrays of primitive values (such as byte,
* boolean, short, char, int, long, float or double) can be passed as value to {@link #visit
* visit}. This is what {@link ClassReader} does.
* visit}. This is what {@link ClassReader} does for non empty arrays of primitive values.
*
* @param name the value name.
* @return a visitor to visit the actual array value elements, or {@literal null} if this visitor
@@ -100,8 +100,10 @@ public class ClassReader {
@Deprecated
// DontCheck(MemberName): can't be renamed (for backward binary compatibility).
public final byte[] b;
/** The offset in bytes of the ClassFile's access_flags field. */
public final int header;
/**
* A byte array containing the JVMS ClassFile structure to be parsed. <i>The content of this array
* must not be modified. This field is intended for {@link Attribute} sub classes, and is normally
@@ -112,6 +114,7 @@ public class ClassReader {
* ClassFile element offsets within this byte array.
*/
final byte[] classFileBuffer;
/**
* The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's
* constant_pool array, <i>plus one</i>. In other words, the offset of constant pool entry i is
@@ -119,16 +122,19 @@ public class ClassReader {
* 1].
*/
private final int[] cpInfoOffsets;
/**
* The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids
* multiple parsing of a given CONSTANT_Utf8 constant pool item.
*/
private final String[] constantUtf8Values;
/**
* The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This
* cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item.
*/
private final ConstantDynamic[] constantDynamicValues;
/**
* The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array
* (in the BootstrapMethods attribute).
@@ -137,6 +143,7 @@ public class ClassReader {
* 4.7.23</a>
*/
private final int[] bootstrapMethodOffsets;
/**
* A conservative estimate of the maximum length of the strings contained in the constant pool of
* the class.
@@ -184,7 +191,7 @@ public class ClassReader {
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V16) {
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V17) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
@@ -499,6 +506,9 @@ public class ClassReader {
} else if (Constants.SYNTHETIC.equals(attributeName)) {
accessFlags |= Opcodes.ACC_SYNTHETIC;
} else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
if (attributeLength > classFileBuffer.length - currentAttributeOffset) {
throw new IllegalArgumentException();
}
sourceDebugExtension =
readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
@@ -1509,6 +1519,9 @@ public class ClassReader {
final int maxLocals = readUnsignedShort(currentOffset + 2);
final int codeLength = readInt(currentOffset + 4);
currentOffset += 8;
if (codeLength > classFileBuffer.length - currentOffset) {
throw new IllegalArgumentException();
}
// Read the bytecode 'code' array to create a label for each referenced instruction.
final int bytecodeStartOffset = currentOffset;
@@ -2965,7 +2978,7 @@ public class ClassReader {
// Parse the array_value array.
while (numElementValuePairs-- > 0) {
currentOffset =
readElementValue(annotationVisitor, currentOffset, /* elementName = */ null, charBuffer);
readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer);
}
}
if (annotationVisitor != null) {
@@ -77,7 +77,7 @@ public abstract class ClassVisitor {
&& api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 9 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.cv = classVisitor;
}
@@ -76,7 +76,7 @@ public abstract class FieldVisitor {
&& api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 9 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.fv = fieldVisitor;
}
@@ -89,7 +89,7 @@ public abstract class MethodVisitor {
&& api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 9 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.mv = methodVisitor;
}
@@ -75,7 +75,7 @@ public abstract class ModuleVisitor {
&& api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 9 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.mv = moduleVisitor;
}
@@ -132,7 +132,7 @@ public interface Opcodes {
* <pre>
* public class StuffVisitor {
* &#64;Deprecated public void visitOldStuff(int arg, ...) {
* visitNewStuf(arg | SOURCE_DEPRECATED, ...);
* visitNewStuff(arg | SOURCE_DEPRECATED, ...);
* }
* public void visitNewStuff(int argAndSource...) {
* if ((argAndSource & SOURCE_DEPRECATED) == 0) {
@@ -154,7 +154,7 @@ public interface Opcodes {
* <p>and there are two cases:
*
* <ul>
* <li>call visitOldSuff: in the call to super.visitOldStuff, the source is set to
* <li>call visitOldStuff: in the call to super.visitOldStuff, the source is set to
* SOURCE_DEPRECATED and visitNewStuff is called. Here 'do stuff' is run because the source
* was previously set to SOURCE_DEPRECATED, and execution eventually returns to
* UserStuffVisitor.visitOldStuff, where 'do user stuff' is run.
@@ -281,6 +281,7 @@ public interface Opcodes {
int V14 = 0 << 16 | 58;
int V15 = 0 << 16 | 59;
int V16 = 0 << 16 | 60;
int V17 = 0 << 16 | 61;
/**
* Version flag indicating that the class is using 'preview' features.
@@ -75,7 +75,7 @@ public abstract class RecordComponentVisitor {
&& api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 9 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.delegate = recordComponentVisitor;
}
@@ -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.
@@ -18,7 +18,7 @@ package org.springframework.asm;
/**
* Utility class exposing constants related to Spring's internal repackaging
* of the ASM bytecode library: currently based on ASM 9.0 plus minor patches.
* of the ASM bytecode library: currently based on ASM 9.x plus minor patches.
*
* <p>See <a href="package-summary.html">package-level javadocs</a> for more
* information on {@code org.springframework.asm}.
@@ -1,6 +1,6 @@
/**
* Spring's repackaging of
* <a href="https://gitlab.ow2.org/asm/asm">ASM 9.0</a>
* <a href="https://gitlab.ow2.org/asm/asm">ASM 9.x</a>
* (with Spring-specific patches; for internal use only).
*
* <p>This repackaging technique avoids any potential conflicts with
@@ -522,26 +522,46 @@ public class ReflectUtils {
}
}
// Classic option: protected ClassLoader.defineClass method
if (c == null && classLoaderDefineClassMethod != null) {
// Direct defineClass attempt on the target Classloader
if (c == null) {
if (protectionDomain == null) {
protectionDomain = PROTECTION_DOMAIN;
}
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
// Look for publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain)
try {
if (!classLoaderDefineClassMethod.isAccessible()) {
classLoaderDefineClassMethod.setAccessible(true);
}
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
Method publicDefineClass = loader.getClass().getMethod(
"publicDefineClass", String.class, byte[].class, ProtectionDomain.class);
c = (Class) publicDefineClass.invoke(loader, className, b, protectionDomain);
}
catch (InvocationTargetException ex) {
throw new CodeGenerationException(ex.getTargetException());
if (!(ex.getTargetException() instanceof UnsupportedOperationException)) {
throw new CodeGenerationException(ex.getTargetException());
}
// in case of UnsupportedOperationException, fall through
}
catch (Throwable ex) {
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new CodeGenerationException(ex);
// publicDefineClass method not available -> fall through
}
// Classic option: protected ClassLoader.defineClass method
if (c == null && classLoaderDefineClassMethod != null) {
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
try {
if (!classLoaderDefineClassMethod.isAccessible()) {
classLoaderDefineClassMethod.setAccessible(true);
}
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
}
catch (InvocationTargetException ex) {
throw new CodeGenerationException(ex.getTargetException());
}
catch (Throwable ex) {
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new CodeGenerationException(ex);
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,20 +16,24 @@
package org.springframework.core;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Interface defining a generic contract for attaching and accessing metadata
* to/from arbitrary objects.
*
* @author Rob Harrop
* @author Sam Brannen
* @since 2.0
*/
public interface AttributeAccessor {
/**
* Set the attribute defined by {@code name} to the supplied {@code value}.
* If {@code value} is {@code null}, the attribute is {@link #removeAttribute removed}.
* <p>If {@code value} is {@code null}, the attribute is {@link #removeAttribute removed}.
* <p>In general, users should take care to prevent overlaps with other
* metadata attributes by using fully-qualified names, perhaps using
* class or package names as prefix.
@@ -40,16 +44,48 @@ public interface AttributeAccessor {
/**
* Get the value of the attribute identified by {@code name}.
* Return {@code null} if the attribute doesn't exist.
* <p>Return {@code null} if the attribute doesn't exist.
* @param name the unique attribute key
* @return the current value of the attribute, if any
*/
@Nullable
Object getAttribute(String name);
/**
* Compute a new value for the attribute identified by {@code name} if
* necessary and {@linkplain #setAttribute set} the new value in this
* {@code AttributeAccessor}.
* <p>If a value for the attribute identified by {@code name} already exists
* in this {@code AttributeAccessor}, the existing value will be returned
* without applying the supplied compute function.
* <p>The default implementation of this method is not thread safe but can
* overridden by concrete implementations of this interface.
* @param <T> the type of the attribute value
* @param name the unique attribute key
* @param computeFunction a function that computes a new value for the attribute
* name; the function must not return a {@code null} value
* @return the existing value or newly computed value for the named attribute
* @see #getAttribute(String)
* @see #setAttribute(String, Object)
* @since 5.3.3
*/
@SuppressWarnings("unchecked")
default <T> T computeAttribute(String name, Function<String, T> computeFunction) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(computeFunction, "Compute function must not be null");
Object value = getAttribute(name);
if (value == null) {
value = computeFunction.apply(name);
Assert.state(value != null,
() -> String.format("Compute function must not return null for attribute named '%s'", name));
setAttribute(name, value);
}
return (T) value;
}
/**
* Remove the attribute identified by {@code name} and return its value.
* Return {@code null} if no attribute under {@code name} is found.
* <p>Return {@code null} if no attribute under {@code name} is found.
* @param name the unique attribute key
* @return the last value of the attribute, if any
*/
@@ -58,7 +94,7 @@ public interface AttributeAccessor {
/**
* Return {@code true} if the attribute identified by {@code name} exists.
* Otherwise return {@code false}.
* <p>Otherwise return {@code false}.
* @param name the unique attribute key
*/
boolean hasAttribute(String name);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.core;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -32,6 +33,7 @@ import org.springframework.util.StringUtils;
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.0
*/
@SuppressWarnings("serial")
@@ -59,6 +61,17 @@ public abstract class AttributeAccessorSupport implements AttributeAccessor, Ser
return this.attributes.get(name);
}
@Override
@SuppressWarnings("unchecked")
public <T> T computeAttribute(String name, Function<String, T> computeFunction) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(computeFunction, "Compute function must not be null");
Object value = this.attributes.computeIfAbsent(name, computeFunction);
Assert.state(value != null,
() -> String.format("Compute function must not return null for attribute named '%s'", name));
return (T) value;
}
@Override
@Nullable
public Object removeAttribute(String name) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,15 +39,9 @@ package org.springframework.core;
*/
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
/**
* Whether this environment lives within a native image.
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
*/
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
public DefaultParameterNameDiscoverer() {
if (KotlinDetector.isKotlinReflectPresent() && !IN_NATIVE_IMAGE) {
// TODO Remove this conditional inclusion when upgrading to Kotlin 1.5, see https://youtrack.jetbrains.com/issue/KT-44594
if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
}
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
@@ -0,0 +1,39 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core;
/**
* A common delegate for detecting a GraalVM native image environment.
*
* <p>Requires using the {@code -H:+InlineBeforeAnalysis} native image compiler flag in order to allow code removal at
* build time.
*
* @author Sebastien Deleuze
* @since 5.3.4
*/
public abstract class NativeDetector {
// See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java
private static final boolean imageCode = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
/**
* Returns {@code true} if invoked in the context of image building or during image runtime, else {@code false}.
*/
public static boolean inNativeImage() {
return imageCode;
}
}
@@ -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.
@@ -60,7 +60,28 @@ public class ReactiveAdapterRegistry {
@Nullable
private static volatile ReactiveAdapterRegistry sharedInstance;
private final boolean reactorPresent;
private static final boolean reactorPresent;
private static final boolean rxjava1Present;
private static final boolean rxjava2Present;
private static final boolean rxjava3Present;
private static final boolean flowPublisherPresent;
private static final boolean kotlinCoroutinesPresent;
static {
ClassLoader classLoader = ReactiveAdapterRegistry.class.getClassLoader();
reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader);
rxjava1Present = ClassUtils.isPresent("rx.Observable", classLoader) &&
ClassUtils.isPresent("rx.RxReactiveStreams", classLoader);
rxjava2Present = ClassUtils.isPresent("io.reactivex.Flowable", classLoader);
rxjava3Present = ClassUtils.isPresent("io.reactivex.rxjava3.core.Flowable", classLoader);
flowPublisherPresent = ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader);
kotlinCoroutinesPresent = ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader);
}
private final List<ReactiveAdapter> adapters = new ArrayList<>();
@@ -70,41 +91,34 @@ public class ReactiveAdapterRegistry {
* @see #getSharedInstance()
*/
public ReactiveAdapterRegistry() {
ClassLoader classLoader = ReactiveAdapterRegistry.class.getClassLoader();
// Reactor
boolean reactorRegistered = false;
if (ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader)) {
if (reactorPresent) {
new ReactorRegistrar().registerAdapters(this);
reactorRegistered = true;
}
this.reactorPresent = reactorRegistered;
// RxJava1 (deprecated)
if (ClassUtils.isPresent("rx.Observable", classLoader) &&
ClassUtils.isPresent("rx.RxReactiveStreams", classLoader)) {
if (rxjava1Present) {
new RxJava1Registrar().registerAdapters(this);
}
// RxJava2
if (ClassUtils.isPresent("io.reactivex.Flowable", classLoader)) {
if (rxjava2Present) {
new RxJava2Registrar().registerAdapters(this);
}
// RxJava3
if (ClassUtils.isPresent("io.reactivex.rxjava3.core.Flowable", classLoader)) {
if (rxjava3Present) {
new RxJava3Registrar().registerAdapters(this);
}
// Java 9+ Flow.Publisher
if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) {
if (flowPublisherPresent) {
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
if (this.reactorPresent && ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader)) {
// Kotlin Coroutines
if (reactorPresent && kotlinCoroutinesPresent) {
new CoroutinesRegistrar().registerAdapters(this);
}
}
@@ -125,7 +139,7 @@ public class ReactiveAdapterRegistry {
public void registerReactiveType(ReactiveTypeDescriptor descriptor,
Function<Object, Publisher<?>> toAdapter, Function<Publisher<?>, Object> fromAdapter) {
if (this.reactorPresent) {
if (reactorPresent) {
this.adapters.add(new ReactorAdapter(descriptor, toAdapter, fromAdapter));
}
else {
@@ -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.
@@ -59,13 +59,6 @@ final class SerializableTypeWrapper {
private static final Class<?>[] SUPPORTED_SERIALIZABLE_TYPES = {
GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class};
/**
* Whether this environment lives within a native image.
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
*/
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
static final ConcurrentReferenceHashMap<Type, Type> cache = new ConcurrentReferenceHashMap<>(256);
@@ -116,7 +109,7 @@ final class SerializableTypeWrapper {
// No serializable type wrapping necessary (e.g. for java.lang.Class)
return providedType;
}
if (IN_NATIVE_IMAGE || !Serializable.class.isAssignableFrom(Class.class)) {
if (NativeDetector.inNativeImage() || !Serializable.class.isAssignableFrom(Class.class)) {
// Let's skip any wrapping attempts if types are generally not serializable in
// the current runtime environment (even java.lang.Class itself, e.g. on GraalVM native images)
return providedType;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 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,10 @@
package org.springframework.core;
import java.security.ProtectionDomain;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by a reloading-aware ClassLoader
* (e.g. a Groovy-based ClassLoader). Detected for example by
@@ -34,10 +38,41 @@ public interface SmartClassLoader {
* Determine whether the given class is reloadable (in this ClassLoader).
* <p>Typically used to check whether the result may be cached (for this
* ClassLoader) or whether it should be reobtained every time.
* The default implementation always returns {@code false}.
* @param clazz the class to check (usually loaded from this ClassLoader)
* @return whether the class should be expected to appear in a reloaded
* version (with a different {@code Class} object) later on
*/
boolean isClassReloadable(Class<?> clazz);
default boolean isClassReloadable(Class<?> clazz) {
return false;
}
/**
* Define a custom class (typically a CGLIB proxy class) in this class loader.
* <p>This is a public equivalent of the protected
* {@code defineClass(String, byte[], int, int, ProtectionDomain)} method
* in {@link ClassLoader} which is traditionally invoked via reflection.
* A concrete implementation in a custom class loader should simply delegate
* to that protected method in order to make classloader-specific definitions
* publicly available without "illegal access" warnings on JDK 9+:
* {@code return defineClass(name, b, 0, b.length, protectionDomain)}.
* Note that the JDK 9+ {@code Lookup#defineClass} method does not support
* a custom target class loader for the new definition; it rather always
* defines the class in the same class loader as the lookup's context class.
* @param name the name of the class
* @param b the bytes defining the class
* @param protectionDomain the protection domain for the class, if any
* @return the newly created class
* @throws LinkageError in case of a bad class definition
* @throws SecurityException in case of an invalid definition attempt
* @throws UnsupportedOperationException in case of a custom definition attempt
* not being possible (thrown by the default implementation in this interface)
* @since 5.3.4
* @see ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)
* @see java.lang.invoke.MethodHandles.Lookup#defineClass(byte[])
*/
default Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
throw new UnsupportedOperationException();
}
}
@@ -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.
@@ -38,9 +38,15 @@ import org.springframework.lang.Nullable;
*
* @author Juergen Hoeller
* @since 3.2.7
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
* @see org.springframework.beans.CachedIntrospectionResults#IGNORE_BEANINFO_PROPERTY_NAME
* @see org.springframework.context.index.CandidateComponentsIndexLoader#IGNORE_INDEX
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
* @see org.springframework.jndi.JndiLocatorDelegate#IGNORE_JNDI_PROPERTY_NAME
* @see org.springframework.objenesis.SpringObjenesis#IGNORE_OBJENESIS_PROPERTY_NAME
* @see org.springframework.test.context.NestedTestConfiguration#ENCLOSING_CONFIGURATION_PROPERTY_NAME
* @see org.springframework.test.context.TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME
* @see org.springframework.test.context.cache.ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
*/
public final class SpringProperties {
@@ -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,6 +16,7 @@
package org.springframework.core.codec;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@@ -59,7 +60,7 @@ public interface Decoder<T> {
* this type must have been previously passed to the {@link #canDecode}
* method and it must have returned {@code true}.
* @param mimeType the MIME type associated with the input stream (optional)
* @param hints additional information about how to do encode
* @param hints additional information about how to do decode
* @return the output stream with decoded elements
*/
Flux<T> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
@@ -72,7 +73,7 @@ public interface Decoder<T> {
* this type must have been previously passed to the {@link #canDecode}
* method and it must have returned {@code true}.
* @param mimeType the MIME type associated with the input stream (optional)
* @param hints additional information about how to do encode
* @param hints additional information about how to do decode
* @return the output stream with the decoded element
*/
Mono<T> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
@@ -85,7 +86,7 @@ public interface Decoder<T> {
* @param buffer the {@code DataBuffer} to decode
* @param targetType the expected output type
* @param mimeType the MIME type associated with the data
* @param hints additional information about how to do encode
* @param hints additional information about how to do decode
* @return the decoded value, possibly {@code null}
* @since 5.2
*/
@@ -111,8 +112,27 @@ public interface Decoder<T> {
}
/**
* Return the list of MIME types this decoder supports.
* Return the list of MIME types supported by this Decoder. The list may not
* apply to every possible target element type and calls to this method
* should typically be guarded via {@link #canDecode(ResolvableType, MimeType)
* canDecode(elementType, null)}. The list may also exclude MIME types
* supported only for a specific element type. Alternatively, use
* {@link #getDecodableMimeTypes(ResolvableType)} for a more precise list.
* @return the list of supported MIME types
*/
List<MimeType> getDecodableMimeTypes();
/**
* Return the list of MIME types supported by this Decoder for the given type
* of element. This list may differ from {@link #getDecodableMimeTypes()}
* if the Decoder doesn't support the given element type or if it supports
* it only for a subset of MIME types.
* @param targetType the type of element to check for decoding
* @return the list of MIME types supported for the given target type
* @since 5.3.4
*/
default List<MimeType> getDecodableMimeTypes(ResolvableType targetType) {
return (canDecode(targetType, null) ? getDecodableMimeTypes() : Collections.emptyList());
}
}
@@ -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,6 +16,7 @@
package org.springframework.core.codec;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -90,8 +91,27 @@ public interface Encoder<T> {
}
/**
* Return the list of mime types this encoder supports.
* Return the list of MIME types supported by this Encoder. The list may not
* apply to every possible target element type and calls to this method should
* typically be guarded via {@link #canEncode(ResolvableType, MimeType)
* canEncode(elementType, null)}. The list may also exclude MIME types
* supported only for a specific element type. Alternatively, use
* {@link #getEncodableMimeTypes(ResolvableType)} for a more precise list.
* @return the list of supported MIME types
*/
List<MimeType> getEncodableMimeTypes();
/**
* Return the list of MIME types supported by this Encoder for the given type
* of element. This list may differ from the {@link #getEncodableMimeTypes()}
* if the Encoder doesn't support the element type or if it supports it only
* for a subset of MIME types.
* @param elementType the type of element to check for encoding
* @return the list of MIME types supported for the given element type
* @since 5.3.4
*/
default List<MimeType> getEncodableMimeTypes(ResolvableType elementType) {
return (canEncode(elementType, null) ? getEncodableMimeTypes() : Collections.emptyList());
}
}
@@ -157,16 +157,20 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
int startIndex = buffer.readPosition();
int length = (endIndex - startIndex + 1);
DataBuffer slice = buffer.retainedSlice(startIndex, length);
if (this.stripDelimiter) {
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
}
result = (result != null ? result : new ArrayList<>());
if (chunks.isEmpty()) {
if (this.stripDelimiter) {
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
}
result.add(slice);
}
else {
chunks.add(slice);
result.add(buffer.factory().join(chunks));
DataBuffer joined = buffer.factory().join(chunks);
if (this.stripDelimiter) {
joined.writePosition(joined.writePosition() - matcher.delimiter().length);
}
result.add(joined);
chunks.clear();
}
buffer.readPosition(endIndex + 1);
@@ -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.
@@ -107,10 +107,9 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
private final MutablePropertySources propertySources = new MutablePropertySources();
private final MutablePropertySources propertySources;
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
private final ConfigurablePropertyResolver propertyResolver;
/**
@@ -121,9 +120,44 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
* @see #customizePropertySources(MutablePropertySources)
*/
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
this(new MutablePropertySources());
}
/**
* Create a new {@code Environment} instance with a specific
* {@link MutablePropertySources} instance, calling back to
* {@link #customizePropertySources(MutablePropertySources)} during
* construction to allow subclasses to contribute or manipulate
* {@link PropertySource} instances as appropriate.
* @since 5.3.4
* @see #customizePropertySources(MutablePropertySources)
*/
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources;
this.propertyResolver = createPropertyResolver(propertySources);
customizePropertySources(propertySources);
}
/**
* Factory method used to create the {@link ConfigurablePropertyResolver}
* instance used by the Environment.
* @since 5.3.4
* @see #getPropertyResolver()
*/
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
return new PropertySourcesPropertyResolver(propertySources);
}
/**
* Return the {@link ConfigurablePropertyResolver} being used by the
* {@link Environment}.
* @since 5.3.4
* @see #createPropertyResolver(MutablePropertySources)
*/
protected final ConfigurablePropertyResolver getPropertyResolver() {
return this.propertyResolver;
}
/**
* Customize the set of {@link PropertySource} objects to be searched by this
@@ -227,15 +261,15 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
/**
* Return the set of active profiles as explicitly set through
* {@link #setActiveProfiles} or if the current set of active profiles
* is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
* property and assign its value to the set of active profiles.
* is empty, check for the presence of {@link #doGetActiveProfilesProperty()}
* and assign its value to the set of active profiles.
* @see #getActiveProfiles()
* @see #ACTIVE_PROFILES_PROPERTY_NAME
* @see #doGetActiveProfilesProperty()
*/
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
String profiles = doGetActiveProfilesProperty();
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
@@ -245,6 +279,16 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
}
/**
* Return the property value for the active profiles.
* @since 5.3.4
* @see #ACTIVE_PROFILES_PROPERTY_NAME
*/
@Nullable
protected String doGetActiveProfilesProperty() {
return getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Override
public void setActiveProfiles(String... profiles) {
Assert.notNull(profiles, "Profile array must not be null");
@@ -282,18 +326,17 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
* Return the set of default profiles explicitly set via
* {@link #setDefaultProfiles(String...)} or if the current set of default profiles
* consists only of {@linkplain #getReservedDefaultProfiles() reserved default
* profiles}, then check for the presence of the
* {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any)
* to the set of default profiles.
* profiles}, then check for the presence of {@link #doGetActiveProfilesProperty()}
* and assign its value (if any) to the set of default profiles.
* @see #AbstractEnvironment()
* @see #getDefaultProfiles()
* @see #DEFAULT_PROFILES_PROPERTY_NAME
* @see #getReservedDefaultProfiles()
* @see #doGetDefaultProfilesProperty()
*/
protected Set<String> doGetDefaultProfiles() {
synchronized (this.defaultProfiles) {
if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
String profiles = doGetDefaultProfilesProperty();
if (StringUtils.hasText(profiles)) {
setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
@@ -303,6 +346,16 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
}
/**
* Return the property value for the default profiles.
* @since 5.3.4
* @see #DEFAULT_PROFILES_PROPERTY_NAME
*/
@Nullable
protected String doGetDefaultProfilesProperty() {
return getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
}
/**
* Specify the set of profiles to be made active by default if no other profiles
* are explicitly made active through {@link #setActiveProfiles}.
@@ -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.
@@ -60,6 +60,14 @@ public class StandardEnvironment extends AbstractEnvironment {
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
public StandardEnvironment() {
}
protected StandardEnvironment(MutablePropertySources propertySources) {
super(propertySources);
}
/**
* Customize the set of property sources with those appropriate for any standard
* Java environment:
@@ -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.
@@ -20,15 +20,15 @@ import org.springframework.lang.Nullable;
import org.springframework.util.ResourceUtils;
/**
* Strategy interface for loading resources (e.. class path or file system
* Strategy interface for loading resources (e.g., class path or file system
* resources). An {@link org.springframework.context.ApplicationContext}
* is required to provide this functionality, plus extended
* is required to provide this functionality plus extended
* {@link org.springframework.core.io.support.ResourcePatternResolver} support.
*
* <p>{@link DefaultResourceLoader} is a standalone implementation that is
* usable outside an ApplicationContext, also used by {@link ResourceEditor}.
* usable outside an ApplicationContext and is also used by {@link ResourceEditor}.
*
* <p>Bean properties of type Resource and Resource array can be populated
* <p>Bean properties of type {@code Resource} and {@code Resource[]} can be populated
* from Strings when running in an ApplicationContext, using the particular
* context's resource loading strategy.
*
@@ -46,7 +46,7 @@ public interface ResourceLoader {
/**
* Return a Resource handle for the specified resource location.
* Return a {@code Resource} handle for the specified resource location.
* <p>The handle should always be a reusable resource descriptor,
* allowing for multiple {@link Resource#getInputStream()} calls.
* <p><ul>
@@ -56,10 +56,10 @@ public interface ResourceLoader {
* (This will be implementation-specific, typically provided by an
* ApplicationContext implementation.)
* </ul>
* <p>Note that a Resource handle does not imply an existing resource;
* <p>Note that a {@code Resource} handle does not imply an existing resource;
* you need to invoke {@link Resource#exists} to check for existence.
* @param location the resource location
* @return a corresponding Resource handle (never {@code null})
* @return a corresponding {@code Resource} handle (never {@code null})
* @see #CLASSPATH_URL_PREFIX
* @see Resource#exists()
* @see Resource#getInputStream()
@@ -67,12 +67,12 @@ public interface ResourceLoader {
Resource getResource(String location);
/**
* Expose the ClassLoader used by this ResourceLoader.
* <p>Clients which need to access the ClassLoader directly can do so
* in a uniform manner with the ResourceLoader, rather than relying
* on the thread context ClassLoader.
* @return the ClassLoader
* (only {@code null} if even the system ClassLoader isn't accessible)
* Expose the {@link ClassLoader} used by this {@code ResourceLoader}.
* <p>Clients which need to access the {@code ClassLoader} directly can do so
* in a uniform manner with the {@code ResourceLoader}, rather than relying
* on the thread context {@code ClassLoader}.
* @return the {@code ClassLoader}
* (only {@code null} if even the system {@code ClassLoader} isn't accessible)
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 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,17 +23,18 @@ import org.springframework.core.io.ResourceLoader;
/**
* Strategy interface for resolving a location pattern (for example,
* an Ant-style path pattern) into Resource objects.
* an Ant-style path pattern) into {@link Resource} objects.
*
* <p>This is an extension to the {@link org.springframework.core.io.ResourceLoader}
* interface. A passed-in ResourceLoader (for example, an
* interface. A passed-in {@code ResourceLoader} (for example, an
* {@link org.springframework.context.ApplicationContext} passed in via
* {@link org.springframework.context.ResourceLoaderAware} when running in a context)
* can be checked whether it implements this extended interface too.
*
* <p>{@link PathMatchingResourcePatternResolver} is a standalone implementation
* that is usable outside an ApplicationContext, also used by
* {@link ResourceArrayPropertyEditor} for populating Resource array bean properties.
* that is usable outside an {@code ApplicationContext}, also used by
* {@link ResourceArrayPropertyEditor} for populating {@code Resource} array bean
* properties.
*
* <p>Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"):
* Input patterns have to match the strategy implementation. This interface just
@@ -42,7 +43,8 @@ import org.springframework.core.io.ResourceLoader;
* <p>This interface also suggests a new resource prefix "classpath*:" for all
* matching resources from the class path. Note that the resource location is
* expected to be a path without placeholders in this case (e.g. "/beans.xml");
* JAR files or classes directories can contain multiple files of the same name.
* JAR files or different directories in the class path can contain multiple files
* of the same name.
*
* @author Juergen Hoeller
* @since 1.0.2
@@ -55,7 +57,7 @@ public interface ResourcePatternResolver extends ResourceLoader {
/**
* Pseudo URL prefix for all matching resources from the class path: "classpath*:"
* This differs from ResourceLoader's classpath URL prefix in that it
* <p>This differs from ResourceLoader's classpath URL prefix in that it
* retrieves all matching resources for a given name (e.g. "/beans.xml"),
* for example in the root of all deployed JAR files.
* @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
@@ -63,12 +65,12 @@ public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* Resolve the given location pattern into Resource objects.
* Resolve the given location pattern into {@code Resource} objects.
* <p>Overlapping resource entries that point to the same physical
* resource should be avoided, as far as possible. The result should
* have set semantics.
* @param locationPattern the location pattern to resolve
* @return the corresponding Resource objects
* @return the corresponding {@code Resource} objects
* @throws IOException in case of I/O errors
*/
Resource[] getResources(String locationPattern) 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.
@@ -74,8 +74,8 @@ public abstract class ClassUtils {
/** The path separator character: {@code '/'}. */
private static final char PATH_SEPARATOR = '/';
/** The inner class separator character: {@code '$'}. */
private static final char INNER_CLASS_SEPARATOR = '$';
/** The nested class separator character: {@code '$'}. */
private static final char NESTED_CLASS_SEPARATOR = '$';
/** The CGLIB class separator: {@code "$$"}. */
public static final String CGLIB_CLASS_SEPARATOR = "$$";
@@ -232,7 +232,7 @@ public abstract class ClassUtils {
/**
* Replacement for {@code Class.forName()} that also returns Class instances
* for primitives (e.g. "int") and array class names (e.g. "String[]").
* Furthermore, it is also capable of resolving inner class names in Java source
* Furthermore, it is also capable of resolving nested class names in Java source
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
* @param name the name of the Class
* @param classLoader the class loader to use
@@ -286,10 +286,10 @@ public abstract class ClassUtils {
catch (ClassNotFoundException ex) {
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
String nestedClassName =
name.substring(0, lastDotIndex) + NESTED_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return Class.forName(innerClassName, false, clToUse);
return Class.forName(nestedClassName, false, clToUse);
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
@@ -953,7 +953,7 @@ public abstract class ClassUtils {
nameEndIndex = className.length();
}
String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
shortName = shortName.replace(NESTED_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
return shortName;
}
@@ -968,7 +968,7 @@ public abstract class ClassUtils {
/**
* Return the short string name of a Java class in uncapitalized JavaBeans
* property format. Strips the outer class name in case of an inner class.
* property format. Strips the outer class name in case of a nested class.
* @param clazz the class
* @return the short name rendered in a standard JavaBeans property format
* @see java.beans.Introspector#decapitalize(String)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,39 +17,61 @@
package org.springframework.core;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link AttributeAccessorSupport}.
*
* @author Rob Harrop
* @author Sam Brannen
* @since 2.0
*/
class AttributeAccessorSupportTests {
private static final String NAME = "foo";
private static final String NAME = "name";
private static final String VALUE = "bar";
private static final String VALUE = "value";
private final AttributeAccessor attributeAccessor = new SimpleAttributeAccessorSupport();
private AttributeAccessor attributeAccessor = new SimpleAttributeAccessorSupport();
@Test
void setAndGet() throws Exception {
void setAndGet() {
this.attributeAccessor.setAttribute(NAME, VALUE);
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo(VALUE);
}
@Test
void setAndHas() throws Exception {
void setAndHas() {
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
this.attributeAccessor.setAttribute(NAME, VALUE);
assertThat(this.attributeAccessor.hasAttribute(NAME)).isTrue();
}
@Test
void remove() throws Exception {
void computeAttribute() {
AtomicInteger atomicInteger = new AtomicInteger();
Function<String, String> computeFunction = name -> "computed-" + atomicInteger.incrementAndGet();
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
this.attributeAccessor.computeAttribute(NAME, computeFunction);
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-1");
this.attributeAccessor.computeAttribute(NAME, computeFunction);
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-1");
this.attributeAccessor.removeAttribute(NAME);
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
this.attributeAccessor.computeAttribute(NAME, computeFunction);
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-2");
}
@Test
void remove() {
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
this.attributeAccessor.setAttribute(NAME, VALUE);
assertThat(this.attributeAccessor.removeAttribute(NAME)).isEqualTo(VALUE);
@@ -57,13 +79,13 @@ class AttributeAccessorSupportTests {
}
@Test
void attributeNames() throws Exception {
void attributeNames() {
this.attributeAccessor.setAttribute(NAME, VALUE);
this.attributeAccessor.setAttribute("abc", "123");
String[] attributeNames = this.attributeAccessor.attributeNames();
Arrays.sort(attributeNames);
assertThat(Arrays.binarySearch(attributeNames, NAME) > -1).isTrue();
assertThat(Arrays.binarySearch(attributeNames, "abc") > -1).isTrue();
assertThat(Arrays.binarySearch(attributeNames, "abc")).isEqualTo(0);
assertThat(Arrays.binarySearch(attributeNames, NAME)).isEqualTo(1);
}
@SuppressWarnings("serial")
@@ -125,10 +125,10 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
);
testDecode(input, String.class, step -> step
.expectNext("")
.expectNext("").as("1st")
.expectNext("abc")
.expectNext("defghi")
.expectNext("")
.expectNext("").as("2nd")
.expectNext("jklmno")
.expectNext("pqr")
.expectNext("stuvwxyz")
@@ -136,6 +136,21 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
.verify());
}
@Test
void decodeNewlinesAcrossBuffers() {
Flux<DataBuffer> input = Flux.just(
stringBuffer("\r"),
stringBuffer("\n"),
stringBuffer("xyz")
);
testDecode(input, String.class, step -> step
.expectNext("")
.expectNext("xyz")
.expectComplete()
.verify());
}
@Test
void maxInMemoryLimit() {
Flux<DataBuffer> input = Flux.just(
@@ -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.
@@ -18,10 +18,14 @@ package org.springframework.core.env;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -32,8 +36,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class CustomEnvironmentTests {
// -- tests relating to customizing reserved default profiles ----------------------
@Test
void control() {
Environment env = new AbstractEnvironment() { };
@@ -105,10 +107,66 @@ class CustomEnvironmentTests {
assertThat(env.acceptsProfiles(Profiles.of("a1 | a2"))).isFalse();
}
@Test
public void withNoProfileProperties() {
ConfigurableEnvironment env = new AbstractEnvironment() {
@Override
@Nullable
protected String doGetActiveProfilesProperty() {
return null;
}
@Override
@Nullable
protected String doGetDefaultProfilesProperty() {
return null;
}
};
Map<String, Object> values = new LinkedHashMap<>();
values.put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "a,b,c");
values.put(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "d,e,f");
PropertySource<?> propertySource = new MapPropertySource("test", values);
env.getPropertySources().addFirst(propertySource);
assertThat(env.getActiveProfiles()).isEmpty();
assertThat(env.getDefaultProfiles()).containsExactly(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME);
}
@Test
public void withCustomMutablePropertySources() {
class CustomMutablePropertySources extends MutablePropertySources {}
MutablePropertySources propertySources = new CustomMutablePropertySources();
ConfigurableEnvironment env = new AbstractEnvironment(propertySources) {};
assertThat(env.getPropertySources()).isInstanceOf(CustomMutablePropertySources.class);
}
@Test
void withCustomPropertyResolver() {
class CustomPropertySourcesPropertyResolver extends PropertySourcesPropertyResolver {
public CustomPropertySourcesPropertyResolver(PropertySources propertySources) {
super(propertySources);
}
@Override
@Nullable
public String getProperty(String key) {
return super.getProperty(key)+"-test";
}
}
ConfigurableEnvironment env = new AbstractEnvironment() {
@Override
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
return new CustomPropertySourcesPropertyResolver(propertySources);
}
};
Map<String, Object> values = new LinkedHashMap<>();
values.put("spring", "framework");
PropertySource<?> propertySource = new MapPropertySource("test", values);
env.getPropertySources().addFirst(propertySource);
assertThat(env.getProperty("spring")).isEqualTo("framework-test");
}
private Profiles defaultProfile() {
return Profiles.of(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME);
}
// -- tests relating to customizing property sources -------------------------------
}
@@ -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.
@@ -33,6 +33,7 @@ import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@@ -59,14 +60,6 @@ class ClassUtilsTests {
private final ClassLoader classLoader = getClass().getClassLoader();
@BeforeEach
void clearStatics() {
InnerClass.noArgCalled = false;
InnerClass.argCalled = false;
InnerClass.overloadedCalled = false;
}
@Test
void isPresent() {
assertThat(ClassUtils.isPresent("java.lang.String", classLoader)).isTrue();
@@ -88,6 +81,12 @@ class ClassUtilsTests {
assertThat(ClassUtils.forName("[[[S", classLoader)).isEqualTo(short[][][].class);
}
@Test
void forNameWithNestedType() throws ClassNotFoundException {
assertThat(ClassUtils.forName("org.springframework.util.ClassUtilsTests$NestedClass", classLoader)).isEqualTo(NestedClass.class);
assertThat(ClassUtils.forName("org.springframework.util.ClassUtilsTests.NestedClass", classLoader)).isEqualTo(NestedClass.class);
}
@Test
void forNameWithPrimitiveClasses() throws ClassNotFoundException {
assertThat(ClassUtils.forName("boolean", classLoader)).isEqualTo(boolean.class);
@@ -143,11 +142,11 @@ class ClassUtilsTests {
assertThat(ClassUtils.isCacheSafe(String.class, childLoader1)).isTrue();
assertThat(ClassUtils.isCacheSafe(String.class, childLoader2)).isTrue();
assertThat(ClassUtils.isCacheSafe(String.class, childLoader3)).isTrue();
assertThat(ClassUtils.isCacheSafe(InnerClass.class, null)).isFalse();
assertThat(ClassUtils.isCacheSafe(InnerClass.class, classLoader)).isTrue();
assertThat(ClassUtils.isCacheSafe(InnerClass.class, childLoader1)).isTrue();
assertThat(ClassUtils.isCacheSafe(InnerClass.class, childLoader2)).isTrue();
assertThat(ClassUtils.isCacheSafe(InnerClass.class, childLoader3)).isTrue();
assertThat(ClassUtils.isCacheSafe(NestedClass.class, null)).isFalse();
assertThat(ClassUtils.isCacheSafe(NestedClass.class, classLoader)).isTrue();
assertThat(ClassUtils.isCacheSafe(NestedClass.class, childLoader1)).isTrue();
assertThat(ClassUtils.isCacheSafe(NestedClass.class, childLoader2)).isTrue();
assertThat(ClassUtils.isCacheSafe(NestedClass.class, childLoader3)).isTrue();
assertThat(ClassUtils.isCacheSafe(composite, null)).isFalse();
assertThat(ClassUtils.isCacheSafe(composite, classLoader)).isFalse();
assertThat(ClassUtils.isCacheSafe(composite, childLoader1)).isTrue();
@@ -209,9 +208,9 @@ class ClassUtilsTests {
}
@Test
void getShortNameForInnerClass() {
String className = ClassUtils.getShortName(InnerClass.class);
assertThat(className).as("Class name did not match").isEqualTo("ClassUtilsTests.InnerClass");
void getShortNameForNestedClass() {
String className = ClassUtils.getShortName(NestedClass.class);
assertThat(className).as("Class name did not match").isEqualTo("ClassUtilsTests.NestedClass");
}
@Test
@@ -299,27 +298,6 @@ class ClassUtilsTests {
assertThat(ClassUtils.hasAtLeastOneMethodWithName(TestObject.class, "setAge")).isTrue();
}
@Test
void noArgsStaticMethod() throws IllegalAccessException, InvocationTargetException {
Method method = ClassUtils.getStaticMethod(InnerClass.class, "staticMethod");
method.invoke(null, (Object[]) null);
assertThat(InnerClass.noArgCalled).as("no argument method was not invoked.").isTrue();
}
@Test
void argsStaticMethod() throws IllegalAccessException, InvocationTargetException {
Method method = ClassUtils.getStaticMethod(InnerClass.class, "argStaticMethod", String.class);
method.invoke(null, "test");
assertThat(InnerClass.argCalled).as("argument method was not invoked.").isTrue();
}
@Test
void overloadedStaticMethod() throws IllegalAccessException, InvocationTargetException {
Method method = ClassUtils.getStaticMethod(InnerClass.class, "staticMethod", String.class);
method.invoke(null, "test");
assertThat(InnerClass.overloadedCalled).as("argument method was not invoked.").isTrue();
}
@Test
void isAssignable() {
assertThat(ClassUtils.isAssignable(Object.class, Object.class)).isTrue();
@@ -431,6 +409,40 @@ class ClassUtilsTests {
}
@Nested
class GetStaticMethodTests {
@BeforeEach
void clearStatics() {
NestedClass.noArgCalled = false;
NestedClass.argCalled = false;
NestedClass.overloadedCalled = false;
}
@Test
void noArgsStaticMethod() throws IllegalAccessException, InvocationTargetException {
Method method = ClassUtils.getStaticMethod(NestedClass.class, "staticMethod");
method.invoke(null, (Object[]) null);
assertThat(NestedClass.noArgCalled).as("no argument method was not invoked.").isTrue();
}
@Test
void argsStaticMethod() throws IllegalAccessException, InvocationTargetException {
Method method = ClassUtils.getStaticMethod(NestedClass.class, "argStaticMethod", String.class);
method.invoke(null, "test");
assertThat(NestedClass.argCalled).as("argument method was not invoked.").isTrue();
}
@Test
void overloadedStaticMethod() throws IllegalAccessException, InvocationTargetException {
Method method = ClassUtils.getStaticMethod(NestedClass.class, "staticMethod", String.class);
method.invoke(null, "test");
assertThat(NestedClass.overloadedCalled).as("argument method was not invoked.").isTrue();
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ValueSource(classes = { Boolean.class, Character.class, Byte.class, Short.class,
@@ -445,7 +457,7 @@ class ClassUtilsTests {
@interface PrimitiveTypes {
}
public static class InnerClass {
public static class NestedClass {
static boolean noArgCalled;
static boolean argCalled;
@@ -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.
@@ -30,10 +30,14 @@ import org.springframework.lang.Nullable;
*/
public class SpelParserConfiguration {
/** System property to configure the default compiler mode for SpEL expression parsers: {@value}. */
public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode";
private static final SpelCompilerMode defaultCompilerMode;
static {
String compilerMode = SpringProperties.getProperty("spring.expression.compiler.mode");
String compilerMode = SpringProperties.getProperty(SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME);
defaultCompilerMode = (compilerMode != null ?
SpelCompilerMode.valueOf(compilerMode.toUpperCase()) : SpelCompilerMode.OFF);
}
@@ -107,7 +111,7 @@ public class SpelParserConfiguration {
/**
* Return the configuration mode for parsers using this configuration object.
* Return the compiler mode for parsers using this configuration object.
*/
public SpelCompilerMode getCompilerMode() {
return this.compilerMode;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
*/
public class GenericCallMetaDataProvider implements CallMetaDataProvider {
@@ -414,8 +415,15 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
}
catch (SQLException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Error while retrieving meta-data for procedure columns: " + ex);
logger.warn("Error while retrieving meta-data for procedure columns. " +
"Consider declaring explicit parameters -- for example, via SimpleJdbcCall#addDeclaredParameter().",
ex);
}
// Although we could invoke `this.callParameterMetaData.clear()` so that
// we don't retain a partial list of column names (like we do in
// GenericTableMetaDataProvider.processTableColumns(...)), we choose
// not to do that here, since invocation of the stored procedure will
// likely fail anyway with an incorrect argument list.
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import org.springframework.lang.Nullable;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
*/
public class GenericTableMetaDataProvider implements TableMetaDataProvider {
@@ -422,8 +423,12 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
}
catch (SQLException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Error while retrieving meta-data for table columns: " + ex.getMessage());
logger.warn("Error while retrieving meta-data for table columns. " +
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().",
ex);
}
// Clear the metadata so that we don't retain a partial list of column names
this.tableParameterMetaData.clear();
}
finally {
JdbcUtils.closeResultSet(tableColumns);
@@ -432,7 +437,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
/**
* Inner class representing table meta-data.
* Class representing table meta-data.
*/
private static class TableMetaData {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -43,6 +43,7 @@ import org.springframework.util.CollectionUtils;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
*/
public class TableMetaDataContext {
@@ -302,8 +303,12 @@ public class TableMetaDataContext {
}
}
else {
throw new InvalidDataAccessApiUsageException("Unable to locate columns for table '" +
getTableName() + "' so an insert statement can't be generated");
String message = "Unable to locate columns for table '" + getTableName()
+ "' so an insert statement can't be generated.";
if (isAccessTableColumnMetaData()) {
message += " Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().";
}
throw new InvalidDataAccessApiUsageException(message);
}
}
String params = String.join(", ", Collections.nCopies(columnCount, "?"));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,6 @@ public final class TableMetaDataProviderFactory {
try {
return JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();
TableMetaDataProvider provider;
if ("Oracle".equals(databaseProductName)) {
@@ -70,15 +69,17 @@ public final class TableMetaDataProviderFactory {
else {
provider = new GenericTableMetaDataProvider(databaseMetaData);
}
if (logger.isDebugEnabled()) {
logger.debug("Using " + provider.getClass().getSimpleName());
}
provider.initializeWithMetaData(databaseMetaData);
if (accessTableColumnMetaData) {
if (context.isAccessTableColumnMetaData()) {
provider.initializeWithTableColumnMetaData(databaseMetaData,
context.getCatalogName(), context.getSchemaName(), context.getTableName());
}
return provider;
});
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Yanming Zhou
* @since 2.0
*/
public abstract class NamedParameterUtils {
@@ -346,8 +347,14 @@ public abstract class NamedParameterUtils {
String paramName = paramNames.get(i);
try {
SqlParameter param = findParameter(declaredParams, paramName, i);
paramArray[i] = (param != null ? new SqlParameterValue(param, paramSource.getValue(paramName)) :
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
Object paramValue = paramSource.getValue(paramName);
if (paramValue instanceof SqlParameterValue) {
paramArray[i] = paramValue;
}
else {
paramArray[i] = (param != null ? new SqlParameterValue(param, paramValue) :
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
}
}
catch (IllegalArgumentException ex) {
throw new InvalidDataAccessApiUsageException(
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -347,7 +347,7 @@ public abstract class AbstractJdbcCall {
/**
* Check whether this operation has been compiled already;
* lazily compile it if not already compiled.
* <p>Automatically called by {@code doExecute}.
* <p>Automatically called by all {@code doExecute(...)} methods.
*/
protected void checkCompiled() {
if (!isCompiled()) {
@@ -433,7 +433,7 @@ public abstract class AbstractJdbcCall {
/**
* Match the provided in parameter values with registered parameters and
* parameters defined via meta-data processing.
* @param parameterSource the parameter vakues provided as a {@link SqlParameterSource}
* @param parameterSource the parameter values provided as a {@link SqlParameterSource}
* @return a Map with parameter names and values
*/
protected Map<String, Object> matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
@@ -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.
@@ -70,7 +70,7 @@ public abstract class AbstractJdbcInsert {
/** Context used to retrieve and manage database meta-data. */
private final TableMetaDataContext tableMetaDataContext = new TableMetaDataContext();
/** List of columns objects to be used in insert statement. */
/** List of column names to be used in insert statement. */
private final List<String> declaredColumns = new ArrayList<>();
/** The names of the columns holding the generated key. */
@@ -301,7 +301,7 @@ public abstract class AbstractJdbcInsert {
/**
* Check whether this operation has been compiled already;
* lazily compile it if not already compiled.
* <p>Automatically called by {@code validateParameters}.
* <p>Automatically called by all {@code doExecute*(...)} methods.
*/
protected void checkCompiled() {
if (!isCompiled()) {
@@ -312,7 +312,7 @@ public abstract class AbstractJdbcInsert {
/**
* Method to check whether we are allowed to make any configuration changes at this time.
* If the class has been compiled, then no further changes to the configuration are allowed.
* <p>If the class has been compiled, then no further changes to the configuration are allowed.
*/
protected void checkIfConfigurationModificationIsAllowed() {
if (isCompiled()) {
@@ -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.
@@ -54,8 +54,10 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<ou
* @author Mario Arias
* @since 5.0
*/
@Suppress("DEPRECATION")
// TODO Replace by the vararg variant in Spring Framework 6
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T? =
queryForObject(sql, T::class.java, args) as T
queryForObject(sql, args, T::class.java) as T
/**
* Extension for [JdbcOperations.queryForList] providing a `queryForList<Foo>("...")` variant.
@@ -86,8 +88,11 @@ inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out
* @author Mario Arias
* @since 5.0
*/
@Suppress("DEPRECATION")
// TODO Replace by the vararg variant in Spring Framework 6
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
queryForList(sql, T::class.java, args)
queryForList(sql, args, T::class.java)
/**
* Extension for [JdbcOperations.query] providing a ResultSetExtractor-like function
@@ -26,7 +26,7 @@ package org.springframework.jdbc.core.namedparam
* @since 5.0
*
*/
operator fun MapSqlParameterSource.set(paramName: String, value: Any) {
operator fun MapSqlParameterSource.set(paramName: String, value: Any?) {
this.addValue(paramName, value)
}
@@ -40,7 +40,7 @@ operator fun MapSqlParameterSource.set(paramName: String, value: Any) {
* @since 5.0
*
*/
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, value: Any) {
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, value: Any?) {
this.addValue(paramName, value, sqlType)
}
@@ -54,6 +54,6 @@ operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, value: A
* @since 5.0
*
*/
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, typeName: String, value: Any) {
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, typeName: String, value: Any?) {
this.addValue(paramName, value, sqlType, typeName)
}
@@ -247,7 +247,7 @@
<value>03000,42000,42601,42602,42622,42804,42P01</value>
</property>
<property name="duplicateKeyCodes">
<value>23505</value>
<value>21000,23505</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>23000,23502,23503,23514</value>

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