Compare commits

...

128 Commits

Author SHA1 Message Date
Spring Buildmaster 6ffc44db78 Release v5.3.2 2020-12-09 06:03:13 +00:00
Rossen Stoyanchev 25101fb034 Additional fixes for discarding data buffers
Closes gh-26232
2020-12-08 21:33:50 +00:00
Rossen Stoyanchev cb44ae62e9 Additional DataBuffer hints
See gh-26230
2020-12-08 18:43:54 +00:00
Juergen Hoeller 01fb4dbeba Polishing
See gh-26237
2020-12-08 14:59:10 +01:00
fengyuanwei 07fadae27d Remove duplicate "property" in PropertyCacheKey.toString() 2020-12-08 14:49:33 +01:00
Arjen Poutsma 2b77c08e09 Clear path pattern in HandlerMapping
This commit refactors cb2b141d31 to move
the cleaning code from a DeferredResultProcessingInterceptor to the
RouterFunctionMapping.

See gh-26239
2020-12-08 13:06:45 +01:00
Arjen Poutsma cb2b141d31 Clear path pattern after async result
This commit makes sure that the matching pattern attributes is cleared
when an async result has been obtained, so that the subsequent
dispatch starts from scratch.

Closes gh-26239
2020-12-08 12:08:56 +01:00
Brian Clozel 3b92d45984 Upgrade reference docs dependencies
This commit upgrades the spring-doc-resources dependency to 0.2.5
and the spring-asciidoctor-extensions-block-switch to 0.5.0
2020-12-08 11:38:39 +01:00
Juergen Hoeller 1195b3a0b0 Polishing 2020-12-08 10:39:56 +01:00
Sébastien Deleuze 56721669d1 Upgrade to Kotlin 1.4.21
See gh-26132
2020-12-08 08:29:21 +01:00
Sébastien Deleuze e87e03c539 Refine ConfigurationClassPostProcessor behavior in native images
This commit refines ConfigurationClassPostProcessor behavior in
native images by skipping configuration classes enhancement
instead of raising an error.

See spring-projects-experimental/spring-graalvm-native#248 for
more details.

Closes gh-26236
2020-12-08 00:12:02 +01:00
Rossen Stoyanchev 194cebd730 Upgrade to Reactor 2020.0.2
Closes gh-26176
2020-12-07 22:59:49 +00:00
Rossen Stoyanchev 7418c4b7b7 Fix buffer leak in AbstractServerHttpResponse
See gh-26232
2020-12-07 22:52:08 +00:00
Rossen Stoyanchev ad42010785 Correlate data buffers to request log messages
HttpMessageWriter implementations now attach the request log prefix
as a hint to created data buffers when the logger associated with
the writer is at DEBUG level.

Closes gh-26230
2020-12-07 22:29:07 +00:00
Juergen Hoeller 10f6a22315 Upgrade to Undertow 2.2.3 and Apache HttpCore Reactive 5.0.3 2020-12-07 22:08:50 +01:00
Juergen Hoeller c970c318f4 Polishing 2020-12-07 22:08:01 +01:00
Juergen Hoeller 834032df1f Clarify intended advice execution behavior (includes related polishing)
Closes gh-26202
2020-12-07 22:06:22 +01:00
Brian Clozel 5efa4ad442 Pull images with registry-image resource in build 2020-12-07 20:38:07 +01:00
Rossen Stoyanchev 7ef3257b03 Correctly determine HttpServletMapping for INCLUDE
Closes gh-26216
2020-12-07 17:44:22 +00:00
Sébastien Deleuze 0172424635 Avoid CGLIB proxies on websocket/messaging configurations
This commit updates websocket and messaging configurations in order
to not use CGLIB proxies anymore. The goal here is to allow support
in native executables and to increase the consistency across the
portfolio.

Closes gh-26227
2020-12-07 12:18:29 +01:00
Sébastien Deleuze 994ec708fc Upgrade to Kotlin Coroutines 1.4.2
Closes gh-26226
2020-12-07 10:49:12 +01:00
Stephane Nicoll da15fe4c94 Upgrade to Gradle 6.7.1
Closes gh-26225
2020-12-07 10:43:22 +01:00
mplain a1d134b086 Add missing Kotlin extensions for WebClient.ResponseSpec
Closes gh-26030
2020-12-07 10:34:39 +01:00
Juergen Hoeller 3c85c263d2 Upgrade to Groovy 3.0.7, RxJava 3.0.8, Mockito 3.6.28 2020-12-04 23:29:21 +01:00
Sam Brannen 7b3f53de50 Polish SpelParserTests 2020-12-04 18:49:05 +01:00
VonUniGE ec7425c1f4 Fix typo in javadoc for ResponseExtractor 2020-12-04 16:18:59 +00:00
limo520 ef05eb3729 Fix assertions in SpelParserTests.generalExpressions
Closes gh-26211

Co-authored-by: fengyuanwei <fengyuanwei@wondersgroup.com>
2020-12-04 13:59:16 +01:00
Rossen Stoyanchev 8ac39a50fe ServletServerHttpResponse reflects Content-Type override
Closes gh-25490
2020-12-03 18:41:18 +00:00
Rossen Stoyanchev d82cb15439 Ensure both mock files and parts can be provided
Closes gh-26166
2020-12-03 17:53:26 +00:00
Rossen Stoyanchev 8c1d06e0c4 Polishing contribution
Closes gh-25927
2020-12-03 17:16:27 +00:00
shevtsiv 01892c6524 Optimization in ResourceArrayPropertyEditor
The previous implementation uses ArrayList for storing resolved
resources and ArrayList has O(n) time complexity for the contains
operation. By switching to the HashSet for storing resolved
resources we improve the time complexity of this operation to be O(1).

See gh-25927
2020-12-03 17:16:27 +00:00
Brian Clozel 9776929a9d Mention security considerations in Forwarded filters
This commit improves the Javadoc for the `ForwardedHeaderFilter`
(Servlet Filter) and `ForwardedHeaderTransformer` (reactive variant) so
as to mention security considerations linked to Forwarded HTTP headers.

Closes gh-26081
2020-12-03 15:40:31 +01:00
Brian Clozel 4337d8465c Remove spring.event.invoke-listener startup event
Prior to this commit, the `SimpleApplicationEventMulticaster` would be
instrumented with the `ApplicationStartup` and start/stop events for
invoking event listeners (`spring.event.invoke-listener`).

This feature was already limited to single-threaded event publishers,
but is still flawed since several types of events can happen
concurrently. Due to the single-threaded nature of the startup sequence,
our implementation should not produce startup events concurrently.

This can cause issues like gh-26057, where concurrent events lead to
inconcistencies when tracking parent/child relationships.

This commit removes the `spring.event.invoke-listener` startup event as
a result.

Fixes gh-26057
2020-12-03 15:40:31 +01:00
Arjen Poutsma 8f0ad73bfd Merge pull request #26133 from alexfeigin/master
* gh-26133:
  Expose future response in new AsyncServerResponse
2020-12-03 14:51:49 +01:00
Alex Feigin 3714d0e401 Expose future response in new AsyncServerResponse
This commit introduces AsyncServerResponse, an extension of
ServerResponse that is returned from ServerResponse.async and that
allows users to get the future response by calling the block method.
This is particularly useful for testing purposes.
2020-12-03 14:45:56 +01:00
Juergen Hoeller 227d85a6b4 Upgrade to Jackson 2.12, Hibernate ORM 5.4.25, Checkstyle 8.38
See gh-25907
2020-12-02 17:22:22 +01:00
Arjen Poutsma a1320cd450 Add SSE support to WebMvc.fn
This commit adds support for sending Server-Sent Events in WebMvc.fn,
through the ServerResponse.sse method that takes a SseBuilder DSL.
It also includes reference documentation.

Closes gh-25920
2020-12-02 15:14:47 +01:00
Johnny Lim c7f2f50c15 Polish ConfigurationClassPostProcessorTests
Closes gh-26197
2020-12-02 15:07:15 +01:00
Juergen Hoeller 396fb0cd51 Support for multi-threaded addConverter calls
Closes gh-26183
2020-12-02 12:25:37 +01:00
Rossen Stoyanchev 5328184f3a ContentCachingResponseWrapper skips contentLength for chunked responses
Closes gh-26182
2020-12-01 17:46:12 +00:00
Rossen Stoyanchev 1e19e51c9c Fix exchangeToMono sample in reference
Closes gh-26189
2020-12-01 17:46:12 +00:00
Arjen Poutsma 69aed83504 Use nested path variables instead of Servlet's
Remove the overriden pathVariable method from the nested request
wrapper,so that the merged attributes are used instead of the ones
provided by servlet.

Closes gh-26163
2020-12-01 11:24:45 +01:00
Arjen Poutsma 0978cc629a Polishing 2020-12-01 11:19:10 +01:00
Rossen Stoyanchev 0c825621b8 Avoid use of Optional wrapper to get List<MediaType>
See gh-26170
2020-11-30 17:30:45 +00:00
Rossen Stoyanchev e7b0b65244 Support for MediaType mappings in ResourceWebHandler
Closes gh-26170
2020-11-30 17:16:55 +00:00
Stephane Nicoll e9caee1b6e Start building against Reactor 2020.0.2 snapshots
See gh-26176
2020-11-30 07:21:51 +01:00
Juergen Hoeller 622d0831a3 Polishing 2020-11-27 21:46:45 +01:00
Juergen Hoeller 56dbe06d9b Register @Bean definitions as dependent on containing configuration class
Closes gh-26167
2020-11-27 17:00:16 +01:00
Rossen Stoyanchev 11f6c4e7ab Matching update for copyHeadersIfAbsent
The change to copyHeaders from d46091 also applied to copyHeadersIfPresent.

Closes gh-26155
2020-11-26 21:51:53 +00:00
Rossen Stoyanchev d46091565b MessageHeaderAccessor handle self-copy correctly
1. Revert changes in setHeader from 5.2.9 that caused regression on self-copy.
2. Update copyHeaders to ensure it still gets a copy of native headers.
3. Exit if source and target are the same instance, as an optimization.

Closes gh-26155
2020-11-26 16:14:51 +00:00
Сергей Цыпанов 42216b77df Remove unused package-private class o.s.w.u.p.SubSequence 2020-11-26 15:31:21 +01:00
Sébastien Deleuze 43faa439ab Refine kotlinx.serialization support
This commit introduces the following changes:
 - Converters/codecs are now used based on generic type info.
 - On WebMvc and WebFlux, kotlinx.serialization is enabled along
   to Jackson because it only serializes Kotlin @Serializable classes
   which is not enough for error or actuator endpoints in Boot as
   described on spring-projects/spring-boot#24238.

TODO: leverage Kotlin/kotlinx.serialization#1164 when fixed.

Closes gh-26147
2020-11-26 12:36:35 +01:00
Juergen Hoeller 1f701d9bad Upgrade to Jetty 9.4.35 2020-11-25 11:41:06 +01:00
Juergen Hoeller 73e1f24ac1 Restore HttpHeaders-based constructor for binary compatibility
Closes gh-26151
2020-11-25 11:36:12 +01:00
Juergen Hoeller 86f9716fef Remove misleading default note on ISO.DATE_TIME
Closes gh-26134
2020-11-25 11:35:06 +01:00
Rossen Stoyanchev 80701082cd Set favorPathExtension to false by default
Applies a change that was intended in #23915 but wasn't.

Closes gh-26119
2020-11-24 17:31:28 +00:00
Arjen Poutsma 3612990344 Polishing 2020-11-24 14:41:39 +01:00
Arjen Poutsma dea2029e94 Only write non-default charset in MultipartWriterSupport
This commit only writes the 'charset' parameter in the written headers
if it is non-default (not UTF-8), since RFC7578 states that the only
allowed parameter is 'boundary'.

Closes gh-25885
2020-11-24 14:41:39 +01:00
Sam Brannen 77d6f8bc00 Polishing 2020-11-24 14:04:33 +01:00
Rossen Stoyanchev 23006d417b Deprecate context method in WebClient
See gh-25710
2020-11-23 21:46:14 +00:00
Rossen Stoyanchev d8dafbc49d Add DEBUG log message in MetadataExtractor
Closes gh-26130
2020-11-23 17:22:09 +00:00
Rossen Stoyanchev cc4a1af091 Ensure AsyncSupportConfigurer is created once
Now that we have two adapters that need access to the configurer, we need
to save it in a field like others.

See gh-25931
2020-11-23 17:22:09 +00:00
Sam Brannen fd70a9e95d Fix comment
See gh-26140
2020-11-23 17:42:49 +01:00
Sam Brannen 298cb22bfe Ensure that projects can be imported into Eclipse IDE
Recently the Spring Framework projects could no longer be imported into
Eclipse IDE without compilation errors in JMH sources.

This commit addresses this issue by applying workarounds for bugs in
Gradle and the JMH plugin for Gradle.

Gradle bug: https://github.com/gradle/gradle/issues/14932

JMH plugin bug: https://github.com/melix/jmh-gradle-plugin/issues/157

The Gradle bug has already been fixed in Gradle 6.8 RC1; however, the
issue for the JMH plugin bug seems not to have been triaged yet.

Closes gh-26140
2020-11-23 17:24:57 +01:00
Sam Brannen 4ed645e710 Fix broken links to XSD schemas in ref docs
Closes gh-26129
2020-11-23 14:49:43 +01:00
Arjen Poutsma 7c47f554c0 Remove unnecessary check in RequestMappingHandlerAdapter 2020-11-23 13:33:00 +01:00
Sébastien Deleuze 642d47ef8d Upgrade to Kotlin 1.4.20
Closes gh-26132
2020-11-23 11:24:37 +01:00
Juergen Hoeller 42e492ef2a Polishing 2020-11-20 19:00:06 +01:00
Juergen Hoeller 4fb5d59c64 Declare resolvedCharset as transient (restoring serializability)
Closes gh-26127
2020-11-20 18:40:10 +01:00
Arjen Poutsma e6324bd578 Using WebAsyncManager in WebMvc.fn
This commit removed the existing code for dealing with asynchronous
results, and replaced it with code using the WebAsyncManager.

Closes gh-25931
2020-11-20 15:50:58 +01:00
Sam Brannen 45a9227047 Remove TODO from AbstractAspectJAdvice
Despite the code duplication, we will not delegate to
AopUtils.invokeJoinpointUsingReflection() from
AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs().

The rationale is that the exception message in
invokeAdviceMethodWithGivenArgs() provides additional context via the
pointcut expression, and we would lose that additional context if we
simply delegate to AopUtils.invokeJoinpointUsingReflection(). We could
introduce an overloaded variant of invokeJoinpointUsingReflection() that
accepts an additional argument to provide the additional context for the
exception message, but we don't think that would be the best solution
for this particular use case.

In light of that, we are simply removing the TODO.

Closes gh-26126
2020-11-20 12:39:28 +01:00
Rossen Stoyanchev f1af8a6ae9 Fix checkstyle violation 2020-11-19 21:41:39 +00:00
Rossen Stoyanchev c4d7e6ff46 Fix checkstyle violations
See gh-26117
2020-11-19 21:25:21 +00:00
Spindler, Justin 922d5d271a Add ResponseSpec#toEntityFlux overload with BodyExtractor
See gh-26114
2020-11-19 21:12:56 +00:00
Rossen Stoyanchev 05e3f271b6 Consistently apply PrincipalMethodArgumentResolver
Closes gh-26117
2020-11-19 21:11:37 +00:00
Juergen Hoeller a7cce64e5d Add assertion check to FieldFilter.and(FieldFilter) method as well
See gh-26121
2020-11-19 21:05:24 +01:00
Juergen Hoeller a0544e78ea Replace early SpringProperties logger usage with System.err
Closes gh-26120
2020-11-19 18:47:31 +01:00
Сергей Цыпанов c92dccea1b Simplify XMLEventStreamWriter.writeEndElement() 2020-11-19 18:32:05 +01:00
Сергей Цыпанов 9ec96f6141 Fail MethodFilter.and() immediately when null is passed 2020-11-19 18:31:39 +01:00
Juergen Hoeller 135682e073 Upgrade to Hibernate ORM 5.4.24 2020-11-19 16:02:23 +01:00
Juergen Hoeller 6a0377b1a2 Upgrade to Tomcat 9.0.40 and Apache HttpClient 4.5.13 2020-11-19 15:09:30 +01:00
Rossen Stoyanchev 8c3a05bbcf Allow "*" for Access-Control-Expose-Headers
Closes gh-26113
2020-11-19 09:20:55 +00:00
Rossen Stoyanchev 684e695b08 Expose allowedOriginPatterns in SocketJS XML config
Closes gh-26108
2020-11-18 21:20:38 +00:00
Rossen Stoyanchev 8130bf505f Apply allowedOriginPatterns in SockJsService
See gh-26108
2020-11-18 20:57:49 +00:00
Rossen Stoyanchev 9beca06404 Polishing contribution
See gh-26108
2020-11-18 20:25:39 +00:00
Benjamin Faal ae75db2657 Add allowedOriginPatterns to SockJS config
See gh-26108
2020-11-18 19:41:32 +00:00
Juergen Hoeller 4cc831238c Revise Servlet 4 HttpServletMapping check
Closes gh-26112
2020-11-18 15:30:20 +01:00
Marten Deinum 41835ba5a4 Re-use the isVariableName method
Prior to this change the checks for isVariableName were duplicated
in 2 different locations. The logic has been moved to AspectJProxyUtils
to allow for re-use in those places and so that it benefits from any
optimizations that are done.
2020-11-17 15:58:53 +01:00
Brian Clozel de0b5bc5a1 Polish Kotlin code snippet in reference docs 2020-11-17 15:21:10 +01:00
Juergen Hoeller b29723623b Encode hash symbol in jar file path (for compatibility with JDK 11+)
Closes gh-26104
2020-11-17 14:45:45 +01:00
Juergen Hoeller 2ee231dee2 Document that @Transactional does not propagate to new threads
Closes gh-25439
2020-11-17 14:43:17 +01:00
Juergen Hoeller b56dbd2aa8 Explicitly mention Jakarta Mail 1.6 next to JavaMail
See gh-25855
2020-11-17 14:42:41 +01:00
Marten Deinum c9b27af64f Reduce overhead of char[] creation
There are more locations which could benefit from not using a
toCharArray on a String, but rather use the charAt method from
the String itself. This to prevent an additional copy of the
char[] being created.
2020-11-17 11:57:09 +01:00
thanus 9139cb85bd Update javax.mail reference to jakarta.mail 2020-11-17 11:49:01 +01:00
Stephane Nicoll 367fbbb981 Merge pull request #26100 from izeye
* pr/26100:
  Avoid char array creation in AbstractAspectJAdvice.isVariableName()

Closes gh-26100
2020-11-17 07:55:38 +01:00
izeye 1b1ba47912 Avoid char array creation in AbstractAspectJAdvice.isVariableName()
See gh-26100
2020-11-17 07:54:06 +01:00
Juergen Hoeller 041bff4e56 Upgrade to Log4J 2.14, Reactor 2020.0.1, Netty 4.1.54, Protobuf 3.14, XStream 1.4.14, OpenPDF 1.3.23, AssertJ 3.18.1, MockK 1.10.2, HtmlUnit 2.45, Checkstyle 8.37 2020-11-16 20:36:29 +01:00
Juergen Hoeller bc5a10c70d Document that Hibernate Search 5.11.6 is required for Spring JPA compatibility
Closes gh-26090
2020-11-16 19:51:50 +01:00
Brian Clozel 3d31750acc Don't swallow logs during promotion job in CI
Prior to this commit, the promote task in the CI release pipeline would
write the "concourse-release-scripts" CONSOLE logs to /dev/null; this
commit ensures that we can read those while promoting artifacts.
2020-11-16 18:20:19 +01:00
Rossen Stoyanchev c22a483c3d Update section on type conversion for web method arguments
Closes gh-26088
2020-11-16 17:01:57 +00:00
Juergen Hoeller 238354a081 Polishing 2020-11-16 17:42:22 +01:00
Juergen Hoeller 51b1306d70 Move coroutines invocation decision to invokeWithinTransaction
See gh-26092
2020-11-16 17:41:09 +01:00
Juergen Hoeller 7206a23d33 Consistent attribute value spelling for PATH_ATTRIBUTE
See gh-24945
2020-11-16 17:40:39 +01:00
Сергей Цыпанов 83996b12cc Simplify AstUtils.getPropertyAccessorsToTry() 2020-11-16 16:11:31 +01:00
Sébastien Deleuze 0fd6c100a6 Polishing
See gh-24103
2020-11-16 14:33:21 +01:00
Sam Brannen 0819a9fcc9 Discover @DynamicPropertySource methods on enclosing test classes
gh-19930 introduced support for finding class-level test configuration
annotations on enclosing classes when using JUnit Jupiter @Nested test
classes, but support for @DynamicPropertySource methods got overlooked
since they are method-level annotations.

