Compare commits

...

152 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
233 changed files with 6071 additions and 2064 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.
-->
-->
+28 -27
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.56.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.3"
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.20"
dependency "io.reactivex.rxjava3:rxjava:3.0.9"
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"
}
@@ -96,7 +97,7 @@ configure(allprojects) { project ->
dependency "com.h2database:h2:1.4.200"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
dependency "com.github.librepdf:openpdf:1.3.24"
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.27.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.41') {
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.41') {
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"
@@ -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"
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.7.0') {
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.46.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.47.1") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.46.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.9"
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"
@@ -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 -3
View File
@@ -3,13 +3,13 @@ set -e
case "$1" in
java8)
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u275-b01/OpenJDK8U-jdk_x64_linux_hotspot_8u275b01.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.9.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.9.1_1.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.0.1%2B9/OpenJDK15U-jdk_x64_linux_hotspot_15.0.1_9.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"
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20201106
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20201106
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20201106
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.3-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"
@@ -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;
}
@@ -779,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();
@@ -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-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.
@@ -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
@@ -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;
@@ -97,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));
@@ -155,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);
}
@@ -171,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) {
@@ -193,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
@@ -200,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);
};
}
@@ -228,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);
@@ -253,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();
}
}
}
@@ -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);
}
}
}
@@ -1137,4 +1137,110 @@ class CronExpressionTests {
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);
}
}
@@ -98,6 +98,7 @@ class QuartzCronFieldTests {
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 -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;
}
@@ -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;
}
@@ -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-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.
@@ -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)
@@ -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()) {
@@ -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()) {
@@ -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)
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.SqlParameterValue;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Juergen Hoeller
* @author Rick Evans
* @author Artur Geraschenko
* @author Yanming Zhou
*/
public class NamedParameterUtilsTests {
@@ -96,6 +98,20 @@ public class NamedParameterUtilsTests {
.buildSqlTypeArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams)[4]).isEqualTo(2);
}
@Test
public void convertSqlParameterValueToArray() {
SqlParameterValue sqlParameterValue = new SqlParameterValue(2, "b");
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("a", "a");
paramMap.put("b", sqlParameterValue);
paramMap.put("c", "c");
assertThat(NamedParameterUtils.buildValueArray("xxx :a :b :c xx :a :b", paramMap)[4]).isSameAs(sqlParameterValue);
MapSqlParameterSource namedParams = new MapSqlParameterSource();
namedParams.addValue("a", "a", 1).addValue("b", sqlParameterValue).addValue("c", "c", 3);
assertThat(NamedParameterUtils
.buildValueArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams, null)[4]).isSameAs(sqlParameterValue);
}
@Test
public void convertTypeMapToSqlParameterList() {
MapSqlParameterSource namedParams = new MapSqlParameterSource();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,31 +46,28 @@ import static org.mockito.Mockito.verify;
*
* @author Thomas Risberg
* @author Kiril Nugmanov
* @author Sam Brannen
*/
public class SimpleJdbcCallTests {
class SimpleJdbcCallTests {
private Connection connection;
private final Connection connection = mock(Connection.class);
private DatabaseMetaData databaseMetaData;
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
private DataSource dataSource;
private final DataSource dataSource = mock(DataSource.class);
private CallableStatement callableStatement;
private final CallableStatement callableStatement = mock(CallableStatement.class);
@BeforeEach
public void setUp() throws Exception {
connection = mock(Connection.class);
databaseMetaData = mock(DatabaseMetaData.class);
dataSource = mock(DataSource.class);
callableStatement = mock(CallableStatement.class);
void setUp() throws Exception {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(dataSource.getConnection()).willReturn(connection);
}
@Test
public void testNoSuchStoredProcedure() throws Exception {
void noSuchStoredProcedure() throws Exception {
final String NO_SUCH_PROC = "x";
SQLException sqlException = new SQLException("Syntax error or access violation exception", "42000");
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
@@ -81,8 +78,8 @@ public class SimpleJdbcCallTests {
given(connection.prepareCall("{call " + NO_SUCH_PROC + "()}")).willReturn(callableStatement);
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(NO_SUCH_PROC);
try {
assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(() ->
sproc.execute())
assertThatExceptionOfType(BadSqlGrammarException.class)
.isThrownBy(() -> sproc.execute())
.withCause(sqlException);
}
finally {
@@ -92,7 +89,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testUnnamedParameterHandling() throws Exception {
void unnamedParameterHandling() throws Exception {
final String MY_PROC = "my_proc";
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(MY_PROC);
// Shouldn't succeed in adding unnamed parameter
@@ -101,7 +98,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
void addInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithoutMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
adder.declareParameters(
@@ -117,7 +114,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
void addInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithoutMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
adder.declareParameters(
@@ -131,7 +128,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
void addInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
Number newId = adder.executeObject(Number.class, new MapSqlParameterSource()
@@ -143,7 +140,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
void addInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
Number newId = adder.executeObject(Number.class, 1103, 3);
@@ -153,7 +150,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
void addInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithoutMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
adder.declareParameters(
@@ -169,7 +166,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
void addInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithoutMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
adder.declareParameters(
@@ -183,7 +180,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testAddInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
void addInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
Number newId = adder.executeFunction(Number.class, new MapSqlParameterSource()
@@ -192,22 +189,20 @@ public class SimpleJdbcCallTests {
assertThat(newId.intValue()).isEqualTo(4);
verifyAddInvoiceWithMetaData(true);
verify(connection, atLeastOnce()).close();
}
@Test
public void testAddInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
void addInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
Number newId = adder.executeFunction(Number.class, 1103, 3);
assertThat(newId.intValue()).isEqualTo(4);
verifyAddInvoiceWithMetaData(true);
verify(connection, atLeastOnce()).close();
}
@Test
public void testCorrectFunctionStatement() throws Exception {
void correctFunctionStatement() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
adder.compile();
@@ -215,7 +210,7 @@ public class SimpleJdbcCallTests {
}
@Test
public void testCorrectFunctionStatementNamed() throws Exception {
void correctFunctionStatementNamed() throws Exception {
initializeAddInvoiceWithMetaData(true);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withFunctionName("add_invoice");
adder.compile();
@@ -223,13 +218,51 @@ public class SimpleJdbcCallTests {
}
@Test
public void testCorrectProcedureStatementNamed() throws Exception {
void correctProcedureStatementNamed() throws Exception {
initializeAddInvoiceWithMetaData(false);
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
adder.compile();
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}");
}
/**
* This test demonstrates that a CALL statement will still be generated if
* an exception occurs while retrieving metadata, potentially resulting in
* missing metadata and consequently a failure while invoking the stored
* procedure.
*/
@Test // gh-26486
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
ResultSet proceduresResultSet = mock(ResultSet.class);
ResultSet procedureColumnsResultSet = mock(ResultSet.class);
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
given(databaseMetaData.getUserName()).willReturn("ME");
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
given(proceduresResultSet.next()).willReturn(true, false);
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
given(procedureColumnsResultSet.next()).willReturn(true, true, true, false);
given(procedureColumnsResultSet.getString("COLUMN_NAME")).willReturn("amount", "custid", "newid");
given(procedureColumnsResultSet.getInt("DATA_TYPE"))
// Return a valid data type for the first 2 columns.
.willReturn(Types.INTEGER, Types.INTEGER)
// 3rd time, simulate an error while retrieving metadata.
.willThrow(new SQLException("error with DATA_TYPE for column 3"));
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
adder.compile();
// If an exception were not thrown for column 3, we would expect:
// {call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?)}");
verify(proceduresResultSet).close();
verify(procedureColumnsResultSet).close();
}
private void verifyStatement(SimpleJdbcCall adder, String expected) {
assertThat(adder.getCallString()).as("Incorrect call statement").isEqualTo(expected);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,9 @@ package org.springframework.jdbc.core.simple;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.HashMap;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collections;
import javax.sql.DataSource;
@@ -29,45 +31,44 @@ import org.junit.jupiter.api.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Mock object based tests for SimpleJdbcInsert.
* Mock object based tests for {@link SimpleJdbcInsert}.
*
* @author Thomas Risberg
* @author Sam Brannen
*/
public class SimpleJdbcInsertTests {
class SimpleJdbcInsertTests {
private Connection connection;
private final Connection connection = mock(Connection.class);
private DatabaseMetaData databaseMetaData;
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
private DataSource dataSource;
private final DataSource dataSource = mock(DataSource.class);
@BeforeEach
public void setUp() throws Exception {
connection = mock(Connection.class);
databaseMetaData = mock(DatabaseMetaData.class);
dataSource = mock(DataSource.class);
void setUp() throws Exception {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(dataSource.getConnection()).willReturn(connection);
}
@AfterEach
public void verifyClosed() throws Exception {
void verifyClosed() throws Exception {
verify(connection).close();
}
@Test
public void testNoSuchTable() throws Exception {
void noSuchTable() throws Exception {
ResultSet resultSet = mock(ResultSet.class);
given(resultSet.next()).willReturn(false);
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
given(databaseMetaData.getDatabaseProductVersion()).willReturn("1.0");
given(databaseMetaData.getUserName()).willReturn("me");
@@ -76,9 +77,65 @@ public class SimpleJdbcInsertTests {
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("x");
// Shouldn't succeed in inserting into table which doesn't exist
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
insert.execute(new HashMap<>()));
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(() -> insert.execute(Collections.emptyMap()))
.withMessageStartingWith("Unable to locate columns for table 'x' so an insert statement can't be generated");
verify(resultSet).close();
}
@Test // gh-26486
void retrieveColumnNamesFromMetadata() throws Exception {
ResultSet tableResultSet = mock(ResultSet.class);
given(tableResultSet.next()).willReturn(true, false);
given(databaseMetaData.getUserName()).willReturn("me");
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
ResultSet columnResultSet = mock(ResultSet.class);
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
given(columnResultSet.next()).willReturn(true, true, false);
given(columnResultSet.getString("COLUMN_NAME")).willReturn("col1", "col2");
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO me (col1, col2) VALUES(?, ?)");
verify(columnResultSet).close();
verify(tableResultSet).close();
}
@Test // gh-26486
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
ResultSet tableResultSet = mock(ResultSet.class);
given(tableResultSet.next()).willReturn(true, false);
given(databaseMetaData.getUserName()).willReturn("me");
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
ResultSet columnResultSet = mock(ResultSet.class);
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
// true, true, false --> simulates processing of two columns
given(columnResultSet.next()).willReturn(true, true, false);
given(columnResultSet.getString("COLUMN_NAME"))
// Return a column name the first time.
.willReturn("col1")
// Second time, simulate an error while retrieving metadata.
.willThrow(new SQLException("error with col2"));
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(insert::compile)
.withMessage("Unable to locate columns for table 'me' so an insert statement can't be generated. " +
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().");
verify(columnResultSet).close();
verify(tableResultSet).close();
}
}
@@ -19,6 +19,7 @@ package org.springframework.messaging.simp.broker;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
@@ -58,6 +59,9 @@ public abstract class AbstractBrokerMessageHandler
private final Collection<String> destinationPrefixes;
@Nullable
private Predicate<String> userDestinationPredicate;
private boolean preservePublishOrder = false;
@Nullable
@@ -135,6 +139,21 @@ public abstract class AbstractBrokerMessageHandler
return this.destinationPrefixes;
}
/**
* Configure a Predicate to identify messages with a user destination. When
* no {@link #getDestinationPrefixes() destination prefixes} are configured,
* this helps to recognize and skip user destination messages that need to
* be pre-processed by the
* {@link org.springframework.messaging.simp.user.UserDestinationMessageHandler}
* before they reach the broker.
* @param predicate the predicate to identify user messages with a non-null
* destination as messages with a user destinations.
* @since 5.3.4
*/
public void setUserDestinationPredicate(@Nullable Predicate<String> predicate) {
this.userDestinationPredicate = predicate;
}
/**
* Whether the client must receive messages in the order of publication.
* <p>By default messages sent to the {@code "clientOutboundChannel"} may
@@ -265,10 +284,27 @@ public abstract class AbstractBrokerMessageHandler
protected abstract void handleMessageInternal(Message<?> message);
/**
* Whether a message with the given destination should be processed. This is
* the case if one of the following conditions is true:
* <ol>
* <li>The destination starts with one of the configured
* {@link #getDestinationPrefixes() destination prefixes}.
* <li>No prefixes are configured and the destination isn't matched
* by the {@link #setUserDestinationPredicate(Predicate)
* userDestinationPredicate}.
* <li>The message has no destination.
* </ol>
* @param destination the destination to check
* @return whether to process (true) or skip (false) the destination
*/
protected boolean checkDestinationPrefix(@Nullable String destination) {
if (destination == null || CollectionUtils.isEmpty(this.destinationPrefixes)) {
if (destination == null) {
return true;
}
if (CollectionUtils.isEmpty(this.destinationPrefixes)) {
return !isUserDestination(destination);
}
for (String prefix : this.destinationPrefixes) {
if (destination.startsWith(prefix)) {
return true;
@@ -277,6 +313,10 @@ public abstract class AbstractBrokerMessageHandler
return false;
}
private boolean isUserDestination(String destination) {
return (this.userDestinationPredicate != null && this.userDestinationPredicate.test(destination));
}
protected void publishBrokerAvailableEvent() {
boolean shouldPublish = this.brokerAvailable.compareAndSet(false, true);
if (this.eventPublisher != null && shouldPublish) {
@@ -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.
@@ -64,6 +64,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@@ -71,18 +72,18 @@ import org.springframework.validation.Validator;
* Provides essential configuration for handling messages with simple messaging
* protocols such as STOMP.
*
* <p>{@link #clientInboundChannel(TaskExecutor)} and {@link #clientOutboundChannel(TaskExecutor)} deliver
* <p>{@link #clientInboundChannel} and {@link #clientOutboundChannel} deliver
* messages to and from remote clients to several message handlers such as the
* following.
* <ul>
* <li>{@link #simpAnnotationMethodMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, SimpMessagingTemplate, CompositeMessageConverter)}</li>
* <li>{@link #simpleBrokerMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, UserDestinationResolver)}</li>
* <li>{@link #stompBrokerRelayMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, UserDestinationMessageHandler, MessageHandler, UserDestinationResolver)}</li>
* <li>{@link #userDestinationMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, UserDestinationResolver)}</li>
* <li>{@link #simpAnnotationMethodMessageHandler}</li>
* <li>{@link #simpleBrokerMessageHandler}</li>
* <li>{@link #stompBrokerRelayMessageHandler}</li>
* <li>{@link #userDestinationMessageHandler}</li>
* </ul>
*
* <p>{@link #brokerChannel(AbstractSubscribableChannel, AbstractSubscribableChannel, TaskExecutor)} delivers messages from within the application to the
* the respective message handlers. {@link #brokerMessagingTemplate(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, CompositeMessageConverter)} can be injected
* <p>{@link #brokerChannel} delivers messages from within the application to the
* the respective message handlers. {@link #brokerMessagingTemplate} can be injected
* into any application component to send messages.
*
* <p>Subclasses are responsible for the parts of the configuration that feed messages
@@ -222,22 +223,26 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public AbstractSubscribableChannel brokerChannel(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, TaskExecutor brokerChannelExecutor) {
ChannelRegistration reg = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getBrokerChannelRegistration();
ExecutorSubscribableChannel channel = (reg.hasTaskExecutor() ?
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
ChannelRegistration registration = registry.getBrokerChannelRegistration();
ExecutorSubscribableChannel channel = (registration.hasTaskExecutor() ?
new ExecutorSubscribableChannel(brokerChannelExecutor) : new ExecutorSubscribableChannel());
reg.interceptors(new ImmutableMessageChannelInterceptor());
registration.interceptors(new ImmutableMessageChannelInterceptor());
channel.setLogger(SimpLogging.forLog(channel.getLogger()));
channel.setInterceptors(reg.getInterceptors());
channel.setInterceptors(registration.getInterceptors());
return channel;
}
@Bean
public TaskExecutor brokerChannelExecutor(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
ChannelRegistration reg = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getBrokerChannelRegistration();
public TaskExecutor brokerChannelExecutor(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
ChannelRegistration registration = registry.getBrokerChannelRegistration();
ThreadPoolTaskExecutor executor;
if (reg.hasTaskExecutor()) {
executor = reg.taskExecutor().getTaskExecutor();
if (registration.hasTaskExecutor()) {
executor = registration.taskExecutor().getTaskExecutor();
}
else {
// Should never be used
@@ -254,8 +259,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
* An accessor for the {@link MessageBrokerRegistry} that ensures its one-time creation
* and initialization through {@link #configureMessageBroker(MessageBrokerRegistry)}.
*/
protected final MessageBrokerRegistry getBrokerRegistry(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
protected final MessageBrokerRegistry getBrokerRegistry(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
if (this.brokerRegistry == null) {
MessageBrokerRegistry registry = new MessageBrokerRegistry(clientInboundChannel, clientOutboundChannel);
configureMessageBroker(registry);
@@ -276,8 +282,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
* configuration classes.
*/
@Nullable
public final PathMatcher getPathMatcher(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
public final PathMatcher getPathMatcher(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
return getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getPathMatcher();
}
@@ -285,8 +292,10 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
SimpMessagingTemplate brokerMessagingTemplate, CompositeMessageConverter brokerMessageConverter) {
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler(clientInboundChannel,
clientOutboundChannel, brokerMessagingTemplate);
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler(
clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
handler.setDestinationPrefixes(brokerRegistry.getApplicationDestinationPrefixes());
handler.setMessageConverter(brokerMessageConverter);
@@ -316,7 +325,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
SimpMessagingTemplate brokerMessagingTemplate) {
return new SimpAnnotationMethodMessageHandler(clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
return new SimpAnnotationMethodMessageHandler(
clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
}
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
@@ -327,22 +338,30 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
@Nullable
public AbstractBrokerMessageHandler simpleBrokerMessageHandler(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
UserDestinationResolver userDestinationResolver) {
SimpleBrokerMessageHandler handler = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getSimpleBroker(brokerChannel);
public AbstractBrokerMessageHandler simpleBrokerMessageHandler(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
AbstractSubscribableChannel brokerChannel, UserDestinationResolver userDestinationResolver) {
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
SimpleBrokerMessageHandler handler = registry.getSimpleBroker(brokerChannel);
if (handler == null) {
return null;
}
updateUserDestinationResolver(handler, userDestinationResolver);
updateUserDestinationResolver(handler, userDestinationResolver, registry.getUserDestinationPrefix());
return handler;
}
private void updateUserDestinationResolver(AbstractBrokerMessageHandler handler, UserDestinationResolver userDestinationResolver) {
private void updateUserDestinationResolver(
AbstractBrokerMessageHandler handler, UserDestinationResolver userDestinationResolver,
@Nullable String userDestinationPrefix) {
Collection<String> prefixes = handler.getDestinationPrefixes();
if (!prefixes.isEmpty() && !prefixes.iterator().next().startsWith("/")) {
((DefaultUserDestinationResolver) userDestinationResolver).setRemoveLeadingSlash(true);
}
if (StringUtils.hasText(userDestinationPrefix)) {
handler.setUserDestinationPredicate(destination -> destination.startsWith(userDestinationPrefix));
}
}
@Bean
@@ -351,32 +370,36 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
UserDestinationMessageHandler userDestinationMessageHandler, @Nullable MessageHandler userRegistryMessageHandler,
UserDestinationResolver userDestinationResolver) {
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
StompBrokerRelayMessageHandler handler = brokerRegistry.getStompBrokerRelay(brokerChannel);
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
StompBrokerRelayMessageHandler handler = registry.getStompBrokerRelay(brokerChannel);
if (handler == null) {
return null;
}
Map<String, MessageHandler> subscriptions = new HashMap<>(4);
String destination = brokerRegistry.getUserDestinationBroadcast();
String destination = registry.getUserDestinationBroadcast();
if (destination != null) {
subscriptions.put(destination, userDestinationMessageHandler);
}
destination = brokerRegistry.getUserRegistryBroadcast();
destination = registry.getUserRegistryBroadcast();
if (destination != null) {
subscriptions.put(destination, userRegistryMessageHandler);
}
handler.setSystemSubscriptions(subscriptions);
updateUserDestinationResolver(handler, userDestinationResolver);
updateUserDestinationResolver(handler, userDestinationResolver, registry.getUserDestinationPrefix());
return handler;
}
@Bean
public UserDestinationMessageHandler userDestinationMessageHandler(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
UserDestinationResolver userDestinationResolver) {
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(clientInboundChannel,
brokerChannel, userDestinationResolver);
String destination = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationBroadcast();
public UserDestinationMessageHandler userDestinationMessageHandler(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
AbstractSubscribableChannel brokerChannel, UserDestinationResolver userDestinationResolver) {
UserDestinationMessageHandler handler =
new UserDestinationMessageHandler(clientInboundChannel, brokerChannel, userDestinationResolver);
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
String destination = registry.getUserDestinationBroadcast();
if (destination != null) {
handler.setBroadcastDestination(destination);
}
@@ -385,9 +408,11 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
@Nullable
public MessageHandler userRegistryMessageHandler(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, SimpUserRegistry userRegistry,
SimpMessagingTemplate brokerMessagingTemplate, TaskScheduler messageBrokerTaskScheduler) {
public MessageHandler userRegistryMessageHandler(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
SimpUserRegistry userRegistry, SimpMessagingTemplate brokerMessagingTemplate,
TaskScheduler messageBrokerTaskScheduler) {
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
if (brokerRegistry.getUserRegistryBroadcast() == null) {
return null;
@@ -409,11 +434,13 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
@Bean
public SimpMessagingTemplate brokerMessagingTemplate(AbstractSubscribableChannel brokerChannel,
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
CompositeMessageConverter brokerMessageConverter) {
public SimpMessagingTemplate brokerMessagingTemplate(
AbstractSubscribableChannel brokerChannel, AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, CompositeMessageConverter brokerMessageConverter) {
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel);
String prefix = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationPrefix();
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
String prefix = registry.getUserDestinationPrefix();
if (prefix != null) {
template.setUserDestinationPrefix(prefix);
}
@@ -463,10 +490,13 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
@Bean
public UserDestinationResolver userDestinationResolver(SimpUserRegistry userRegistry,
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
public UserDestinationResolver userDestinationResolver(
SimpUserRegistry userRegistry, AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userRegistry);
String prefix = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationPrefix();
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
String prefix = registry.getUserDestinationPrefix();
if (prefix != null) {
resolver.setUserDestinationPrefix(prefix);
}
@@ -475,15 +505,16 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
@SuppressWarnings("deprecation")
public SimpUserRegistry userRegistry(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
SimpUserRegistry registry = createLocalUserRegistry();
public SimpUserRegistry userRegistry(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
SimpUserRegistry userRegistry = createLocalUserRegistry();
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
if (registry == null) {
registry = createLocalUserRegistry(brokerRegistry.getUserRegistryOrder());
if (userRegistry == null) {
userRegistry = createLocalUserRegistry(brokerRegistry.getUserRegistryOrder());
}
boolean broadcast = brokerRegistry.getUserRegistryBroadcast() != null;
return (broadcast ? new MultiServerUserRegistry(registry) : registry);
return (broadcast ? new MultiServerUserRegistry(userRegistry) : userRegistry);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,10 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -74,28 +77,25 @@ public class BrokerMessageHandlerTests {
@Test
public void publishBrokerAvailableEvent() {
assertThat(this.handler.isBrokerAvailable()).isFalse();
assertThat(this.handler.availabilityEvents).isEqualTo(Collections.emptyList());
this.handler.publishBrokerAvailableEvent();
assertThat(this.handler.isBrokerAvailable()).isTrue();
assertThat(this.handler.availabilityEvents).isEqualTo(Arrays.asList(true));
assertThat(this.handler.availabilityEvents).isEqualTo(Collections.singletonList(true));
}
@Test
public void publishBrokerAvailableEventWhenAlreadyAvailable() {
this.handler.publishBrokerAvailableEvent();
this.handler.publishBrokerAvailableEvent();
assertThat(this.handler.availabilityEvents).isEqualTo(Arrays.asList(true));
assertThat(this.handler.availabilityEvents).isEqualTo(Collections.singletonList(true));
}
@Test
public void publishBrokerUnavailableEvent() {
this.handler.publishBrokerAvailableEvent();
assertThat(this.handler.isBrokerAvailable()).isTrue();
@@ -107,7 +107,6 @@ public class BrokerMessageHandlerTests {
@Test
public void publishBrokerUnavailableEventWhenAlreadyUnavailable() {
this.handler.publishBrokerAvailableEvent();
this.handler.publishBrokerUnavailableEvent();
this.handler.publishBrokerUnavailableEvent();
@@ -115,6 +114,47 @@ public class BrokerMessageHandlerTests {
assertThat(this.handler.availabilityEvents).isEqualTo(Arrays.asList(true, false));
}
@Test
public void checkDestination() {
TestBrokerMessageHandler theHandler = new TestBrokerMessageHandler("/topic");
theHandler.start();
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
accessor.setLeaveMutable(true);
accessor.setDestination("/topic/foo");
theHandler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
accessor.setDestination("/app/foo");
theHandler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
accessor.setDestination(null);
theHandler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
List<Message<?>> list = theHandler.messages;
assertThat(list).hasSize(2);
assertThat(list.get(0).getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER)).isEqualTo("/topic/foo");
assertThat(list.get(1).getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER)).isNull();
}
@Test
public void checkDestinationWithoutConfiguredPrefixes() {
this.handler.setUserDestinationPredicate(destination -> destination.startsWith("/user/"));
this.handler.start();
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
accessor.setLeaveMutable(true);
accessor.setDestination("/user/1/foo");
this.handler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
accessor.setDestination("/foo");
this.handler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
List<Message<?>> list = this.handler.messages;
assertThat(list).hasSize(1);
assertThat(list.get(0).getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER)).isEqualTo("/foo");
}
private static class TestBrokerMessageHandler extends AbstractBrokerMessageHandler
implements ApplicationEventPublisher {
@@ -124,14 +164,19 @@ public class BrokerMessageHandlerTests {
private final List<Boolean> availabilityEvents = new ArrayList<>();
private TestBrokerMessageHandler() {
super(mock(SubscribableChannel.class), mock(MessageChannel.class), mock(SubscribableChannel.class));
TestBrokerMessageHandler(String... destinationPrefixes) {
super(mock(SubscribableChannel.class), mock(MessageChannel.class),
mock(SubscribableChannel.class), Arrays.asList(destinationPrefixes));
setApplicationEventPublisher(this);
}
@Override
protected void handleMessageInternal(Message<?> message) {
this.messages.add(message);
String destination = (String) message.getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER);
if (checkDestinationPrefix(destination)) {
this.messages.add(message);
}
}
@Override
@@ -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.
@@ -21,14 +21,12 @@ import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Internal helper class that serves as value holder for request headers.
* Internal helper class that serves as a value holder for request headers.
*
* @author Juergen Hoeller
* @author Rick Evans
@@ -39,30 +37,30 @@ class HeaderValueHolder {
private final List<Object> values = new LinkedList<>();
public void setValue(@Nullable Object value) {
void setValue(@Nullable Object value) {
this.values.clear();
if (value != null) {
this.values.add(value);
}
}
public void addValue(Object value) {
void addValue(Object value) {
this.values.add(value);
}
public void addValues(Collection<?> values) {
void addValues(Collection<?> values) {
this.values.addAll(values);
}
public void addValueArray(Object values) {
void addValueArray(Object values) {
CollectionUtils.mergeArrayIntoCollection(values, this.values);
}
public List<Object> getValues() {
List<Object> getValues() {
return Collections.unmodifiableList(this.values);
}
public List<String> getStringValues() {
List<String> getStringValues() {
List<String> stringList = new ArrayList<>(this.values.size());
for (Object value : this.values) {
stringList.add(value.toString());
@@ -71,12 +69,12 @@ class HeaderValueHolder {
}
@Nullable
public Object getValue() {
Object getValue() {
return (!this.values.isEmpty() ? this.values.get(0) : null);
}
@Nullable
public String getStringValue() {
String getStringValue() {
return (!this.values.isEmpty() ? String.valueOf(this.values.get(0)) : null);
}
@@ -85,25 +83,4 @@ class HeaderValueHolder {
return this.values.toString();
}
/**
* Find a HeaderValueHolder by name, ignoring casing.
* @param headers the Map of header names to HeaderValueHolders
* @param name the name of the desired header
* @return the corresponding HeaderValueHolder, or {@code null} if none found
* @deprecated as of 5.1.10 in favor of using
* {@link org.springframework.util.LinkedCaseInsensitiveMap}.
*/
@Nullable
@Deprecated
public static HeaderValueHolder getByName(Map<String, HeaderValueHolder> headers, String name) {
Assert.notNull(name, "Header name must not be null");
for (Map.Entry<String, HeaderValueHolder> entry : headers.entrySet()) {
if (entry.getKey().equalsIgnoreCase(name)) {
return entry.getValue();
}
}
return null;
}
}
@@ -0,0 +1,81 @@
/*
* 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.mock.web;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.MappingMatch;
import org.springframework.lang.Nullable;
/**
* Mock implementation of {@link HttpServletMapping}.
*
* @author Rossen Stoyanchev
* @since 5.3.4
*/
public class MockHttpServletMapping implements HttpServletMapping {
private final String matchValue;
private final String pattern;
private final String servletName;
@Nullable
private final MappingMatch mappingMatch;
public MockHttpServletMapping(
String matchValue, String pattern, String servletName, @Nullable MappingMatch match) {
this.matchValue = matchValue;
this.pattern = pattern;
this.servletName = servletName;
this.mappingMatch = match;
}
@Override
public String getMatchValue() {
return this.matchValue;
}
@Override
public String getPattern() {
return this.pattern;
}
@Override
public String getServletName() {
return this.servletName;
}
@Override
@Nullable
public MappingMatch getMappingMatch() {
return this.mappingMatch;
}
@Override
public String toString() {
return "MockHttpServletMapping [matchValue=\"" + this.matchValue + "\", " +
"pattern=\"" + this.pattern + "\", servletName=\"" + this.servletName + "\", " +
"mappingMatch=" + this.mappingMatch + "]";
}
}
@@ -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 javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@@ -274,6 +275,8 @@ public class MockHttpServletRequest implements HttpServletRequest {
private final MultiValueMap<String, Part> parts = new LinkedMultiValueMap<>();
private HttpServletMapping httpServletMapping = new MockHttpServletMapping("", "", "", null);
// ---------------------------------------------------------------------
// Constructors
@@ -1390,6 +1393,15 @@ public class MockHttpServletRequest implements HttpServletRequest {
return result;
}
public void setHttpServletMapping(HttpServletMapping httpServletMapping) {
this.httpServletMapping = httpServletMapping;
}
@Override
public HttpServletMapping getHttpServletMapping() {
return this.httpServletMapping;
}
@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
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.
@@ -344,6 +344,9 @@ public class MockHttpServletResponse implements HttpServletResponse {
@Override
public void setLocale(Locale locale) {
if (locale == null) {
return;
}
this.locale = locale;
doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, locale.toLanguageTag(), true);
}
@@ -421,7 +424,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
@Override
public boolean containsHeader(String name) {
return (this.headers.get(name) != null);
return this.headers.containsKey(name);
}
/**
@@ -594,12 +597,12 @@ public class MockHttpServletResponse implements HttpServletResponse {
}
@Override
public void setHeader(String name, String value) {
public void setHeader(String name, @Nullable String value) {
setHeaderValue(name, value);
}
@Override
public void addHeader(String name, String value) {
public void addHeader(String name, @Nullable String value) {
addHeaderValue(name, value);
}
@@ -613,7 +616,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
addHeaderValue(name, value);
}
private void setHeaderValue(String name, Object value) {
private void setHeaderValue(String name, @Nullable Object value) {
if (value == null) {
return;
}
boolean replaceHeader = true;
if (setSpecialHeader(name, value, replaceHeader)) {
return;
@@ -621,7 +627,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
doAddHeaderValue(name, value, replaceHeader);
}
private void addHeaderValue(String name, Object value) {
private void addHeaderValue(String name, @Nullable Object value) {
if (value == null) {
return;
}
boolean replaceHeader = false;
if (setSpecialHeader(name, value, replaceHeader)) {
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.
@@ -17,6 +17,7 @@
package org.springframework.mock.web;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
@@ -33,6 +34,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
@@ -155,15 +157,28 @@ public class MockMultipartHttpServletRequest extends MockHttpServletRequest impl
@Override
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
String contentType = getMultipartContentType(paramOrFileName);
if (contentType != null) {
MultipartFile file = getFile(paramOrFileName);
if (file != null) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, contentType);
if (file.getContentType() != null) {
headers.add(HttpHeaders.CONTENT_TYPE, file.getContentType());
}
return headers;
}
else {
return null;
try {
Part part = getPart(paramOrFileName);
if (part != null) {
HttpHeaders headers = new HttpHeaders();
for (String headerName : part.getHeaderNames()) {
headers.put(headerName, new ArrayList<>(part.getHeaders(headerName)));
}
return headers;
}
}
catch (Throwable ex) {
throw new MultipartException("Could not access multipart servlet request", ex);
}
return null;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,7 +67,6 @@ public abstract class ApplicationEventsHolder {
* @throws IllegalStateException if an instance of {@code ApplicationEvents}
* has not been registered for the current thread
*/
@Nullable
public static ApplicationEvents getRequiredApplicationEvents() {
ApplicationEvents events = applicationEvents.get();
Assert.state(events != null, "Failed to retrieve ApplicationEvents for the current thread. " +
@@ -46,6 +46,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -258,7 +259,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
else {
DataSource dataSourceFromTxMgr = getDataSourceFromTransactionManager(txMgr);
// Ensure user configured an appropriate DataSource/TransactionManager pair.
if (dataSource != null && dataSourceFromTxMgr != null && !dataSource.equals(dataSourceFromTxMgr)) {
if (dataSource != null && dataSourceFromTxMgr != null && !sameDataSource(dataSource, dataSourceFromTxMgr)) {
throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: " +
"the configured DataSource [%s] (named '%s') is not the one associated with " +
"transaction manager [%s] (named '%s').", testContext, dataSource.getClass().getName(),
@@ -292,6 +293,17 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
return populator;
}
/**
* Determine if the two data sources are effectively the same, unwrapping
* proxies as necessary to compare the target instances.
* @since 5.3.4
* @see TransactionSynchronizationUtils#unwrapResourceIfNecessary(Object)
*/
private static boolean sameDataSource(DataSource ds1, DataSource ds2) {
return TransactionSynchronizationUtils.unwrapResourceIfNecessary(ds1)
.equals(TransactionSynchronizationUtils.unwrapResourceIfNecessary(ds2));
}
@Nullable
private DataSource getDataSourceFromTransactionManager(PlatformTransactionManager transactionManager) {
try {
@@ -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,6 +18,8 @@ package org.springframework.test.web.client;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -144,25 +146,42 @@ public abstract class AbstractRequestExpectationManager implements RequestExpect
@Override
public void verify() {
if (this.expectations.isEmpty()) {
return;
}
int count = 0;
for (RequestExpectation expectation : this.expectations) {
if (!expectation.isSatisfied()) {
count++;
}
}
int count = verifyInternal();
if (count > 0) {
String message = "Further request(s) expected leaving " + count + " unsatisfied expectation(s).\n";
throw new AssertionError(message + getRequestDetails());
}
}
@Override
public void verify(Duration timeout) {
Instant endTime = Instant.now().plus(timeout);
do {
if (verifyInternal() == 0) {
return;
}
}
while (Instant.now().isBefore(endTime));
verify();
}
private int verifyInternal() {
if (this.expectations.isEmpty()) {
return 0;
}
if (!this.requestFailures.isEmpty()) {
throw new AssertionError("Some requests did not execute successfully.\n" +
this.requestFailures.entrySet().stream()
.map(entry -> "Failed request:\n" + entry.getKey() + "\n" + entry.getValue())
.collect(Collectors.joining("\n", "\n", "")));
}
int count = 0;
for (RequestExpectation expectation : this.expectations) {
if (!expectation.isSatisfied()) {
count++;
}
}
return count;
}
/**
@@ -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,6 +18,7 @@ package org.springframework.test.web.client;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
@@ -110,12 +111,25 @@ public final class MockRestServiceServer {
/**
* Verify that all expected requests set up via
* {@link #expect(RequestMatcher)} were indeed performed.
* @throws AssertionError when some expectations were not met
* @throws AssertionError if not all expectations are met
*/
public void verify() {
this.expectationManager.verify();
}
/**
* Variant of {@link #verify()} that waits for up to the specified time for
* all expectations to be fulfilled. This can be useful for tests that
* involve asynchronous requests.
* @param timeout how long to wait for all expecations to be met
* @throws AssertionError if not all expectations are met by the specified
* timeout, or if any expectation fails at any time before that.
* @since 5.3.4
*/
public void verify(Duration timeout) {
this.expectationManager.verify(timeout);
}
/**
* Reset the internal state removing all expectations and recorded requests.
*/
@@ -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.test.web.client;
import java.io.IOException;
import java.time.Duration;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
@@ -52,11 +53,22 @@ public interface RequestExpectationManager {
/**
* Verify that all expectations have been met.
* <p>This is a delegate for {@link MockRestServiceServer#verify()}.
* @throws AssertionError when some expectations were not met
* @throws AssertionError if not all expectations are met
* @see MockRestServiceServer#verify()
*/
void verify();
/**
* Variant of {@link #verify()} that waits for up to the specified time for
* all expectations to be fulfilled. This can be useful for tests that
* involve asynchronous requests.
* @param timeout how long to wait for all expecations to be met
* @throws AssertionError if not all expectations are met by the specified
* timeout, or if any expectation fails at any time before that.
* @since 5.3.4
*/
void verify(Duration timeout);
/**
* Reset the internal state removing all expectations and recorded requests.
* <p>This is a delegate for {@link MockRestServiceServer#reset()}.
@@ -75,5 +87,4 @@ public interface RequestExpectationManager {
* @throws IOException in case of any validation errors
*/
ClientHttpResponse validateRequest(ClientHttpRequest request) 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.
@@ -17,6 +17,7 @@
package org.springframework.test.web.servlet.request;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.Charset;
@@ -38,7 +39,6 @@ import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
/**
* Default builder for {@link MockMultipartHttpServletRequest}.
@@ -144,47 +144,40 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque
@Override
protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(servletContext);
Charset defaultCharset = (request.getCharacterEncoding() != null ?
Charset.forName(request.getCharacterEncoding()) : StandardCharsets.UTF_8);
this.files.forEach(request::addFile);
this.parts.values().stream().flatMap(Collection::stream).forEach(part -> {
request.addPart(part);
try {
MultipartFile file = asMultipartFile(part);
if (file != null) {
request.addFile(file);
return;
String name = part.getName();
String filename = part.getSubmittedFileName();
InputStream is = part.getInputStream();
if (filename != null) {
request.addFile(new MockMultipartFile(name, filename, part.getContentType(), is));
}
String value = toParameterValue(part);
if (value != null) {
request.addParameter(part.getName(), toParameterValue(part));
else {
InputStreamReader reader = new InputStreamReader(is, getCharsetOrDefault(part, defaultCharset));
String value = FileCopyUtils.copyToString(reader);
request.addParameter(part.getName(), value);
}
}
catch (IOException ex) {
throw new IllegalStateException("Failed to read content for part " + part.getName(), ex);
}
});
return request;
}
@Nullable
private MultipartFile asMultipartFile(Part part) throws IOException {
String name = part.getName();
String filename = part.getSubmittedFileName();
if (filename != null) {
return new MockMultipartFile(name, filename, part.getContentType(), part.getInputStream());
private Charset getCharsetOrDefault(Part part, Charset defaultCharset) {
if (part.getContentType() != null) {
MediaType mediaType = MediaType.parseMediaType(part.getContentType());
if (mediaType.getCharset() != null) {
return mediaType.getCharset();
}
}
return null;
return defaultCharset;
}
@Nullable
private String toParameterValue(Part part) throws IOException {
String rawType = part.getContentType();
MediaType mediaType = (rawType != null ? MediaType.parseMediaType(rawType) : MediaType.TEXT_PLAIN);
if (!mediaType.isCompatibleWith(MediaType.TEXT_PLAIN)) {
return null;
}
Charset charset = (mediaType.getCharset() != null ? mediaType.getCharset() : StandardCharsets.UTF_8);
InputStreamReader reader = new InputStreamReader(part.getInputStream(), charset);
return FileCopyUtils.copyToString(reader);
}
}
@@ -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.
@@ -26,6 +26,8 @@ import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.web.util.WebUtils;
@@ -57,6 +59,39 @@ class MockHttpServletResponseTests {
private MockHttpServletResponse response = new MockHttpServletResponse();
@ParameterizedTest // gh-26488
@ValueSource(strings = {
CONTENT_TYPE,
CONTENT_LENGTH,
CONTENT_LANGUAGE,
SET_COOKIE,
"enigma"
})
void addHeaderWithNullValue(String headerName) {
response.addHeader(headerName, null);
assertThat(response.containsHeader(headerName)).isFalse();
}
@ParameterizedTest // gh-26488
@ValueSource(strings = {
CONTENT_TYPE,
CONTENT_LENGTH,
CONTENT_LANGUAGE,
SET_COOKIE,
"enigma"
})
void setHeaderWithNullValue(String headerName) {
response.setHeader(headerName, null);
assertThat(response.containsHeader(headerName)).isFalse();
}
@Test // gh-26493
void setLocaleWithNullValue() {
assertThat(response.getLocale()).isEqualTo(Locale.getDefault());
response.setLocale(null);
assertThat(response.getLocale()).isEqualTo(Locale.getDefault());
}
@Test
void setContentType() {
String contentType = "test/plain";
@@ -0,0 +1,128 @@
/*
* 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.test.context.jdbc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.InfrastructureProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Transactional integration tests for {@link Sql @Sql} support when the
* {@link DataSource} is wrapped in a proxy that implements
* {@link InfrastructureProxy}.
*
* @author Sam Brannen
* @since 5.3.4
*/
@SpringJUnitConfig
@DirtiesContext
class InfrastructureProxyTransactionalSqlScriptsTests extends AbstractTransactionalTests {
@BeforeEach
void preconditions(@Autowired DataSource dataSource, @Autowired DataSourceTransactionManager transactionManager) {
assertThat(dataSource).isNotEqualTo(transactionManager.getDataSource());
assertThat(transactionManager.getDataSource()).isNotEqualTo(dataSource);
assertThat(transactionManager.getDataSource()).isInstanceOf(InfrastructureProxy.class);
}
@Test
@Sql({ "schema.sql", "data.sql", "data-add-dogbert.sql" })
void methodLevelScripts() {
assertNumUsers(2);
}
@Configuration
static class DatabaseConfig {
@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(wrapDataSource(dataSource));
}
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()//
.setName("empty-sql-scripts-test-db")//
.build();
}
}
private static DataSource wrapDataSource(DataSource dataSource) {
return (DataSource) Proxy.newProxyInstance(
InfrastructureProxyTransactionalSqlScriptsTests.class.getClassLoader(),
new Class<?>[] { DataSource.class, InfrastructureProxy.class },
new DataSourceInvocationHandler(dataSource));
}
private static class DataSourceInvocationHandler implements InvocationHandler {
private final DataSource dataSource;
DataSourceInvocationHandler(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "equals":
return (proxy == args[0]);
case "hashCode":
return System.identityHashCode(proxy);
case "getWrappedObject":
return this.dataSource;
default:
try {
return method.invoke(this.dataSource, args);
}
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.
@@ -74,10 +74,18 @@ class ParallelApplicationEventsIntegrationTests {
assertThat(payloads).hasSize(10);
assertThat(testNames).hasSize(10);
// There are probably 10 different thread names on a developer's machine,
// but we really just want to assert that at least two different threads
// were used, since the CI server seems to have fewer threads available.
assertThat(threadNames).hasSizeGreaterThanOrEqualTo(2);
int availableProcessors = Runtime.getRuntime().availableProcessors();
// Skip the following assertion entirely if too few processors are available
// to the current JVM.
if (availableProcessors >= 6) {
// There are probably 10 different thread names on a developer's machine,
// but we really just want to assert that at least two different threads
// were used, since the CI server often has fewer threads available.
assertThat(threadNames)
.as("number of threads used with " + availableProcessors + " available processors")
.hasSizeGreaterThanOrEqualTo(2);
}
}
@@ -17,6 +17,7 @@
package org.springframework.test.web.client;
import java.net.SocketException;
import java.time.Duration;
import org.junit.jupiter.api.Test;
@@ -24,6 +25,7 @@ import org.springframework.test.web.client.MockRestServiceServer.MockRestService
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.test.web.client.ExpectedCount.once;
@@ -171,4 +173,27 @@ public class MockRestServiceServerTests {
.withMessageStartingWith("Some requests did not execute successfully");
}
@Test
public void verifyWithTimeout() {
MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(this.restTemplate);
MockRestServiceServer server1 = builder.build();
server1.expect(requestTo("/foo")).andRespond(withSuccess());
server1.expect(requestTo("/bar")).andRespond(withSuccess());
this.restTemplate.getForObject("/foo", Void.class);
assertThatThrownBy(() -> server1.verify(Duration.ofMillis(100))).hasMessage(
"Further request(s) expected leaving 1 unsatisfied expectation(s).\n" +
"1 request(s) executed:\n" +
"GET /foo, headers: [Accept:\"application/json, application/*+json\"]\n");
MockRestServiceServer server2 = builder.build();
server2.expect(requestTo("/foo")).andRespond(withSuccess());
server2.expect(requestTo("/bar")).andRespond(withSuccess());
this.restTemplate.getForObject("/foo", Void.class);
this.restTemplate.getForObject("/bar", Void.class);
server2.verify(Duration.ofMillis(100));
}
}
@@ -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.
@@ -50,7 +50,7 @@ public class MockMultipartHttpServletRequestBuilderTests {
assertThat(mockRequest.getParts()).extracting(Part::getName).containsExactly("name");
}
@Test // gh-26261
@Test // gh-26261, gh-26400
void addFileWithoutFilename() throws Exception {
MockPart jsonPart = new MockPart("data", "{\"node\":\"node\"}".getBytes(UTF_8));
jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
@@ -62,7 +62,8 @@ public class MockMultipartHttpServletRequestBuilderTests {
.buildRequest(new MockServletContext());
assertThat(mockRequest.getFileMap()).containsOnlyKeys("file");
assertThat(mockRequest.getParameterMap()).isEmpty();
assertThat(mockRequest.getParameterMap()).hasSize(1);
assertThat(mockRequest.getParameter("data")).isEqualTo("{\"node\":\"node\"}");
assertThat(mockRequest.getParts()).extracting(Part::getName).containsExactly("data");
}
@@ -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.
@@ -45,10 +45,10 @@ public abstract class TransactionSynchronizationUtils {
/**
* Check whether the given resource transaction managers refers to the given
* Check whether the given resource transaction manager refers to the given
* (underlying) resource factory.
* @see ResourceTransactionManager#getResourceFactory()
* @see org.springframework.core.InfrastructureProxy#getWrappedObject()
* @see InfrastructureProxy#getWrappedObject()
*/
public static boolean sameResourceFactory(ResourceTransactionManager tm, Object resourceFactory) {
return unwrapResourceIfNecessary(tm.getResourceFactory()).equals(unwrapResourceIfNecessary(resourceFactory));
@@ -57,9 +57,11 @@ public abstract class TransactionSynchronizationUtils {
/**
* Unwrap the given resource handle if necessary; otherwise return
* the given handle as-is.
* @see org.springframework.core.InfrastructureProxy#getWrappedObject()
* @since 5.3.4
* @see InfrastructureProxy#getWrappedObject()
* @see ScopedProxyUnwrapper#unwrapIfNecessary(Object)
*/
static Object unwrapResourceIfNecessary(Object resource) {
public static Object unwrapResourceIfNecessary(Object resource) {
Assert.notNull(resource, "Resource must not be null");
Object resourceRef = resource;
// unwrap infrastructure proxy
@@ -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.
@@ -22,7 +22,10 @@ import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -45,6 +48,9 @@ import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
*/
public final class ContentDisposition {
private final static Pattern BASE64_ENCODED_PATTERN =
Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?B\\?([+/0-9a-zA-Z]+=*)\\?=");
private static final String INVALID_HEADER_FIELD_PARAMETER_FORMAT =
"Invalid header field parameter format (as defined in RFC 5987)";
@@ -136,8 +142,9 @@ public final class ContentDisposition {
}
/**
* Return the value of the {@literal filename} parameter (or the value of the
* {@literal filename*} one decoded as defined in the RFC 5987), or {@code null} if not defined.
* Return the value of the {@literal filename} parameter, possibly decoded
* from BASE64 encoding based on RFC 2047, or of the {@literal filename*}
* parameter, possibly decoded as defined in the RFC 5987.
*/
@Nullable
public String getFilename() {
@@ -362,7 +369,20 @@ public final class ContentDisposition {
}
}
else if (attribute.equals("filename") && (filename == null)) {
filename = value;
if (value.startsWith("=?") ) {
Matcher matcher = BASE64_ENCODED_PATTERN.matcher(value);
if (matcher.find()) {
String match1 = matcher.group(1);
String match2 = matcher.group(2);
filename = new String(Base64.getDecoder().decode(match2), Charset.forName(match1));
}
else {
filename = value;
}
}
else {
filename = value;
}
}
else if (attribute.equals("size") ) {
size = Long.parseLong(value);
@@ -31,8 +31,8 @@ import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
/**
* Extension of {@link HttpEntity} that adds a {@link HttpStatus} status code.
* Used in {@code RestTemplate} as well {@code @Controller} methods.
* Extension of {@link HttpEntity} that adds an {@link HttpStatus} status code.
* Used in {@code RestTemplate} as well as in {@code @Controller} methods.
*
* <p>In {@code RestTemplate}, this class is returned by
* {@link org.springframework.web.client.RestTemplate#getForEntity getForEntity()} and
@@ -44,7 +44,8 @@ import org.springframework.util.ObjectUtils;
* HttpStatus statusCode = entity.getStatusCode();
* </pre>
*
* <p>Can also be used in Spring MVC, as the return value from a @Controller method:
* <p>This can also be used in Spring MVC as the return value from an
* {@code @Controller} method:
* <pre class="code">
* &#64;RequestMapping("/handle")
* public ResponseEntity&lt;String&gt; handle() {
@@ -81,7 +82,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
/**
* Create a new {@code ResponseEntity} with the given status code, and no body nor headers.
* Create an {@code ResponseEntity} with a status code only.
* @param status the status code
*/
public ResponseEntity(HttpStatus status) {
@@ -89,7 +90,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
}
/**
* Create a new {@code ResponseEntity} with the given body and status code, and no headers.
* Create an {@code ResponseEntity} with a body and status code.
* @param body the entity body
* @param status the status code
*/
@@ -98,7 +99,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
}
/**
* Create a new {@code HttpEntity} with the given headers and status code, and no body.
* Create an {@code HttpEntity} with headers and a status code.
* @param headers the entity headers
* @param status the status code
*/
@@ -107,7 +108,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
}
/**
* Create a new {@code HttpEntity} with the given body, headers, and status code.
* Create an {@code HttpEntity} with a body, headers, and a status code.
* @param body the entity body
* @param headers the entity headers
* @param status the status code
@@ -117,7 +118,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
}
/**
* Create a new {@code HttpEntity} with the given body, headers, and status code.
* Create an {@code HttpEntity} with a body, headers, and a raw status code.
* @param body the entity body
* @param headers the entity headers
* @param rawStatus the status code value
@@ -128,11 +129,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
}
/**
* Create a new {@code HttpEntity} with the given body, headers, and status code.
* Just used behind the nested builder API.
* @param body the entity body
* @param headers the entity headers
* @param status the status code (as {@code HttpStatus} or as {@code Integer} value)
* Private constructor.
*/
private ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, Object status) {
super(body, headers);

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