This commit addresses this shortcoming by introducing full support for
@NestedTestConfiguration semantics for @DynamicPropertySource methods
on enclosing classes.

Closes gh-26091
2020-11-15 20:59:15 +01:00
Juergen Hoeller 0b580d194d Early log entry for async EntityManagerFactory initialization failure
Closes gh-26093
2020-11-13 17:52:51 +01:00
Juergen Hoeller 500b54b86f Expose plain Hibernate Session(Factory) interface by default again
See gh-26090
2020-11-13 17:50:51 +01:00
Juergen Hoeller 942400ae47 Expose Hibernate Session(Factory)Implementor interface by default
Closes gh-26090
2020-11-13 11:35:34 +01:00
Rossen Stoyanchev b92d249f45 AntPathMatcher allows newline in URI template variables
Closes gh-23252
2020-11-12 22:00:47 +00:00
Rossen Stoyanchev 204a7fe91f UrlPathHelper.removeJsessionid correctly appends remainder
Closes gh-26079
2020-11-12 21:28:18 +00:00
Rossen Stoyanchev 42d3bc47c9 toEntityFlux methods apply error status handling
Closes gh-26069
2020-11-12 18:43:36 +00:00
Rossen Stoyanchev 94fcb37d30 Re-order methods in DefaultResponseSpec
See gh-26069
2020-11-12 18:43:36 +00:00
Rossen Stoyanchev ba9325446c Optimize WebClientUtils
Use constant Predicate for exception wrapping.
Use ResponseEntity constructor instead of builder.

See gh-26069
2020-11-12 18:43:36 +00:00
Arjen Poutsma 5338d8b5e9 Add cron expression documentation
This commit adds a section about the cron expression format supported by
 Spring.

Closes gh-26067
2020-11-12 15:57:14 +01:00
Sam Brannen 1b0be5862d Polishing 2020-11-12 15:32:41 +01:00
Sam Brannen 48af36c6fa Ensure test() conditions in JUnit TestKit match method names
Prior to this commit, the test("test") conditions used in
AutowiredConfigurationErrorsIntegrationTests inadvertently asserted
that the invoked test methods reside in an org.springframework.test
subpackage, which is always the case for any test method in the
`spring-test` module. In other words, "test" is always a substring of
"org.springframework.test...", which is not a meaningful assertion.

This commit ensures that the JUnit Platform Test Kit is asserting the
actual names of test methods.
2020-11-12 15:11:54 +01:00
Sam Brannen bd4e915abf Assert same instance returned for cached merged BeanDefinition 2020-11-12 14:57:05 +01:00
Sam Brannen c419ea7ba7 Use MethodFilter.and() in ReflectiveAspectJAdvisorFactory 2020-11-12 14:35:28 +01:00
Juergen Hoeller 66292cd7a1 Individually apply the SQL type from each SqlParameterSource argument
Closes gh-26071
2020-11-12 14:05:31 +01:00
fengyuanwei 6eec1acdac Make tests meaningful in DefaultListableBeanFactoryTests 2020-11-12 13:50:30 +01:00
izeye bc32d513d9 Polish Javadoc for InjectionMetadata.forElements() 2020-11-12 13:50:00 +01:00
Sam Brannen 3851b291da Use MethodFilter.and() in TransactionalTestExecutionListener 2020-11-11 11:11:34 +01:00
Rossen Stoyanchev 2b1f229998 LimitedDataBufferList adds or raises error
Closes gh-26060
2020-11-10 19:59:36 +00:00
Sam Brannen fc5e3c335f Introduce and() methods in MethodFilter & FieldFilter for composition
This commit introduces `and()` default methods in the MethodFilter and
FieldFilter functional interfaces in ReflectionUtils in order to simplify
uses cases that need to compose filter logic.

Closes gh-26063
2020-11-10 18:00:24 +01:00
Сергей Цыпанов daf9a82e8c Simplify AbstractAspectJAdvice.isVariableName() 2020-11-10 17:30:00 +01:00
Sam Brannen b019f30a15 Validate that test & lifecycle methods are not @Autowired
Prior to this commit, a developer may have accidentally annotated a
JUnit Jupiter test method or lifecycle method with @Autowired, and that
would have potentially resulted in an exception that was hard to
understand. This is because the Spring container considers any
@Autowired method to be a "configuration method" when autowiring the
test class instance. Consequently, such an @Autowired method would be
invoked twice: once by Spring while attempting to autowire the test
instance and another time by JUnit Jupiter when invoking the test or
lifecycle method. The autowiring invocation of the method often leads
to an exception, either because Spring cannot satisfy a dependency (such
as JUnit Jupiter's TestInfo) or because the body of the method fails due
to test setup that has not yet been invoked.

This commit introduces validation for @Autowired test and lifecycle
methods in the SpringExtension that will throw an IllegalStateException
if any @Autowired method in a test class is also annotated with any of
the following JUnit Jupiter annotations.

- @Test
- @TestFactory
- @TestTemplate
- @RepeatedTest
- @ParameterizedTest
- @BeforeAll
- @AfterAll
- @BeforeEach
- @AfterEach

Closes gh-25966
2020-11-10 15:28:34 +01:00
Spring Buildmaster d5b5e894c9 Next development version (v5.3.2-SNAPSHOT) 2020-11-10 09:02:35 +00:00
203 changed files with 4339 additions and 1690 deletions
+26 -26
View File
@@ -1,7 +1,7 @@
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.10' apply false
id 'org.jetbrains.kotlin.jvm' version '1.4.21' 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'
@@ -9,7 +9,7 @@ plugins {
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.10" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.21" apply false
}
ext {
@@ -25,18 +25,18 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.11.3"
mavenBom "io.netty:netty-bom:4.1.53.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.0"
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.0"
mavenBom "io.netty:netty-bom:4.1.54.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.2"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR8"
mavenBom "io.rsocket:rsocket-bom:1.1.0"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.34.v20201102"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.10"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.1"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.35.v20201120"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.21"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.2"
mavenBom "org.junit:junit-bom:5.7.0"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.13.3') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.14.0') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
@@ -53,7 +53,7 @@ configure(allprojects) { project ->
entry 'aspectjtools'
entry 'aspectjweaver'
}
dependencySet(group: 'org.codehaus.groovy', version: '3.0.6') {
dependencySet(group: 'org.codehaus.groovy', version: '3.0.7') {
entry 'groovy'
entry 'groovy-jsr223'
entry 'groovy-templates' // requires findbugs for warning-free compilation
@@ -64,7 +64,7 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.19"
dependency "io.reactivex.rxjava3:rxjava:3.0.7"
dependency "io.reactivex.rxjava3:rxjava:3.0.8"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "com.caucho:hessian:4.0.63"
@@ -73,9 +73,9 @@ configure(allprojects) { project ->
exclude group: "stax", name: "stax-api"
}
dependency "com.google.code.gson:gson:2.8.6"
dependency "com.google.protobuf:protobuf-java-util:3.13.0"
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.13") {
dependency("com.thoughtworks.xstream:xstream:1.4.14") {
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
}
@@ -96,7 +96,7 @@ configure(allprojects) { project ->
dependency "com.h2database:h2:1.4.200"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.6"
dependency "com.github.librepdf:openpdf:1.3.22"
dependency "com.github.librepdf:openpdf:1.3.23"
dependency "com.rometools:rome:1.15.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.3"
@@ -123,23 +123,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.23.Final"
dependency "org.hibernate:hibernate-core:5.4.25.Final"
dependency "org.hibernate:hibernate-validator:6.1.6.Final"
dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.39') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.40') {
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.39') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.40') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.2.Final') {
dependencySet(group: 'io.undertow', version: '2.2.3.Final') {
entry 'undertow-core'
entry('undertow-websockets-jsr') {
exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec"
@@ -154,14 +154,14 @@ configure(allprojects) { project ->
entry 'okhttp'
entry 'mockwebserver'
}
dependency("org.apache.httpcomponents:httpclient:4.5.12") {
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.apache.httpcomponents:httpasyncclient:4.1.4") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency 'org.apache.httpcomponents.client5:httpclient5:5.0.3'
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.0.2'
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.0.3'
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.4"
dependency "org.jruby:jruby:9.2.13.0"
@@ -190,25 +190,25 @@ 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.0"
dependency "org.assertj:assertj-core:3.18.1"
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '3.6.0') {
dependencySet(group: 'org.mockito', version: '3.6.28') {
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
entry 'mockito-junit-jupiter'
}
dependency "io.mockk:mockk:1.10.0"
dependency "io.mockk:mockk:1.10.2"
dependency("net.sourceforge.htmlunit:htmlunit:2.44.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.45.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.44.0") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.45.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -339,7 +339,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "8.36.2"
toolVersion = "8.38"
configDirectory.set(rootProject.file("src/checkstyle"))
}
+3 -3
View File
@@ -37,17 +37,17 @@ anchors:
resource_types:
- name: artifactory-resource
type: docker-image
type: registry-image
source:
repository: springio/artifactory-resource
tag: 0.0.12
- name: github-status-resource
type: docker-image
type: registry-image
source:
repository: dpb587/github-status-resource
tag: master
- name: slack-notification
type: docker-image
type: registry-image
source:
repository: cfcommunity/slack-notification-resource
tag: latest
+2 -2
View File
@@ -6,11 +6,11 @@ CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
java -jar /opt/concourse-release-scripts.jar promote $RELEASE_TYPE $BUILD_INFO_LOCATION > /dev/null || { exit 1; }
java -jar /opt/concourse-release-scripts.jar promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
java -jar /opt/concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
distribute $RELEASE_TYPE $BUILD_INFO_LOCATION > /dev/null || { exit 1; }
distribute $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
echo "Promotion complete"
echo $version > version/version
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.1-SNAPSHOT
version=5.3.2
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
+2 -2
View File
@@ -3,7 +3,7 @@ configurations {
}
dependencies {
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.3.RELEASE")
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.5.0")
}
repositories {
@@ -113,7 +113,7 @@ dokka {
}
task downloadResources(type: Download) {
def version = "0.2.2.RELEASE"
def version = "0.2.5"
src "https://repo.spring.io/release/io/spring/docresources/" +
"spring-doc-resources/$version/spring-doc-resources-${version}.zip"
dest project.file("$buildDir/docs/spring-doc-resources.zip")
+18 -2
View File
@@ -29,12 +29,11 @@ eclipse.classpath.file.whenMerged { classpath ->
classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
}
// Use separate main/test outputs (prevents WTP from packaging test classes)
eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse")
eclipse.classpath.file.beforeMerged { classpath ->
classpath.entries.findAll{ it instanceof SourceFolder }.each {
if(it.output.startsWith("bin/")) {
if (it.output.startsWith("bin/")) {
it.output = null
}
}
@@ -56,6 +55,23 @@ 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
eclipse.classpath.file.whenMerged {
entries.findAll { it.path =~ /src\/jmh\/(java|resources)/ }.each {
it.entryAttributes['test'] = 'true'
}
}
// Allow projects to be used as WTP modules
eclipse.project.natures "org.eclipse.wst.common.project.facet.core.nature"
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -350,17 +350,8 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
return this.discoveredThrowingType;
}
private boolean isVariableName(String name) {
char[] chars = name.toCharArray();
if (!Character.isJavaIdentifierStart(chars[0])) {
return false;
}
for (int i = 1; i < chars.length; i++) {
if (!Character.isJavaIdentifierPart(chars[i])) {
return false;
}
}
return true;
private static boolean isVariableName(String name) {
return AspectJProxyUtils.isVariableName(name);
}
@@ -640,7 +631,6 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
}
try {
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
// TODO AopUtils.invokeJoinpointUsingReflection
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
catch (IllegalArgumentException ex) {
@@ -470,22 +470,10 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
*/
@Nullable
private String maybeExtractVariableName(@Nullable String candidateToken) {
if (!StringUtils.hasLength(candidateToken)) {
return null;
}
if (Character.isJavaIdentifierStart(candidateToken.charAt(0)) &&
Character.isLowerCase(candidateToken.charAt(0))) {
char[] tokenChars = candidateToken.toCharArray();
for (char tokenChar : tokenChars) {
if (!Character.isJavaIdentifierPart(tokenChar)) {
return null;
}
}
if (AspectJProxyUtils.isVariableName(candidateToken)) {
return candidateToken;
}
else {
return null;
}
return null;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.util.List;
import org.springframework.aop.Advisor;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Utility methods for working with AspectJ proxies.
@@ -73,4 +75,19 @@ public abstract class AspectJProxyUtils {
((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut));
}
static boolean isVariableName(@Nullable String name) {
if (!StringUtils.hasLength(name)) {
return false;
}
if (!Character.isJavaIdentifierStart(name.charAt(0))) {
return false;
}
for (int i = 1; i < name.length(); i++) {
if (!Character.isJavaIdentifierPart(name.charAt(i))) {
return false;
}
}
return true;
}
}
@@ -51,6 +51,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConvertingComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.util.StringUtils;
import org.springframework.util.comparator.InstanceComparator;
@@ -70,7 +71,11 @@ import org.springframework.util.comparator.InstanceComparator;
@SuppressWarnings("serial")
public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable {
private static final Comparator<Method> METHOD_COMPARATOR;
// Exclude @Pointcut methods
private static final MethodFilter adviceMethodFilter = ReflectionUtils.USER_DECLARED_METHODS
.and(method -> (AnnotationUtils.getAnnotation(method, Pointcut.class) == null));
private static final Comparator<Method> adviceMethodComparator;
static {
// Note: although @After is ordered before @AfterReturning and @AfterThrowing,
@@ -86,7 +91,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
return (ann != null ? ann.getAnnotation() : null);
});
Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);
METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
adviceMethodComparator = adviceKindComparator.thenComparing(methodNameComparator);
}
@@ -160,15 +165,10 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
}
private List<Method> getAdvisorMethods(Class<?> aspectClass) {
final List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, method -> {
// Exclude pointcuts
if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
methods.add(method);
}
}, ReflectionUtils.USER_DECLARED_METHODS);
List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter);
if (methods.size() > 1) {
methods.sort(METHOD_COMPARATOR);
methods.sort(adviceMethodComparator);
}
return methods;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,7 +59,8 @@ public abstract aspect AbstractTransactionAspect extends TransactionAspectSuppor
@Override
public void destroy() {
clearTransactionManagerCache(); // An aspect is basically a singleton
// An aspect is basically a singleton -> cleanup on destruction
clearTransactionManagerCache();
}
@SuppressAjWarnings("adviceDidNotMatch")
@@ -141,8 +141,7 @@ public class InjectionMetadata {
* Return an {@code InjectionMetadata} instance, possibly for empty elements.
* @param elements the elements to inject (possibly empty)
* @param clazz the target class
* @return a new {@link #InjectionMetadata(Class, Collection)} instance,
* or {@link #EMPTY} in case of no elements
* @return a new {@link #InjectionMetadata(Class, Collection)} instance
* @since 5.2
*/
public static InjectionMetadata forElements(Collection<InjectedElement> elements, Class<?> clazz) {
@@ -67,6 +67,7 @@ import org.springframework.util.StringUtils;
/**
* Delegate for resolving constructors and factory methods.
*
* <p>Performs constructor resolution through argument matching.
*
* @author Juergen Hoeller
@@ -85,7 +86,7 @@ class ConstructorResolver {
private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Marker for autowired arguments in a cached argument array, to be later replaced
* Marker for autowired arguments in a cached argument array, to be replaced
* by a {@linkplain #resolveAutowiredArgument resolved autowired argument}.
*/
private static final Object autowiredArgumentMarker = new Object();
@@ -149,7 +150,7 @@ class ConstructorResolver {
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
}
}
@@ -410,6 +411,7 @@ class ConstructorResolver {
if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
throw new ImplicitlyAppearedSingletonException();
}
this.beanFactory.registerDependentBean(factoryBeanName, beanName);
factoryClass = factoryBean.getClass();
isStatic = false;
}
@@ -444,7 +446,7 @@ class ConstructorResolver {
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true);
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
}
}
@@ -816,7 +818,7 @@ class ConstructorResolver {
* Resolve the prepared arguments stored in the given bean definition.
*/
private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
Executable executable, Object[] argsToResolve, boolean fallback) {
Executable executable, Object[] argsToResolve) {
TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
TypeConverter converter = (customConverter != null ? customConverter : bw);
@@ -829,7 +831,7 @@ class ConstructorResolver {
Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
if (argValue == autowiredArgumentMarker) {
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, fallback);
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true);
}
else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
@@ -63,9 +63,8 @@ public class BeanFactoryUtilsTests {
@BeforeEach
public void setUp() {
public void setup() {
// Interesting hierarchical factory to test counts.
// Slow to read so we cache it.
DefaultListableBeanFactory grandParent = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(grandParent).loadBeanDefinitions(ROOT_CONTEXT);
@@ -93,7 +92,7 @@ public class BeanFactoryUtilsTests {
* Check that override doesn't count as two separate beans.
*/
@Test
public void testHierarchicalCountBeansWithOverride() throws Exception {
public void testHierarchicalCountBeansWithOverride() {
// Leaf count
assertThat(this.listableBeanFactory.getBeanDefinitionCount() == 1).isTrue();
// Count minus duplicate
@@ -101,14 +100,14 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalNamesWithNoMatch() throws Exception {
public void testHierarchicalNamesWithNoMatch() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, NoOp.class));
assertThat(names.size()).isEqualTo(0);
}
@Test
public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception {
public void testHierarchicalNamesWithMatchOnlyInRoot() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, IndexedTestBean.class));
assertThat(names.size()).isEqualTo(1);
@@ -118,7 +117,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testGetBeanNamesForTypeWithOverride() throws Exception {
public void testGetBeanNamesForTypeWithOverride() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class));
// includes 2 TestBeans from FactoryBeans (DummyFactory definitions)
@@ -236,7 +235,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalResolutionWithOverride() throws Exception {
public void testHierarchicalResolutionWithOverride() {
Object test3 = this.listableBeanFactory.getBean("test3");
Object test = this.listableBeanFactory.getBean("test");
@@ -276,14 +275,14 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalNamesForAnnotationWithNoMatch() throws Exception {
public void testHierarchicalNamesForAnnotationWithNoMatch() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, Override.class));
assertThat(names.size()).isEqualTo(0);
}
@Test
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() throws Exception {
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class));
assertThat(names.size()).isEqualTo(1);
@@ -293,7 +292,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testGetBeanNamesForAnnotationWithOverride() throws Exception {
public void testGetBeanNamesForAnnotationWithOverride() {
AnnotatedBean annotatedBean = new AnnotatedBean();
this.listableBeanFactory.registerSingleton("anotherAnnotatedBean", annotatedBean);
List<String> names = Arrays.asList(
@@ -433,6 +432,7 @@ public class BeanFactoryUtilsTests {
String basePackage() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@ControllerAdvice
@interface RestControllerAdvice {
@@ -444,18 +444,23 @@ public class BeanFactoryUtilsTests {
String basePackage() default "";
}
@ControllerAdvice("com.example")
static class ControllerAdviceClass {
}
@RestControllerAdvice("com.example")
static class RestControllerAdviceClass {
}
static class TestBeanSmartFactoryBean implements SmartFactoryBean<TestBean> {
private final TestBean testBean = new TestBean("enigma", 42);
private final boolean singleton;
private final boolean prototype;
TestBeanSmartFactoryBean(boolean singleton, boolean prototype) {
@@ -478,7 +483,7 @@ public class BeanFactoryUtilsTests {
return TestBean.class;
}
public TestBean getObject() throws Exception {
public TestBean getObject() {
// We don't really care if the actual instance is a singleton or prototype
// for the tests that use this factory.
return this.testBean;
@@ -785,12 +785,13 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", childDefinition);
factory.registerAlias("parent", "alias");
TestBean child = (TestBean) factory.getBean("child");
TestBean child = factory.getBean("child", TestBean.class);
assertThat(child.getName()).isEqualTo(EXPECTED_NAME);
assertThat(child.getAge()).isEqualTo(EXPECTED_AGE);
Object mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
BeanDefinition mergedBeanDefinition1 = factory.getMergedBeanDefinition("child");
BeanDefinition mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
assertThat(mergedBeanDefinition2).as("Use cached merged bean definition").isEqualTo(mergedBeanDefinition2);
assertThat(mergedBeanDefinition1).as("Use cached merged bean definition").isSameAs(mergedBeanDefinition2);
}
@Test
@@ -1838,8 +1839,7 @@ class DefaultListableBeanFactoryTests {
assertThat(factoryBean).as("The FactoryBean should have been registered.").isNotNull();
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) lbf.autowire(FactoryBeanDependentBean.class,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
Object mergedBeanDefinition2 = bean.getFactoryBean();
assertThat(mergedBeanDefinition2).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(mergedBeanDefinition2);
assertThat(bean.getFactoryBean()).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(factoryBean);
}
@Test
@@ -2388,8 +2388,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
}
@Test
@@ -2403,8 +2402,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(2);
}
@Test
@@ -2419,8 +2417,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
}
@Test
@@ -2542,14 +2539,15 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", child);
AbstractBeanDefinition def = (AbstractBeanDefinition) factory.getBeanDefinition("child");
Object mergedBeanDefinition2 = def.getScope();
assertThat(mergedBeanDefinition2).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(mergedBeanDefinition2);
assertThat(def.getScope()).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(theChildScope);
}
@Test
void scopeInheritanceForChildBeanDefinitions() {
String theParentScope = "bonanza!";
RootBeanDefinition parent = new RootBeanDefinition();
parent.setScope("bonanza!");
parent.setScope(theParentScope);
AbstractBeanDefinition child = new ChildBeanDefinition("parent");
child.setBeanClass(TestBean.class);
@@ -2559,8 +2557,7 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", child);
BeanDefinition def = factory.getMergedBeanDefinition("child");
Object mergedBeanDefinition2 = def.getScope();
assertThat(mergedBeanDefinition2).as("Child 'scope' not inherited").isEqualTo(mergedBeanDefinition2);
assertThat(def.getScope()).as("Child 'scope' not inherited").isEqualTo(theParentScope);
}
@Test
@@ -2596,15 +2593,12 @@ class DefaultListableBeanFactoryTests {
});
lbf.preInstantiateSingletons();
TestBean tb = (TestBean) lbf.getBean("test");
Object mergedBeanDefinition2 = tb.getName();
assertThat(mergedBeanDefinition2).as("Name was set on field by IAPP").isEqualTo(mergedBeanDefinition2);
assertThat(tb.getName()).as("Name was set on field by IAPP").isEqualTo(nameSetOnField);
if (!skipPropertyPopulation) {
Object mergedBeanDefinition21 = tb.getAge();
assertThat(mergedBeanDefinition21).as("Property value still set").isEqualTo(mergedBeanDefinition21);
assertThat(tb.getAge()).as("Property value still set").isEqualTo(ageSetByPropertyValue);
}
else {
Object mergedBeanDefinition21 = tb.getAge();
assertThat(mergedBeanDefinition21).as("Property value was NOT set and still has default value").isEqualTo(mergedBeanDefinition21);
assertThat(tb.getAge()).as("Property value was NOT set and still has default value").isEqualTo(0);
}
}
@@ -427,15 +427,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
if (configBeanDefs.isEmpty() || IN_NATIVE_IMAGE) {
// nothing to enhance -> return immediately
enhanceConfigClasses.end();
return;
}
if (IN_NATIVE_IMAGE) {
throw new BeanDefinitionStoreException("@Configuration classes need to be marked as " +
"proxyBeanMethods=false. Found: " + configBeanDefs.keySet());
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
@@ -25,8 +25,6 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ResolvableType;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
import org.springframework.lang.Nullable;
import org.springframework.util.ErrorHandler;
@@ -57,9 +55,6 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
@Nullable
private ErrorHandler errorHandler;
@Nullable
private ApplicationStartup applicationStartup;
/**
* Create a new SimpleApplicationEventMulticaster.
@@ -127,22 +122,6 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
return this.errorHandler;
}
/**
* Set the {@link ApplicationStartup} to track event listener invocations during startup.
* @since 5.3
*/
public void setApplicationStartup(@Nullable ApplicationStartup applicationStartup) {
this.applicationStartup = applicationStartup;
}
/**
* Return the current application startup for this multicaster.
*/
@Nullable
public ApplicationStartup getApplicationStartup() {
return this.applicationStartup;
}
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
@@ -156,16 +135,6 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else if (this.applicationStartup != null) {
StartupStep invocationStep = this.applicationStartup.start("spring.event.invoke-listener");
invokeListener(listener, event);
invocationStep.tag("event", event::toString);
if (eventType != null) {
invocationStep.tag("eventType", eventType::toString);
}
invocationStep.tag("listener", listener::toString);
invocationStep.end();
}
else {
invokeListener(listener, event);
}
@@ -815,9 +815,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
}
else {
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
simpleApplicationEventMulticaster.setApplicationStartup(getApplicationStartup());
this.applicationEventMulticaster = simpleApplicationEventMulticaster;
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -106,7 +106,6 @@ public @interface DateTimeFormat {
/**
* The most common ISO DateTime Format {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX},
* e.g. "2000-10-31T01:30:00.000-05:00".
* <p>This is the default if no annotation value is specified.
*/
DATE_TIME,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,24 +36,26 @@ import static org.assertj.core.api.Assertions.assertThat;
public class AtAspectJAfterThrowingTests {
@Test
public void testAccessThrowable() throws Exception {
public void testAccessThrowable() {
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
ITestBean bean = (ITestBean) ctx.getBean("testBean");
ExceptionHandlingAspect aspect = (ExceptionHandlingAspect) ctx.getBean("aspect");
assertThat(AopUtils.isAopProxy(bean)).isTrue();
IOException exceptionThrown = null;
try {
bean.unreliableFileOperation();
}
catch (IOException e) {
//
catch (IOException ex) {
exceptionThrown = ex;
}
assertThat(aspect.handled).isEqualTo(1);
assertThat(aspect.lastException).isNotNull();
assertThat(aspect.lastException).isSameAs(exceptionThrown);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class AtAspectJAnnotationBindingTests {
private AnnotatedTestBean testBean;
private ClassPathXmlApplicationContext ctx;
@@ -70,8 +71,7 @@ public class AtAspectJAnnotationBindingTests {
class AtAspectJAnnotationBindingTestAspect {
@Around("execution(* *(..)) && @annotation(testAnn)")
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn)
throws Throwable {
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn) throws Throwable {
String annValue = testAnn.value();
Object result = pjp.proceed();
return (result instanceof String ? annValue + " " + result : result);
@@ -20,7 +20,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -66,7 +65,6 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -77,13 +75,13 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class ConfigurationClassPostProcessorTests {
class ConfigurationClassPostProcessorTests {
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@BeforeEach
public void setup() {
void setup() {
QualifierAnnotationAutowireCandidateResolver acr = new QualifierAnnotationAutowireCandidateResolver();
acr.setBeanFactory(this.beanFactory);
this.beanFactory.setAutowireCandidateResolver(acr);
@@ -99,7 +97,7 @@ public class ConfigurationClassPostProcessorTests {
* We test for such a case below, and in doing so prove that enhancement is working.
*/
@Test
public void enhancementIsPresentBecauseSingletonSemanticsAreRespected() {
void enhancementIsPresentBecauseSingletonSemanticsAreRespected() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -107,11 +105,13 @@ public class ConfigurationClassPostProcessorTests {
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
assertThat(beanFactory.getDependentBeans("foo")).contains("bar");
assertThat(beanFactory.getDependentBeans("config")).contains("foo");
assertThat(beanFactory.getDependentBeans("config")).contains("bar");
}
@Test
public void enhancementIsPresentBecauseSingletonSemanticsAreRespectedUsingAsm() {
void enhancementIsPresentBecauseSingletonSemanticsAreRespectedUsingAsm() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class.getName()));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -119,11 +119,13 @@ public class ConfigurationClassPostProcessorTests {
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
assertThat(beanFactory.getDependentBeans("foo")).contains("bar");
assertThat(beanFactory.getDependentBeans("config")).contains("foo");
assertThat(beanFactory.getDependentBeans("config")).contains("bar");
}
@Test
public void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() {
void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -134,7 +136,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalseUsingAsm() {
void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalseUsingAsm() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class.getName()));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -145,7 +147,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void enhancementIsNotPresentForStaticMethods() {
void enhancementIsNotPresentForStaticMethods() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(StaticSingletonBeanConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -158,7 +160,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void enhancementIsNotPresentForStaticMethodsUsingAsm() {
void enhancementIsNotPresentForStaticMethodsUsingAsm() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(StaticSingletonBeanConfig.class.getName()));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -171,7 +173,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig"));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -185,7 +187,7 @@ public class ConfigurationClassPostProcessorTests {
* if a bean class is already loaded.
*/
@Test
public void alreadyLoadedConfigurationClasses() {
void alreadyLoadedConfigurationClasses() {
beanFactory.registerBeanDefinition("unloadedConfig", new RootBeanDefinition(UnloadedConfig.class.getName()));
beanFactory.registerBeanDefinition("loadedConfig", new RootBeanDefinition(LoadedConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
@@ -198,7 +200,7 @@ public class ConfigurationClassPostProcessorTests {
* Tests whether a bean definition without a specified bean class is handled correctly.
*/
@Test
public void postProcessorIntrospectsInheritedDefinitionsCorrectly() {
void postProcessorIntrospectsInheritedDefinitionsCorrectly() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
beanFactory.registerBeanDefinition("parent", new RootBeanDefinition(TestBean.class));
beanFactory.registerBeanDefinition("child", new ChildBeanDefinition("parent"));
@@ -210,96 +212,96 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void postProcessorWorksWithComposedConfigurationUsingReflection() {
void postProcessorWorksWithComposedConfigurationUsingReflection() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(ComposedConfigurationClass.class);
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithComposedConfigurationUsingAsm() {
void postProcessorWorksWithComposedConfigurationUsingAsm() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(ComposedConfigurationClass.class.getName());
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForBasePackageUsingReflection() {
void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForBasePackageUsingReflection() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ComposedConfigurationWithAttributeOverrideForBasePackage.class);
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForBasePackageUsingAsm() {
void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForBasePackageUsingAsm() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ComposedConfigurationWithAttributeOverrideForBasePackage.class.getName());
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForExcludeFilterUsingReflection() {
void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForExcludeFilterUsingReflection() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ComposedConfigurationWithAttributeOverrideForExcludeFilter.class);
assertSupportForComposedAnnotationWithExclude(beanDefinition);
}
@Test
public void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForExcludeFilterUsingAsm() {
void postProcessorWorksWithComposedConfigurationWithAttributeOverrideForExcludeFilterUsingAsm() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ComposedConfigurationWithAttributeOverrideForExcludeFilter.class.getName());
assertSupportForComposedAnnotationWithExclude(beanDefinition);
}
@Test
public void postProcessorWorksWithExtendedConfigurationWithAttributeOverrideForExcludesFilterUsingReflection() {
void postProcessorWorksWithExtendedConfigurationWithAttributeOverrideForExcludesFilterUsingReflection() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ExtendedConfigurationWithAttributeOverrideForExcludeFilter.class);
assertSupportForComposedAnnotationWithExclude(beanDefinition);
}
@Test
public void postProcessorWorksWithExtendedConfigurationWithAttributeOverrideForExcludesFilterUsingAsm() {
void postProcessorWorksWithExtendedConfigurationWithAttributeOverrideForExcludesFilterUsingAsm() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ExtendedConfigurationWithAttributeOverrideForExcludeFilter.class.getName());
assertSupportForComposedAnnotationWithExclude(beanDefinition);
}
@Test
public void postProcessorWorksWithComposedComposedConfigurationWithAttributeOverridesUsingReflection() {
void postProcessorWorksWithComposedComposedConfigurationWithAttributeOverridesUsingReflection() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ComposedComposedConfigurationWithAttributeOverridesClass.class);
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithComposedComposedConfigurationWithAttributeOverridesUsingAsm() {
void postProcessorWorksWithComposedComposedConfigurationWithAttributeOverridesUsingAsm() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
ComposedComposedConfigurationWithAttributeOverridesClass.class.getName());
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesUsingReflection() {
void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesUsingReflection() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
MetaComponentScanConfigurationWithAttributeOverridesClass.class);
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesUsingAsm() {
void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesUsingAsm() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
MetaComponentScanConfigurationWithAttributeOverridesClass.class.getName());
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclassUsingReflection() {
void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclassUsingReflection() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
SubMetaComponentScanConfigurationWithAttributeOverridesClass.class);
assertSupportForComposedAnnotation(beanDefinition);
}
@Test
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclassUsingAsm() {
void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclassUsingAsm() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
SubMetaComponentScanConfigurationWithAttributeOverridesClass.class.getName());
assertSupportForComposedAnnotation(beanDefinition);
@@ -324,7 +326,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void postProcessorOverridesNonApplicationBeanDefinitions() {
void postProcessorOverridesNonApplicationBeanDefinitions() {
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setRole(RootBeanDefinition.ROLE_SUPPORT);
beanFactory.registerBeanDefinition("bar", rbd);
@@ -337,7 +339,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void postProcessorDoesNotOverrideRegularBeanDefinitions() {
void postProcessorDoesNotOverrideRegularBeanDefinitions() {
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setResource(new DescriptiveResource("XML or something"));
beanFactory.registerBeanDefinition("bar", rbd);
@@ -349,7 +351,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void postProcessorDoesNotOverrideRegularBeanDefinitionsEvenWithScopedProxy() {
void postProcessorDoesNotOverrideRegularBeanDefinitionsEvenWithScopedProxy() {
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setResource(new DescriptiveResource("XML or something"));
BeanDefinitionHolder proxied = ScopedProxyUtils.createScopedProxy(
@@ -363,7 +365,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void postProcessorFailsOnImplicitOverrideIfOverridingIsNotAllowed() {
void postProcessorFailsOnImplicitOverrideIfOverridingIsNotAllowed() {
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setResource(new DescriptiveResource("XML or something"));
beanFactory.registerBeanDefinition("bar", rbd);
@@ -378,7 +380,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test // gh-25430
public void detectAliasOverride() {
void detectAliasOverride() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
beanFactory.setAllowBeanDefinitionOverriding(false);
@@ -390,7 +392,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void configurationClassesProcessedInCorrectOrder() {
void configurationClassesProcessedInCorrectOrder() {
beanFactory.registerBeanDefinition("config1", new RootBeanDefinition(OverridingSingletonBeanConfig.class));
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(SingletonBeanConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
@@ -404,7 +406,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void configurationClassesWithValidOverridingForProgrammaticCall() {
void configurationClassesWithValidOverridingForProgrammaticCall() {
beanFactory.registerBeanDefinition("config1", new RootBeanDefinition(OverridingAgainSingletonBeanConfig.class));
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(OverridingSingletonBeanConfig.class));
beanFactory.registerBeanDefinition("config3", new RootBeanDefinition(SingletonBeanConfig.class));
@@ -419,7 +421,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void configurationClassesWithInvalidOverridingForProgrammaticCall() {
void configurationClassesWithInvalidOverridingForProgrammaticCall() {
beanFactory.registerBeanDefinition("config1", new RootBeanDefinition(InvalidOverridingSingletonBeanConfig.class));
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(OverridingSingletonBeanConfig.class));
beanFactory.registerBeanDefinition("config3", new RootBeanDefinition(SingletonBeanConfig.class));
@@ -435,7 +437,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test // SPR-15384
public void nestedConfigurationClassesProcessedInCorrectOrder() {
void nestedConfigurationClassesProcessedInCorrectOrder() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithOrderedNestedClasses.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -448,7 +450,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test // SPR-16734
public void innerConfigurationClassesProcessedInCorrectOrder() {
void innerConfigurationClassesProcessedInCorrectOrder() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithOrderedInnerClasses.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -462,7 +464,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void scopedProxyTargetMarkedAsNonAutowireCandidate() {
void scopedProxyTargetMarkedAsNonAutowireCandidate() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -479,7 +481,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void processingAllowedOnlyOncePerProcessorRegistryPair() {
void processingAllowedOnlyOncePerProcessorRegistryPair() {
DefaultListableBeanFactory bf1 = new DefaultListableBeanFactory();
DefaultListableBeanFactory bf2 = new DefaultListableBeanFactory();
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
@@ -492,7 +494,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjection() {
void genericsBasedInjection() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -509,7 +511,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithScoped() {
void genericsBasedInjectionWithScoped() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -526,7 +528,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithScopedProxy() {
void genericsBasedInjectionWithScopedProxy() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -546,7 +548,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithScopedProxyUsingAsm() {
void genericsBasedInjectionWithScopedProxyUsingAsm() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -566,7 +568,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithImplTypeAtInjectionPoint() {
void genericsBasedInjectionWithImplTypeAtInjectionPoint() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -583,7 +585,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithFactoryBean() {
void genericsBasedInjectionWithFactoryBean() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -602,7 +604,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithRawMatch() {
void genericsBasedInjectionWithRawMatch() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawMatchingConfiguration.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -611,7 +613,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithWildcardMatch() {
void genericsBasedInjectionWithWildcardMatch() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(WildcardMatchingConfiguration.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
@@ -620,7 +622,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithWildcardWithExtendsMatch() {
void genericsBasedInjectionWithWildcardWithExtendsMatch() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(WildcardWithExtendsConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
@@ -628,7 +630,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithWildcardWithGenericExtendsMatch() {
void genericsBasedInjectionWithWildcardWithGenericExtendsMatch() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(WildcardWithGenericExtendsConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
@@ -636,12 +638,12 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithEarlyGenericsMatching() {
void genericsBasedInjectionWithEarlyGenericsMatching() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -653,13 +655,13 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatching() {
void genericsBasedInjectionWithLateGenericsMatching() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -671,12 +673,12 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithEarlyGenericsMatchingAndRawFactoryMethod() {
void genericsBasedInjectionWithEarlyGenericsMatchingAndRawFactoryMethod() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(0);
@@ -686,13 +688,13 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingAndRawFactoryMethod() {
void genericsBasedInjectionWithLateGenericsMatchingAndRawFactoryMethod() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -704,12 +706,12 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithEarlyGenericsMatchingAndRawInstance() {
void genericsBasedInjectionWithEarlyGenericsMatchingAndRawInstance() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -721,13 +723,13 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingAndRawInstance() {
void genericsBasedInjectionWithLateGenericsMatchingAndRawInstance() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -739,7 +741,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithEarlyGenericsMatchingOnCglibProxy() {
void genericsBasedInjectionWithEarlyGenericsMatchingOnCglibProxy() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -749,7 +751,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.registerSingleton("traceInterceptor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -763,7 +765,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxy() {
void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxy() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -774,7 +776,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -788,7 +790,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawFactoryMethod() {
void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawFactoryMethod() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -799,7 +801,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -813,7 +815,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawInstance() {
void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawInstance() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -824,7 +826,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -838,7 +840,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithEarlyGenericsMatchingOnJdkProxy() {
void genericsBasedInjectionWithEarlyGenericsMatchingOnJdkProxy() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -847,7 +849,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.registerSingleton("traceInterceptor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
String[] beanNames = beanFactory.getBeanNamesForType(RepositoryInterface.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -861,7 +863,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxy() {
void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxy() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -871,7 +873,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(RepositoryInterface.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -885,7 +887,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawFactoryMethod() {
void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawFactoryMethod() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -895,7 +897,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(RepositoryInterface.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -909,7 +911,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawInstance() {
void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawInstance() {
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
@@ -919,7 +921,7 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.preInstantiateSingletons();
String[] beanNames = beanFactory.getBeanNamesForType(RepositoryInterface.class);
assertThat(ObjectUtils.containsElement(beanNames, "stringRepo")).isTrue();
assertThat(beanNames).contains("stringRepo");
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class));
assertThat(beanNames.length).isEqualTo(1);
@@ -933,7 +935,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testSelfReferenceExclusionForFactoryMethodOnSameBean() {
void testSelfReferenceExclusionForFactoryMethodOnSameBean() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -947,7 +949,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testConfigWithDefaultMethods() {
void testConfigWithDefaultMethods() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -961,7 +963,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testConfigWithDefaultMethodsUsingAsm() {
void testConfigWithDefaultMethodsUsingAsm() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -975,7 +977,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testCircularDependency() {
void testCircularDependency() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -988,38 +990,38 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testCircularDependencyWithApplicationContext() {
void testCircularDependencyWithApplicationContext() {
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
new AnnotationConfigApplicationContext(A.class, AStrich.class))
.withMessageContaining("Circular reference");
}
@Test
public void testPrototypeArgumentThroughBeanMethodCall() {
void testPrototypeArgumentThroughBeanMethodCall() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithPrototype.class);
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
}
@Test
public void testSingletonArgumentThroughBeanMethodCall() {
void testSingletonArgumentThroughBeanMethodCall() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithSingleton.class);
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
}
@Test
public void testNullArgumentThroughBeanMethodCall() {
void testNullArgumentThroughBeanMethodCall() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithNull.class);
ctx.getBean("aFoo");
}
@Test
public void testInjectionPointMatchForNarrowTargetReturnType() {
void testInjectionPointMatchForNarrowTargetReturnType() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(FooBarConfiguration.class);
assertThat(ctx.getBean(FooImpl.class).bar).isSameAs(ctx.getBean(BarImpl.class));
}
@Test
public void testVarargOnBeanMethod() {
void testVarargOnBeanMethod() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class, TestBean.class);
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1028,7 +1030,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testEmptyVarargOnBeanMethod() {
void testEmptyVarargOnBeanMethod() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class);
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1036,7 +1038,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testCollectionArgumentOnBeanMethod() {
void testCollectionArgumentOnBeanMethod() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class, TestBean.class);
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1045,7 +1047,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testEmptyCollectionArgumentOnBeanMethod() {
void testEmptyCollectionArgumentOnBeanMethod() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class);
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1053,7 +1055,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testMapArgumentOnBeanMethod() {
void testMapArgumentOnBeanMethod() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class, DummyRunnable.class);
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1062,7 +1064,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testEmptyMapArgumentOnBeanMethod() {
void testEmptyMapArgumentOnBeanMethod() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class);
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1070,7 +1072,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testCollectionInjectionFromSameConfigurationClass() {
void testCollectionInjectionFromSameConfigurationClass() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class);
CollectionInjectionConfiguration bean = ctx.getBean(CollectionInjectionConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1079,7 +1081,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testMapInjectionFromSameConfigurationClass() {
void testMapInjectionFromSameConfigurationClass() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MapInjectionConfiguration.class);
MapInjectionConfiguration bean = ctx.getBean(MapInjectionConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1088,7 +1090,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testBeanLookupFromSameConfigurationClass() {
void testBeanLookupFromSameConfigurationClass() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanLookupConfiguration.class);
BeanLookupConfiguration bean = ctx.getBean(BeanLookupConfiguration.class);
assertThat(bean.getTestBean()).isNotNull();
@@ -1096,7 +1098,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testNameClashBetweenConfigurationClassAndBean() {
void testNameClashBetweenConfigurationClassAndBean() {
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() -> {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyTestBean.class);
ctx.getBean("myTestBean", TestBean.class);
@@ -1104,7 +1106,7 @@ public class ConfigurationClassPostProcessorTests {
}
@Test
public void testBeanDefinitionRegistryPostProcessorConfig() {
void testBeanDefinitionRegistryPostProcessorConfig() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanDefinitionRegistryPostProcessorConfig.class);
boolean condition = ctx.getBean("myTestBean") instanceof TestBean;
assertThat(condition).isTrue();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,10 +58,10 @@ internal fun <T: Any> monoToDeferred(source: Mono<T>) =
* @since 5.2
*/
@Suppress("UNCHECKED_CAST")
fun invokeSuspendingFunction(method: Method, bean: Any, vararg args: Any?): Publisher<*> {
fun invokeSuspendingFunction(method: Method, target: Any, vararg args: Any?): Publisher<*> {
val function = method.kotlinFunction!!
val mono = mono(Dispatchers.Unconfined) {
function.callSuspend(bean, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
}.onErrorMap(InvocationTargetException::class.java) { it.targetException }
return if (function.returnType.classifier == Flow::class) {
mono.flatMapMany { (it as Flow<Any>).asFlux() }
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -225,11 +225,11 @@ public final class Conventions {
if (!attributeName.contains("-")) {
return attributeName;
}
char[] chars = attributeName.toCharArray();
char[] result = new char[chars.length -1]; // not completely accurate but good guess
char[] result = new char[attributeName.length() -1]; // not completely accurate but good guess
int currPos = 0;
boolean upperCaseNext = false;
for (char c : chars) {
for (int i = 0; i < attributeName.length(); i++ ) {
char c = attributeName.charAt(i);
if (c == '-') {
upperCaseNext = true;
}
@@ -21,9 +21,6 @@ import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
/**
@@ -50,8 +47,6 @@ public final class SpringProperties {
private static final String PROPERTIES_RESOURCE_LOCATION = "spring.properties";
private static final Log logger = LogFactory.getLog(SpringProperties.class);
private static final Properties localProperties = new Properties();
@@ -61,16 +56,13 @@ public final class SpringProperties {
URL url = (cl != null ? cl.getResource(PROPERTIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResource(PROPERTIES_RESOURCE_LOCATION));
if (url != null) {
logger.debug("Found 'spring.properties' file in local classpath");
try (InputStream is = url.openStream()) {
localProperties.load(is);
}
}
}
catch (IOException ex) {
if (logger.isInfoEnabled()) {
logger.info("Could not load 'spring.properties' file from local classpath: " + ex);
}
System.err.println("Could not load 'spring.properties' file from local classpath: " + ex);
}
}
@@ -108,9 +100,7 @@ public final class SpringProperties {
value = System.getProperty(key);
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not retrieve system property '" + key + "': " + ex);
}
System.err.println("Could not retrieve system property '" + key + "': " + ex);
}
}
return value;
@@ -21,6 +21,8 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
@@ -148,4 +150,22 @@ public abstract class Hints {
}
}
/**
* If the hints contain a {@link #LOG_PREFIX_HINT} and the given logger has
* DEBUG level enabled, apply the log prefix as a hint to the given buffer
* via {@link DataBufferUtils#touch(DataBuffer, Object)}.
* @param buffer the buffer to touch
* @param hints the hints map to check for a log prefix
* @param logger the logger whose level to check
* @since 5.3.2
*/
public static void touchDataBuffer(DataBuffer buffer, @Nullable Map<String, Object> hints, Log logger) {
if (logger.isDebugEnabled() && hints != null) {
Object logPrefix = hints.get(LOG_PREFIX_HINT);
if (logPrefix != null) {
DataBufferUtils.touch(buffer, logPrefix);
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,6 +130,9 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
}
Flux<DataBuffer> in = DataBufferUtils.read(resource, position, bufferFactory, this.bufferSize);
if (logger.isDebugEnabled()) {
in = in.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
}
return DataBufferUtils.takeUntilByteCount(in, count);
}
@@ -17,17 +17,17 @@
package org.springframework.core.convert.support;
import java.lang.reflect.Array;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.ResolvableType;
@@ -500,9 +500,9 @@ public class GenericConversionService implements ConfigurableConversionService {
*/
private static class Converters {
private final Set<GenericConverter> globalConverters = new LinkedHashSet<>();
private final Set<GenericConverter> globalConverters = new CopyOnWriteArraySet<>();
private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<>(36);
private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);
public void add(GenericConverter converter) {
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
@@ -513,8 +513,7 @@ public class GenericConversionService implements ConfigurableConversionService {
}
else {
for (ConvertiblePair convertiblePair : convertibleTypes) {
ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
convertersForPair.add(converter);
getMatchableConverters(convertiblePair).add(converter);
}
}
}
@@ -652,7 +651,7 @@ public class GenericConversionService implements ConfigurableConversionService {
*/
private static class ConvertersForPair {
private final Deque<GenericConverter> converters = new ArrayDeque<>(1);
private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
public void add(GenericConverter converter) {
this.converters.addFirst(converter);
@@ -487,6 +487,24 @@ public abstract class DataBufferUtils {
}
}
/**
* Associate the given hint with the data buffer if it is a pooled buffer
* and supports leak tracking.
* @param dataBuffer the data buffer to attach the hint to
* @param hint the hint to attach
* @return the input buffer
* @since 5.3.2
*/
@SuppressWarnings("unchecked")
public static <T extends DataBuffer> T touch(T dataBuffer, Object hint) {
if (dataBuffer instanceof PooledDataBuffer) {
return (T) ((PooledDataBuffer) dataBuffer).touch(hint);
}
else {
return dataBuffer;
}
}
/**
* Release the given data buffer, if it is a {@link PooledDataBuffer} and
* has been {@linkplain PooledDataBuffer#isAllocated() allocated}.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,11 +54,8 @@ public class LimitedDataBufferList extends ArrayList<DataBuffer> {
@Override
public boolean add(DataBuffer buffer) {
boolean result = super.add(buffer);
if (result) {
updateCount(buffer.readableByteCount());
}
return result;
updateCount(buffer.readableByteCount());
return super.add(buffer);
}
@Override
@@ -315,6 +315,12 @@ public class NettyDataBuffer implements PooledDataBuffer {
return new NettyDataBuffer(this.byteBuf.retain(), this.dataBufferFactory);
}
@Override
public PooledDataBuffer touch(Object hint) {
this.byteBuf.touch(hint);
return this;
}
@Override
public boolean release() {
return this.byteBuf.release();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,13 @@ public interface PooledDataBuffer extends DataBuffer {
*/
PooledDataBuffer retain();
/**
* Associate the given hint with the data buffer for debugging purposes.
* @return this buffer
* @since 5.3.2
*/
PooledDataBuffer touch(Object hint);
/**
* Decrease the reference count for this buffer by one,
* and deallocate it once the count reaches zero.
@@ -432,6 +432,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
filePath = StringUtils.capitalize(filePath);
}
// # can appear in directories/filenames, java.net.URL should not treat it as a fragment
filePath = StringUtils.replace(filePath, "#", "%23");
// Build URL that points to the root of the jar file
UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
ResourceUtils.FILE_URL_PREFIX + filePath + ResourceUtils.JAR_URL_SEPARATOR);
// Potentially overlapping with URLClassLoader.getURLs() result above!
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,11 @@ package org.springframework.core.io.support;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -129,7 +130,7 @@ public class ResourceArrayPropertyEditor extends PropertyEditorSupport {
public void setValue(Object value) throws IllegalArgumentException {
if (value instanceof Collection || (value instanceof Object[] && !(value instanceof Resource[]))) {
Collection<?> input = (value instanceof Collection ? (Collection<?>) value : Arrays.asList((Object[]) value));
List<Resource> merged = new ArrayList<>();
Set<Resource> merged = new LinkedHashSet<>();
for (Object element : input) {
if (element instanceof String) {
// A location pattern: resolve it into a Resource array.
@@ -137,11 +138,7 @@ public class ResourceArrayPropertyEditor extends PropertyEditorSupport {
String pattern = resolvePath((String) element).trim();
try {
Resource[] resources = this.resourcePatternResolver.getResources(pattern);
for (Resource resource : resources) {
if (!merged.contains(resource)) {
merged.add(resource);
}
}
Collections.addAll(merged, resources);
}
catch (IOException ex) {
// ignore - might be an unresolved placeholder or non-existing base directory
@@ -152,10 +149,7 @@ public class ResourceArrayPropertyEditor extends PropertyEditorSupport {
}
else if (element instanceof Resource) {
// A Resource object: add it to the result.
Resource resource = (Resource) element;
if (!merged.contains(resource)) {
merged.add(resource);
}
merged.add((Resource) element);
}
else {
throw new IllegalArgumentException("Cannot convert element [" + element + "] to [" +
@@ -644,7 +644,7 @@ public class AntPathMatcher implements PathMatcher {
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}");
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)";
private final String rawPattern;
@@ -16,6 +16,8 @@
package org.springframework.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.BitSet;
@@ -104,7 +106,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
private final Map<String, String> parameters;
@Nullable
private Charset resolvedCharset;
private transient Charset resolvedCharset;
@Nullable
private volatile String toStringValue;
@@ -184,9 +186,9 @@ public class MimeType implements Comparable<MimeType>, Serializable {
this.subtype = subtype.toLowerCase(Locale.ENGLISH);
if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH);
parameters.forEach((attribute, value) -> {
checkParameters(attribute, value);
map.put(attribute, value);
parameters.forEach((parameter, value) -> {
checkParameters(parameter, value);
map.put(parameter, value);
});
this.parameters = Collections.unmodifiableMap(map);
}
@@ -224,11 +226,11 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
}
protected void checkParameters(String attribute, String value) {
Assert.hasLength(attribute, "'attribute' must not be empty");
protected void checkParameters(String parameter, String value) {
Assert.hasLength(parameter, "'parameter' must not be empty");
Assert.hasLength(value, "'value' must not be empty");
checkToken(attribute);
if (PARAM_CHARSET.equals(attribute)) {
checkToken(parameter);
if (PARAM_CHARSET.equals(parameter)) {
if (this.resolvedCharset == null) {
this.resolvedCharset = Charset.forName(unquote(value));
}
@@ -591,6 +593,17 @@ public class MimeType implements Comparable<MimeType>, Serializable {
return 0;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization, just initialize state after deserialization.
ois.defaultReadObject();
// Initialize transient fields.
String charsetName = getParameter(PARAM_CHARSET);
if (charsetName != null) {
this.resolvedCharset = Charset.forName(unquote(charsetName));
}
}
/**
* Parse the given String value into a {@code MimeType} object,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -824,6 +824,19 @@ public abstract class ReflectionUtils {
* @param method the method to check
*/
boolean matches(Method method);
/**
* Create a composite filter based on this filter <em>and</em> the provided filter.
* <p>If this filter does not match, the next filter will not be applied.
* @param next the next {@code MethodFilter}
* @return a composite {@code MethodFilter}
* @throws IllegalArgumentException if the MethodFilter argument is {@code null}
* @since 5.3.2
*/
default MethodFilter and(MethodFilter next) {
Assert.notNull(next, "Next MethodFilter must not be null");
return method -> matches(method) && next.matches(method);
}
}
@@ -852,6 +865,19 @@ public abstract class ReflectionUtils {
* @param field the field to check
*/
boolean matches(Field field);
/**
* Create a composite filter based on this filter <em>and</em> the provided filter.
* <p>If this filter does not match, the next filter will not be applied.
* @param next the next {@code FieldFilter}
* @return a composite {@code FieldFilter}
* @throws IllegalArgumentException if the FieldFilter argument is {@code null}
* @since 5.3.2
*/
default FieldFilter and(FieldFilter next) {
Assert.notNull(next, "Next FieldFilter must not be null");
return field -> matches(field) && next.matches(field);
}
}
}
@@ -161,9 +161,8 @@ class XMLEventStreamWriter implements XMLStreamWriter {
public void writeEndElement() throws XMLStreamException {
closeEmptyElementIfNecessary();
int last = this.endElements.size() - 1;
EndElement lastEndElement = this.endElements.get(last);
EndElement lastEndElement = this.endElements.remove(last);
this.eventWriter.add(lastEndElement);
this.endElements.remove(last);
}
@Override
@@ -367,8 +367,9 @@ class ReactiveAdapterRegistryTests {
private static class ExtendedFlux<T> extends Flux<T> {
@Override
public void subscribe(CoreSubscriber actual) {
public void subscribe(CoreSubscriber<? super T> actual) {
throw new UnsupportedOperationException();
}
}
}
@@ -35,6 +35,8 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import org.junit.jupiter.api.Test;
import org.mockito.stubbing.Answer;
import org.reactivestreams.Subscription;
import reactor.core.publisher.BaseSubscriber;
@@ -834,6 +836,22 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
.verifyError(DataBufferLimitException.class);
}
@Test // gh-26060
void joinWithLimitDoesNotOverRelease() {
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
byte[] bytes = "foo-bar-baz".getBytes(StandardCharsets.UTF_8);
NettyDataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
buffer.getNativeBuffer().retain(); // should be at 2 now
buffer.write(bytes);
Mono<DataBuffer> result = DataBufferUtils.join(Flux.just(buffer), 8);
StepVerifier.create(result).verifyError(DataBufferLimitException.class);
assertThat(buffer.getNativeBuffer().refCnt()).isEqualTo(1);
buffer.release();
}
@ParameterizedDataBufferAllocatingTest
void joinErrors(String displayName, DataBufferFactory bufferFactory) {
super.bufferFactory = bufferFactory;
@@ -17,9 +17,11 @@ package org.springframework.core.io.buffer;
import java.nio.charset.StandardCharsets;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Unit tests for {@link LimitedDataBufferList}.
* @author Rossen Stoyanchev
@@ -29,8 +31,10 @@ public class LimitedDataBufferListTests {
@Test
void limitEnforced() {
Assertions.assertThatThrownBy(() -> new LimitedDataBufferList(5).add(toDataBuffer("123456")))
.isInstanceOf(DataBufferLimitException.class);
LimitedDataBufferList list = new LimitedDataBufferList(5);
assertThatThrownBy(() -> list.add(toDataBuffer("123456"))).isInstanceOf(DataBufferLimitException.class);
assertThat(list).isEmpty();
}
@Test
@@ -130,6 +130,7 @@ class AntPathMatcherTests {
assertThat(pathMatcher.match("", "")).isTrue();
assertThat(pathMatcher.match("/{bla}.*", "/testing.html")).isTrue();
assertThat(pathMatcher.match("/{bla}", "//x\ny")).isTrue();
}
@Test
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
@@ -267,13 +268,13 @@ class MimeTypeTests {
assertThat(mimeType.getParameter("attr")).isEqualTo("'v>alue'");
}
@Test // SPR-16630
@Test // SPR-16630
void parseMimeTypeWithSpacesAroundEquals() {
MimeType mimeType = MimeTypeUtils.parseMimeType("multipart/x-mixed-replace;boundary = --myboundary");
assertThat(mimeType.getParameter("boundary")).isEqualTo("--myboundary");
}
@Test // SPR-16630
@Test // SPR-16630
void parseMimeTypeWithSpacesAroundEqualsAndQuotedValue() {
MimeType mimeType = MimeTypeUtils.parseMimeType("text/plain; foo = \" bar \" ");
assertThat(mimeType.getParameter("foo")).isEqualTo("\" bar \"");
@@ -303,14 +304,14 @@ class MimeTypeTests {
assertThat(mimeTypes.size()).as("Invalid amount of mime types").isEqualTo(0);
}
@Test // gh-23241
@Test // gh-23241
void parseMimeTypesWithTrailingComma() {
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes("text/plain, text/html,");
assertThat(mimeTypes).as("No mime types returned").isNotNull();
assertThat(mimeTypes.size()).as("Incorrect number of mime types").isEqualTo(2);
}
@Test // SPR-17459
@Test // SPR-17459
void parseMimeTypesWithQuotedParameters() {
testWithQuotedParameters("foo/bar;param=\",\"");
testWithQuotedParameters("foo/bar;param=\"s,a,\"");
@@ -332,7 +333,7 @@ class MimeTypeTests {
assertThat(type.getSubtypeSuffix()).isEqualTo("json");
}
@Test // gh-25350
@Test // gh-25350
void wildcardSubtypeCompatibleWithSuffix() {
MimeType applicationStar = new MimeType("application", "*");
MimeType applicationVndJson = new MimeType("application", "vnd.something+json");
@@ -342,8 +343,9 @@ class MimeTypeTests {
private void testWithQuotedParameters(String... mimeTypes) {
String s = String.join(",", mimeTypes);
List<MimeType> actual = MimeTypeUtils.parseMimeTypes(s);
assertThat(actual.size()).isEqualTo(mimeTypes.length);
for (int i=0; i < mimeTypes.length; i++) {
for (int i = 0; i < mimeTypes.length; i++) {
assertThat(actual.get(i).toString()).isEqualTo(mimeTypes[i]);
}
}
@@ -370,6 +372,7 @@ class MimeTypeTests {
List<MimeType> result = new ArrayList<>(expected);
Random rnd = new Random();
// shuffle & sort 10 times
for (int i = 0; i < 10; i++) {
Collections.shuffle(result, rnd);
@@ -399,11 +402,7 @@ class MimeTypeTests {
assertThat(m2.compareTo(m1) != 0).as("Invalid comparison result").isTrue();
}
/**
* SPR-13157
* @since 4.2
*/
@Test
@Test // SPR-13157
void equalsIsCaseInsensitiveForCharsets() {
MimeType m1 = new MimeType("text", "plain", singletonMap("charset", "UTF-8"));
MimeType m2 = new MimeType("text", "plain", singletonMap("charset", "utf-8"));
@@ -413,4 +412,12 @@ class MimeTypeTests {
assertThat(m2.compareTo(m1)).isEqualTo(0);
}
@Test // gh-26127
void serialize() throws Exception {
MimeType original = new MimeType("text", "plain", StandardCharsets.UTF_8);
MimeType deserialized = SerializationTestUtils.serializeAndDeserialize(original);
assertThat(deserialized).isEqualTo(original);
assertThat(original).isEqualTo(deserialized);
}
}
@@ -186,12 +186,7 @@ class ReflectionUtilsTests {
@Test
void doWithProtectedMethods() {
ListSavingMethodCallback mc = new ListSavingMethodCallback();
ReflectionUtils.doWithMethods(TestObject.class, mc, new ReflectionUtils.MethodFilter() {
@Override
public boolean matches(Method m) {
return Modifier.isProtected(m.getModifiers());
}
});
ReflectionUtils.doWithMethods(TestObject.class, mc, method -> Modifier.isProtected(method.getModifiers()));
assertThat(mc.getMethodNames().isEmpty()).isFalse();
assertThat(mc.getMethodNames().contains("clone")).as("Must find protected method on Object").isTrue();
assertThat(mc.getMethodNames().contains("finalize")).as("Must find protected method on Object").isTrue();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.core.testfixture.io.buffer;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DataBufferWrapper;
import org.springframework.core.io.buffer.PooledDataBuffer;
import org.springframework.util.Assert;
@@ -67,19 +68,19 @@ class LeakAwareDataBuffer extends DataBufferWrapper implements PooledDataBuffer
@Override
public PooledDataBuffer retain() {
DataBuffer delegate = dataBuffer();
if (delegate instanceof PooledDataBuffer) {
((PooledDataBuffer) delegate).retain();
}
DataBufferUtils.retain(dataBuffer());
return this;
}
@Override
public PooledDataBuffer touch(Object hint) {
DataBufferUtils.touch(dataBuffer(), hint);
return this;
}
@Override
public boolean release() {
DataBuffer delegate = dataBuffer();
if (delegate instanceof PooledDataBuffer) {
((PooledDataBuffer) delegate).release();
}
DataBufferUtils.release(dataBuffer());
return isAllocated();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,10 +54,9 @@ public abstract class AstUtils {
}
else {
if (targetType != null) {
int pos = 0;
for (Class<?> clazz : targets) {
if (clazz == targetType) { // put exact matches on the front to be tried first?
specificAccessors.add(pos++, resolver);
specificAccessors.add(resolver);
}
else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the
// specificAccessor list
@@ -47,7 +47,7 @@ import org.springframework.util.StringUtils;
/**
* A powerful {@link PropertyAccessor} that uses reflection to access properties
* for reading and possibly also for writing.
* for reading and possibly also for writing on a target instance.
*
* <p>A property can be referenced through a public getter method (when being read)
* or a public setter method (when being written), and also as a public field.
@@ -98,8 +98,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
/**
* Create a new property accessor for reading and possibly writing.
* @param allowWrite whether to also allow for write operations
* Create a new property accessor for reading and possibly also writing.
* @param allowWrite whether to allow write operations on a target instance
* @since 4.3.15
* @see #canWrite
*/
@@ -628,8 +628,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
@Override
public String toString() {
return "CacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property + ", " +
this.property + ", targetIsClass=" + this.targetIsClass + "]";
return "PropertyCacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property +
", targetIsClass=" + this.targetIsClass + "]";
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,10 +36,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Andy Clement
* @author Juergen Hoeller
*/
public class SpelParserTests {
class SpelParserTests {
@Test
public void theMostBasic() {
void theMostBasic() {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expr = parser.parseRaw("2");
assertThat(expr).isNotNull();
@@ -50,7 +50,7 @@ public class SpelParserTests {
}
@Test
public void valueType() {
void valueType() {
SpelExpressionParser parser = new SpelExpressionParser();
EvaluationContext ctx = new StandardEvaluationContext();
Class<?> c = parser.parseRaw("2").getValueType();
@@ -66,7 +66,7 @@ public class SpelParserTests {
}
@Test
public void whitespace() {
void whitespace() {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expr = parser.parseRaw("2 + 3");
assertThat(expr.getValue()).isEqualTo(5);
@@ -79,7 +79,7 @@ public class SpelParserTests {
}
@Test
public void arithmeticPlus1() {
void arithmeticPlus1() {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expr = parser.parseRaw("2+2");
assertThat(expr).isNotNull();
@@ -88,66 +88,65 @@ public class SpelParserTests {
}
@Test
public void arithmeticPlus2() {
void arithmeticPlus2() {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expr = parser.parseRaw("37+41");
assertThat(expr.getValue()).isEqualTo(78);
}
@Test
public void arithmeticMultiply1() {
void arithmeticMultiply1() {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expr = parser.parseRaw("2*3");
assertThat(expr).isNotNull();
assertThat(expr.getAST()).isNotNull();
// printAst(expr.getAST(),0);
assertThat(expr.getValue()).isEqualTo(6);
}
@Test
public void arithmeticPrecedence1() {
void arithmeticPrecedence1() {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expr = parser.parseRaw("2*3+5");
assertThat(expr.getValue()).isEqualTo(11);
}
@Test
public void generalExpressions() {
void generalExpressions() {
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String");
})
.satisfies(ex -> parseExceptionRequirements(SpelMessage.MISSING_CONSTRUCTOR_ARGS, 10));
.satisfies(parseExceptionRequirements(SpelMessage.MISSING_CONSTRUCTOR_ARGS, 10));
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String(3,");
})
.satisfies(ex -> parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
.satisfies(parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String(3");
})
.satisfies(ex -> parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
.satisfies(parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String(");
})
.satisfies(ex -> parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
.satisfies(parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("\"abc");
})
.satisfies(ex -> parseExceptionRequirements(SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING, 0));
.satisfies(parseExceptionRequirements(SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING, 0));
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("'abc");
})
.satisfies(ex -> parseExceptionRequirements(SpelMessage.NON_TERMINATING_QUOTED_STRING, 0));
.satisfies(parseExceptionRequirements(SpelMessage.NON_TERMINATING_QUOTED_STRING, 0));
}
@@ -161,38 +160,38 @@ public class SpelParserTests {
}
@Test
public void arithmeticPrecedence2() {
void arithmeticPrecedence2() {
SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expr = parser.parseRaw("2+3*5");
assertThat(expr.getValue()).isEqualTo(17);
}
@Test
public void arithmeticPrecedence3() {
void arithmeticPrecedence3() {
SpelExpression expr = new SpelExpressionParser().parseRaw("3+10/2");
assertThat(expr.getValue()).isEqualTo(8);
}
@Test
public void arithmeticPrecedence4() {
void arithmeticPrecedence4() {
SpelExpression expr = new SpelExpressionParser().parseRaw("10/2+3");
assertThat(expr.getValue()).isEqualTo(8);
}
@Test
public void arithmeticPrecedence5() {
void arithmeticPrecedence5() {
SpelExpression expr = new SpelExpressionParser().parseRaw("(4+10)/2");
assertThat(expr.getValue()).isEqualTo(7);
}
@Test
public void arithmeticPrecedence6() {
void arithmeticPrecedence6() {
SpelExpression expr = new SpelExpressionParser().parseRaw("(3+2)*2");
assertThat(expr.getValue()).isEqualTo(10);
}
@Test
public void booleanOperators() {
void booleanOperators() {
SpelExpression expr = new SpelExpressionParser().parseRaw("true");
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
expr = new SpelExpressionParser().parseRaw("false");
@@ -210,7 +209,7 @@ public class SpelParserTests {
}
@Test
public void booleanOperators_symbolic_spr9614() {
void booleanOperators_symbolic_spr9614() {
SpelExpression expr = new SpelExpressionParser().parseRaw("true");
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
expr = new SpelExpressionParser().parseRaw("false");
@@ -228,7 +227,7 @@ public class SpelParserTests {
}
@Test
public void stringLiterals() {
void stringLiterals() {
SpelExpression expr = new SpelExpressionParser().parseRaw("'howdy'");
assertThat(expr.getValue()).isEqualTo("howdy");
expr = new SpelExpressionParser().parseRaw("'hello '' world'");
@@ -236,13 +235,13 @@ public class SpelParserTests {
}
@Test
public void stringLiterals2() {
void stringLiterals2() {
SpelExpression expr = new SpelExpressionParser().parseRaw("'howdy'.substring(0,2)");
assertThat(expr.getValue()).isEqualTo("ho");
}
@Test
public void testStringLiterals_DoubleQuotes_spr9620() {
void testStringLiterals_DoubleQuotes_spr9620() {
SpelExpression expr = new SpelExpressionParser().parseRaw("\"double quote: \"\".\"");
assertThat(expr.getValue()).isEqualTo("double quote: \".");
expr = new SpelExpressionParser().parseRaw("\"hello \"\" world\"");
@@ -250,7 +249,7 @@ public class SpelParserTests {
}
@Test
public void testStringLiterals_DoubleQuotes_spr9620_2() {
void testStringLiterals_DoubleQuotes_spr9620_2() {
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() ->
new SpelExpressionParser().parseRaw("\"double quote: \\\"\\\".\""))
.satisfies(ex -> {
@@ -260,7 +259,7 @@ public class SpelParserTests {
}
@Test
public void positionalInformation() {
void positionalInformation() {
SpelExpression expr = new SpelExpressionParser().parseRaw("true and true or false");
SpelNode rootAst = expr.getAST();
OpOr operatorOr = (OpOr) rootAst;
@@ -289,7 +288,7 @@ public class SpelParserTests {
}
@Test
public void tokenKind() {
void tokenKind() {
TokenKind tk = TokenKind.NOT;
assertThat(tk.hasPayload()).isFalse();
assertThat(tk.toString()).isEqualTo("NOT(!)");
@@ -304,7 +303,7 @@ public class SpelParserTests {
}
@Test
public void token() {
void token() {
Token token = new Token(TokenKind.NOT, 0, 3);
assertThat(token.kind).isEqualTo(TokenKind.NOT);
assertThat(token.startPos).isEqualTo(0);
@@ -319,7 +318,7 @@ public class SpelParserTests {
}
@Test
public void exceptions() {
void exceptions() {
ExpressionException exprEx = new ExpressionException("test");
assertThat(exprEx.getSimpleMessage()).isEqualTo("test");
assertThat(exprEx.toDetailedString()).isEqualTo("test");
@@ -337,13 +336,13 @@ public class SpelParserTests {
}
@Test
public void parseMethodsOnNumbers() {
void parseMethodsOnNumbers() {
checkNumber("3.14.toString()", "3.14", String.class);
checkNumber("3.toString()", "3", String.class);
}
@Test
public void numerics() {
void numerics() {
checkNumber("2", 2, Integer.class);
checkNumber("22", 22, Integer.class);
checkNumber("+22", 22, Integer.class);
@@ -385,8 +384,7 @@ public class SpelParserTests {
private void checkNumberError(String expression, SpelMessage expectedMessage) {
SpelExpressionParser parser = new SpelExpressionParser();
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() ->
parser.parseRaw(expression))
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> parser.parseRaw(expression))
.satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(expectedMessage));
}
@@ -30,7 +30,6 @@ import java.util.Set;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Helper class that efficiently creates multiple {@link PreparedStatementCreator}
@@ -200,9 +199,8 @@ public class PreparedStatementCreatorFactory {
public PreparedStatementCreatorImpl(String actualSql, List<?> parameters) {
this.actualSql = actualSql;
Assert.notNull(parameters, "Parameters List must not be null");
this.parameters = parameters;
if (this.parameters.size() != declaredParameters.size()) {
if (parameters.size() != declaredParameters.size()) {
// Account for named parameters being used multiple times
Set<String> names = new HashSet<>();
for (int i = 0; i < parameters.size(); i++) {
@@ -345,9 +345,9 @@ public abstract class NamedParameterUtils {
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
try {
Object value = paramSource.getValue(paramName);
SqlParameter param = findParameter(declaredParams, paramName, i);
paramArray[i] = (param != null ? new SqlParameterValue(param, value) : value);
paramArray[i] = (param != null ? new SqlParameterValue(param, paramSource.getValue(paramName)) :
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
}
catch (IllegalArgumentException ex) {
throw new InvalidDataAccessApiUsageException(
@@ -92,17 +92,13 @@ public abstract class SqlParameterSourceUtils {
* @param source the source of parameter values and type information
* @param parameterName the name of the parameter
* @return the value object
* @see SqlParameterValue
*/
@Nullable
public static Object getTypedValue(SqlParameterSource source, String parameterName) {
int sqlType = source.getSqlType(parameterName);
if (sqlType != SqlParameterSource.TYPE_UNKNOWN) {
if (source.getTypeName(parameterName) != null) {
return new SqlParameterValue(sqlType, source.getTypeName(parameterName), source.getValue(parameterName));
}
else {
return new SqlParameterValue(sqlType, source.getValue(parameterName));
}
return new SqlParameterValue(sqlType, source.getTypeName(parameterName), source.getValue(parameterName));
}
else {
return source.getValue(parameterName);
@@ -561,10 +561,11 @@ public class NamedParameterJdbcTemplateTests {
@Test
public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception {
SqlParameterSource[] ids = new SqlParameterSource[2];
ids[0] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC);
ids[1] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC);
final int[] rowsAffected = new int[] {1, 2};
SqlParameterSource[] ids = new SqlParameterSource[3];
ids[0] = new MapSqlParameterSource().addValue("id", null, Types.NULL);
ids[1] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC);
ids[2] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC);
final int[] rowsAffected = new int[] {1, 2, 3};
given(preparedStatement.executeBatch()).willReturn(rowsAffected);
given(connection.getMetaData()).willReturn(databaseMetaData);
@@ -572,13 +573,15 @@ public class NamedParameterJdbcTemplateTests {
int[] actualRowsAffected = namedParameterTemplate.batchUpdate(
"UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids);
assertThat(actualRowsAffected.length == 2).as("executed 2 updates").isTrue();
assertThat(actualRowsAffected.length == 3).as("executed 3 updates").isTrue();
assertThat(actualRowsAffected[0]).isEqualTo(rowsAffected[0]);
assertThat(actualRowsAffected[1]).isEqualTo(rowsAffected[1]);
assertThat(actualRowsAffected[2]).isEqualTo(rowsAffected[2]);
verify(connection).prepareStatement("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?");
verify(preparedStatement).setNull(1, Types.NULL);
verify(preparedStatement).setObject(1, 100, Types.NUMERIC);
verify(preparedStatement).setObject(1, 200, Types.NUMERIC);
verify(preparedStatement, times(2)).addBatch();
verify(preparedStatement, times(3)).addBatch();
verify(preparedStatement, atLeastOnce()).close();
verify(connection, atLeastOnce()).close();
}
@@ -94,6 +94,7 @@ public class KotlinSerializationJsonMessageConverter extends AbstractJsonMessage
* Tries to find a serializer that can marshall or unmarshall instances of the given type
* using kotlinx.serialization. If no serializer can be found, an exception is thrown.
* <p>Resolved serializers are cached and cached results are returned on successive calls.
* TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed
* @param type the type to find a serializer for
* @return a resolved serializer for the given type
* @throws RuntimeException if no serializer supporting the given type can be found
@@ -31,6 +31,8 @@ import io.rsocket.Payload;
import io.rsocket.metadata.CompositeMetadata;
import io.rsocket.metadata.RoutingMetadata;
import io.rsocket.metadata.WellKnownMimeType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
@@ -52,6 +54,9 @@ import org.springframework.util.MimeType;
*/
public class DefaultMetadataExtractor implements MetadataExtractor, MetadataExtractorRegistry {
private static final Log logger = LogFactory.getLog(DefaultMetadataExtractor.class);
private final List<Decoder<?>> decoders;
private final Map<String, EntryExtractor<?>> registrations = new HashMap<>();
@@ -119,6 +124,10 @@ public class DefaultMetadataExtractor implements MetadataExtractor, MetadataExtr
else {
extractEntry(payload.metadata().slice(), metadataMimeType.toString(), result);
}
if (logger.isDebugEnabled()) {
logger.debug("Values extracted from metadata: " + result +
" with registrations for " + this.registrations.keySet() + ".");
}
return result;
}
@@ -175,7 +184,7 @@ public class DefaultMetadataExtractor implements MetadataExtractor, MetadataExtr
@Override
public String toString() {
return "mimeType=" + this.mimeType + ", targetType=" + this.targetType;
return "\"" + this.mimeType + "\" => " + this.targetType;
}
}
@@ -71,18 +71,18 @@ import org.springframework.validation.Validator;
* Provides essential configuration for handling messages with simple messaging
* protocols such as STOMP.
*
* <p>{@link #clientInboundChannel()} and {@link #clientOutboundChannel()} deliver
* <p>{@link #clientInboundChannel(TaskExecutor)} and {@link #clientOutboundChannel(TaskExecutor)} deliver
* messages to and from remote clients to several message handlers such as the
* following.
* <ul>
* <li>{@link #simpAnnotationMethodMessageHandler()}</li>
* <li>{@link #simpleBrokerMessageHandler()}</li>
* <li>{@link #stompBrokerRelayMessageHandler()}</li>
* <li>{@link #userDestinationMessageHandler()}</li>
* <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>
* </ul>
*
* <p>{@link #brokerChannel()} delivers messages from within the application to the
* the respective message handlers. {@link #brokerMessagingTemplate()} can be injected
* <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
* into any application component to send messages.
*
* <p>Subclasses are responsible for the parts of the configuration that feed messages
@@ -90,6 +90,7 @@ import org.springframework.validation.Validator;
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sebastien Deleuze
* @since 4.0
*/
public abstract class AbstractMessageBrokerConfiguration implements ApplicationContextAware {
@@ -147,8 +148,8 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public AbstractSubscribableChannel clientInboundChannel() {
ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientInboundChannelExecutor());
public AbstractSubscribableChannel clientInboundChannel(TaskExecutor clientInboundChannelExecutor) {
ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientInboundChannelExecutor);
channel.setLogger(SimpLogging.forLog(channel.getLogger()));
ChannelRegistration reg = getClientInboundChannelRegistration();
if (reg.hasInterceptors()) {
@@ -183,8 +184,8 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
@Bean
public AbstractSubscribableChannel clientOutboundChannel() {
ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientOutboundChannelExecutor());
public AbstractSubscribableChannel clientOutboundChannel(TaskExecutor clientOutboundChannelExecutor) {
ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientOutboundChannelExecutor);
channel.setLogger(SimpLogging.forLog(channel.getLogger()));
ChannelRegistration reg = getClientOutboundChannelRegistration();
if (reg.hasInterceptors()) {
@@ -219,10 +220,11 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
@Bean
public AbstractSubscribableChannel brokerChannel() {
ChannelRegistration reg = getBrokerRegistry().getBrokerChannelRegistration();
public AbstractSubscribableChannel brokerChannel(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, TaskExecutor brokerChannelExecutor) {
ChannelRegistration reg = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getBrokerChannelRegistration();
ExecutorSubscribableChannel channel = (reg.hasTaskExecutor() ?
new ExecutorSubscribableChannel(brokerChannelExecutor()) : new ExecutorSubscribableChannel());
new ExecutorSubscribableChannel(brokerChannelExecutor) : new ExecutorSubscribableChannel());
reg.interceptors(new ImmutableMessageChannelInterceptor());
channel.setLogger(SimpLogging.forLog(channel.getLogger()));
channel.setInterceptors(reg.getInterceptors());
@@ -230,8 +232,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
@Bean
public TaskExecutor brokerChannelExecutor() {
ChannelRegistration reg = getBrokerRegistry().getBrokerChannelRegistration();
public TaskExecutor brokerChannelExecutor(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
ChannelRegistration reg = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getBrokerChannelRegistration();
ThreadPoolTaskExecutor executor;
if (reg.hasTaskExecutor()) {
executor = reg.taskExecutor().getTaskExecutor();
@@ -251,9 +254,10 @@ 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() {
protected final MessageBrokerRegistry getBrokerRegistry(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
if (this.brokerRegistry == null) {
MessageBrokerRegistry registry = new MessageBrokerRegistry(clientInboundChannel(), clientOutboundChannel());
MessageBrokerRegistry registry = new MessageBrokerRegistry(clientInboundChannel, clientOutboundChannel);
configureMessageBroker(registry);
this.brokerRegistry = registry;
}
@@ -272,15 +276,20 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
* configuration classes.
*/
@Nullable
public final PathMatcher getPathMatcher() {
return getBrokerRegistry().getPathMatcher();
public final PathMatcher getPathMatcher(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
return getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getPathMatcher();
}
@Bean
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler();
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
handler.setMessageConverter(brokerMessageConverter());
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
SimpMessagingTemplate brokerMessagingTemplate, CompositeMessageConverter brokerMessageConverter) {
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler(clientInboundChannel,
clientOutboundChannel, brokerMessagingTemplate);
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
handler.setDestinationPrefixes(brokerRegistry.getApplicationDestinationPrefixes());
handler.setMessageConverter(brokerMessageConverter);
handler.setValidator(simpValidator());
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
@@ -291,7 +300,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
addReturnValueHandlers(returnValueHandlers);
handler.setCustomReturnValueHandlers(returnValueHandlers);
PathMatcher pathMatcher = getBrokerRegistry().getPathMatcher();
PathMatcher pathMatcher = brokerRegistry.getPathMatcher();
if (pathMatcher != null) {
handler.setPathMatcher(pathMatcher);
}
@@ -302,11 +311,12 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
* Protected method for plugging in a custom subclass of
* {@link org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler
* SimpAnnotationMethodMessageHandler}.
* @since 4.2
* @since 5.3.2
*/
protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler() {
return new SimpAnnotationMethodMessageHandler(clientInboundChannel(),
clientOutboundChannel(), brokerMessagingTemplate());
protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler(
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
SimpMessagingTemplate brokerMessagingTemplate) {
return new SimpAnnotationMethodMessageHandler(clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
}
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
@@ -317,48 +327,56 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
@Nullable
public AbstractBrokerMessageHandler simpleBrokerMessageHandler() {
SimpleBrokerMessageHandler handler = getBrokerRegistry().getSimpleBroker(brokerChannel());
public AbstractBrokerMessageHandler simpleBrokerMessageHandler(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
UserDestinationResolver userDestinationResolver) {
SimpleBrokerMessageHandler handler = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getSimpleBroker(brokerChannel);
if (handler == null) {
return null;
}
updateUserDestinationResolver(handler);
updateUserDestinationResolver(handler, userDestinationResolver);
return handler;
}
private void updateUserDestinationResolver(AbstractBrokerMessageHandler handler) {
private void updateUserDestinationResolver(AbstractBrokerMessageHandler handler, UserDestinationResolver userDestinationResolver) {
Collection<String> prefixes = handler.getDestinationPrefixes();
if (!prefixes.isEmpty() && !prefixes.iterator().next().startsWith("/")) {
((DefaultUserDestinationResolver) userDestinationResolver()).setRemoveLeadingSlash(true);
((DefaultUserDestinationResolver) userDestinationResolver).setRemoveLeadingSlash(true);
}
}
@Bean
@Nullable
public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler() {
StompBrokerRelayMessageHandler handler = getBrokerRegistry().getStompBrokerRelay(brokerChannel());
public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
UserDestinationMessageHandler userDestinationMessageHandler, @Nullable MessageHandler userRegistryMessageHandler,
UserDestinationResolver userDestinationResolver) {
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
StompBrokerRelayMessageHandler handler = brokerRegistry.getStompBrokerRelay(brokerChannel);
if (handler == null) {
return null;
}
Map<String, MessageHandler> subscriptions = new HashMap<>(4);
String destination = getBrokerRegistry().getUserDestinationBroadcast();
String destination = brokerRegistry.getUserDestinationBroadcast();
if (destination != null) {
subscriptions.put(destination, userDestinationMessageHandler());
subscriptions.put(destination, userDestinationMessageHandler);
}
destination = getBrokerRegistry().getUserRegistryBroadcast();
destination = brokerRegistry.getUserRegistryBroadcast();
if (destination != null) {
subscriptions.put(destination, userRegistryMessageHandler());
subscriptions.put(destination, userRegistryMessageHandler);
}
handler.setSystemSubscriptions(subscriptions);
updateUserDestinationResolver(handler);
updateUserDestinationResolver(handler, userDestinationResolver);
return handler;
}
@Bean
public UserDestinationMessageHandler userDestinationMessageHandler() {
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(clientInboundChannel(),
brokerChannel(), userDestinationResolver());
String destination = getBrokerRegistry().getUserDestinationBroadcast();
public UserDestinationMessageHandler userDestinationMessageHandler(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
UserDestinationResolver userDestinationResolver) {
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(clientInboundChannel,
brokerChannel, userDestinationResolver);
String destination = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationBroadcast();
if (destination != null) {
handler.setBroadcastDestination(destination);
}
@@ -367,15 +385,17 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
@Nullable
public MessageHandler userRegistryMessageHandler() {
if (getBrokerRegistry().getUserRegistryBroadcast() == null) {
public MessageHandler userRegistryMessageHandler(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, SimpUserRegistry userRegistry,
SimpMessagingTemplate brokerMessagingTemplate, TaskScheduler messageBrokerTaskScheduler) {
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
if (brokerRegistry.getUserRegistryBroadcast() == null) {
return null;
}
SimpUserRegistry userRegistry = userRegistry();
Assert.isInstanceOf(MultiServerUserRegistry.class, userRegistry, "MultiServerUserRegistry required");
return new UserRegistryMessageHandler((MultiServerUserRegistry) userRegistry,
brokerMessagingTemplate(), getBrokerRegistry().getUserRegistryBroadcast(),
messageBrokerTaskScheduler());
brokerMessagingTemplate, brokerRegistry.getUserRegistryBroadcast(),
messageBrokerTaskScheduler);
}
// Expose alias for 4.1 compatibility
@@ -389,13 +409,15 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
@Bean
public SimpMessagingTemplate brokerMessagingTemplate() {
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel());
String prefix = getBrokerRegistry().getUserDestinationPrefix();
public SimpMessagingTemplate brokerMessagingTemplate(AbstractSubscribableChannel brokerChannel,
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
CompositeMessageConverter brokerMessageConverter) {
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel);
String prefix = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationPrefix();
if (prefix != null) {
template.setUserDestinationPrefix(prefix);
}
template.setMessageConverter(brokerMessageConverter());
template.setMessageConverter(brokerMessageConverter);
return template;
}
@@ -441,9 +463,10 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
@Bean
public UserDestinationResolver userDestinationResolver() {
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userRegistry());
String prefix = getBrokerRegistry().getUserDestinationPrefix();
public UserDestinationResolver userDestinationResolver(SimpUserRegistry userRegistry,
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userRegistry);
String prefix = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationPrefix();
if (prefix != null) {
resolver.setUserDestinationPrefix(prefix);
}
@@ -452,12 +475,14 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
@SuppressWarnings("deprecation")
public SimpUserRegistry userRegistry() {
public SimpUserRegistry userRegistry(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel) {
SimpUserRegistry registry = createLocalUserRegistry();
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
if (registry == null) {
registry = createLocalUserRegistry(getBrokerRegistry().getUserRegistryOrder());
registry = createLocalUserRegistry(brokerRegistry.getUserRegistryOrder());
}
boolean broadcast = getBrokerRegistry().getUserRegistryBroadcast() != null;
boolean broadcast = brokerRegistry.getUserRegistryBroadcast() != null;
return (broadcast ? new MultiServerUserRegistry(registry) : registry);
}
@@ -378,13 +378,14 @@ public class MessageHeaderAccessor {
* {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values.
*/
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy != null) {
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeader(key, value);
}
});
if (headersToCopy == null || this.headers == headersToCopy) {
return;
}
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeader(key, value);
}
});
}
/**
@@ -392,13 +393,14 @@ public class MessageHeaderAccessor {
* <p>This operation will <em>not</em> overwrite any existing values.
*/
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy != null) {
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeaderIfAbsent(key, value);
}
});
if (headersToCopy == null || this.headers == headersToCopy) {
return;
}
headersToCopy.forEach((key, value) -> {
if (!isReadOnly(key)) {
setHeaderIfAbsent(key, value);
}
});
}
protected boolean isReadOnly(String headerName) {
@@ -75,6 +75,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
@SuppressWarnings("unchecked")
Map<String, List<String>> map = (Map<String, List<String>>) getHeader(NATIVE_HEADERS);
if (map != null) {
// setHeader checks for equality but we need copy of native headers
setHeader(NATIVE_HEADERS, null);
setHeader(NATIVE_HEADERS, new LinkedMultiValueMap<>(map));
}
}
@@ -103,6 +105,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
if (isMutable()) {
Map<String, List<String>> map = getNativeHeaders();
if (map != null) {
// setHeader checks for equality but we need immutable wrapper
setHeader(NATIVE_HEADERS, null);
setHeader(NATIVE_HEADERS, Collections.unmodifiableMap(map));
}
super.setImmutable();
@@ -110,31 +114,34 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
}
@Override
public void setHeader(String name, @Nullable Object value) {
if (name.equalsIgnoreCase(NATIVE_HEADERS)) {
// Force removal since setHeader checks for equality
super.setHeader(NATIVE_HEADERS, null);
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy == null) {
return;
}
super.setHeader(name, value);
@SuppressWarnings("unchecked")
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
if (map != null && map != getNativeHeaders()) {
map.forEach(this::setNativeHeaderValues);
}
// setHeader checks for equality, native headers should be equal by now
super.copyHeaders(headersToCopy);
}
@Override
@SuppressWarnings("unchecked")
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy != null) {
Map<String, List<String>> nativeHeaders = getNativeHeaders();
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
if (map != null) {
if (nativeHeaders != null) {
nativeHeaders.putAll(map);
}
else {
nativeHeaders = new LinkedMultiValueMap<>(map);
}
}
super.copyHeaders(headersToCopy);
setHeader(NATIVE_HEADERS, nativeHeaders);
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
if (headersToCopy == null) {
return;
}
@SuppressWarnings("unchecked")
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
if (map != null && getNativeHeaders() == null) {
map.forEach(this::setNativeHeaderValues);
}
super.copyHeadersIfAbsent(headersToCopy);
}
/**
@@ -201,6 +208,30 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
}
}
/**
* Variant of {@link #addNativeHeader(String, String)} for all values.
* @since 5.2.12
*/
public void setNativeHeaderValues(String name, @Nullable List<String> values) {
Assert.state(isMutable(), "Already immutable");
Map<String, List<String>> map = getNativeHeaders();
if (values == null) {
if (map != null && map.get(name) != null) {
setModified(true);
map.remove(name);
}
return;
}
if (map == null) {
map = new LinkedMultiValueMap<>(3);
setHeader(NATIVE_HEADERS, map);
}
if (!ObjectUtils.nullSafeEquals(values, getHeader(name))) {
setModified(true);
map.put(name, new ArrayList<>(values));
}
}
/**
* Add the specified native header value to existing values.
* <p>In order for this to work, the accessor must be {@link #isMutable()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.task.TaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -594,19 +595,20 @@ public class MessageBrokerConfigurationTests {
@Override
@Bean
public AbstractSubscribableChannel clientInboundChannel() {
public AbstractSubscribableChannel clientInboundChannel(TaskExecutor clientInboundChannelExecutor) {
return new TestChannel();
}
@Override
@Bean
public AbstractSubscribableChannel clientOutboundChannel() {
public AbstractSubscribableChannel clientOutboundChannel(TaskExecutor clientOutboundChannelExecutor) {
return new TestChannel();
}
@Override
@Bean
public AbstractSubscribableChannel brokerChannel() {
public AbstractSubscribableChannel brokerChannel(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, TaskExecutor brokerChannelExecutor) {
return new TestChannel();
}
}
@@ -680,20 +682,21 @@ public class MessageBrokerConfigurationTests {
@Override
@Bean
public AbstractSubscribableChannel clientInboundChannel() {
public AbstractSubscribableChannel clientInboundChannel(TaskExecutor clientInboundChannelExecutor) {
// synchronous
return new ExecutorSubscribableChannel(null);
}
@Override
@Bean
public AbstractSubscribableChannel clientOutboundChannel() {
public AbstractSubscribableChannel clientOutboundChannel(TaskExecutor clientOutboundChannelExecutor) {
return new TestChannel();
}
@Override
@Bean
public AbstractSubscribableChannel brokerChannel() {
public AbstractSubscribableChannel brokerChannel(AbstractSubscribableChannel clientInboundChannel,
AbstractSubscribableChannel clientOutboundChannel, TaskExecutor brokerChannelExecutor) {
// synchronous
return new ExecutorSubscribableChannel(null);
}
@@ -226,19 +226,46 @@ public class NativeMessageHeaderAccessorTests {
@Test // gh-25821
void copyImmutableToMutable() {
NativeMessageHeaderAccessor source = new NativeMessageHeaderAccessor();
source.addNativeHeader("foo", "bar");
Message<String> message = MessageBuilder.createMessage("payload", source.getMessageHeaders());
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
sourceAccessor.addNativeHeader("foo", "bar");
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
NativeMessageHeaderAccessor target = new NativeMessageHeaderAccessor();
target.copyHeaders(message.getHeaders());
target.setLeaveMutable(true);
message = MessageBuilder.createMessage(message.getPayload(), target.getMessageHeaders());
NativeMessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
targetAccessor.copyHeaders(source.getHeaders());
targetAccessor.setLeaveMutable(true);
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(message);
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
assertThat(accessor.isMutable());
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
}
@Test // gh-25821
void copyIfAbsentImmutableToMutable() {
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
sourceAccessor.addNativeHeader("foo", "bar");
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
MessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
targetAccessor.copyHeadersIfAbsent(source.getHeaders());
targetAccessor.setLeaveMutable(true);
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
assertThat(accessor.isMutable());
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
}
@Test // gh-26155
void copySelf() {
NativeMessageHeaderAccessor accessor = new NativeMessageHeaderAccessor();
accessor.addNativeHeader("foo", "bar");
accessor.setHeader("otherHeader", "otherHeaderValue");
accessor.setLeaveMutable(true);
// Does not fail with ConcurrentModificationException
accessor.copyHeaders(accessor.getMessageHeaders());
}
}
@@ -418,10 +418,13 @@ public abstract class AbstractEntityManagerFactoryBean implements
String message = ex.getMessage();
String causeString = cause.toString();
if (!message.endsWith(causeString)) {
throw new PersistenceException(message + "; nested exception is " + causeString, cause);
ex = new PersistenceException(message + "; nested exception is " + causeString, cause);
}
}
}
if (logger.isErrorEnabled()) {
logger.error("Failed to initialize JPA EntityManagerFactory: " + ex.getMessage());
}
throw ex;
}
@@ -48,9 +48,9 @@ import org.springframework.lang.Nullable;
* EntityManager. Developed and tested against Hibernate 5.3 and 5.4;
* backwards-compatible with Hibernate 5.2 at runtime on a best-effort basis.
*
* <p>Exposes Hibernate's persistence provider and EntityManager extension interface,
* and adapts {@link AbstractJpaVendorAdapter}'s common configuration settings.
* Also supports the detection of annotated packages (through
* <p>Exposes Hibernate's persistence provider and Hibernate's Session as extended
* EntityManager interface, and adapts {@link AbstractJpaVendorAdapter}'s common
* configuration settings. Also supports the detection of annotated packages (through
* {@link org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo#getManagedPackages()}),
* e.g. containing Hibernate {@link org.hibernate.annotations.FilterDef} annotations,
* along with Spring-driven entity scanning which requires no {@code persistence.xml}
@@ -82,8 +82,8 @@ public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
public HibernateJpaVendorAdapter() {
this.persistenceProvider = new SpringHibernateJpaPersistenceProvider();
this.entityManagerFactoryInterface = SessionFactory.class;
this.entityManagerInterface = Session.class;
this.entityManagerFactoryInterface = SessionFactory.class; // as of Spring 5.3
this.entityManagerInterface = Session.class; // as of Spring 5.3
}
@@ -41,6 +41,11 @@ import java.lang.annotation.Target;
* is resolved. Typically, method references are used to supply values, as in the
* example below.
*
* <p>As of Spring Framework 5.3.2, dynamic properties from methods annotated with
* {@code @DynamicPropertySource} will be <em>inherited</em> from enclosing test
* classes, analogous to inheritance from superclasses and interfaces. See
* {@link NestedTestConfiguration @NestedTestConfiguration} for details.
*
* <p><strong>NOTE</strong>: if you use {@code @DynamicPropertySource} in a base
* class and discover that tests in subclasses fail because the dynamic properties
* change between subclasses, you may need to annotate your base class with
@@ -74,6 +74,7 @@ import org.springframework.lang.Nullable;
* <li>{@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}</li>
* <li>{@link ActiveProfiles @ActiveProfiles}</li>
* <li>{@link TestPropertySource @TestPropertySource}</li>
* <li>{@link DynamicPropertySource @DynamicPropertySource}</li>
* <li>{@link org.springframework.test.annotation.DirtiesContext @DirtiesContext}</li>
* <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
* <li>{@link org.springframework.test.annotation.Rollback @Rollback}</li>
@@ -16,11 +16,19 @@
package org.springframework.test.context.junit.jupiter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
@@ -33,15 +41,22 @@ import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.platform.commons.annotation.Testable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.ParameterResolutionDelegate;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestConstructor;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.support.PropertyProvider;
import org.springframework.test.context.support.TestConstructorUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* {@code SpringExtension} integrates the <em>Spring TestContext Framework</em>
@@ -64,10 +79,29 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
ParameterResolver {
/**
* {@link Namespace} in which {@code TestContextManagers} are stored,
* keyed by test class.
* {@link Namespace} in which {@code TestContextManagers} are stored, keyed
* by test class.
*/
private static final Namespace NAMESPACE = Namespace.create(SpringExtension.class);
private static final Namespace TEST_CONTEXT_MANAGER_NAMESPACE = Namespace.create(SpringExtension.class);
/**
* {@link Namespace} in which {@code @Autowired} validation error messages
* are stored, keyed by test class.
*/
private static final Namespace AUTOWIRED_VALIDATION_NAMESPACE = Namespace.create(SpringExtension.class.getName() +
"#autowired.validation");
private static final String NO_AUTOWIRED_VIOLATIONS_DETECTED = "NO AUTOWIRED VIOLATIONS DETECTED";
// Note that @Test, @TestFactory, @TestTemplate, @RepeatedTest, and @ParameterizedTest
// are all meta-annotated with @Testable.
private static final List<Class<? extends Annotation>> JUPITER_ANNOTATION_TYPES =
Arrays.asList(BeforeAll.class, AfterAll.class, BeforeEach.class, AfterEach.class, Testable.class);
private static final MethodFilter autowiredTestOrLifecycleMethodFilter =
ReflectionUtils.USER_DECLARED_METHODS
.and(method -> !Modifier.isPrivate(method.getModifiers()))
.and(SpringExtension::isAutowiredTestOrLifecycleMethod);
/**
@@ -93,12 +127,42 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
/**
* Delegates to {@link TestContextManager#prepareTestInstance}.
* <p>As of Spring Framework 5.3.2, this method also validates that test
* methods and test lifecycle methods are not annotated with
* {@link Autowired @Autowired}.
*/
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
validateAutowiredConfig(context);
getTestContextManager(context).prepareTestInstance(testInstance);
}
/**
* Validate that test methods and test lifecycle methods in the supplied
* test class are not annotated with {@link Autowired @Autowired}.
* @since 5.3.2
*/
private void validateAutowiredConfig(ExtensionContext context) {
// We save the result in the ExtensionContext.Store so that we don't
// re-validate all methods for the same test class multiple times.
Store store = context.getStore(AUTOWIRED_VALIDATION_NAMESPACE);
String errorMessage = store.getOrComputeIfAbsent(context.getRequiredTestClass(),
testClass -> {
Method[] methodsWithErrors =
ReflectionUtils.getUniqueDeclaredMethods(testClass, autowiredTestOrLifecycleMethodFilter);
return (methodsWithErrors.length == 0 ? NO_AUTOWIRED_VIOLATIONS_DETECTED :
String.format(
"Test methods and test lifecycle methods must not be annotated with @Autowired. " +
"You should instead annotate individual method parameters with @Autowired, " +
"@Qualifier, or @Value. Offending methods in test class %s: %s",
testClass.getName(), Arrays.toString(methodsWithErrors)));
}, String.class);
if (errorMessage != NO_AUTOWIRED_VIOLATIONS_DETECTED) {
throw new IllegalStateException(errorMessage);
}
}
/**
* Delegates to {@link TestContextManager#beforeTestMethod}.
*/
@@ -219,7 +283,21 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
}
private static Store getStore(ExtensionContext context) {
return context.getRoot().getStore(NAMESPACE);
return context.getRoot().getStore(TEST_CONTEXT_MANAGER_NAMESPACE);
}
private static boolean isAutowiredTestOrLifecycleMethod(Method method) {
MergedAnnotations mergedAnnotations =
MergedAnnotations.from(method, SearchStrategy.DIRECT, RepeatableContainers.none());
if (!mergedAnnotations.isPresent(Autowired.class)) {
return false;
}
for (Class<? extends Annotation> annotationType : JUPITER_ANNOTATION_TYPES) {
if (mergedAnnotations.isPresent(annotationType)) {
return true;
}
}
return false;
}
}
@@ -17,6 +17,7 @@
package org.springframework.test.context.support;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -26,12 +27,14 @@ import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
* {@link ContextCustomizerFactory} to support
* {@link DynamicPropertySource @DynamicPropertySource} methods.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2.5
* @see DynamicPropertiesContextCustomizer
*/
@@ -42,13 +45,21 @@ class DynamicPropertiesContextCustomizerFactory implements ContextCustomizerFact
public DynamicPropertiesContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
Set<Method> methods = MethodIntrospector.selectMethods(testClass, this::isAnnotated);
Set<Method> methods = new LinkedHashSet<>();
findMethods(testClass, methods);
if (methods.isEmpty()) {
return null;
}
return new DynamicPropertiesContextCustomizer(methods);
}
private void findMethods(Class<?> testClass, Set<Method> methods) {
methods.addAll(MethodIntrospector.selectMethods(testClass, this::isAnnotated));
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
findMethods(testClass.getEnclosingClass(), methods);
}
}
private boolean isAnnotated(Method method) {
return MergedAnnotations.from(method).isPresent(DynamicPropertySource.class);
}
@@ -22,7 +22,6 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -46,6 +45,7 @@ import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.util.StringUtils;
/**
@@ -462,9 +462,9 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* as well as annotated interface default methods
*/
private List<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationType) {
return Arrays.stream(ReflectionUtils.getUniqueDeclaredMethods(clazz, ReflectionUtils.USER_DECLARED_METHODS))
.filter(method -> AnnotatedElementUtils.hasAnnotation(method, annotationType))
.collect(Collectors.toList());
MethodFilter methodFilter = ReflectionUtils.USER_DECLARED_METHODS
.and(method -> AnnotatedElementUtils.hasAnnotation(method, annotationType));
return Arrays.asList(ReflectionUtils.getUniqueDeclaredMethods(clazz, methodFilter));
}
}
@@ -16,6 +16,7 @@
package org.springframework.test.web.servlet.request;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
@@ -24,12 +25,14 @@ import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.Part;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.mock.web.MockMultipartHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -119,7 +122,7 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque
if (parent instanceof MockMultipartHttpServletRequestBuilder) {
MockMultipartHttpServletRequestBuilder parentBuilder = (MockMultipartHttpServletRequestBuilder) parent;
this.files.addAll(parentBuilder.files);
parentBuilder.parts.keySet().stream().forEach(name ->
parentBuilder.parts.keySet().forEach(name ->
this.parts.putIfAbsent(name, parentBuilder.parts.get(name)));
}
@@ -138,9 +141,26 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque
@Override
protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(servletContext);
this.files.forEach(request::addFile);
this.files.forEach(file -> request.addPart(toMockPart(file)));
this.parts.values().stream().flatMap(Collection::stream).forEach(request::addPart);
return request;
}
private MockPart toMockPart(MockMultipartFile file) {
byte[] bytes = null;
if (!file.isEmpty()) {
try {
bytes = file.getBytes();
}
catch (IOException ex) {
throw new IllegalStateException("Unexpected IOException", ex);
}
}
MockPart part = new MockPart(file.getName(), file.getOriginalFilename(), bytes);
if (file.getContentType() != null) {
part.getHeaders().set(HttpHeaders.CONTENT_TYPE, file.getContentType());
}
return part;
}
}
@@ -60,7 +60,7 @@ class ModelResultMatchersDsl internal constructor (private val actions: ResultAc
/**
* @see ModelResultMatchers.attributeErrorCount
*/
fun <T> attributeErrorCount(name: String, expectedCount: Int) {
fun attributeErrorCount(name: String, expectedCount: Int) {
actions.andExpect(matchers.attributeErrorCount(name, expectedCount))
}
@@ -0,0 +1,308 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.junit.jupiter;
import java.util.stream.Stream;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Event;
import org.junit.platform.testkit.engine.EventConditions;
import org.junit.platform.testkit.engine.Events;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.allOf;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.container;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
/**
* Integration tests for {@link Autowired @Autowired} configuration errors in
* JUnit Jupiter test classes.
*
* @author Sam Brannen
* @since 5.3.2
*/
class AutowiredConfigurationErrorsIntegrationTests {
private static final String DISPLAY_NAME = "TEST";
@ParameterizedTest
@ValueSource(classes = {
StaticAutowiredBeforeAllMethod.class,
StaticAutowiredAfterAllMethod.class,
AutowiredBeforeEachMethod.class,
AutowiredAfterEachMethod.class,
AutowiredTestMethod.class,
AutowiredRepeatedTestMethod.class,
AutowiredParameterizedTestMethod.class
})
void autowiredTestMethodsTestTemplateMethodsAndLifecyleMethods(Class<?> testClass) {
testEventsFor(testClass)
.assertStatistics(stats -> stats.started(1).succeeded(0).failed(1))
.assertThatEvents().haveExactly(1,
event(testWithDisplayName(DISPLAY_NAME),
finishedWithFailure(
instanceOf(IllegalStateException.class),
message(msg -> msg.matches(".+must not be annotated with @Autowired.+")))));
}
/**
* A non-autowired test method should fail the same as an autowired test
* method in the same class, since Spring still should not autowire the
* autowired test method as a "configuration method" when JUnit attempts to
* execute the non-autowired test method.
*/
@Test
void autowiredAndNonAutowiredTestMethods() {
testEventsFor(AutowiredAndNonAutowiredTestMethods.class)
.assertStatistics(stats -> stats.started(2).succeeded(0).failed(2))
.assertThatEvents()
.haveExactly(1,
event(testWithDisplayName("autowired(TestInfo)"),
finishedWithFailure(
instanceOf(IllegalStateException.class),
message(msg -> msg.matches(".+must not be annotated with @Autowired.+")))))
.haveExactly(1,
event(testWithDisplayName("nonAutowired(TestInfo)"),
finishedWithFailure(
instanceOf(IllegalStateException.class),
message(msg -> msg.matches(".+must not be annotated with @Autowired.+")))));
}
@ParameterizedTest
@ValueSource(classes = {
NonStaticAutowiredBeforeAllMethod.class,
NonStaticAutowiredAfterAllMethod.class
})
void autowiredNonStaticClassLevelLifecyleMethods(Class<?> testClass) {
containerEventsFor(testClass)
.assertStatistics(stats -> stats.started(2).succeeded(1).failed(1))
.assertThatEvents().haveExactly(1,
event(container(),
finishedWithFailure(
instanceOf(IllegalStateException.class),
message(msg -> msg.matches(".+must not be annotated with @Autowired.+")))));
}
@Test
void autowiredTestFactoryMethod() {
containerEventsFor(AutowiredTestFactoryMethod.class)
.assertStatistics(stats -> stats.started(3).succeeded(2).failed(1))
.assertThatEvents().haveExactly(1,
event(container(),
finishedWithFailure(
instanceOf(IllegalStateException.class),
message(msg -> msg.matches(".+must not be annotated with @Autowired.+")))));
}
private Events testEventsFor(Class<?> testClass) {
return EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(testClass))
.execute()
.testEvents();
}
private Events containerEventsFor(Class<?> testClass) {
return EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(testClass))
.execute()
.containerEvents();
}
private static Condition<Event> testWithDisplayName(String displayName) {
return allOf(EventConditions.test(), EventConditions.displayName(displayName));
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class StaticAutowiredBeforeAllMethod {
@Autowired
@BeforeAll
static void beforeAll(TestInfo testInfo) {
}
@Test
@DisplayName(DISPLAY_NAME)
void test() {
}
}
@SpringJUnitConfig(Config.class)
@TestInstance(PER_CLASS)
@FailingTestCase
static class NonStaticAutowiredBeforeAllMethod {
@Autowired
@BeforeAll
void beforeAll(TestInfo testInfo) {
}
@Test
@DisplayName(DISPLAY_NAME)
void test() {
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class StaticAutowiredAfterAllMethod {
@Test
@DisplayName(DISPLAY_NAME)
void test() {
}
@AfterAll
@Autowired
static void afterAll(TestInfo testInfo) {
}
}
@SpringJUnitConfig(Config.class)
@TestInstance(PER_CLASS)
@FailingTestCase
static class NonStaticAutowiredAfterAllMethod {
@Test
@DisplayName(DISPLAY_NAME)
void test() {
}
@AfterAll
@Autowired
void afterAll(TestInfo testInfo) {
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class AutowiredBeforeEachMethod {
@Autowired
@BeforeEach
void beforeEach(TestInfo testInfo) {
}
@Test
@DisplayName(DISPLAY_NAME)
void test() {
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class AutowiredAfterEachMethod {
@Test
@DisplayName(DISPLAY_NAME)
void test() {
}
@Autowired
@AfterEach
void afterEach(TestInfo testInfo) {
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class AutowiredTestMethod {
@Autowired
@Test
@DisplayName(DISPLAY_NAME)
void test(TestInfo testInfo) {
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class AutowiredAndNonAutowiredTestMethods {
@Autowired
@Test
void autowired(TestInfo testInfo) {
}
@Test
void nonAutowired(TestInfo testInfo) {
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class AutowiredRepeatedTestMethod {
@Autowired
@RepeatedTest(value = 1, name = DISPLAY_NAME)
void test(TestInfo testInfo) {
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class AutowiredTestFactoryMethod {
@Autowired
@TestFactory
Stream<DynamicTest> testFactory(TestInfo testInfo) {
return Stream.of(dynamicTest("dynamicTest", () -> {}));
}
}
@SpringJUnitConfig(Config.class)
@FailingTestCase
static class AutowiredParameterizedTestMethod {
@Autowired
@ParameterizedTest(name = DISPLAY_NAME)
@ValueSource(strings = "ignored")
void test(TestInfo testInfo) {
}
}
@Configuration
static class Config {
}
}
@@ -0,0 +1,185 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.junit.jupiter.nested;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.NestedTestConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
/**
* Integration tests that verify support for {@code @Nested} test classes using
* {@link DynamicPropertySource @DynamicPropertySource} in conjunction with the
* {@link SpringExtension} in a JUnit Jupiter environment.
*
* @author Sam Brannen
* @since 5.3.2
*/
@SpringJUnitConfig
class DynamicPropertySourceNestedTests {
private static final String TEST_CONTAINER_IP = "DynamicPropertySourceNestedTests.test.container.ip";
private static final String TEST_CONTAINER_PORT = "DynamicPropertySourceNestedTests.test.container.port";
static DemoContainer container = new DemoContainer();
@DynamicPropertySource
static void containerProperties(DynamicPropertyRegistry registry) {
registry.add(TEST_CONTAINER_IP, container::getIpAddress);
registry.add(TEST_CONTAINER_PORT, container::getPort);
}
@Test
@DisplayName("@Service has values injected from @DynamicPropertySource")
void serviceHasInjectedValues(@Autowired Service service) {
assertServiceHasInjectedValues(service);
}
private static void assertServiceHasInjectedValues(Service service) {
assertThat(service.getIp()).isEqualTo("127.0.0.1");
assertThat(service.getPort()).isEqualTo(4242);
}
@Nested
@NestedTestConfiguration(OVERRIDE)
@SpringJUnitConfig(Config.class)
class DynamicPropertySourceFromSuperclassTests extends DynamicPropertySourceSuperclass {
@Test
@DisplayName("@Service has values injected from @DynamicPropertySource in superclass")
void serviceHasInjectedValues(@Autowired Service service) {
assertServiceHasInjectedValues(service);
}
}
@Nested
@NestedTestConfiguration(OVERRIDE)
@SpringJUnitConfig(Config.class)
class DynamicPropertySourceFromInterfaceTests implements DynamicPropertySourceInterface {
@Test
@DisplayName("@Service has values injected from @DynamicPropertySource in interface")
void serviceHasInjectedValues(@Autowired Service service) {
assertServiceHasInjectedValues(service);
}
}
@Nested
@NestedTestConfiguration(OVERRIDE)
@SpringJUnitConfig(Config.class)
class OverriddenConfigTests {
@Test
@DisplayName("@Service does not have values injected from @DynamicPropertySource in enclosing class")
void serviceHasDefaultInjectedValues(@Autowired Service service) {
assertThat(service.getIp()).isEqualTo("10.0.0.1");
assertThat(service.getPort()).isEqualTo(-999);
}
}
@Nested
class DynamicPropertySourceFromEnclosingClassTests {
@Test
@DisplayName("@Service has values injected from @DynamicPropertySource in enclosing class")
void serviceHasInjectedValues(@Autowired Service service) {
assertServiceHasInjectedValues(service);
}
@Nested
class DoubleNestedDynamicPropertySourceFromEnclosingClassTests {
@Test
@DisplayName("@Service has values injected from @DynamicPropertySource in enclosing class")
void serviceHasInjectedValues(@Autowired Service service) {
assertServiceHasInjectedValues(service);
}
}
}
static abstract class DynamicPropertySourceSuperclass {
@DynamicPropertySource
static void containerProperties(DynamicPropertyRegistry registry) {
registry.add(TEST_CONTAINER_IP, container::getIpAddress);
registry.add(TEST_CONTAINER_PORT, container::getPort);
}
}
interface DynamicPropertySourceInterface {
@DynamicPropertySource
static void containerProperties(DynamicPropertyRegistry registry) {
registry.add(TEST_CONTAINER_IP, container::getIpAddress);
registry.add(TEST_CONTAINER_PORT, container::getPort);
}
}
@Configuration
@Import(Service.class)
static class Config {
}
static class Service {
private final String ip;
private final int port;
Service(@Value("${" + TEST_CONTAINER_IP + ":10.0.0.1}") String ip, @Value("${" + TEST_CONTAINER_PORT + ":-999}") int port) {
this.ip = ip;
this.port = port;
}
String getIp() {
return this.ip;
}
int getPort() {
return this.port;
}
}
static class DemoContainer {
String getIpAddress() {
return "127.0.0.1";
}
int getPort() {
return 4242;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,21 +16,43 @@
package org.springframework.test.web.servlet.request;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.Part;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.mock.web.MockPart;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link MockMultipartHttpServletRequestBuilder}.
* @author Rossen Stoyanchev
*/
public class MockMultipartHttpServletRequestBuilderTests {
@Test // gh-26166
void addFilesAndParts() throws Exception {
MockHttpServletRequest mockRequest = new MockMultipartHttpServletRequestBuilder("/upload")
.file(new MockMultipartFile("file", "test.txt", "text/plain", "Test".getBytes(StandardCharsets.UTF_8)))
.part(new MockPart("data", "{\"node\":\"node\"}".getBytes(StandardCharsets.UTF_8)))
.buildRequest(new MockServletContext());
StandardMultipartHttpServletRequest parsedRequest = new StandardMultipartHttpServletRequest(mockRequest);
assertThat(parsedRequest.getParameterMap()).containsOnlyKeys("data");
assertThat(parsedRequest.getFileMap()).containsOnlyKeys("file");
assertThat(parsedRequest.getParts()).extracting(Part::getName).containsExactly("file", "data");
}
@Test
public void test() {
void mergeAndBuild() {
MockHttpServletRequestBuilder parent = new MockHttpServletRequestBuilder(HttpMethod.GET, "/");
parent.characterEncoding("UTF-8");
Object result = new MockMultipartHttpServletRequestBuilder("/fileUpload").merge(parent);
@@ -38,16 +38,25 @@ import org.springframework.transaction.TransactionDefinition;
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
* class, and in fact {@link AnnotationTransactionAttributeSource} will directly
* convert the data to the latter class, so that Spring's transaction support code
* does not have to know about annotations. If no rules are relevant to the exception,
* it will be treated like
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
* (rolling back on {@link RuntimeException} and {@link Error} but not on checked
* exceptions).
* does not have to know about annotations. If no custom rollback rules apply,
* the transaction will roll back on {@link RuntimeException} and {@link Error}
* but not on checked exceptions.
*
* <p>For specific information about the semantics of this annotation's attributes,
* consult the {@link org.springframework.transaction.TransactionDefinition} and
* {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
*
* <p>This annotation commonly works with thread-bound transactions managed by
* {@link org.springframework.transaction.PlatformTransactionManager}, exposing a
* transaction to all data access operations within the current execution thread.
* <b>Note: This does NOT propagate to newly started threads within the method.</b>
*
* <p>Alternatively, this annotation may demarcate a reactive transaction managed
* by {@link org.springframework.transaction.ReactiveTransactionManager} which
* uses the Reactor context instead of thread-local attributes. As a consequence,
* all participating data access operations need to execute within the same
* Reactor context in the same reactive pipeline.
*
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @author Sam Brannen
@@ -28,7 +28,7 @@ import org.springframework.lang.Nullable;
/**
* TransactionAttribute implementation that works out whether a given exception
* should cause transaction rollback by applying a number of rollback rules,
* both positive and negative. If no rules are relevant to the exception, it
* both positive and negative. If no custom rollback rules apply, this attribute
* behaves like DefaultTransactionAttribute (rolling back on runtime exceptions).
*
* <p>{@link TransactionAttributeEditor} creates objects of this class.
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.CoroutinesUtils;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.NamedThreadLocal;
@@ -342,9 +343,16 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
boolean hasSuspendingFlowReturnType = isSuspendingFunction && COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());
boolean hasSuspendingFlowReturnType = isSuspendingFunction &&
COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());
if (isSuspendingFunction && !(invocation instanceof CoroutinesInvocationCallback)) {
throw new IllegalStateException("Coroutines invocation not supported: " + method);
}
CoroutinesInvocationCallback corInv = (isSuspendingFunction ? (CoroutinesInvocationCallback) invocation : null);
ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
Class<?> reactiveType = (isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType());
Class<?> reactiveType =
(isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType());
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType);
if (adapter == null) {
throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
@@ -352,9 +360,18 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
}
return new ReactiveTransactionSupport(adapter);
});
Object result = txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
return (isSuspendingFunction ? (hasSuspendingFlowReturnType ? KotlinDelegate.asFlow((Publisher<?>) result) :
KotlinDelegate.awaitSingleOrNull((Publisher<?>) result, ((CoroutinesInvocationCallback) invocation).getContinuation())) : result);
InvocationCallback callback = invocation;
if (corInv != null) {
callback = () -> CoroutinesUtils.invokeSuspendingFunction(method, corInv.getTarget(), corInv.getArguments());
}
Object result = txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, (ReactiveTransactionManager) tm);
if (corInv != null) {
Publisher<?> pr = (Publisher<?>) result;
return (hasSuspendingFlowReturnType ? KotlinDelegate.asFlow(pr) :
KotlinDelegate.awaitSingleOrNull(pr, corInv.getContinuation()));
}
return result;
}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
@@ -789,9 +806,20 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
Object proceedWithInvocation() throws Throwable;
}
/**
* Coroutines-supporting extension of the callback interface.
*/
protected interface CoroutinesInvocationCallback extends InvocationCallback {
Object getContinuation();
Object getTarget();
Object[] getArguments();
default Object getContinuation() {
Object[] args = getArguments();
return args[args.length - 1];
}
}
@@ -876,7 +904,9 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// For Mono and suspending functions not returning kotlinx.coroutines.flow.Flow
if (Mono.class.isAssignableFrom(method.getReturnType()) || (KotlinDetector.isSuspendingFunction(method) && !COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()))) {
if (Mono.class.isAssignableFrom(method.getReturnType()) || (KotlinDetector.isSuspendingFunction(method) &&
!COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()))) {
return TransactionContextManager.currentContext().flatMap(context ->
createTransactionIfNecessary(rtm, txAttr, joinpointIdentification).flatMap(it -> {
try {
@@ -27,8 +27,6 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.CoroutinesUtils;
import org.springframework.core.KotlinDetector;
import org.springframework.lang.Nullable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
@@ -118,20 +116,21 @@ public class TransactionInterceptor extends TransactionAspectSupport implements
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
if (KotlinDetector.isSuspendingFunction(invocation.getMethod())) {
InvocationCallback callback = new CoroutinesInvocationCallback() {
@Override
public Object proceedWithInvocation() {
return CoroutinesUtils.invokeSuspendingFunction(invocation.getMethod(), invocation.getThis(), invocation.getArguments());
}
@Override
public Object getContinuation() {
return invocation.getArguments()[invocation.getArguments().length - 1];
}
};
return invokeWithinTransaction(invocation.getMethod(), targetClass, callback);
}
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
@@ -485,7 +485,8 @@ public final class ContentDisposition {
}
boolean escaped = false;
StringBuilder sb = new StringBuilder();
for (char c : filename.toCharArray()) {
for (int i = 0; i < filename.length() ; i++) {
char c = filename.charAt(i);
if (!escaped && c == '"') {
sb.append("\\\"");
}
@@ -514,9 +514,9 @@ public class MediaType extends MimeType implements Serializable {
@Override
protected void checkParameters(String attribute, String value) {
super.checkParameters(attribute, value);
if (PARAM_QUALITY_FACTOR.equals(attribute)) {
protected void checkParameters(String parameter, String value) {
super.checkParameters(parameter, value);
if (PARAM_QUALITY_FACTOR.equals(parameter)) {
value = unquote(value);
double d = Double.parseDouble(value);
Assert.isTrue(d >= 0D && d <= 1D,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -112,10 +112,12 @@ public final class MediaTypeFactory {
* @return the corresponding media types, or an empty list if none found
*/
public static List<MediaType> getMediaTypes(@Nullable String filename) {
return Optional.ofNullable(StringUtils.getFilenameExtension(filename))
.map(s -> s.toLowerCase(Locale.ENGLISH))
.map(fileExtensionToMediaTypes::get)
.orElse(Collections.emptyList());
List<MediaType> mediaTypes = null;
String ext = StringUtils.getFilenameExtension(filename);
if (ext != null) {
mediaTypes = fileExtensionToMediaTypes.get(ext.toLowerCase(Locale.ENGLISH));
}
return (mediaTypes != null ? mediaTypes : Collections.emptyList());
}
}
@@ -386,9 +386,8 @@ public final class ResponseCookie extends HttpCookie {
start = 1;
end--;
}
char[] chars = value.toCharArray();
for (int i = start; i < end; i++) {
char c = chars[i];
char c = value.charAt(i);
if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c || c == 0x7f) {
throw new IllegalArgumentException(
"RFC2616 cookie value cannot have '" + c + "'");
@@ -113,9 +113,18 @@ public class ResponseEntity<T> extends HttpEntity<T> {
* @param status the status code
*/
public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
super(body, headers);
Assert.notNull(status, "HttpStatus must not be null");
this.status = status;
this(body, headers, (Object) status);
}
/**
* Create a new {@code HttpEntity} with the given body, headers, and status code.
* @param body the entity body
* @param headers the entity headers
* @param rawStatus the status code value
* @since 5.3.2
*/
public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, int rawStatus) {
this(body, headers, (Object) rawStatus);
}
/**
@@ -57,6 +57,9 @@ import org.springframework.util.StringUtils;
*/
public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
private static final Log logger = HttpLogging.forLogName(EncoderHttpMessageWriter.class);
private final Encoder<T> encoder;
private final List<MediaType> mediaTypes;
@@ -125,17 +128,24 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
return message.setComplete().then(Mono.empty());
}))
.flatMap(buffer -> {
Hints.touchDataBuffer(buffer, hints, logger);
message.getHeaders().setContentLength(buffer.readableByteCount());
return message.writeWith(Mono.just(buffer)
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
});
})
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
}
if (isStreamingMediaType(contentType)) {
return message.writeAndFlushWith(body.map(buffer ->
Mono.just(buffer).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)));
return message.writeAndFlushWith(body.map(buffer -> {
Hints.touchDataBuffer(buffer, hints, logger);
return Mono.just(buffer).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
}));
}
if (logger.isDebugEnabled()) {
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
}
return message.writeWith(body);
}
@@ -166,6 +176,9 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
return main;
}
private static void touch(DataBuffer buffer, Map<String, Object> hints) {
}
private boolean isStreamingMediaType(@Nullable MediaType mediaType) {
if (mediaType == null || !(this.encoder instanceof HttpMessageEncoder)) {
return false;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -132,6 +132,9 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> {
Mono<Resource> input = Mono.just(resource);
DataBufferFactory factory = message.bufferFactory();
Flux<DataBuffer> body = this.encoder.encode(input, factory, type, resourceMediaType, hints);
if (logger.isDebugEnabled()) {
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
}
return message.writeWith(body);
});
}
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -35,6 +36,7 @@ import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.PooledDataBuffer;
import org.springframework.http.HttpLogging;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -57,6 +59,8 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
private static final List<MediaType> WRITABLE_MEDIA_TYPES = Collections.singletonList(MediaType.TEXT_EVENT_STREAM);
private static final Log logger = HttpLogging.forLogName(ServerSentEventHttpMessageWriter.class);
@Nullable
private final Encoder<?> encoder;
@@ -167,9 +171,11 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
if (this.encoder == null) {
throw new CodecException("No SSE encoder configured and the data is not String.");
}
DataBuffer buffer = ((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints);
Hints.touchDataBuffer(buffer, hints, logger);
return Flux.just(factory.join(Arrays.asList(
encodeText(eventContent, mediaType, factory),
((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints),
buffer,
encodeText("\n\n", mediaType, factory))));
}
@@ -223,6 +223,7 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
byte[] bytes = byteBuilder.toByteArray();
DataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
Hints.touchDataBuffer(buffer, hints, logger);
return buffer;
}
@@ -267,6 +268,7 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
DataBuffer buffer = bufferFactory.allocateBuffer(length + separator.length);
buffer.write(bytes, offset, length);
buffer.write(separator);
Hints.touchDataBuffer(buffer, hints, logger);
return buffer;
}
@@ -69,10 +69,38 @@ public class KotlinSerializationJsonDecoder extends AbstractDecoder<Object> {
this.json = json;
}
/**
* Configure a limit on the number of bytes that can be buffered whenever
* the input stream needs to be aggregated. This can be a result of
* decoding to a single {@code DataBuffer},
* {@link java.nio.ByteBuffer ByteBuffer}, {@code byte[]},
* {@link org.springframework.core.io.Resource Resource}, {@code String}, etc.
* It can also occur when splitting the input stream, e.g. delimited text,
* in which case the limit applies to data buffered between delimiters.
* <p>By default this is set to 256K.
* @param byteCount the max number of bytes to buffer, or -1 for unlimited
*/
public void setMaxInMemorySize(int byteCount) {
this.stringDecoder.setMaxInMemorySize(byteCount);
}
/**
* Return the {@link #setMaxInMemorySize configured} byte count limit.
*/
public int getMaxInMemorySize() {
return this.stringDecoder.getMaxInMemorySize();
}
@Override
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
return (super.canDecode(elementType, mimeType) && !CharSequence.class.isAssignableFrom(elementType.toClass()));
try {
serializer(elementType.getType());
return (super.canDecode(elementType, mimeType) && !CharSequence.class.isAssignableFrom(elementType.toClass()));
}
catch (Exception ex) {
return false;
}
}
@Override
@@ -95,6 +123,7 @@ public class KotlinSerializationJsonDecoder extends AbstractDecoder<Object> {
* Tries to find a serializer that can marshall or unmarshall instances of the given type
* using kotlinx.serialization. If no serializer can be found, an exception is thrown.
* <p>Resolved serializers are cached and cached results are returned on successive calls.
* TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed
* @param type the type to find a serializer for
* @return a resolved serializer for the given type
* @throws RuntimeException if no serializer supporting the given type can be found
@@ -71,8 +71,14 @@ public class KotlinSerializationJsonEncoder extends AbstractEncoder<Object> {
@Override
public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType) {
return (super.canEncode(elementType, mimeType) && !String.class.isAssignableFrom(elementType.toClass()) &&
!ServerSentEvent.class.isAssignableFrom(elementType.toClass()));
try {
serializer(elementType.getType());
return (super.canEncode(elementType, mimeType) && !String.class.isAssignableFrom(elementType.toClass()) &&
!ServerSentEvent.class.isAssignableFrom(elementType.toClass()));
}
catch (Exception ex) {
return false;
}
}
@Override
@@ -105,6 +111,7 @@ public class KotlinSerializationJsonEncoder extends AbstractEncoder<Object> {
* Tries to find a serializer that can marshall or unmarshall instances of the given type
* using kotlinx.serialization. If no serializer can be found, an exception is thrown.
* <p>Resolved serializers are cached and cached results are returned on successive calls.
* TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed
* @param type the type to find a serializer for
* @return a resolved serializer for the given type
* @throws RuntimeException if no serializer supporting the given type can be found
@@ -16,7 +16,6 @@
package org.springframework.http.codec.multipart;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -146,15 +145,6 @@ public class MultipartHttpMessageWriter extends MultipartWriterSupport
return this.formWriter;
}
/**
* Set the character set to use for part headers such as
* "Content-Disposition" (and its filename parameter).
* <p>By default this is set to "UTF-8".
*/
public void setCharset(Charset charset) {
Assert.notNull(charset, "Charset must not be null");
this.charset = charset;
}
@Override
@@ -209,6 +199,10 @@ public class MultipartHttpMessageWriter extends MultipartWriterSupport
.concatWith(generateLastLine(boundary, bufferFactory))
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
if (logger.isDebugEnabled()) {
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
}
return outputMessage.writeWith(body);
}
@@ -30,6 +30,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.LoggingCodecSupport;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
@@ -44,9 +45,9 @@ public class MultipartWriterSupport extends LoggingCodecSupport {
/** THe default charset used by the writer. */
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
protected final List<MediaType> supportedMediaTypes;
private final List<MediaType> supportedMediaTypes;
protected Charset charset = DEFAULT_CHARSET;
private Charset charset = DEFAULT_CHARSET;
/**
@@ -64,6 +65,18 @@ public class MultipartWriterSupport extends LoggingCodecSupport {
return this.charset;
}
/**
* Set the character set to use for part headers such as
* "Content-Disposition" (and its filename parameter).
* <p>By default this is set to "UTF-8". If changed from this default,
* the "Content-Type" header will have a "charset" parameter that specifies
* the character set used.
*/
public void setCharset(Charset charset) {
Assert.notNull(charset, "Charset must not be null");
this.charset = charset;
}
public List<MediaType> getWritableMediaTypes() {
return this.supportedMediaTypes;
}
@@ -102,7 +115,11 @@ public class MultipartWriterSupport extends LoggingCodecSupport {
params.putAll(mediaType.getParameters());
}
params.put("boundary", new String(boundary, StandardCharsets.US_ASCII));
params.put("charset", getCharset().name());
Charset charset = getCharset();
if (!charset.equals(StandardCharsets.UTF_8) &&
!charset.equals(StandardCharsets.US_ASCII) ) {
params.put("charset", getCharset().name());
}
mediaType = (mediaType != null ? mediaType : MediaType.MULTIPART_FORM_DATA);
mediaType = new MediaType(mediaType, params);
@@ -69,6 +69,10 @@ public class PartHttpMessageWriter extends MultipartWriterSupport implements Htt
.concatWith(generateLastLine(boundary, outputMessage.bufferFactory()))
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
if (logger.isDebugEnabled()) {
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
}
return outputMessage.writeWith(body);
}
@@ -312,6 +312,11 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
((ProtobufDecoder) codec).setMaxMessageSize(size);
}
}
if (kotlinSerializationJsonPresent) {
if (codec instanceof KotlinSerializationJsonDecoder) {
((KotlinSerializationJsonDecoder) codec).setMaxInMemorySize(size);
}
}
if (jackson2Present) {
if (codec instanceof AbstractJackson2Decoder) {
((AbstractJackson2Decoder) codec).setMaxInMemorySize(size);
@@ -385,12 +390,12 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
return Collections.emptyList();
}
List<HttpMessageReader<?>> readers = new ArrayList<>();
if (kotlinSerializationJsonPresent) {
addCodec(readers, new DecoderHttpMessageReader<>(getKotlinSerializationJsonDecoder()));
}
if (jackson2Present) {
addCodec(readers, new DecoderHttpMessageReader<>(getJackson2JsonDecoder()));
}
else if (kotlinSerializationJsonPresent) {
addCodec(readers, new DecoderHttpMessageReader<>(getKotlinSerializationJsonDecoder()));
}
if (jackson2SmilePresent) {
addCodec(readers, new DecoderHttpMessageReader<>(this.jackson2SmileDecoder != null ?
(Jackson2SmileDecoder) this.jackson2SmileDecoder : new Jackson2SmileDecoder()));
@@ -484,12 +489,12 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
*/
final List<HttpMessageWriter<?>> getBaseObjectWriters() {
List<HttpMessageWriter<?>> writers = new ArrayList<>();
if (kotlinSerializationJsonPresent) {
writers.add(new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
}
if (jackson2Present) {
writers.add(new EncoderHttpMessageWriter<>(getJackson2JsonEncoder()));
}
else if (kotlinSerializationJsonPresent) {
writers.add(new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
}
if (jackson2SmilePresent) {
writers.add(new EncoderHttpMessageWriter<>(this.jackson2SmileEncoder != null ?
(Jackson2SmileEncoder) this.jackson2SmileEncoder : new Jackson2SmileEncoder()));
@@ -88,6 +88,28 @@ public class KotlinSerializationJsonHttpMessageConverter extends AbstractGeneric
}
}
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
try {
serializer(GenericTypeResolver.resolveType(type, contextClass));
return canRead(mediaType);
}
catch (Exception ex) {
return false;
}
}
@Override
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
try {
serializer(type != null ? GenericTypeResolver.resolveType(type, clazz) : clazz);
return canWrite(mediaType);
}
catch (Exception ex) {
return false;
}
}
@Override
public final Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
@@ -151,6 +173,7 @@ public class KotlinSerializationJsonHttpMessageConverter extends AbstractGeneric
* Tries to find a serializer that can marshall or unmarshall instances of the given type
* using kotlinx.serialization. If no serializer can be found, an exception is thrown.
* <p>Resolved serializers are cached and cached results are returned on successive calls.
* TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed
* @param type the type to find a serializer for
* @return a resolved serializer for the given type
* @throws RuntimeException if no serializer supporting the given type can be found
@@ -232,8 +232,6 @@ final class DefaultPathContainer implements PathContainer {
private final String valueToMatch;
private final char[] valueToMatchAsChars;
private final MultiValueMap<String, String> parameters;
@@ -243,7 +241,6 @@ final class DefaultPathContainer implements PathContainer {
DefaultPathSegment(String value, String valueToMatch, MultiValueMap<String, String> params) {
this.value = value;
this.valueToMatch = valueToMatch;
this.valueToMatchAsChars = valueToMatch.toCharArray();
this.parameters = CollectionUtils.unmodifiableMultiValueMap(params);
}
@@ -254,7 +251,6 @@ final class DefaultPathContainer implements PathContainer {
this.value = value;
this.valueToMatch = value.contains(separator.encodedSequence()) ?
value.replaceAll(separator.encodedSequence(), separator.value()) : value;
this.valueToMatchAsChars = this.valueToMatch.toCharArray();
this.parameters = EMPTY_PARAMS;
}
@@ -271,7 +267,7 @@ final class DefaultPathContainer implements PathContainer {
@Override
public char[] valueToMatchAsChars() {
return this.valueToMatchAsChars;
return this.valueToMatch.toCharArray();
}
@Override
@@ -0,0 +1,79 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.http.server;
import java.io.IOException;
import java.io.OutputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
/**
* Implementation of {@code ServerHttpResponse} that delegates all calls to a
* given target {@code ServerHttpResponse}.
*
* @author Arjen Poutsma
* @since 5.3.2
*/
public class DelegatingServerHttpResponse implements ServerHttpResponse {
private final ServerHttpResponse delegate;
/**
* Create a new {@code DelegatingServerHttpResponse}.
* @param delegate the response to delegate to
*/
public DelegatingServerHttpResponse(ServerHttpResponse delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
/**
* Returns the target response that this response delegates to.
* @return the delegate
*/
public ServerHttpResponse getDelegate() {
return this.delegate;
}
@Override
public void setStatusCode(HttpStatus status) {
this.delegate.setStatusCode(status);
}
@Override
public void flush() throws IOException {
this.delegate.flush();
}
@Override
public void close() {
this.delegate.close();
}
@Override
public OutputStream getBody() throws IOException {
return this.delegate.getBody();
}
@Override
public HttpHeaders getHeaders() {
return this.delegate.getHeaders();
}
}
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
@@ -152,12 +153,14 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
@Override
@Nullable
public String getFirst(String headerName) {
String value = servletResponse.getHeader(headerName);
if (value != null) {
return value;
if (headerName.equalsIgnoreCase(CONTENT_TYPE)) {
// Content-Type is written as an override so check super first
String value = super.getFirst(headerName);
return (value != null ? value : servletResponse.getHeader(headerName));
}
else {
return super.getFirst(headerName);
String value = servletResponse.getHeader(headerName);
return (value != null ? value : super.getFirst(headerName));
}
}
@@ -165,7 +168,13 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
public List<String> get(Object key) {
Assert.isInstanceOf(String.class, key, "Key must be a String-based header name");
Collection<String> values1 = servletResponse.getHeaders((String) key);
String headerName = (String) key;
if (headerName.equalsIgnoreCase(CONTENT_TYPE)) {
// Content-Type is written as an override so don't merge
return Collections.singletonList(getFirst(headerName));
}
Collection<String> values1 = servletResponse.getHeaders(headerName);
if (headersWritten) {
return new ArrayList<>(values1);
}
@@ -69,7 +69,8 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
* Constructor with the URI and headers for the request.
* @param uri the URI for the request
* @param contextPath the context path for the request
* @param headers the headers for the request
* @param headers the headers for the request (as {@link MultiValueMap})
* @since 5.3
*/
public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, MultiValueMap<String, String> headers) {
this.uri = uri;
@@ -77,6 +78,18 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
}
/**
* Constructor with the URI and headers for the request.
* @param uri the URI for the request
* @param contextPath the context path for the request
* @param headers the headers for the request (as {@link HttpHeaders})
*/
public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, HttpHeaders headers) {
this.uri = uri;
this.path = RequestPath.parse(uri, contextPath);
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
}
@Override
public String getId() {
@@ -211,10 +211,29 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
// We must resolve value first however, for a chance to handle potential error.
if (body instanceof Mono) {
return ((Mono<? extends DataBuffer>) body)
.flatMap(buffer -> doCommit(() ->
writeWithInternal(Mono.fromCallable(() -> buffer)
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release))))
.doOnError(t -> getHeaders().clearContentHeaders());
.flatMap(buffer -> {
touchDataBuffer(buffer);
AtomicReference<Boolean> subscribed = new AtomicReference<>(false);
return doCommit(
() -> {
try {
return writeWithInternal(Mono.fromCallable(() -> buffer)
.doOnSubscribe(s -> subscribed.set(true))
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
}
catch (Throwable ex) {
return Mono.error(ex);
}
})
.doOnError(ex -> DataBufferUtils.release(buffer))
.doOnCancel(() -> {
if (!subscribed.get()) {
DataBufferUtils.release(buffer);
}
});
})
.doOnError(t -> getHeaders().clearContentHeaders())
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
}
else {
return new ChannelSendOperator<>(body, inner -> doCommit(() -> writeWithInternal(inner)))
@@ -316,4 +335,13 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
*/
protected abstract void applyCookies();
/**
* Allow sub-classes to associate a hint with the data buffer if it is a
* pooled buffer and supports leak tracking.
* @param buffer the buffer to attach a hint to
* @since 5.3.2
*/
protected void touchDataBuffer(DataBuffer buffer) {
}
}
@@ -20,6 +20,9 @@ import java.nio.file.Path;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelId;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -27,6 +30,7 @@ import reactor.netty.http.server.HttpServerResponse;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -43,6 +47,9 @@ import org.springframework.util.Assert;
*/
class ReactorServerHttpResponse extends AbstractServerHttpResponse implements ZeroCopyHttpOutputMessage {
private static final Log logger = LogFactory.getLog(ReactorServerHttpResponse.class);
private final HttpServerResponse response;
@@ -115,4 +122,14 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze
Flux.from(dataBuffers).map(NettyDataBufferFactory::toByteBuf);
}
@Override
protected void touchDataBuffer(DataBuffer buffer) {
if (logger.isDebugEnabled()) {
this.response.withConnection(connection -> {
ChannelId id = connection.channel().id();
DataBufferUtils.touch(buffer, "Channel id: " + id.asShortText());
});
}
}
}
@@ -108,7 +108,7 @@ public class ContentNegotiationManagerFactoryBean
private String parameterName = "format";
private boolean favorPathExtension = true;
private boolean favorPathExtension = false;
private Map<String, MediaType> mediaTypes = new HashMap<>();
@@ -114,6 +114,8 @@ public @interface CrossOrigin {
* {@code Expires}, {@code Last-Modified}, or {@code Pragma},
* <p>Exposed headers are listed in the {@code Access-Control-Expose-Headers}
* response header of actual CORS requests.
* <p>The special value {@code "*"} allows all headers to be exposed for
* non-credentialed requests.
* <p>By default no headers are listed as exposed.
*/
String[] exposedHeaders() default {};

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