Compare commits

...

84 Commits

Author SHA1 Message Date
Spring Buildmaster d51d8aeaf6 Release v5.3.8 2021-06-09 07:38:20 +00:00
Brian Clozel c7c35aff42 Fix missing attribute in reference documentation
This commit adds the missing `spring-framework-main-code` asciidoctor
attribute in the reference documentation.

Fixes gh-27041
2021-06-09 09:21:28 +02:00
Sam Brannen 000b6a7e95 Polish @ResponseStatus javadoc and StatusAssertionTests 2021-06-08 18:55:05 +02:00
Sam Brannen f3db6b996d Polish contribution
See gh-27031
2021-06-08 18:55:05 +02:00
Rossen Stoyanchev a42540e64a Multipart writer detects custom Jackson encoder
Closes gh-27017
2021-06-08 17:41:04 +01:00
Rossen Stoyanchev cc2b980e5c Update MockMvc default for suffixPattern matching
Closes gh-27030
2021-06-08 17:41:04 +01:00
Sviatoslav Hryb aa2bfd4b62 Document that class-level @ResponseStatus is inherited by @ExceptionHandler methods
Closes gh-27031
2021-06-08 18:39:18 +02:00
Sam Brannen 29fa9c1728 Polish contribution
See gh-27034
2021-06-08 17:52:07 +02:00
Beca-se 39660710f6 Cross-ref additional *Aware APIs in BeanFactory Javadoc
Closes gh-27034
2021-06-08 17:47:47 +02:00
Brian Clozel e35fe33b72 Revisit fix for gh-26905 in UriComponentsBuilder
This commit revisits the recently updated `PORT_PATTERN` in
`UriComponentsBuilder`. The fix introduced in gh-26905 fails with
ambiguous URL patterns, especially when the port and path parts of the
pattern are hard to differentiate, for example
"https://localhost:{port}{path}".

This commit reinstates the previous behavior without undoing the actual
fix. The only limitation introduced here is the fact that only a single
pattern variable is allowed for the port pattern part.

Fixes gh-27039
2021-06-08 15:26:13 +02:00
Juergen Hoeller 23f396a231 Nullability refinements 2021-06-08 14:23:06 +02:00
izeye 090e394fb5 Clean up @SuppressWarnings("ConstantConditions") 2021-06-08 13:47:00 +02:00
Sviatoslav Hryb 708e61a7ef Fix WebMvcConfigurationSupport javadoc 2021-06-08 12:50:22 +02:00
Gabriel e24b2e6b5d Add awaitExchangeOrNull extension function to reactive webclient
Closes gh-26778
2021-06-08 12:06:53 +02:00
Sébastien Deleuze aff0d8efe7 Upgrade to Kotlin Serialization 1.2.1 2021-06-08 11:44:03 +02:00
Sébastien Deleuze e3e4e49319 Upgrade to Kotlin 1.5.10
Closes gh-27035
2021-06-08 11:44:03 +02:00
Sébastien Deleuze 5420d7113b Polish Kotlin reference documentation 2021-06-08 11:44:03 +02:00
Sébastien Deleuze 6251222a23 Remove jackson-module-kotlin warning
Closes gh-26962
2021-06-08 11:44:03 +02:00
Brian Clozel 05eca05671 Close resources in HttpComponents client connector
Prior to this commit, the `HttpComponentsClientHttpConnector`
implementation could accept or create a default `HttpClient` instance
but not expose it as part of its API. This effectively prevents
applications from properly closing the associated resources when
disposing of the connector.

This commit implements the `Closeable` interface on the connector to
allow this use case.

Closes gh-27032
2021-06-08 11:22:30 +02:00
izeye 3fa4e4168d Remove redundant Collections.unmodifiableList() 2021-06-08 10:39:23 +02:00
Sam Brannen f0f450a18d Polishing 2021-06-07 13:24:46 +02:00
Juergen Hoeller f854803e18 Upgrade to Hibernate ORM 5.4.32, RxJava 3.0.13, Aalto 1.3, Gson 2.8.7, Protobuf 3.17.2, XStream 1.4.17, ActiveMQ 5.16.2, HSQLDB 2.5.2, JRuby 9.2.17, Mockito 3.11, HtmlUnit 2.50, Checkstyle 8.43 2021-06-07 12:28:38 +02:00
Juergen Hoeller d29d4d45aa Upgrade to ASM master
Closes gh-27023
2021-06-07 12:28:17 +02:00
Sam Brannen 432fdad7d0 Polish contribution
See gh-26982
2021-06-04 15:32:29 +02:00
nhavuong 345d8186d4 Add more test cases for SimpleAliasRegistry
Closes gh-26982
2021-06-04 15:18:46 +02:00
Sam Brannen ad63a534cb Polish @CookieValue Javadoc 2021-06-04 15:14:42 +02:00
Сергей Цыпанов c9d341e9c0 Simplify String construction in StringUtils.changeFirstCharacterCase()
Closes gh-27012
2021-06-04 11:49:59 +02:00
Angel Aguilera 950b183715 Adjust expected IdentityHashMap sizes in ClassUtils
Proposed change aimed to keep coherence between variable declaration 
and static initialization code, which stores 9 values, not 8, in these
two maps.

Closes gh-27016
2021-06-04 10:48:44 +02:00
Rossen Stoyanchev 821ecb4cfd Update Javadoc for CookieValue
See gh-26989
2021-06-02 17:28:28 +01:00
Rossen Stoyanchev 1c17305d7b Check for null in allowedOrigin list
Closes gh-26987
2021-06-02 17:14:28 +01:00
jbotuck 491d1c4c78 issue-26994 add missing ) to kotlin example 2021-06-02 12:51:37 +01:00
Brian Clozel 7dbbf364f7 Make FlightRecorderApplicationStartup thread safe
Prior to this commit, in some cases application context startup steps
could be created concurrently, which could cause issues with the current
implementation tracking the parent/child relationship between steps.

This commit ensures that the flight recorder implementation is using
thread safe collection implementations for that.

Fixes gh-26941
2021-06-01 15:11:23 +02:00
Brian Clozel 5204d736f3 Return static DefaultApplicationStartup step
This commit ensures that `DefaultApplicationStartup` returns a static
step for each call to avoid allocations.

Closes gh-26939
2021-06-01 15:06:19 +02:00
Sam Brannen e0690335fc Polish Javadoc in spring-context-indexer 2021-05-31 16:24:09 +02:00
Arjen Poutsma 5cbbbed377 Use given name in MultipartBodyBuilder::part
Make sure that we use the parameter name in MultipartBodyBuilder::part
when adding a Part, instead of using the name specified in the
'Content-Disposition' header that might have been in the part's headers.

Closes gh-27007
2021-05-31 15:17:12 +02:00
Arjen Poutsma 6f2fe5ddcb (Re)introduce CronExpression::isValidExpression
This commit introduces CronExpression::isValidExpression, which was also
present on the deprecated CronSequenceGenerator.

Closes: gh-26996
2021-05-31 14:28:55 +02:00
Sam Brannen da9ee06e05 Improve @Transactional docs regarding method visibility
Closes gh-27003
2021-05-30 17:10:01 +02:00
Sviatoslav Hryb 4c28266eb6 Fix @Transactional docs regarding method visibility
Closes gh-27001
2021-05-30 13:48:31 +02:00
Juergen Hoeller b3dcb64ff1 Generic bean type resolution for lookup methods
Closes gh-26998
2021-05-28 17:23:52 +02:00
Juergen Hoeller b18f8771c8 Upgrade to R2DBC Arabba-SR10, Netty 4.1.65, Undertow 2.2.8, Jetty 9.4.41, Jetty Reactive HttpClient 1.1.8, Apache HttpClient 5.1 2021-05-28 14:31:15 +02:00
Stephane Nicoll 93244e9f9d Merge pull request #26980 from hunjipo
* pr/26980:
  Fix typo in code example

Closes gh-26980
2021-05-27 08:43:41 +02:00
hunjipo f45a852aca Fix typo in code example
See gh-26980
2021-05-27 08:37:44 +02:00
Arjen Poutsma 4203e90655 Fix CronExpression roll-forward issue
This commit makes sure that BitsCronField rolls the date forward in
cases where we cannot find a next bit to elapse to.

Closes: gh-26964
2021-05-26 16:46:15 +02:00
Sam Brannen 1e2e114e3c Polish contribution
See gh-26871
2021-05-26 14:46:32 +02:00
Michael Edwards 0e73213144 Add ApplicationEvent constructor for specifying timestamp
Closes gh-26871
2021-05-26 14:32:41 +02:00
Sam Brannen 1e1045ba42 Doc tx semantics for @TransactionalEventListener after completion phases
This commit improves the Javadoc regarding transactional semantics for
@TransactionalEventListener methods invoked in the AFTER_COMMIT,
AFTER_ROLLBACK, or AFTER_COMPLETION phase. Specifically, the
documentation now points out that interactions with the underlying
transactional resource will not be committed in those phases.

Closes gh-26974
2021-05-26 14:21:08 +02:00
Rossen Stoyanchev 8d17bcea5b Validate top-level SockJS URL
Closes gh-26933
2021-05-26 11:26:02 +01:00
Stephane Nicoll 597b31b1fc Merge pull request #26973 from dregimbal
* pr/26973:
  Update copyright year of changed file
  Fix typo

Closes gh-26973
2021-05-26 08:50:38 +02:00
Stephane Nicoll 4049951c03 Update copyright year of changed file
See gh-26973
2021-05-26 08:50:15 +02:00
Dillon Regimbal 89441ea006 Fix typo
See gh-26973
2021-05-26 08:49:48 +02:00
Rossen Stoyanchev de1ba79c55 Switch to non-blocking thread after WebSession creation
See gh-26958
2021-05-24 16:27:56 +01:00
Sam Brannen ffbc537835 Polish ConcurrentBeanWrapperTests 2021-05-23 18:50:50 +02:00
Elvys Soares 9295bcc4a4 Refactor ConcurrentBeanWrapperTests to use @RepeatedTest
Closes gh-26968
2021-05-23 18:40:36 +02:00
Sam Brannen 746b1b2fe1 Polishing 2021-05-23 18:27:31 +02:00
izeye ab2c78a9d5 Fix broken Javadoc tags
Closes gh-26967
2021-05-23 18:09:34 +02:00
Brian Clozel ac0e71745b Set best matching pattern attribute in WebMvc.fn
Prior to this commit, the `RouterFunctionMapping` WebFlux.fn variant
would set the `HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE` as an
exchange attribute. This is useful for instrumentation purposes.
The WebMvc.fn variant would not do the same; this would lead to
"UNKNOWN" path metrics tags.

This commit ensures that the `RouterFunctionMapping` WebMvc.fn variant
does set the `BEST_MATCHING_PATTERN_ATTRIBUTE` and
`BEST_MATCHING_HANDLER_ATTRIBUTE` request attributes.

Closes gh-26963
2021-05-21 11:54:06 +02:00
izeye f65cbe0992 Polish PORT_PATTERN in UriComponentsBuilder 2021-05-20 18:20:57 +01:00
Johnny Lim a2e5d9a14f Polishing
Closes gh-26960
2021-05-20 15:49:13 +02:00
Christoph Dreis 360f555e77 Introduce ResponseEntity.internalServerError()
Closes gh-26952
2021-05-18 16:57:20 +02:00
Sam Brannen 7c756027a0 Deprecate internal APIs in ScriptUtils in spring-jdbc
Many of the utility methods in ScriptUtils are public only because they
were once invoked from JdbdTestUtils in spring-test, which is no longer
the case. Consequently, there should no longer be a need for any
external clients to invoke such methods.

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

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

Closes gh-26947
2021-05-18 15:43:54 +02:00
Sam Brannen 4c642ccdcd Logically reorganize methods in ScriptUtils
See gh-26947
2021-05-18 14:56:49 +02:00
Sam Brannen fab7b1cebe Polish ScriptUtils internals in spring-r2dbc
See gh-26947
2021-05-18 14:29:18 +02:00
Rossen Stoyanchev 101ed17b6f Polishing contribution
Closes gh-26927
2021-05-17 17:28:16 +01:00
Ruslan Akhundov 66588bae92 CorsConfiguration supports port pattern and port lists
See gh-26927
2021-05-17 17:28:16 +01:00
Sam Brannen d33545bdce Avoid duplicate comment processing in ScriptUtils in spring-r2dbc
See gh-26947
2021-05-17 13:15:44 +02:00
Sam Brannen e8e157ebf8 Polish ScriptUtils in spring-r2dbc 2021-05-17 12:49:46 +02:00
Sam Brannen 7f5904c68b Remove internal APIs from ScriptUtils in spring-r2dbc
Prior to this commit, many of the utility methods in ScriptUtils in
spring-r2dbc were public by accident: they were copied from ScriptUtils
in spring-jdbc without changing the visibility when spring-r2dbc was
introduced in Spring Framework. 5.3 GA.

This commit corrects this mistake by removing internal APIs from
ScriptUtils in spring-r2dbc from the public contract.

See gh-26947
2021-05-17 12:15:30 +02:00
Sam Brannen 66e488850b Ignore comments when searching for statement delimiter in ScriptUtils
This commit picks up where 569ce840cf left off, by correctly invoking
the new containsStatementSeparator(...) method from
executeSqlScript(...) in ScriptUtils in spring-r2dbc.

See gh-26911
2021-05-17 12:07:04 +02:00
Sébastien Deleuze 3dba3691a7 Upgrade to Coroutines 1.5.0
This commit upgrades to Coroutines 1.5.0 while retaining
Coroutines 1.4.x compatibility.

Closes gh-26897
2021-05-17 11:37:02 +02:00
Sam Brannen c80c4e001a Add missing author tags and sync SQL script support for JDBC & R2DBC 2021-05-16 17:18:56 +02:00
Sam Brannen 8da049b613 Update Javadoc URL for Apache HttpComponents Client 2021-05-16 15:22:37 +02:00
Sam Brannen 1acd832759 Fix link in @EventListener's id Javadoc 2021-05-16 15:08:33 +02:00
Sam Brannen 261927b72a Stop using deprecated Netty feature in ReactorHttpsServer 2021-05-16 15:03:42 +02:00
Sam Brannen 24aa7f546b Upgrade to JUnit 5.7.2
Closes gh-26946
2021-05-16 15:00:45 +02:00
Sam Brannen 4cfad25468 Polish ScriptUtils implementations 2021-05-14 15:15:12 +02:00
Sam Brannen 569ce840cf Ignore comments when searching for statement delimiter in ScriptUtils
Prior to this commit, the implementations of
ScriptUtils.containsSqlScriptDelimiters() in spring-jdbc and
spring-r2dbc did not ignore comments when searching for the statement
delimiter within an SQL script. This resulted in subtle bugs if a
comment contained a single single-quote or single double-quote, since
the absence of the closing single-quote or double-quote led the
algorithm to believe that it was still "within a text literal". Similar
issues could arise if a comment contained the sought statement
delimiter but the rest of the script did not contain the sought
statement delimiter. In such cases, the algorithms in ScriptUtils could
erroneously choose an incorrect statement delimiter -- for example,
using the fallback statement delimiter instead of the delimiter
specified by the user.

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

Closes gh-26911
2021-05-14 14:23:27 +02:00
Sam Brannen fae484855b Fix grammar in inline comment in HttpStatus
See gh-26842
2021-05-13 16:21:45 +02:00
Qimiao Chen cab20d1202 Fix punctuation in Custom Cache Resolution section of ref docs
Closes gh-26938
2021-05-13 15:30:58 +02:00
Sam Brannen e2bf513563 Remove accidental dependency on Spring 5.2.x in spring-r2dbc tests 2021-05-12 13:43:36 +02:00
Sam Brannen 84e56bb5d9 Sync ScriptUtilsUnitTests implementations to simplify maintenance 2021-05-12 12:54:35 +02:00
Sam Brannen 6f5e66f00c Add missing tests for multiple comment prefixes in R2DBC ScriptUtils
See gh-23289
2021-05-12 12:23:54 +02:00
Sam Brannen cda72e4a70 Ignore delimiter enclosed in double quotes in ScriptUtils
Prior to this commit, the containsSqlScriptDelimiters() method in
ScriptUtils ignored delimiters enclosed in single quotes but not those
enclosed within double quotes, which contradicts the algorithm in
splitSqlScript() and therefore constitutes a bug.

This commit fixes this bug in the ScriptUtils implementations in
spring-jdbc and spring-r2dbc.

Closes gh-26935
2021-05-12 12:07:55 +02:00
Sam Brannen e31a4c4c77 Polishing 2021-05-12 11:31:15 +02:00
Spring Buildmaster 4809632f44 Next development version (v5.3.8-SNAPSHOT) 2021-05-12 05:50:28 +00:00
132 changed files with 2195 additions and 1542 deletions
+29 -29
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.5.0' apply false
id 'org.jetbrains.kotlin.jvm' version '1.5.10' 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'
@@ -10,7 +10,7 @@ plugins {
id "com.github.ben-manes.versions" version '0.28.0'
id "com.github.johnrengelman.shadow" version "6.1.0" apply false
id "me.champeau.jmh" version "0.6.4" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.5.0" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.5.10" apply false
id "org.unbroken-dome.xjc" version '2.0.0' apply false
}
@@ -28,15 +28,15 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.3"
mavenBom "io.netty:netty-bom:4.1.63.Final"
mavenBom "io.netty:netty-bom:4.1.65.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.7"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR9"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR10"
mavenBom "io.rsocket:rsocket-bom:1.1.0"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.40.v20210413"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.0"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.3"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.0"
mavenBom "org.junit:junit-bom:5.7.1"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.41.v20210516"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.10"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.0"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.1"
mavenBom "org.junit:junit-bom:5.7.2"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.14.1') {
@@ -67,18 +67,18 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.0.12"
dependency "io.reactivex.rxjava3:rxjava:3.0.13"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "com.caucho:hessian:4.0.63"
dependency "com.fasterxml:aalto-xml:1.2.2"
dependency "com.fasterxml:aalto-xml:1.3.0"
dependency("com.fasterxml.woodstox:woodstox-core:6.2.6") {
exclude group: "stax", name: "stax-api"
}
dependency "com.google.code.gson:gson:2.8.6"
dependency "com.google.protobuf:protobuf-java-util:3.15.8"
dependency "com.google.code.gson:gson:2.8.7"
dependency "com.google.protobuf:protobuf-java-util:3.17.2"
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
dependency("com.thoughtworks.xstream:xstream:1.4.16") {
dependency("com.thoughtworks.xstream:xstream:1.4.17") {
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
}
@@ -100,7 +100,7 @@ configure(allprojects) { project ->
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.3"
dependency "net.sf.jopt-simple:jopt-simple:5.0.4"
dependencySet(group: 'org.apache.activemq', version: '5.16.1') {
dependencySet(group: 'org.apache.activemq', version: '5.16.2') {
entry 'activemq-broker'
entry('activemq-kahadb-store') {
exclude group: "org.springframework", name: "spring-context"
@@ -116,13 +116,13 @@ configure(allprojects) { project ->
dependency "org.apache.poi:poi-ooxml:4.1.2"
dependency "org.apache-extras.beanshell:bsh:2.0b6"
dependency "org.freemarker:freemarker:2.3.31"
dependency "org.hsqldb:hsqldb:2.5.1"
dependency "org.hsqldb:hsqldb:2.5.2"
dependency "org.quartz-scheduler:quartz:2.3.2"
dependency "org.codehaus.fabric3.api:commonj:1.1.0"
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.31.Final"
dependency "org.hibernate:hibernate-core:5.4.32.Final"
dependency "org.hibernate:hibernate-validator:6.2.0.Final"
dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
@@ -138,7 +138,7 @@ configure(allprojects) { project ->
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.7.Final') {
dependencySet(group: 'io.undertow', version: '2.2.8.Final') {
entry 'undertow-core'
entry('undertow-websockets-jsr') {
exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec"
@@ -159,11 +159,11 @@ configure(allprojects) { project ->
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.3'
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.6"
dependency 'org.apache.httpcomponents.client5:httpclient5:5.1'
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.1'
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.8"
dependency "org.jruby:jruby:9.2.16.0"
dependency "org.jruby:jruby:9.2.17.0"
dependency "org.python:jython-standalone:2.7.1"
dependency "org.mozilla:rhino:1.7.11"
@@ -196,7 +196,7 @@ configure(allprojects) { project ->
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '3.9.0') {
dependencySet(group: 'org.mockito', version: '3.11.0') {
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
@@ -204,10 +204,10 @@ configure(allprojects) { project ->
}
dependency "io.mockk:mockk:1.10.2"
dependency("net.sourceforge.htmlunit:htmlunit:2.49.1") {
dependency("net.sourceforge.htmlunit:htmlunit:2.50.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.49.1") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.50.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -316,7 +316,7 @@ configure([rootProject] + javaProjects) { project ->
kotlinOptions {
languageVersion = "1.3"
apiVersion = "1.3"
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings"]
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings", "-Xopt-in=kotlin.RequiresOptIn"]
allWarningsAsErrors = true
}
}
@@ -336,7 +336,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "8.42"
toolVersion = "8.43"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -375,10 +375,10 @@ configure([rootProject] + javaProjects) { project ->
"https://fasterxml.github.io/jackson-core/javadoc/2.10/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.10/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
"https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/",
"https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
"https://junit.org/junit5/docs/5.7.1/api/"
"https://junit.org/junit5/docs/5.7.2/api/"
] as String[]
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.7-SNAPSHOT
version=5.3.8
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
+2 -1
View File
@@ -143,7 +143,8 @@ asciidoctorj {
'highlightjs-theme': 'googlecode',
stylesdir: 'css/',
stylesheet: 'stylesheet.css',
'spring-version': project.version
'spring-version': project.version,
'spring-framework-main-code': 'https://github.com/spring-projects/spring-framework/tree/main'
])
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -84,7 +84,7 @@ import org.springframework.lang.Nullable;
* (only applicable when running in a web application context)
* <li>{@code postProcessBeforeInitialization} methods of BeanPostProcessors
* <li>InitializingBean's {@code afterPropertiesSet}
* <li>a custom init-method definition
* <li>a custom {@code init-method} definition
* <li>{@code postProcessAfterInitialization} methods of BeanPostProcessors
* </ol>
*
@@ -92,7 +92,7 @@ import org.springframework.lang.Nullable;
* <ol>
* <li>{@code postProcessBeforeDestruction} methods of DestructionAwareBeanPostProcessors
* <li>DisposableBean's {@code destroy}
* <li>a custom destroy-method definition
* <li>a custom {@code destroy-method} definition
* </ol>
*
* @author Rod Johnson
@@ -102,6 +102,8 @@ import org.springframework.lang.Nullable;
* @see BeanNameAware#setBeanName
* @see BeanClassLoaderAware#setBeanClassLoader
* @see BeanFactoryAware#setBeanFactory
* @see org.springframework.context.EnvironmentAware#setEnvironment
* @see org.springframework.context.EmbeddedValueResolverAware#setEmbeddedValueResolver
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher
* @see org.springframework.context.MessageSourceAware#setMessageSource
@@ -111,6 +113,7 @@ import org.springframework.lang.Nullable;
* @see InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization
* @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor#postProcessBeforeDestruction
* @see DisposableBean#destroy
* @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -244,8 +245,10 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
return (bean.equals(null) ? null : bean);
}
else {
return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
this.owner.getBean(method.getReturnType()));
// Find target bean matching the (potentially generic) method return type
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method);
return (argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) :
this.owner.getBeanProvider(genericReturnType).getObject());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,17 +19,25 @@ package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Represents an override of a method that looks up an object in the same IoC context.
* Represents an override of a method that looks up an object in the same IoC context,
* either by bean name or by bean type (based on the declared method return type).
*
* <p>Methods eligible for lookup override must not have arguments.
* <p>Methods eligible for lookup override may declare arguments in which case the
* given arguments are passed to the bean retrieval operation.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 1.1
* @see org.springframework.beans.factory.BeanFactory#getBean(String)
* @see org.springframework.beans.factory.BeanFactory#getBean(Class)
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...)
* @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...)
* @see org.springframework.beans.factory.BeanFactory#getBeanProvider(ResolvableType)
*/
public class LookupOverride extends MethodOverride {
@@ -43,8 +51,8 @@ public class LookupOverride extends MethodOverride {
/**
* Construct a new LookupOverride.
* @param methodName the name of the method to override
* @param beanName the name of the bean in the current {@code BeanFactory}
* that the overridden method should return (may be {@code null})
* @param beanName the name of the bean in the current {@code BeanFactory} that the
* overridden method should return (may be {@code null} for type-based bean retrieval)
*/
public LookupOverride(String methodName, @Nullable String beanName) {
super(methodName);
@@ -53,9 +61,9 @@ public class LookupOverride extends MethodOverride {
/**
* Construct a new LookupOverride.
* @param method the method to override
* @param beanName the name of the bean in the current {@code BeanFactory}
* that the overridden method should return (may be {@code null})
* @param method the method declaration to override
* @param beanName the name of the bean in the current {@code BeanFactory} that the
* overridden method should return (may be {@code null} for type-based bean retrieval)
*/
public LookupOverride(Method method, @Nullable String beanName) {
super(method.getName());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,9 +19,8 @@ package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
/**
* Interface to be implemented by classes that can reimplement any method
* on an IoC-managed object: the <b>Method Injection</b> form of
* Dependency Injection.
* Interface to be implemented by classes that can reimplement any method on an
* IoC-managed object: the <b>Method Injection</b> form of Dependency Injection.
*
* <p>Such methods may be (but need not be) abstract, in which case the
* container will create a concrete subclass to instantiate.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Extension of MethodOverride that represents an arbitrary
* Extension of {@link MethodOverride} that represents an arbitrary
* override of a method by the IoC container.
*
* <p>Any non-final method can be overridden, irrespective of its
@@ -45,7 +45,7 @@ public class ReplaceOverride extends MethodOverride {
/**
* Construct a new ReplaceOverride.
* @param methodName the name of the method to override
* @param methodReplacerBeanName the bean name of the MethodReplacer
* @param methodReplacerBeanName the bean name of the {@link MethodReplacer}
*/
public ReplaceOverride(String methodName, String methodReplacerBeanName) {
super(methodName);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,14 @@ package org.springframework.beans;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,23 +36,21 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Chris Beams
* @since 08.03.2004
*/
public class ConcurrentBeanWrapperTests {
class ConcurrentBeanWrapperTests {
private final Log logger = LogFactory.getLog(getClass());
private Set<TestRun> set = Collections.synchronizedSet(new HashSet<TestRun>());
private final Set<TestRun> set = ConcurrentHashMap.newKeySet();
private Throwable ex = null;
@Test
public void testSingleThread() {
for (int i = 0; i < 100; i++) {
performSet();
}
@RepeatedTest(100)
void testSingleThread() {
performSet();
}
@Test
public void testConcurrent() {
void testConcurrent() {
for (int i = 0; i < 10; i++) {
TestRun run = new TestRun(this);
set.add(run);
@@ -82,7 +80,7 @@ public class ConcurrentBeanWrapperTests {
Properties p = (Properties) System.getProperties().clone();
assertThat(p.size() != 0).as("The System properties must not be empty").isTrue();
assertThat(p).as("The System properties must not be empty").isNotEmpty();
for (Iterator<?> i = p.entrySet().iterator(); i.hasNext();) {
i.next();
@@ -111,7 +109,7 @@ public class ConcurrentBeanWrapperTests {
private ConcurrentBeanWrapperTests test;
public TestRun(ConcurrentBeanWrapperTests test) {
TestRun(ConcurrentBeanWrapperTests test) {
this.test = test;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -121,6 +121,18 @@ public class LookupAnnotationTests {
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
}
@Test
public void testWithGenericBean() {
beanFactory.registerBeanDefinition("numberBean", new RootBeanDefinition(NumberBean.class));
beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class));
beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class));
NumberBean bean = (NumberBean) beanFactory.getBean("numberBean");
assertThat(bean).isNotNull();
assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore());
assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore());
}
public static abstract class AbstractBean {
@@ -147,4 +159,26 @@ public class LookupAnnotationTests {
AbstractBean abstractBean;
}
public static class NumberStore<T extends Number> {
}
public static class DoubleStore extends NumberStore<Double> {
}
public static class FloatStore extends NumberStore<Float> {
}
public static abstract class NumberBean {
@Lookup
public abstract NumberStore<Double> getDoubleStore();
@Lookup
public abstract NumberStore<Float> getFloatStore();
}
}
@@ -141,6 +141,7 @@ class YamlProcessorTests {
}
@Test
@SuppressWarnings("unchecked")
void standardTypesSupportedByDefault() throws Exception {
setYaml("value: !!set\n ? first\n ? second");
this.processor.process((properties, map) -> {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ public class LookupMethodTests {
@BeforeEach
public void setUp() {
public void setup() {
beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("lookupMethodTests.xml", getClass()));
@@ -83,8 +83,8 @@ public class LookupMethodTests {
public void testWithThreeArgsShouldFail() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull();
assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor").isThrownBy(() ->
bean.getThreeArguments("name", 1, 2));
assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor")
.isThrownBy(() -> bean.getThreeArguments("name", 1, 2));
}
@Test
@@ -97,6 +97,21 @@ public class LookupMethodTests {
assertThat(expected.isJedi()).isTrue();
}
@Test
public void testWithGenericBean() {
RootBeanDefinition bd = new RootBeanDefinition(NumberBean.class);
bd.getMethodOverrides().addOverride(new LookupOverride("getDoubleStore", null));
bd.getMethodOverrides().addOverride(new LookupOverride("getFloatStore", null));
beanFactory.registerBeanDefinition("numberBean", bd);
beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class));
beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class));
NumberBean bean = (NumberBean) beanFactory.getBean("numberBean");
assertThat(bean).isNotNull();
assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore());
assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore());
}
public static abstract class AbstractBean {
@@ -111,4 +126,24 @@ public class LookupMethodTests {
public abstract TestBean getThreeArguments(String name, int age, int anotherArg);
}
public static class NumberStore<T extends Number> {
}
public static class DoubleStore extends NumberStore<Double> {
}
public static class FloatStore extends NumberStore<Float> {
}
public static abstract class NumberBean {
public abstract NumberStore<Double> getDoubleStore();
public abstract NumberStore<Float> getFloatStore();
}
}
@@ -36,7 +36,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
/**
* Annotation {@link Processor} that writes {@link CandidateComponentsMetadata}
* Annotation {@link Processor} that writes a {@link CandidateComponentsMetadata}
* file for spring components.
*
* @author Stephane Nicoll
@@ -26,7 +26,7 @@ import javax.lang.model.element.ElementKind;
/**
* A {@link StereotypesProvider} implementation that extracts the stereotypes
* flagged by the {@value INDEXED_ANNOTATION} annotation. This implementation
* flagged by the {@value #INDEXED_ANNOTATION} annotation. This implementation
* honors stereotypes defined this way on meta-annotations.
*
* @author Stephane Nicoll
@@ -24,7 +24,7 @@ import javax.lang.model.element.ElementKind;
/**
* A {@link StereotypesProvider} implementation that provides the
* {@value STEREOTYPE} stereotype for each package-info.
* {@value #STEREOTYPE} stereotype for each package-info.
*
* @author Stephane Nicoll
* @since 5.0
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,8 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
/**
* A {@link StereotypesProvider} that extract a stereotype for each
* {@code javax.*} annotation placed on a class or interface.
* A {@link StereotypesProvider} that extracts a stereotype for each
* {@code javax.*} annotation <i>present</i> on a class or interface.
*
* @author Stephane Nicoll
* @since 5.0
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import javax.lang.model.element.Element;
/**
* Provide the list of stereotypes that match an {@link Element}.
* If an element has one more stereotypes, it is referenced in the index
* <p>If an element has one or more stereotypes, it is referenced in the index
* of candidate components and each stereotype can be queried individually.
*
* @author Stephane Nicoll
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.context;
import java.time.Clock;
import java.util.EventObject;
/**
@@ -37,18 +38,37 @@ public abstract class ApplicationEvent extends EventObject {
/**
* Create a new {@code ApplicationEvent}.
* Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
* set to {@link System#currentTimeMillis()}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
* @see #ApplicationEvent(Object, Clock)
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
/**
* Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
* set to the value returned by {@link Clock#millis()} in the provided {@link Clock}.
* <p>This constructor is typically used in testing scenarios.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
* @param clock a clock which will provide the timestamp
* @since 5.3.8
* @see #ApplicationEvent(Object)
*/
public ApplicationEvent(Object source, Clock clock) {
super(source);
this.timestamp = clock.millis();
}
/**
* Return the system time in milliseconds when the event occurred.
* Return the time in milliseconds when the event occurred.
* @see #ApplicationEvent(Object)
* @see #ApplicationEvent(Object, Clock)
*/
public final long getTimestamp() {
return this.timestamp;
@@ -84,6 +84,7 @@ import org.springframework.core.annotation.AliasFor;
* @author Sam Brannen
* @since 4.2
* @see EventListenerMethodProcessor
* @see org.springframework.transaction.event.TransactionalEventListener
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@@ -133,7 +134,7 @@ public @interface EventListener {
* An optional identifier for the listener, defaulting to the fully-qualified
* signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
* @since 5.3.5
* @see org.springframework.context.ApplicationListener#getListenerId()
* @see SmartApplicationListener#getListenerId()
* @see ApplicationEventMulticaster#removeApplicationListeners(Predicate)
*/
String id() default "";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -192,6 +192,11 @@ final class BitsCronField extends CronField {
while (current != next && count++ < CronExpression.MAX_ATTEMPTS) {
temporal = type().elapseUntil(temporal, next);
current = type().get(temporal);
next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
}
if (count >= CronExpression.MAX_ATTEMPTS) {
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -202,6 +202,25 @@ public final class CronExpression {
}
}
/**
* Determine whether the given string represents a valid cron expression.
* @param expression the expression to evaluate
* @return {@code true} if the given expression is a valid cron expression
* @since 5.3.8
*/
public static boolean isValidExpression(@Nullable String expression) {
if (expression == null) {
return false;
}
try {
parse(expression);
return true;
}
catch (IllegalArgumentException ex) {
return false;
}
}
private static String resolveMacros(String expression) {
expression = expression.trim();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.validation.support;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.ui.ConcurrentModel;
import org.springframework.validation.BindingResult;
@@ -36,17 +37,19 @@ import org.springframework.validation.BindingResult;
* @author Rossen Stoyanchev
* @since 5.0
* @see BindingResult
* @see BindingAwareModelMap
*/
@SuppressWarnings("serial")
public class BindingAwareConcurrentModel extends ConcurrentModel {
@Override
public Object put(String key, Object value) {
@Nullable
public Object put(String key, @Nullable Object value) {
removeBindingResultIfNecessary(key, value);
return super.put(key, value);
}
private void removeBindingResultIfNecessary(String key, Object value) {
private void removeBindingResultIfNecessary(String key, @Nullable Object value) {
if (!key.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
String resultKey = BindingResult.MODEL_KEY_PREFIX + key;
BindingResult result = (BindingResult) get(resultKey);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.validation.support;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.validation.BindingResult;
@@ -39,7 +40,7 @@ import org.springframework.validation.BindingResult;
public class BindingAwareModelMap extends ExtendedModelMap {
@Override
public Object put(String key, Object value) {
public Object put(String key, @Nullable Object value) {
removeBindingResultIfNecessary(key, value);
return super.put(key, value);
}
@@ -50,7 +51,7 @@ public class BindingAwareModelMap extends ExtendedModelMap {
super.putAll(map);
}
private void removeBindingResultIfNecessary(Object key, Object value) {
private void removeBindingResultIfNecessary(Object key, @Nullable Object value) {
if (key instanceof String) {
String attributeName = (String) key;
if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
@@ -51,6 +51,16 @@ class CronExpressionTests {
}
};
@Test
public void isValidExpression() {
assertThat(CronExpression.isValidExpression(null)).isFalse();
assertThat(CronExpression.isValidExpression("")).isFalse();
assertThat(CronExpression.isValidExpression("*")).isFalse();
assertThat(CronExpression.isValidExpression("* * * * *")).isFalse();
assertThat(CronExpression.isValidExpression("* * * * * * *")).isFalse();
assertThat(CronExpression.isValidExpression("* * * * * *")).isTrue();
}
@Test
void matchAll() {
@@ -497,6 +507,29 @@ class CronExpressionTests {
assertThat(actual.getDayOfMonth()).isEqualTo(13);
}
@Test
public void everyTenDays() {
CronExpression cronExpression = CronExpression.parse("0 15 12 */10 1-8 5");
LocalDateTime last = LocalDateTime.parse("2021-04-30T12:14:59");
LocalDateTime expected = LocalDateTime.parse("2021-05-21T12:15");
LocalDateTime actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = LocalDateTime.parse("2021-06-11T12:15");
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
last = actual;
expected = LocalDateTime.parse("2022-01-21T12:15");
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
void yearly() {
CronExpression expression = CronExpression.parse("@yearly");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,7 @@
@file:JvmName("CoroutinesUtils")
package org.springframework.core
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.awaitSingleOrNull
import kotlinx.coroutines.reactor.asFlux
@@ -48,6 +45,8 @@ internal fun <T: Any> deferredToMono(source: Deferred<T>) =
* @author Sebastien Deleuze
* @since 5.2
*/
@Suppress("DEPRECATION")
@OptIn(DelicateCoroutinesApi::class)
internal fun <T: Any> monoToDeferred(source: Mono<T>) =
GlobalScope.async(Dispatchers.Unconfined) { source.awaitSingleOrNull() }
@@ -88,6 +88,9 @@ public class ClassReader {
*/
static final int EXPAND_ASM_INSNS = 256;
/** The maximum size of array to allocate. */
private static final int MAX_BUFFER_SIZE = 1024 * 1024;
/** The size of the temporary byte array used to read class input streams chunk by chunk. */
private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
@@ -310,13 +313,19 @@ public class ClassReader {
if (inputStream == null) {
throw new IOException("Class not found");
}
int bufferSize = calculateBufferSize(inputStream);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE];
byte[] data = new byte[bufferSize];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
int readCount = 0;
while ((bytesRead = inputStream.read(data, 0, bufferSize)) != -1) {
outputStream.write(data, 0, bytesRead);
readCount++;
}
outputStream.flush();
if (readCount == 1) {
return data;
}
return outputStream.toByteArray();
} finally {
if (close) {
@@ -325,6 +334,20 @@ public class ClassReader {
}
}
private static int calculateBufferSize(final InputStream inputStream) throws IOException {
int expectedLength = inputStream.available();
/*
* Some implementations can return 0 while holding available data
* (e.g. new FileInputStream("/proc/a_file"))
* Also in some pathological cases a very small number might be returned,
* and in this case we use default size
*/
if (expectedLength < 256) {
return INPUT_STREAM_DATA_CHUNK_SIZE;
}
return Math.min(expectedLength, MAX_BUFFER_SIZE);
}
// -----------------------------------------------------------------------------------------------
// Accessors
// -----------------------------------------------------------------------------------------------
@@ -3456,7 +3479,6 @@ public class ClassReader {
private int[] readBootstrapMethodsAttribute(final int maxStringLength) {
char[] charBuffer = new char[maxStringLength];
int currentAttributeOffset = getFirstAttributeOffset();
int[] currentBootstrapMethodOffsets = null;
for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentAttributeOffset, charBuffer);
@@ -3464,17 +3486,17 @@ public class ClassReader {
currentAttributeOffset += 6;
if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
// Read the num_bootstrap_methods field and create an array of this size.
currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)];
int[] result = new int[readUnsignedShort(currentAttributeOffset)];
// Compute and store the offset of each 'bootstrap_methods' array field entry.
int currentBootstrapMethodOffset = currentAttributeOffset + 2;
for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) {
currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset;
for (int j = 0; j < result.length; ++j) {
result[j] = currentBootstrapMethodOffset;
// Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each),
// as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2).
currentBootstrapMethodOffset +=
4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2;
}
return currentBootstrapMethodOffsets;
return result;
}
currentAttributeOffset += attributeLength;
}
@@ -79,7 +79,7 @@ public class ClassWriter extends ClassVisitor {
/**
* The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific
* access flags, such as {@link Opcodes#ACC_DEPRECATED} or {}@link Opcodes#ACC_RECORD}, which are
* access flags, such as {@link Opcodes#ACC_DEPRECATED} or {@link Opcodes#ACC_RECORD}, which are
* removed when generating the ClassFile structure.
*/
private int accessFlags;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -71,7 +71,7 @@ public abstract class DataBufferUtils {
//---------------------------------------------------------------------
/**
* Obtain a {@link InputStream} from the given supplier, and read it into a
* Obtain an {@link InputStream} from the given supplier, and read it into a
* {@code Flux} of {@code DataBuffer}s. Closes the input stream when the
* Flux is terminated.
* @param inputStreamSupplier the supplier for the input stream to read from
@@ -125,7 +125,7 @@ public abstract class DataBufferUtils {
}
/**
* Obtain a {@code AsynchronousFileChannel} from the given supplier, and
* Obtain an {@code AsynchronousFileChannel} from the given supplier, and
* read it into a {@code Flux} of {@code DataBuffer}s, starting at the given
* position. Closes the channel when the Flux is terminated.
* @param channelSupplier the supplier for the channel to read from
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,16 +29,16 @@ import java.util.function.Supplier;
*/
class DefaultApplicationStartup implements ApplicationStartup {
private static final DefaultStartupStep DEFAULT_STARTUP_STEP = new DefaultStartupStep();
@Override
public DefaultStartupStep start(String name) {
return new DefaultStartupStep();
return DEFAULT_STARTUP_STEP;
}
static class DefaultStartupStep implements StartupStep {
boolean recorded = false;
private final DefaultTags TAGS = new DefaultTags();
@Override
@@ -63,23 +63,17 @@ class DefaultApplicationStartup implements ApplicationStartup {
@Override
public StartupStep tag(String key, String value) {
if (this.recorded) {
throw new IllegalArgumentException();
}
return this;
}
@Override
public StartupStep tag(String key, Supplier<String> value) {
if (this.recorded) {
throw new IllegalArgumentException();
}
return this;
}
@Override
public void end() {
this.recorded = true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,9 @@
package org.springframework.core.metrics.jfr;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
@@ -37,24 +38,23 @@ import org.springframework.core.metrics.StartupStep;
*/
public class FlightRecorderApplicationStartup implements ApplicationStartup {
private long currentSequenceId;
private final AtomicLong currentSequenceId = new AtomicLong(0);
private final Deque<Long> currentSteps;
public FlightRecorderApplicationStartup() {
this.currentSequenceId = 0;
this.currentSteps = new ArrayDeque<>();
this.currentSteps.offerFirst(0L);
this.currentSteps = new ConcurrentLinkedDeque<>();
this.currentSteps.offerFirst(this.currentSequenceId.get());
}
@Override
public StartupStep start(String name) {
FlightRecorderStartupStep step = new FlightRecorderStartupStep(++this.currentSequenceId, name,
this.currentSteps.getFirst(), committedStep -> this.currentSteps.removeFirst());
this.currentSteps.offerFirst(this.currentSequenceId);
return step;
long sequenceId = this.currentSequenceId.incrementAndGet();
this.currentSteps.offerFirst(sequenceId);
return new FlightRecorderStartupStep(sequenceId, name,
this.currentSteps.getFirst(), committedStep -> this.currentSteps.removeFirstOccurrence(sequenceId));
}
}
@@ -88,13 +88,13 @@ public abstract class ClassUtils {
* Map with primitive wrapper type as key and corresponding primitive
* type as value, for example: Integer.class -> int.class.
*/
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap<>(8);
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap<>(9);
/**
* Map with primitive type as key and corresponding wrapper
* type as value, for example: int.class -> Integer.class.
*/
private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(8);
private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(9);
/**
* Map with primitive type name as key and corresponding primitive
@@ -1322,7 +1322,7 @@ public abstract class ClassUtils {
* Note that, despite being synthetic, bridge methods ({@link Method#isBridge()}) are considered
* as user-level methods since they are eventually pointing to a user-declared generic method.
* @param method the method to check
* @return {@code true} if the method can be considered as user-declared; [@code false} otherwise
* @return {@code true} if the method can be considered as user-declared; {@code false} otherwise
*/
public static boolean isUserLevelMethod(Method method) {
Assert.notNull(method, "Method must not be null");
@@ -566,7 +566,7 @@ public abstract class StringUtils {
char[] chars = str.toCharArray();
chars[0] = updatedChar;
return new String(chars, 0, chars.length);
return new String(chars);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,13 +21,18 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link SimpleAliasRegistry}.
*
* @author Juergen Hoeller
* @author Nha Vuong
* @author Sam Brannen
*/
class SimpleAliasRegistryTests {
private final SimpleAliasRegistry registry = new SimpleAliasRegistry();
@Test
void aliasChaining() {
SimpleAliasRegistry registry = new SimpleAliasRegistry();
registry.registerAlias("test", "testAlias");
registry.registerAlias("testAlias", "testAlias2");
registry.registerAlias("testAlias2", "testAlias3");
@@ -42,7 +47,6 @@ class SimpleAliasRegistryTests {
@Test // SPR-17191
void aliasChainingWithMultipleAliases() {
SimpleAliasRegistry registry = new SimpleAliasRegistry();
registry.registerAlias("name", "alias_a");
registry.registerAlias("name", "alias_b");
assertThat(registry.hasAlias("name", "alias_a")).isTrue();
@@ -60,4 +64,35 @@ class SimpleAliasRegistryTests {
assertThat(registry.hasAlias("real_name", "alias_c")).isTrue();
}
@Test
void removeAlias() {
registry.registerAlias("real_name", "nickname");
assertThat(registry.hasAlias("real_name", "nickname")).isTrue();
registry.removeAlias("nickname");
assertThat(registry.hasAlias("real_name", "nickname")).isFalse();
}
@Test
void isAlias() {
registry.registerAlias("real_name", "nickname");
assertThat(registry.isAlias("nickname")).isTrue();
assertThat(registry.isAlias("real_name")).isFalse();
assertThat(registry.isAlias("fake")).isFalse();
}
@Test
void getAliases() {
registry.registerAlias("test", "testAlias1");
assertThat(registry.getAliases("test")).containsExactly("testAlias1");
registry.registerAlias("testAlias1", "testAlias2");
registry.registerAlias("testAlias2", "testAlias3");
assertThat(registry.getAliases("test")).containsExactlyInAnyOrder("testAlias1", "testAlias2", "testAlias3");
assertThat(registry.getAliases("testAlias1")).containsExactlyInAnyOrder("testAlias2", "testAlias3");
assertThat(registry.getAliases("testAlias2")).containsExactly("testAlias3");
assertThat(registry.getAliases("testAlias3")).isEmpty();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.core.io.support.EncodedResource;
public class CannotReadScriptException extends ScriptException {
/**
* Construct a new {@code CannotReadScriptException}.
* Create a new {@code CannotReadScriptException}.
* @param resource the resource that cannot be read from
* @param cause the underlying cause of the resource access failure
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
/**
* Composite {@link DatabasePopulator} that delegates to a list of given
* {@code DatabasePopulator} implementations, executing all scripts.
@@ -52,6 +54,7 @@ public class CompositeDatabasePopulator implements DatabasePopulator {
* @since 4.3
*/
public CompositeDatabasePopulator(Collection<DatabasePopulator> populators) {
Assert.notNull(populators, "DatabasePopulators must not be null");
this.populators.addAll(populators);
}
@@ -61,6 +64,7 @@ public class CompositeDatabasePopulator implements DatabasePopulator {
* @since 4.3
*/
public CompositeDatabasePopulator(DatabasePopulator... populators) {
Assert.notNull(populators, "DatabasePopulators must not be null");
this.populators.addAll(Arrays.asList(populators));
}
@@ -69,6 +73,7 @@ public class CompositeDatabasePopulator implements DatabasePopulator {
* Specify one or more populators to delegate to.
*/
public void setPopulators(DatabasePopulator... populators) {
Assert.notNull(populators, "DatabasePopulators must not be null");
this.populators.clear();
this.populators.addAll(Arrays.asList(populators));
}
@@ -77,12 +82,13 @@ public class CompositeDatabasePopulator implements DatabasePopulator {
* Add one or more populators to the list of delegates.
*/
public void addPopulators(DatabasePopulator... populators) {
Assert.notNull(populators, "DatabasePopulators must not be null");
this.populators.addAll(Arrays.asList(populators));
}
@Override
public void populate(Connection connection) throws SQLException, ScriptException {
Assert.notNull(connection, "Connection must not be null");
for (DatabasePopulator populator : this.populators) {
populator.populate(connection);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import org.springframework.lang.Nullable;
public abstract class ScriptException extends DataAccessException {
/**
* Constructor for {@code ScriptException}.
* Create a new {@code ScriptException}.
* @param message the detail message
*/
public ScriptException(String message) {
@@ -38,7 +38,7 @@ public abstract class ScriptException extends DataAccessException {
}
/**
* Constructor for {@code ScriptException}.
* Create a new {@code ScriptException}.
* @param message the detail message
* @param cause the root cause
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
public class ScriptParseException extends ScriptException {
/**
* Construct a new {@code ScriptParseException}.
* Create a new {@code ScriptParseException}.
* @param message detailed message
* @param resource the resource from which the SQL script was read
*/
@@ -38,7 +38,7 @@ public class ScriptParseException extends ScriptException {
}
/**
* Construct a new {@code ScriptParseException}.
* Create a new {@code ScriptParseException}.
* @param message detailed message
* @param resource the resource from which the SQL script was read
* @param cause the underlying cause of the failure
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Generic utility methods for working with SQL scripts.
* Generic utility methods for working with SQL scripts in conjunction with JDBC.
*
* <p>Mainly for internal use within the framework.
*
@@ -50,6 +50,7 @@ import org.springframework.util.StringUtils;
* @author Nicolas Debeissat
* @author Phillip Webb
* @since 4.0.3
* @see org.springframework.r2dbc.connection.init.ScriptUtils
*/
public abstract class ScriptUtils {
@@ -101,349 +102,6 @@ public abstract class ScriptUtils {
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
/**
* Split an SQL script into separate statements delimited by the provided
* separator character. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator character separating each statement (typically a ';')
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, String, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
*/
public static void splitSqlScript(String script, char separator, List<String> statements) throws ScriptException {
splitSqlScript(script, String.valueOf(separator), statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, char, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
*/
public static void splitSqlScript(String script, String separator, List<String> statements) throws ScriptException {
splitSqlScript(null, script, separator, DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefix} will be honored:
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefix the prefix that identifies SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
*/
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String commentPrefix, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
* any text beginning with one of the comment prefixes and extending to the
* end of the line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefixes the prefixes that identify SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @since 5.2
*/
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(script, "'script' must not be null or empty");
Assert.notNull(separator, "'separator' must not be null");
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
for (String commentPrefix : commentPrefixes) {
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
}
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
StringBuilder sb = new StringBuilder();
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
sb.append(c);
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
sb.append(c);
continue;
}
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(separator, i)) {
// We've reached the end of the current statement
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
i += separator.length() - 1;
continue;
}
else if (startsWithAny(script, commentPrefixes, i)) {
// Skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf('\n', i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// If there's no EOL, we must be at the end of the script, so stop here.
break;
}
}
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// Skip over any block comments
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new ScriptParseException(
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
}
}
else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
// Avoid multiple adjacent whitespace characters
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
c = ' ';
}
else {
continue;
}
}
}
sb.append(c);
}
if (StringUtils.hasText(sb)) {
statements.add(sb.toString());
}
}
/**
* Read a script from the given resource, using "{@code --}" as the comment prefix
* and "{@code ;}" as the statement separator, and build a String containing the lines.
* @param resource the {@code EncodedResource} to be read
* @return {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
static String readScript(EncodedResource resource) throws IOException {
return readScript(resource, DEFAULT_COMMENT_PREFIXES, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
/**
* Read a script from the provided resource, using the supplied comment prefixes
* and statement separator, and build a {@code String} containing the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param commentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
private static String readScript(EncodedResource resource, @Nullable String[] commentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
try (LineNumberReader lnr = new LineNumberReader(resource.getReader())) {
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
}
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param lineCommentPrefix the prefix that identifies comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
public static String readScript(LineNumberReader lineNumberReader, @Nullable String lineCommentPrefix,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String[] lineCommentPrefixes = (lineCommentPrefix != null) ? new String[] { lineCommentPrefix } : null;
return readScript(lineNumberReader, lineCommentPrefixes, separator, blockCommentEndDelimiter);
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefixes and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param lineCommentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @since 5.2
*/
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] lineCommentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
(lineCommentPrefixes != null && !startsWithAny(currentStatement, lineCommentPrefixes, 0))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentStatement);
}
currentStatement = lineNumberReader.readLine();
}
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
return scriptBuilder.toString();
}
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable String separator) {
if (separator == null) {
return;
}
String trimmed = separator.trim();
if (trimmed.length() == separator.length()) {
return;
}
// separator ends in whitespace, so we might want to see if the script is trying
// to end the same way
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
scriptBuilder.append(separator.substring(trimmed.length()));
}
}
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
for (String prefix : prefixes) {
if (script.startsWith(prefix, offset)) {
return true;
}
}
return false;
}
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim the string delimiting each statement - typically a ';' character
*/
public static boolean containsSqlScriptDelimiters(String script, String delim) {
boolean inLiteral = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
continue;
}
if (c == '\'') {
inLiteral = !inLiteral;
}
if (!inLiteral && script.startsWith(delim, i)) {
return true;
}
}
return false;
}
/**
* Execute the given SQL script using default settings for statement
* separators, comment delimiters, and exception handling flags.
@@ -575,7 +233,7 @@ public abstract class ScriptUtils {
String script;
try {
script = readScript(resource, commentPrefixes, separator, blockCommentEndDelimiter);
script = readScript(resource, separator, commentPrefixes, blockCommentEndDelimiter);
}
catch (IOException ex) {
throw new CannotReadScriptException(resource, ex);
@@ -584,7 +242,9 @@ public abstract class ScriptUtils {
if (separator == null) {
separator = DEFAULT_STATEMENT_SEPARATOR;
}
if (!EOF_STATEMENT_SEPARATOR.equals(separator) && !containsSqlScriptDelimiters(script, separator)) {
if (!EOF_STATEMENT_SEPARATOR.equals(separator) &&
!containsStatementSeparator(resource, script, separator, commentPrefixes,
blockCommentStartDelimiter, blockCommentEndDelimiter)) {
separator = FALLBACK_STATEMENT_SEPARATOR;
}
@@ -647,4 +307,424 @@ public abstract class ScriptUtils {
}
}
/**
* Read a script from the provided resource, using the supplied comment prefixes
* and statement separator, and build a {@code String} containing the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param separator the statement separator in the SQL script (typically ";")
* @param commentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
static String readScript(EncodedResource resource, @Nullable String separator,
String[] commentPrefixes, String blockCommentEndDelimiter) throws IOException {
try (LineNumberReader lnr = new LineNumberReader(resource.getReader())) {
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
}
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static String readScript(LineNumberReader lineNumberReader, @Nullable String commentPrefix,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String[] commentPrefixes = (commentPrefix != null) ? new String[] { commentPrefix } : null;
return readScript(lineNumberReader, commentPrefixes, separator, blockCommentEndDelimiter);
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefixes and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param commentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @since 5.2
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] commentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
(commentPrefixes != null && !startsWithAny(currentStatement, commentPrefixes, 0))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentStatement);
}
currentStatement = lineNumberReader.readLine();
}
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
return scriptBuilder.toString();
}
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable String separator) {
if (separator == null) {
return;
}
String trimmed = separator.trim();
if (trimmed.length() == separator.length()) {
return;
}
// separator ends in whitespace, so we might want to see if the script is trying
// to end the same way
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
scriptBuilder.append(separator.substring(trimmed.length()));
}
}
/**
* Determine if the provided SQL script contains the specified delimiter.
* <p>This method is intended to be used to find the string delimiting each
* SQL statement &mdash; for example, a ';' character.
* <p>Any occurrence of the delimiter within the script will be ignored if it
* is within a <em>literal</em> block of text enclosed in single quotes
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
* ({@code \}), or if it is within a single-line comment or block comment.
* @param script the SQL script to search within
* @param delimiter the statement delimiter to search for
* @see #DEFAULT_COMMENT_PREFIXES
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static boolean containsSqlScriptDelimiters(String script, String delimiter) {
return containsStatementSeparator(null, script, delimiter, DEFAULT_COMMENT_PREFIXES,
DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
/**
* Determine if the provided SQL script contains the specified statement separator.
* <p>This method is intended to be used to find the string separating each
* SQL statement &mdash; for example, a ';' character.
* <p>Any occurrence of the separator within the script will be ignored if it
* is within a <em>literal</em> block of text enclosed in single quotes
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
* ({@code \}), or if it is within a single-line comment or block comment.
* @param resource the resource from which the script was read, or {@code null}
* if unknown
* @param script the SQL script to search within
* @param separator the statement separator to search for
* @param commentPrefixes the prefixes that identify single-line comments
* (typically {@code "--"})
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* (typically {@code "/*"})
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* (typically <code>"*&#47;"</code>)
* @since 5.2.16
*/
private static boolean containsStatementSeparator(@Nullable EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws ScriptException {
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
continue;
}
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(separator, i)) {
return true;
}
else if (startsWithAny(script, commentPrefixes, i)) {
// Skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf('\n', i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// If there's no EOL, we must be at the end of the script, so stop here.
break;
}
}
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// Skip over any block comments
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new ScriptParseException(
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
}
}
}
}
return false;
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator character. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator character separating each statement (typically a ';')
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, String, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(String script, char separator, List<String> statements) throws ScriptException {
splitSqlScript(script, String.valueOf(separator), statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, char, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(String script, String separator, List<String> statements) throws ScriptException {
splitSqlScript(null, script, separator, DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefix} will be honored:
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefix the prefix that identifies SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String commentPrefix, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
* any text beginning with one of the comment prefixes and extending to the
* end of the line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefixes the prefixes that identify SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @since 5.2
* @deprecated as of Spring Framework 5.2.16 with no plans for replacement.
* This is an internal API and will likely be removed in Spring Framework 6.0.
*/
@Deprecated
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(script, "'script' must not be null or empty");
Assert.notNull(separator, "'separator' must not be null");
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
for (String commentPrefix : commentPrefixes) {
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
}
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
StringBuilder sb = new StringBuilder();
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
sb.append(c);
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
sb.append(c);
continue;
}
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(separator, i)) {
// We've reached the end of the current statement
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
i += separator.length() - 1;
continue;
}
else if (startsWithAny(script, commentPrefixes, i)) {
// Skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf('\n', i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// If there's no EOL, we must be at the end of the script, so stop here.
break;
}
}
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// Skip over any block comments
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new ScriptParseException(
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
}
}
else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
// Avoid multiple adjacent whitespace characters
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
c = ' ';
}
else {
continue;
}
}
}
sb.append(c);
}
if (StringUtils.hasText(sb)) {
statements.add(sb.toString());
}
}
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
for (String prefix : prefixes) {
if (script.startsWith(prefix, offset)) {
return true;
}
}
return false;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ package org.springframework.jdbc.datasource.init;
public class UncategorizedScriptException extends ScriptException {
/**
* Construct a new {@code UncategorizedScriptException}.
* Create a new {@code UncategorizedScriptException}.
* @param message detailed message
*/
public UncategorizedScriptException(String message) {
@@ -36,7 +36,7 @@ public class UncategorizedScriptException extends ScriptException {
}
/**
* Construct a new {@code UncategorizedScriptException}.
* Create a new {@code UncategorizedScriptException}.
* @param message detailed message
* @param cause the root cause
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,15 +29,13 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import static org.assertj.core.api.Assertions.assertThat;
/**
* Abstract base class for integration tests involving database initialization.
*
* @author Sam Brannen
* @since 4.0.3
*/
public abstract class AbstractDatabaseInitializationTests {
abstract class AbstractDatabaseInitializationTests {
private final ClassRelativeResourceLoader resourceLoader = new ClassRelativeResourceLoader(getClass());
@@ -47,13 +45,13 @@ public abstract class AbstractDatabaseInitializationTests {
@BeforeEach
public void setUp() {
void setUp() {
db = new EmbeddedDatabaseBuilder().setType(getEmbeddedDatabaseType()).build();
jdbcTemplate = new JdbcTemplate(db);
}
@AfterEach
public void shutDown() {
void shutDown() {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.clear();
TransactionSynchronizationManager.unbindResource(db);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ import static org.mockito.Mockito.verify;
* @author Juergen Hoeller
* @since 4.3
*/
public class CompositeDatabasePopulatorTests {
class CompositeDatabasePopulatorTests {
private final Connection mockedConnection = mock(Connection.class);
@@ -44,49 +44,59 @@ public class CompositeDatabasePopulatorTests {
@Test
public void addPopulators() throws SQLException {
void addPopulators() throws SQLException {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2);
populator.populate(mockedConnection);
verify(mockedDatabasePopulator1,times(1)).populate(mockedConnection);
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
}
@Test
public void setPopulatorsWithMultiple() throws SQLException {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.setPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2); // multiple
populator.populate(mockedConnection);
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
}
@Test
public void setPopulatorsForOverride() throws SQLException {
void setPopulatorsWithMultiple() throws SQLException {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.setPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2); // multiple
populator.populate(mockedConnection);
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
}
@Test
void setPopulatorsForOverride() throws SQLException {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.setPopulators(mockedDatabasePopulator1);
populator.setPopulators(mockedDatabasePopulator2); // override
populator.populate(mockedConnection);
verify(mockedDatabasePopulator1, times(0)).populate(mockedConnection);
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
}
@Test
public void constructWithVarargs() throws SQLException {
void constructWithVarargs() throws SQLException {
CompositeDatabasePopulator populator =
new CompositeDatabasePopulator(mockedDatabasePopulator1, mockedDatabasePopulator2);
populator.populate(mockedConnection);
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
}
@Test
public void constructWithCollection() throws SQLException {
void constructWithCollection() throws SQLException {
Set<DatabasePopulator> populators = new LinkedHashSet<>();
populators.add(mockedDatabasePopulator1);
populators.add(mockedDatabasePopulator2);
CompositeDatabasePopulator populator = new CompositeDatabasePopulator(populators);
populator.populate(mockedConnection);
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
}
@@ -26,7 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Sam Brannen
* @since 4.0.3
*/
class H2DatabasePopulatorTests extends AbstractDatabasePopulatorTests {
class H2DatabasePopulatorIntegrationTests extends AbstractDatabasePopulatorTests {
@Override
protected EmbeddedDatabaseType getEmbeddedDatabaseType() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,12 @@
package org.springframework.jdbc.datasource.init;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.mock;
/**
* Unit tests for {@link ResourceDatabasePopulator}.
@@ -31,84 +31,84 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* @since 4.1
* @see AbstractDatabasePopulatorTests
*/
public class ResourceDatabasePopulatorTests {
class ResourceDatabasePopulatorUnitTests {
private static final Resource script1 = Mockito.mock(Resource.class);
private static final Resource script2 = Mockito.mock(Resource.class);
private static final Resource script3 = Mockito.mock(Resource.class);
private static final Resource script1 = mock(Resource.class);
private static final Resource script2 = mock(Resource.class);
private static final Resource script3 = mock(Resource.class);
@Test
public void constructWithNullResource() {
void constructWithNullResource() {
assertThatIllegalArgumentException().isThrownBy(() ->
new ResourceDatabasePopulator((Resource) null));
}
@Test
public void constructWithNullResourceArray() {
void constructWithNullResourceArray() {
assertThatIllegalArgumentException().isThrownBy(() ->
new ResourceDatabasePopulator((Resource[]) null));
}
@Test
public void constructWithResource() {
void constructWithResource() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(script1);
assertThat(databasePopulator.scripts.size()).isEqualTo(1);
assertThat(databasePopulator.scripts).hasSize(1);
}
@Test
public void constructWithMultipleResources() {
void constructWithMultipleResources() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(script1, script2);
assertThat(databasePopulator.scripts.size()).isEqualTo(2);
assertThat(databasePopulator.scripts).hasSize(2);
}
@Test
public void constructWithMultipleResourcesAndThenAddScript() {
void constructWithMultipleResourcesAndThenAddScript() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(script1, script2);
assertThat(databasePopulator.scripts.size()).isEqualTo(2);
assertThat(databasePopulator.scripts).hasSize(2);
databasePopulator.addScript(script3);
assertThat(databasePopulator.scripts.size()).isEqualTo(3);
assertThat(databasePopulator.scripts).hasSize(3);
}
@Test
public void addScriptsWithNullResource() {
void addScriptsWithNullResource() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.addScripts((Resource) null));
}
@Test
public void addScriptsWithNullResourceArray() {
void addScriptsWithNullResourceArray() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.addScripts((Resource[]) null));
}
@Test
public void setScriptsWithNullResource() {
void setScriptsWithNullResource() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.setScripts((Resource) null));
}
@Test
public void setScriptsWithNullResourceArray() {
void setScriptsWithNullResourceArray() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.setScripts((Resource[]) null));
}
@Test
public void setScriptsAndThenAddScript() {
void setScriptsAndThenAddScript() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThat(databasePopulator.scripts.size()).isEqualTo(0);
assertThat(databasePopulator.scripts).isEmpty();
databasePopulator.setScripts(script1, script2);
assertThat(databasePopulator.scripts.size()).isEqualTo(2);
assertThat(databasePopulator.scripts).hasSize(2);
databasePopulator.addScript(script3);
assertThat(databasePopulator.scripts.size()).isEqualTo(3);
assertThat(databasePopulator.scripts).hasSize(3);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,10 @@ package org.springframework.jdbc.datasource.init;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.util.Strings;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
@@ -46,6 +49,7 @@ import static org.springframework.jdbc.datasource.init.ScriptUtils.splitSqlScrip
public class ScriptUtilsUnitTests {
@Test
@SuppressWarnings("deprecation")
public void splitSqlScriptDelimitedWithSemicolon() {
String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
@@ -53,54 +57,72 @@ public class ScriptUtilsUnitTests {
String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
char delim = ';';
String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim;
String delimiter = ";";
String script = Strings.join(rawStatement1, rawStatement2, rawStatement3).with(delimiter);
List<String> statements = new ArrayList<>();
splitSqlScript(script, delim, statements);
splitSqlScript(script, delimiter, statements);
assertThat(statements).containsExactly(cleanedStatement1, cleanedStatement2, cleanedStatement3);
}
@Test
@SuppressWarnings("deprecation")
public void splitSqlScriptDelimitedWithNewLine() {
String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
char delim = '\n';
String script = statement1 + delim + statement2 + delim + statement3 + delim;
String delimiter = "\n";
String script = Strings.join(statement1, statement2, statement3).with(delimiter);
List<String> statements = new ArrayList<>();
splitSqlScript(script, delim, statements);
splitSqlScript(script, delimiter, statements);
assertThat(statements).containsExactly(statement1, statement2, statement3);
}
@Test
@SuppressWarnings("deprecation")
public void splitSqlScriptDelimitedWithNewLineButDefaultDelimiterSpecified() {
String statement1 = "do something";
String statement2 = "do something else";
char delim = '\n';
String script = statement1 + delim + statement2 + delim;
String script = Strings.join(statement1, statement2).with("\n");
List<String> statements = new ArrayList<>();
splitSqlScript(script, DEFAULT_STATEMENT_SEPARATOR, statements);
assertThat(statements).as("stripped but not split statements").containsExactly(script.replace('\n', ' '));
}
@Test // SPR-13218
public void splitScriptWithSingleQuotesNestedInsideDoubleQuotes() throws Exception {
@SuppressWarnings("deprecation")
public void splitScriptWithSingleQuotesNestedInsideDoubleQuotes() {
String statement1 = "select '1' as \"Dogbert's owner's\" from dual";
String statement2 = "select '2' as \"Dilbert's\" from dual";
char delim = ';';
String script = statement1 + delim + statement2 + delim;
String delimiter = ";";
String script = Strings.join(statement1, statement2).with(delimiter);
List<String> statements = new ArrayList<>();
splitSqlScript(script, ';', statements);
splitSqlScript(script, delimiter, statements);
assertThat(statements).containsExactly(statement1, statement2);
}
@Test // SPR-11560
@SuppressWarnings("deprecation")
public void readAndSplitScriptWithMultipleNewlinesAsSeparator() throws Exception {
String script = readScript("db-test-data-multi-newline.sql");
List<String> statements = new ArrayList<>();
splitSqlScript(script, "\n\n", statements);
String statement1 = "insert into T_TEST (NAME) values ('Keith')";
String statement2 = "insert into T_TEST (NAME) values ('Dave')";
assertThat(statements).containsExactly(statement1, statement2);
}
@@ -122,65 +144,103 @@ public class ScriptUtilsUnitTests {
splitScriptContainingComments(script, "--", "#", "^");
}
private void splitScriptContainingComments(String script, String... commentPrefixes) throws Exception {
@SuppressWarnings("deprecation")
private void splitScriptContainingComments(String script, String... commentPrefixes) {
List<String> statements = new ArrayList<>();
splitSqlScript(null, script, ";", commentPrefixes, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
// Statement 4 addresses the error described in SPR-9982.
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
assertThat(statements).containsExactly(statement1, statement2, statement3, statement4);
}
@Test // SPR-10330
@SuppressWarnings("deprecation")
public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception {
String script = readScript("test-data-with-comments-and-leading-tabs.sql");
List<String> statements = new ArrayList<>();
splitSqlScript(script, ';', statements);
String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)";
String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)";
assertThat(statements).containsExactly(statement1, statement2, statement3);
}
@Test // SPR-9531
@SuppressWarnings("deprecation")
public void readAndSplitScriptContainingMultiLineComments() throws Exception {
String script = readScript("test-data-with-multi-line-comments.sql");
List<String> statements = new ArrayList<>();
splitSqlScript(script, ';', statements);
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Sam' , 'Brannen' )";
assertThat(statements).containsExactly(statement1, statement2);
}
@Test
@SuppressWarnings("deprecation")
public void readAndSplitScriptContainingMultiLineNestedComments() throws Exception {
String script = readScript("test-data-with-multi-line-nested-comments.sql");
List<String> statements = new ArrayList<>();
splitSqlScript(script, ';', statements);
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Sam' , 'Brannen' )";
assertThat(statements).containsExactly(statement1, statement2);
}
@Test
public void containsDelimiters() {
assertThat(containsSqlScriptDelimiters("select 1\n select ';'", ";")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1; select 2", ";")).isTrue();
assertThat(containsSqlScriptDelimiters("select 1; select '\\n\n';", "\n")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n")).isTrue();
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n\n")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1\n\n select 2", "\n\n")).isTrue();
// MySQL style escapes '\\'
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('a\\\\', 'b;')", ";")).isFalse();
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('Charles', 'd\\'Artagnan'); select 1;", ";")).isTrue();
@ParameterizedTest
@CsvSource(delimiter = '#', value = {
// semicolon
"'select 1\n select '';''' # ; # false",
"'select 1\n select \";\"' # ; # false",
"'select 1; select 2' # ; # true",
// newline
"'select 1; select ''\n''' # '\n' # false",
"'select 1; select \"\n\"' # '\n' # false",
"'select 1\n select 2' # '\n' # true",
// double newline
"'select 1\n select 2' # '\n\n' # false",
"'select 1\n\n select 2' # '\n\n' # true",
// semicolon with MySQL style escapes '\\'
"'insert into users(first, last)\nvalues(''a\\\\'', ''b;'')' # ; # false",
"'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true",
// semicolon inside comments
"'-- a;b;c\ninsert into colors(color_num) values(42);' # ; # true",
"'/* a;b;c */\ninsert into colors(color_num) values(42);' # ; # true",
"'-- a;b;c\ninsert into colors(color_num) values(42)' # ; # false",
"'/* a;b;c */\ninsert into colors(color_num) values(42)' # ; # false",
// single quotes inside comments
"'-- What\\''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
"'-- What''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
"'/* What\\''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
"'/* What''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
// double quotes inside comments
"'-- double \" quotes\ninsert into colors(color_num) values(42);' # ; # true",
"'-- double \\\" quotes\ninsert into colors(color_num) values(42);' # ; # true",
"'/* double \" quotes */\ninsert into colors(color_num) values(42);' # ; # true",
"'/* double \\\" quotes */\ninsert into colors(color_num) values(42);' # ; # true"
})
@SuppressWarnings("deprecation")
public void containsStatementSeparator(String script, String delimiter, boolean expected) {
// Indirectly tests ScriptUtils.containsStatementSeparator(EncodedResource, String, String, String[], String, String).
assertThat(containsSqlScriptDelimiters(script, delimiter)).isEqualTo(expected);
}
private String readScript(String path) throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource(path, getClass()));
return ScriptUtils.readScript(resource);
return ScriptUtils.readScript(resource, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_COMMENT_PREFIXES,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
}
@@ -5,16 +5,19 @@
* x, y, z...
*/
-- This is a single line comment containing single (') and double quotes (").
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
-- This is also a comment.
/*-------------------------------------------
-- A fancy multi-line comments that puts
-- A fancy multi-line comment that puts
-- single line comments inside of a multi-line
-- comment block.
Moreover, the block comment end delimiter
appears on a line that can potentially also
be a single-line comment if we weren't
already inside a multi-line comment run.
And here's a line containing single and double quotes (").
-------------------------------------------*/
INSERT INTO
users(first_name, last_name) -- This is a single line comment containing the block-end-comment sequence here */ but it's still a single-line comment
@@ -423,6 +423,7 @@ public class CachingConnectionFactory extends SingleConnectionFactory {
return new CachedMessageProducer(producer);
}
@SuppressWarnings("resource")
private MessageConsumer getCachedConsumer(Destination dest, @Nullable String selector,
@Nullable Boolean noLocal, @Nullable String subscription, boolean durable) throws JMSException {
@@ -120,8 +120,7 @@ public class DefaultMessageHandlerMethodFactory
* the ones configured by default. This is an advanced option. For most use cases
* it should be sufficient to use {@link #setCustomArgumentResolvers(java.util.List)}.
*/
@SuppressWarnings("ConstantConditions")
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
public void setArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers == null) {
this.argumentResolvers.clear();
return;
@@ -131,7 +131,6 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
* @param message the current message
* @return the frame type or {@code null} if not found
*/
@SuppressWarnings("ConstantConditions")
@Nullable
public static FrameType getFrameType(Message<?> message) {
return (FrameType) message.getHeaders().get(RSocketFrameTypeMessageCondition.FRAME_TYPE_HEADER);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,8 +29,8 @@ import org.springframework.util.Assert;
* Base class for STOMP client implementations.
*
* <p>Subclasses can connect over WebSocket or TCP using any library. When creating
* a new connection, a subclass can create an instance of @link DefaultStompSession}
* which extends {@link org.springframework.messaging.tcp.TcpConnectionHandler}
* a new connection, a subclass can create an instance of {@link DefaultStompSession}
* which implements {@link org.springframework.messaging.tcp.TcpConnectionHandler}
* whose lifecycle methods the subclass must then invoke.
*
* <p>In effect, {@code TcpConnectionHandler} and {@code TcpConnection} are the
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,6 +102,7 @@ inline fun <reified T : Any> RSocketRequester.RequestSpec.dataWithType(flow: Flo
* @author Sebastien Deleuze
* @since 5.2
*/
@Suppress("DEPRECATION")
suspend fun RSocketRequester.RetrieveSpec.sendAndAwait() {
send().awaitSingleOrNull()
}
@@ -121,6 +122,7 @@ suspend inline fun <reified T : Any> RSocketRequester.RetrieveSpec.retrieveAndAw
* @author Sebastien Deleuze
* @since 5.2.1
*/
@Suppress("DEPRECATION")
suspend inline fun <reified T : Any> RSocketRequester.RetrieveSpec.retrieveAndAwaitOrNull(): T? =
retrieveMono(object : ParameterizedTypeReference<T>() {}).awaitSingleOrNull()
+3 -1
View File
@@ -19,5 +19,7 @@ dependencies {
testCompile(testFixtures(project(":spring-context")))
testCompile("io.projectreactor:reactor-test")
testCompile("io.r2dbc:r2dbc-h2")
testCompile("io.r2dbc:r2dbc-spi-test:0.8.1.RELEASE")
testCompile("io.r2dbc:r2dbc-spi-test:0.8.1.RELEASE") {
exclude group: "org.springframework", module: "spring-jdbc"
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,18 +39,18 @@ import org.springframework.util.Assert;
* {@link org.springframework.transaction.ReactiveTransactionManager}
* implementation for a single R2DBC {@link ConnectionFactory}. This class is
* capable of working in any environment with any R2DBC driver, as long as the
* setup uses a {@link ConnectionFactory} as its {@link Connection} factory
* mechanism. Binds a R2DBC {@link Connection} from the specified
* {@link ConnectionFactory} to the current subscriber context, potentially
* allowing for one context-bound {@link Connection} per {@link ConnectionFactory}.
* setup uses a {@code ConnectionFactory} as its {@link Connection} factory
* mechanism. Binds a R2DBC {@code Connection} from the specified
* {@code ConnectionFactory} to the current subscriber context, potentially
* allowing for one context-bound {@code Connection} per {@code ConnectionFactory}.
*
* <p><b>Note: The {@link ConnectionFactory} that this transaction manager
* operates on needs to return independent {@link Connection}s.</b>
* The {@link Connection}s may come from a pool (the typical case), but the
* {@link ConnectionFactory} must not return scoped scoped {@link Connection}s
* or the like. This transaction manager will associate {@link Connection}
* <p><b>Note: The {@code ConnectionFactory} that this transaction manager
* operates on needs to return independent {@code Connection}s.</b>
* The {@code Connection}s may come from a pool (the typical case), but the
* {@code ConnectionFactory} must not return scoped scoped {@code Connection}s
* or the like. This transaction manager will associate {@code Connection}
* with context-bound transactions itself, according to the specified propagation
* behavior. It assumes that a separate, independent {@link Connection} can
* behavior. It assumes that a separate, independent {@code Connection} can
* be obtained even during an ongoing transaction.
*
* <p>Application code is required to retrieve the R2DBC Connection via
@@ -59,18 +59,18 @@ import org.springframework.util.Assert;
* Spring classes such as {@code DatabaseClient} use this strategy implicitly.
* If not used in combination with this transaction manager, the
* {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the
* native {@link ConnectionFactory} lookup; it can thus be used in a portable fashion.
* native {@code ConnectionFactory} lookup; it can thus be used in a portable fashion.
*
* <p>Alternatively, you can allow application code to work with the standard
* R2DBC lookup pattern {@link ConnectionFactory#create()}, for example for code
* that is not aware of Spring at all. In that case, define a
* {@link TransactionAwareConnectionFactoryProxy} for your target {@link ConnectionFactory},
* and pass that proxy {@link ConnectionFactory} to your DAOs, which will automatically
* {@link TransactionAwareConnectionFactoryProxy} for your target {@code ConnectionFactory},
* and pass that proxy {@code ConnectionFactory} to your DAOs, which will automatically
* participate in Spring-managed transactions when accessing it.
*
* <p>This transaction manager triggers flush callbacks on registered transaction
* synchronizations (if synchronization is generally active), assuming resources
* operating on the underlying R2DBC {@link Connection}.
* operating on the underlying R2DBC {@code Connection}.
*
* @author Mark Paluch
* @since 5.3
@@ -88,14 +88,14 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* Create a new @link ConnectionFactoryTransactionManager} instance.
* Create a new {@code R2dbcTransactionManager} instance.
* A ConnectionFactory has to be set to be able to use it.
* @see #setConnectionFactory
*/
public R2dbcTransactionManager() {}
/**
* Create a new {@link R2dbcTransactionManager} instance.
* Create a new {@code R2dbcTransactionManager} instance.
* @param connectionFactory the R2DBC ConnectionFactory to manage transactions for
*/
public R2dbcTransactionManager(ConnectionFactory connectionFactory) {
@@ -107,10 +107,10 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* Set the R2DBC {@link ConnectionFactory} that this instance should manage transactions for.
* <p>This will typically be a locally defined {@link ConnectionFactory}, for example an connection pool.
* <p><b>The {@link ConnectionFactory} passed in here needs to return independent {@link Connection}s.</b>
* The {@link Connection}s may come from a pool (the typical case), but the {@link ConnectionFactory}
* must not return scoped {@link Connection}s or the like.
* <p>This will typically be a locally defined {@code ConnectionFactory}, for example an connection pool.
* <p><b>The {@code ConnectionFactory} passed in here needs to return independent {@link Connection}s.</b>
* The {@code Connection}s may come from a pool (the typical case), but the {@code ConnectionFactory}
* must not return scoped {@code Connection}s or the like.
* @see TransactionAwareConnectionFactoryProxy
*/
public void setConnectionFactory(@Nullable ConnectionFactory connectionFactory) {
@@ -127,7 +127,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* Obtain the {@link ConnectionFactory} for actual use.
* @return the {@link ConnectionFactory} (never {@code null})
* @return the {@code ConnectionFactory} (never {@code null})
* @throws IllegalStateException in case of no ConnectionFactory set
*/
protected ConnectionFactory obtainConnectionFactory() {
@@ -405,9 +405,9 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
/**
* Resolve the {@link TransactionDefinition#getIsolationLevel() isolation level constant} to a R2DBC
* Resolve the {@linkplain TransactionDefinition#getIsolationLevel() isolation level constant} to a R2DBC
* {@link IsolationLevel}. If you'd like to extend isolation level translation for vendor-specific
* {@link IsolationLevel}s, override this method accordingly.
* {@code IsolationLevel}s, override this method accordingly.
* @param isolationLevel the isolation level to translate.
* @return the resolved isolation level. Can be {@code null} if not resolvable or the isolation level
* should remain {@link TransactionDefinition#ISOLATION_DEFAULT default}.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,25 +36,25 @@ import org.springframework.util.Assert;
/**
* Implementation of {@link DelegatingConnectionFactory} that wraps a
* single R2DBC Connection which is not closed after use.
* single R2DBC {@link Connection} which is not closed after use.
* Obviously, this is not multi-threading capable.
*
* <p>Note that at shutdown, someone should close the underlying
* Connection via the {@code close()} method. Client code will
* never call close on the Connection handle if it is
* {@code Connection} via the {@code close()} method. Client code will
* never call close on the {@code Connection} handle if it is
* SmartConnectionFactory-aware (e.g. uses
* {@link ConnectionFactoryUtils#releaseConnection(Connection, ConnectionFactory)}).
*
* <p>If client code will call {@link Connection#close()} in the
* assumption of a pooled Connection, like when using persistence tools,
* set "suppressClose" to "true". This will return a close-suppressing
* assumption of a pooled {@code Connection}, like when using persistence tools,
* set "suppressClose" to {@code true}. This will return a close-suppressing
* proxy instead of the physical Connection.
*
* <p>This is primarily intended for testing and pipelining usage of connections.
* For example, it enables easy testing outside an application server, for code
* For example, it enables easy testing outside an application server for code
* that expects to work on a {@link ConnectionFactory}.
* Note that this implementation does not act as a connection pool-like utility.
* Connection pooling requires a {@link ConnectionFactory} implemented by e.g.
* Connection pooling requires a pooling {@link ConnectionFactory} such as one from
* {@code r2dbc-pool}.
*
* @author Mark Paluch
@@ -90,10 +90,10 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
}
/**
* Create a new {@link SingleConnectionFactory} using a R2DBC connection URL.
* @param url the R2DBC URL to use for accessing {@link ConnectionFactory} discovery.
* @param suppressClose if the returned {@link Connection} should be a close-suppressing proxy
* or the physical {@link Connection}.
* Create a new {@code SingleConnectionFactory} using an R2DBC connection URL.
* @param url the R2DBC URL to use for accessing {@link ConnectionFactory} discovery
* @param suppressClose if the returned {@link Connection} should be a
* close-suppressing proxy or the physical {@code Connection}
* @see ConnectionFactories#get(String)
*/
public SingleConnectionFactory(String url, boolean suppressClose) {
@@ -103,13 +103,15 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
}
/**
* Create a new {@link SingleConnectionFactory} with a given {@link Connection} and
* {@link ConnectionFactoryMetadata}.
* @param target underlying target {@link Connection}.
* @param metadata {@link ConnectionFactory} metadata to be associated with this {@link ConnectionFactory}.
* @param suppressClose if the {@link Connection} should be wrapped with a {@link Connection} that suppresses
* @code close()} calls (to allow for normal {@code close()} usage in applications that expect a pooled
* @link Connection}).
* Create a new {@code SingleConnectionFactory} with a given {@link Connection}
* and {@link ConnectionFactoryMetadata}.
* @param target underlying target {@code Connection}
* @param metadata {@code ConnectionFactory} metadata to be associated with
* this {@code ConnectionFactory}
* @param suppressClose {@code true} if the {@code Connection} should be wrapped
* with a {@code Connection} that suppresses {@code close()} calls (to allow
* for normal {@code close()} usage in applications that expect a pooled
* {@code Connection})
*/
public SingleConnectionFactory(Connection target, ConnectionFactoryMetadata metadata, boolean suppressClose) {
super(new ConnectionFactory() {
@@ -133,7 +135,7 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Set whether the returned {@link Connection} should be a close-suppressing proxy
* or the physical {@link Connection}.
* or the physical {@code Connection}.
*/
public void setSuppressClose(boolean suppressClose) {
this.suppressClose = suppressClose;
@@ -141,21 +143,23 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Return whether the returned {@link Connection} will be a close-suppressing proxy
* or the physical {@link Connection}.
* or the physical {@code Connection}.
*/
protected boolean isSuppressClose() {
return this.suppressClose;
}
/**
* Set whether the returned {@link Connection}'s "autoCommit" setting should be overridden.
* Set whether the returned {@link Connection}'s "autoCommit" setting should
* be overridden.
*/
public void setAutoCommit(boolean autoCommit) {
this.autoCommit = autoCommit;
}
/**
* Return whether the returned {@link Connection}'s "autoCommit" setting should be overridden.
* Return whether the returned {@link Connection}'s "autoCommit" setting should
* be overridden.
* @return the "autoCommit" value, or {@code null} if none to be applied
*/
@Nullable
@@ -207,9 +211,9 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Prepare the {@link Connection} before using it.
* Applies {@link #getAutoCommitValue() auto-commit} settings if configured.
* @param connection the requested {@link Connection}.
* @return the prepared {@link Connection}.
* Applies {@linkplain #getAutoCommitValue() auto-commit} settings if configured.
* @param connection the requested {@code Connection}
* @return the prepared {@code Connection}
*/
protected Mono<Connection> prepareConnection(Connection connection) {
Boolean autoCommit = getAutoCommitValue();
@@ -222,8 +226,8 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Wrap the given {@link Connection} with a proxy that delegates every method call to it
* but suppresses close calls.
* @param target the original {@link Connection} to wrap.
* @return the wrapped Connection.
* @param target the original {@code Connection} to wrap
* @return the wrapped Connection
*/
protected Connection getCloseSuppressingConnectionProxy(Connection target) {
return (Connection) Proxy.newProxyInstance(SingleConnectionFactory.class.getClassLoader(),
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import org.springframework.core.io.support.EncodedResource;
/**
* Thrown by {@link ScriptUtils} if an SQL script cannot be read.
*
* @author Keith Donald
* @author Sam Brannen
* @author Mark Paluch
* @since 5.3
*/
@@ -29,8 +31,8 @@ public class CannotReadScriptException extends ScriptException {
/**
* Create a new {@code CannotReadScriptException}.
* @param resource the resource that cannot be read from.
* @param cause the underlying cause of the resource access failure.
* @param resource the resource that cannot be read from
* @param cause the underlying cause of the resource access failure
*/
public CannotReadScriptException(EncodedResource resource, Throwable cause) {
super("Cannot read SQL script from " + resource, cause);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,8 +29,12 @@ import org.springframework.util.Assert;
/**
* Composite {@link DatabasePopulator} that delegates to a list of given
* {@link DatabasePopulator} implementations, executing all scripts.
* {@code DatabasePopulator} implementations, executing all scripts.
*
* @author Dave Syer
* @author Juergen Hoeller
* @author Sam Brannen
* @author Kazuki Shimizu
* @author Mark Paluch
* @since 5.3
*/
@@ -44,14 +48,15 @@ public class CompositeDatabasePopulator implements DatabasePopulator {
* @see #setPopulators
* @see #addPopulators
*/
public CompositeDatabasePopulator() {}
public CompositeDatabasePopulator() {
}
/**
* Create a {@code CompositeDatabasePopulator}. with the given populators.
* @param populators one or more populators to delegate to.
*/
public CompositeDatabasePopulator(Collection<DatabasePopulator> populators) {
Assert.notNull(populators, "Collection of DatabasePopulator must not be null");
Assert.notNull(populators, "DatabasePopulators must not be null");
this.populators.addAll(populators);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@ import org.springframework.util.Assert;
* initialization and {@link #setDatabaseCleaner clean up} a database during
* destruction.
*
* @author Dave Syer
* @author Sam Brannen
* @author Mark Paluch
* @since 5.3
* @see DatabasePopulator
@@ -50,7 +52,7 @@ public class ConnectionFactoryInitializer implements InitializingBean, Disposabl
* The {@link ConnectionFactory} for the database to populate when this
* component is initialized and to clean up when this component is shut down.
* <p>This property is mandatory with no default provided.
* @param connectionFactory the R2DBC {@link ConnectionFactory}.
* @param connectionFactory the R2DBC {@link ConnectionFactory}
*/
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
@@ -66,10 +68,9 @@ public class ConnectionFactoryInitializer implements InitializingBean, Disposabl
}
/**
* Set the {@link DatabasePopulator} to execute during the bean destruction phase, cleaning up the database and
* leaving it in a known state for others.
*
* @param databaseCleaner the {@link DatabasePopulator} to use during destruction
* Set the {@link DatabasePopulator} to execute during the bean destruction
* phase, cleaning up the database and leaving it in a known state for others.
* @param databaseCleaner the {@code DatabasePopulator} to use during destruction
* @see #setDatabasePopulator
*/
public void setDatabaseCleaner(DatabasePopulator databaseCleaner) {
@@ -77,8 +78,8 @@ public class ConnectionFactoryInitializer implements InitializingBean, Disposabl
}
/**
* Flag to explicitly enable or disable the {@link #setDatabasePopulator database populator}
* and {@link #setDatabaseCleaner database cleaner}.
* Flag to explicitly enable or disable the {@linkplain #setDatabasePopulator
* database populator} and {@linkplain #setDatabaseCleaner database cleaner}.
* @param enabled {@code true} if the database populator and database cleaner
* should be called on startup and shutdown, respectively
*/
@@ -88,7 +89,8 @@ public class ConnectionFactoryInitializer implements InitializingBean, Disposabl
/**
* Use the {@link #setDatabasePopulator database populator} to set up the database.
* Use the {@linkplain #setDatabasePopulator database populator} to set up
* the database.
*/
@Override
public void afterPropertiesSet() {
@@ -96,7 +98,8 @@ public class ConnectionFactoryInitializer implements InitializingBean, Disposabl
}
/**
* Use the {@link #setDatabaseCleaner database cleaner} to clean up the database.
* Use the {@linkplain #setDatabaseCleaner database cleaner} to clean up the
* database.
*/
@Override
public void destroy() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@ import org.springframework.util.Assert;
* Strategy used to populate, initialize, or clean up a database.
*
* @author Mark Paluch
* @author Keith Donald
* @author Sam Brannen
* @since 5.3
* @see ResourceDatabasePopulator
* @see ConnectionFactoryInitializer
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@ import org.springframework.dao.DataAccessException;
import org.springframework.lang.Nullable;
/**
* Root of the hierarchy of data access exceptions that are related to processing of SQL scripts.
* Root of the hierarchy of data access exceptions that are related to processing
* of SQL scripts.
*
* @author Sam Brannen
* @author Mark Paluch
* @since 5.3
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import org.springframework.lang.Nullable;
/**
* Thrown by {@link ScriptUtils} if an SQL script cannot be properly parsed.
*
* @author Sam Brannen
* @author Mark Paluch
* @since 5.3
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,8 @@ import org.springframework.core.io.support.EncodedResource;
* Thrown by {@link ScriptUtils} if a statement in an SQL script failed when
* executing it against the target database.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @author Mark Paluch
* @since 5.3
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,8 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Generic utility methods for working with SQL scripts.
* Generic utility methods for working with SQL scripts in conjunction with R2DBC.
*
* <p>Mainly for internal use within the framework.
*
* @author Thomas Risberg
@@ -58,6 +59,7 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Mark Paluch
* @since 5.3
* @see org.springframework.jdbc.datasource.init.ScriptUtils
*/
public abstract class ScriptUtils {
@@ -84,15 +86,10 @@ public abstract class ScriptUtils {
*/
public static final String EOF_STATEMENT_SEPARATOR = "^^^ END OF SCRIPT ^^^";
/**
* Default prefix for single-line comments within SQL scripts: {@code "--"}.
*/
public static final String DEFAULT_COMMENT_PREFIX = "--";
/**
* Default prefixes for single-line comments within SQL scripts: {@code ["--"]}.
*/
public static final String[] DEFAULT_COMMENT_PREFIXES = {DEFAULT_COMMENT_PREFIX};
public static final String[] DEFAULT_COMMENT_PREFIXES = {"--"};
/**
* Default start delimiter for block comments within SQL scripts: {@code "/*"}.
@@ -109,90 +106,326 @@ public abstract class ScriptUtils {
/**
* Split an SQL script into separate statements delimited by the provided
* separator character. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator character separating each statement (typically a ';')
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, String, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
* Execute the given SQL script using default settings for statement
* separators, comment delimiters, and exception handling flags.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource to load the SQL script from; encoded with the
* current platform's default encoding
* @throws ScriptException if an error occurred while executing the SQL script
* @see #executeSqlScript(Connection, EncodedResource, DataBufferFactory, boolean, boolean, String[], String, String, String)
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #DEFAULT_COMMENT_PREFIXES
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static void splitSqlScript(String script, char separator, List<String> statements) throws ScriptException {
splitSqlScript(script, String.valueOf(separator), statements);
public static Mono<Void> executeSqlScript(Connection connection, Resource resource) throws ScriptException {
return executeSqlScript(connection, new EncodedResource(resource));
}
/**
* Execute the given SQL script using default settings for statement
* separators, comment delimiters, and exception handling flags.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource (potentially associated with a specific encoding)
* to load the SQL script from
* @throws ScriptException if an error occurred while executing the SQL script
* @see #executeSqlScript(Connection, EncodedResource, DataBufferFactory, boolean, boolean, String[], String, String, String)
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #DEFAULT_COMMENT_PREFIXES
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static Mono<Void> executeSqlScript(Connection connection, EncodedResource resource) throws ScriptException {
return executeSqlScript(connection, resource, DefaultDataBufferFactory.sharedInstance, false, false,
DEFAULT_COMMENT_PREFIXES, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
/**
* Execute the given SQL script.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource (potentially associated with a specific encoding)
* to load the SQL script from
* @param dataBufferFactory the factory to create data buffers with
* @param continueOnError whether or not to continue without throwing an exception
* in the event of an error
* @param ignoreFailedDrops whether or not to continue in the event of specifically
* an error on a {@code DROP} statement
* @param commentPrefix the prefix that identifies single-line comments in the
* SQL script (typically "--")
* @param separator the script statement separator; defaults to
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to
* {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to
* {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a
* single statement without a separator
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @throws ScriptException if an error occurred while executing the SQL script
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #FALLBACK_STATEMENT_SEPARATOR
* @see #EOF_STATEMENT_SEPARATOR
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static Mono<Void> executeSqlScript(Connection connection, EncodedResource resource,
DataBufferFactory dataBufferFactory, boolean continueOnError, boolean ignoreFailedDrops,
String commentPrefix, @Nullable String separator, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws ScriptException {
return executeSqlScript(connection, resource, dataBufferFactory, continueOnError,
ignoreFailedDrops, new String[] { commentPrefix }, separator,
blockCommentStartDelimiter, blockCommentEndDelimiter);
}
/**
* Execute the given SQL script.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource (potentially associated with a specific encoding)
* to load the SQL script from
* @param dataBufferFactory the factory to create data buffers with
* @param continueOnError whether or not to continue without throwing an exception
* in the event of an error
* @param ignoreFailedDrops whether or not to continue in the event of specifically
* an error on a {@code DROP} statement
* @param commentPrefixes the prefixes that identify single-line comments in the
* SQL script (typically "--")
* @param separator the script statement separator; defaults to
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to
* {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to
* {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a
* single statement without a separator
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @throws ScriptException if an error occurred while executing the SQL script
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #FALLBACK_STATEMENT_SEPARATOR
* @see #EOF_STATEMENT_SEPARATOR
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static Mono<Void> executeSqlScript(Connection connection, EncodedResource resource,
DataBufferFactory dataBufferFactory, boolean continueOnError, boolean ignoreFailedDrops,
String[] commentPrefixes, @Nullable String separator, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws ScriptException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL script from " + resource);
}
long startTime = System.currentTimeMillis();
Mono<String> inputScript = readScript(resource, dataBufferFactory, separator)
.onErrorMap(IOException.class, ex -> new CannotReadScriptException(resource, ex));
AtomicInteger statementNumber = new AtomicInteger();
Flux<Void> executeScript = inputScript.flatMapIterable(script -> {
String separatorToUse = separator;
if (separatorToUse == null) {
separatorToUse = DEFAULT_STATEMENT_SEPARATOR;
}
if (!EOF_STATEMENT_SEPARATOR.equals(separatorToUse) &&
!containsStatementSeparator(resource, script, separatorToUse, commentPrefixes,
blockCommentStartDelimiter, blockCommentEndDelimiter)) {
separatorToUse = FALLBACK_STATEMENT_SEPARATOR;
}
return splitSqlScript(resource, script, separatorToUse, commentPrefixes,
blockCommentStartDelimiter, blockCommentEndDelimiter);
}).concatMap(statement -> {
statementNumber.incrementAndGet();
return runStatement(statement, connection, resource, continueOnError, ignoreFailedDrops, statementNumber);
});
if (logger.isDebugEnabled()) {
executeScript = executeScript.doOnComplete(() -> {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.debug("Executed SQL script from " + resource + " in " + elapsedTime + " ms.");
});
}
return executeScript.onErrorMap(ex -> !(ex instanceof ScriptException),
ex -> new UncategorizedScriptException(
"Failed to execute database script from resource [" + resource + "]", ex))
.then();
}
/**
* Read a script from the provided resource, using the supplied statement
* separator, and build a {@code String} containing the lines.
* @param resource the {@code EncodedResource} containing the script to be
* processed
* @param dataBufferFactory the factory to create data buffers with
* @param separator the statement separator in the SQL script (typically ";")
* @return a {@link Mono} of {@link String} containing the script lines that
* completes once the resource has been loaded
*/
static Mono<String> readScript(EncodedResource resource, DataBufferFactory dataBufferFactory,
@Nullable String separator) {
return DataBufferUtils.join(DataBufferUtils.read(resource.getResource(), dataBufferFactory, 8192))
.handle((it, sink) -> {
try (InputStream is = it.asInputStream()) {
InputStreamReader in = (resource.getCharset() != null ?
new InputStreamReader(is, resource.getCharset()) : new InputStreamReader(is));
LineNumberReader lnr = new LineNumberReader(in);
String script = readScript(lnr, separator);
sink.next(script);
sink.complete();
}
catch (Exception ex) {
sink.error(ex);
}
finally {
DataBufferUtils.release(it);
}
});
}
/**
* Read a script from the provided {@code LineNumberReader} and build a
* {@code String} containing the lines.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param separator the statement separator in the SQL script (typically ";")
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
private static String readScript(LineNumberReader lineNumberReader, @Nullable String separator) throws IOException {
StringBuilder scriptBuilder = new StringBuilder();
String currentLine = lineNumberReader.readLine();
while (currentLine != null) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentLine);
currentLine = lineNumberReader.readLine();
}
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
return scriptBuilder.toString();
}
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable String separator) {
if (separator == null) {
return;
}
String trimmed = separator.trim();
if (trimmed.length() == separator.length()) {
return;
}
// separator ends in whitespace, so we might want to see if the script is trying
// to end the same way
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
scriptBuilder.append(separator.substring(trimmed.length()));
}
}
/**
* Determine if the provided SQL script contains the specified statement separator.
* <p>This method is intended to be used to find the string separating each
* SQL statement &mdash; for example, a ';' character.
* <p>Any occurrence of the separator within the script will be ignored if it
* is within a <em>literal</em> block of text enclosed in single quotes
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
* ({@code \}), or if it is within a single-line comment or block comment.
* @param resource the resource from which the script was read, or {@code null}
* if unknown
* @param script the SQL script to search within
* @param separator the statement separator to search for
* @param commentPrefixes the prefixes that identify single-line comments
* (typically {@code "--"})
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* (typically {@code "/*"})
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* (typically <code>"*&#47;"</code>)
* @since 5.3.8
*/
static boolean containsStatementSeparator(EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws ScriptException {
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
continue;
}
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(separator, i)) {
return true;
}
else if (startsWithAny(script, commentPrefixes, i)) {
// Skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf('\n', i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// If there's no EOL, we must be at the end of the script, so stop here.
break;
}
}
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// Skip over any block comments
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new ScriptParseException(
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
}
}
}
}
return false;
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the
* provided {@code List}.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @see #splitSqlScript(String, char, List)
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
*/
public static void splitSqlScript(String script, String separator, List<String> statements) throws ScriptException {
splitSqlScript(null, script, separator, DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefix} will be honored:
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefix the prefix that identifies SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
*/
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String commentPrefix, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* separator string and return a {@code List} containing each individual
* statement.
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
* any text beginning with one of the comment prefixes and extending to the
* end of the line will be omitted from the output. Similarly, the provided
@@ -210,12 +443,12 @@ public abstract class ScriptUtils {
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @return a list of statements
* @throws ScriptException if an error occurred while splitting the SQL script
*/
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
static List<String> splitSqlScript(EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
String blockCommentEndDelimiter) throws ScriptException {
Assert.hasText(script, "'script' must not be null or empty");
Assert.notNull(separator, "'separator' must not be null");
@@ -226,6 +459,7 @@ public abstract class ScriptUtils {
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
List<String> statements = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
@@ -300,126 +534,8 @@ public abstract class ScriptUtils {
if (StringUtils.hasText(sb)) {
statements.add(sb.toString());
}
}
/**
* Read a script from the given resource, using "{@code --}" as the comment prefix
* and "{@code ;}" as the statement separator, and build a String containing the lines.
* @param resource the {@code EncodedResource} to be read
* @return {@code String} containing the script lines
*/
public static Mono<String> readScript(EncodedResource resource, DataBufferFactory dataBufferFactory) {
return readScript(resource, dataBufferFactory, DEFAULT_COMMENT_PREFIXES, DEFAULT_STATEMENT_SEPARATOR,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
/**
* Read a script from the provided resource, using the supplied comment prefixes
* and statement separator, and build a {@code String} containing the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param commentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@link Mono} of {@link String} containing the script lines that
* completes once the resource was loaded
*/
private static Mono<String> readScript(EncodedResource resource, DataBufferFactory dataBufferFactory,
@Nullable String[] commentPrefixes, @Nullable String separator, @Nullable String blockCommentEndDelimiter) {
return DataBufferUtils.join(DataBufferUtils.read(resource.getResource(), dataBufferFactory, 8192))
.handle((it, sink) -> {
try (InputStream is = it.asInputStream()) {
InputStreamReader in = (resource.getCharset() != null ?
new InputStreamReader(is, resource.getCharset()) : new InputStreamReader(is));
LineNumberReader lnr = new LineNumberReader(in);
String script = readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
sink.next(script);
sink.complete();
}
catch (Exception ex) {
sink.error(ex);
}
finally {
DataBufferUtils.release(it);
}
});
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param lineCommentPrefix the prefix that identifies comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
public static String readScript(LineNumberReader lineNumberReader, @Nullable String lineCommentPrefix,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String[] lineCommentPrefixes = (lineCommentPrefix != null) ? new String[] { lineCommentPrefix } : null;
return readScript(lineNumberReader, lineCommentPrefixes, separator, blockCommentEndDelimiter);
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefixes and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with one of the comment prefixes are excluded
* from the results; however, line comments anywhere else &mdash; for example,
* within a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param lineCommentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] lineCommentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
(lineCommentPrefixes != null && !startsWithAny(currentStatement, lineCommentPrefixes, 0))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentStatement);
}
currentStatement = lineNumberReader.readLine();
}
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
return scriptBuilder.toString();
}
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable String separator) {
if (separator == null) {
return;
}
String trimmed = separator.trim();
if (trimmed.length() == separator.length()) {
return;
}
// separator ends in whitespace, so we might want to see if the script is trying
// to end the same way
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
scriptBuilder.append(separator.substring(trimmed.length()));
}
return statements;
}
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
@@ -431,204 +547,6 @@ public abstract class ScriptUtils {
return false;
}
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim the string delimiting each statement - typically a ';' character
*/
public static boolean containsSqlScriptDelimiters(String script, String delim) {
boolean inLiteral = false;
boolean inEscape = false;
for (int i = 0; i < script.length(); i++) {
char c = script.charAt(i);
if (inEscape) {
inEscape = false;
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
continue;
}
if (c == '\'') {
inLiteral = !inLiteral;
}
if (!inLiteral && script.startsWith(delim, i)) {
return true;
}
}
return false;
}
/**
* Execute the given SQL script using default settings for statement
* separators, comment delimiters, and exception handling flags.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource to load the SQL script from; encoded with the
* current platform's default encoding
* @throws ScriptException if an error occurred while executing the SQL script
* @see #executeSqlScript(Connection, EncodedResource, DataBufferFactory, boolean, boolean, String[], String, String, String)
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #DEFAULT_COMMENT_PREFIX
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static Mono<Void> executeSqlScript(Connection connection, Resource resource) throws ScriptException {
return executeSqlScript(connection, new EncodedResource(resource));
}
/**
* Execute the given SQL script using default settings for statement
* separators, comment delimiters, and exception handling flags.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource (potentially associated with a specific encoding)
* to load the SQL script from
* @throws ScriptException if an error occurred while executing the SQL script
* @see #executeSqlScript(Connection, EncodedResource, DataBufferFactory, boolean, boolean, String[], String, String, String)
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #DEFAULT_COMMENT_PREFIX
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static Mono<Void> executeSqlScript(Connection connection, EncodedResource resource) throws ScriptException {
return executeSqlScript(connection, resource, DefaultDataBufferFactory.sharedInstance, false, false,
DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
/**
* Execute the given SQL script.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource (potentially associated with a specific encoding)
* to load the SQL script from
* @param continueOnError whether or not to continue without throwing an exception
* in the event of an error
* @param ignoreFailedDrops whether or not to continue in the event of specifically
* an error on a {@code DROP} statement
* @param commentPrefix the prefix that identifies single-line comments in the
* SQL script (typically "--")
* @param separator the script statement separator; defaults to
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to
* {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to
* {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a
* single statement without a separator
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @throws ScriptException if an error occurred while executing the SQL script
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #FALLBACK_STATEMENT_SEPARATOR
* @see #EOF_STATEMENT_SEPARATOR
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static Mono<Void> executeSqlScript(Connection connection, EncodedResource resource,
DataBufferFactory dataBufferFactory, boolean continueOnError, boolean ignoreFailedDrops,
String commentPrefix, @Nullable String separator, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws ScriptException {
return executeSqlScript(connection, resource, dataBufferFactory, continueOnError,
ignoreFailedDrops, new String[] { commentPrefix }, separator,
blockCommentStartDelimiter, blockCommentEndDelimiter);
}
/**
* Execute the given SQL script.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the R2DBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource (potentially associated with a specific encoding)
* to load the SQL script from
* @param continueOnError whether or not to continue without throwing an exception
* in the event of an error
* @param ignoreFailedDrops whether or not to continue in the event of specifically
* an error on a {@code DROP} statement
* @param commentPrefixes the prefixes that identify single-line comments in the
* SQL script (typically "--")
* @param separator the script statement separator; defaults to
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to
* {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to
* {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a
* single statement without a separator
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @throws ScriptException if an error occurred while executing the SQL script
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #FALLBACK_STATEMENT_SEPARATOR
* @see #EOF_STATEMENT_SEPARATOR
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
public static Mono<Void> executeSqlScript(Connection connection, EncodedResource resource,
DataBufferFactory dataBufferFactory, boolean continueOnError, boolean ignoreFailedDrops,
String[] commentPrefixes, @Nullable String separator, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws ScriptException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL script from " + resource);
}
long startTime = System.currentTimeMillis();
Mono<String> script = readScript(resource, dataBufferFactory, commentPrefixes, separator, blockCommentEndDelimiter)
.onErrorMap(IOException.class, ex -> new CannotReadScriptException(resource, ex));
AtomicInteger statementNumber = new AtomicInteger();
Flux<Void> executeScript = script.flatMapIterable(statement -> {
List<String> statements = new ArrayList<>();
String separatorToUse = separator;
if (separatorToUse == null) {
separatorToUse = DEFAULT_STATEMENT_SEPARATOR;
}
if (!EOF_STATEMENT_SEPARATOR.equals(separatorToUse) && !containsSqlScriptDelimiters(statement, separatorToUse)) {
separatorToUse = FALLBACK_STATEMENT_SEPARATOR;
}
splitSqlScript(resource, statement, separatorToUse, commentPrefixes, blockCommentStartDelimiter,
blockCommentEndDelimiter, statements);
return statements;
}).concatMap(statement -> {
statementNumber.incrementAndGet();
return runStatement(statement, connection, resource, continueOnError, ignoreFailedDrops, statementNumber);
});
if (logger.isDebugEnabled()) {
executeScript = executeScript.doOnComplete(() -> {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.debug("Executed SQL script from " + resource + " in " + elapsedTime + " ms.");
});
}
return executeScript.onErrorMap(ex -> !(ex instanceof ScriptException),
ex -> new UncategorizedScriptException(
"Failed to execute database script from resource [" + resource + "]", ex))
.then();
}
private static Publisher<? extends Void> runStatement(String statement, Connection connection,
EncodedResource resource, boolean continueOnError, boolean ignoreFailedDrops, AtomicInteger statementNumber) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,11 @@
package org.springframework.r2dbc.connection.init;
/**
* Thrown when we cannot determine anything more specific than "something went wrong while
* processing an SQL script": for example, a {@link io.r2dbc.spi.R2dbcException} from
* R2DBC that we cannot pinpoint more precisely.
* Thrown when we cannot determine anything more specific than "something went wrong
* while processing an SQL script": for example, an {@link io.r2dbc.spi.R2dbcException}
* from R2DBC that we cannot pinpoint more precisely.
*
* @author Sam Brannen
* @author Mark Paluch
* @since 5.3
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import kotlinx.coroutines.reactive.awaitSingleOrNull
*
* @author Sebastien Deleuze
*/
@Suppress("DEPRECATION")
suspend fun DatabaseClient.GenericExecuteSpec.await() {
then().awaitSingleOrNull()
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.springframework.dao.EmptyResultDataAccessException
*
* @author Sebastien Deleuze
*/
@Suppress("DEPRECATION")
suspend fun <T> RowsFetchSpec<T>.awaitOne(): T {
return one().awaitSingleOrNull() ?: throw EmptyResultDataAccessException(1)
}
@@ -34,6 +35,7 @@ suspend fun <T> RowsFetchSpec<T>.awaitOne(): T {
*
* @author Sebastien Deleuze
*/
@Suppress("DEPRECATION")
suspend fun <T> RowsFetchSpec<T>.awaitOneOrNull(): T? =
one().awaitSingleOrNull()
@@ -42,6 +44,7 @@ suspend fun <T> RowsFetchSpec<T>.awaitOneOrNull(): T? =
*
* @author Sebastien Deleuze
*/
@Suppress("DEPRECATION")
suspend fun <T> RowsFetchSpec<T>.awaitSingle(): T {
return first().awaitSingleOrNull() ?: throw EmptyResultDataAccessException(1)
}
@@ -51,6 +54,7 @@ suspend fun <T> RowsFetchSpec<T>.awaitSingle(): T {
*
* @author Sebastien Deleuze
*/
@Suppress("DEPRECATION")
suspend fun <T> RowsFetchSpec<T>.awaitSingleOrNull(): T? =
first().awaitSingleOrNull()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,22 +24,23 @@ import org.springframework.core.io.ClassRelativeResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.r2dbc.core.DatabaseClient;
/**
* Abstract test support for {@link DatabasePopulator}.
*
* @author Dave Syer
* @author Sam Brannen
* @author Oliver Gierke
* @author Mark Paluch
*/
public abstract class AbstractDatabaseInitializationTests {
abstract class AbstractDatabasePopulatorTests {
ClassRelativeResourceLoader resourceLoader = new ClassRelativeResourceLoader(
getClass());
ClassRelativeResourceLoader resourceLoader = new ClassRelativeResourceLoader(getClass());
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
@Test
public void scriptWithSingleLineCommentsAndFailedDrop() {
void scriptWithSingleLineCommentsAndFailedDrop() {
databasePopulator.addScript(resource("db-schema-failed-drop-comments.sql"));
databasePopulator.addScript(resource("db-test-data.sql"));
databasePopulator.setIgnoreFailedDrops(true);
@@ -56,7 +57,7 @@ public abstract class AbstractDatabaseInitializationTests {
}
@Test
public void scriptWithStandardEscapedLiteral() {
void scriptWithStandardEscapedLiteral() {
databasePopulator.addScript(defaultSchema());
databasePopulator.addScript(resource("db-test-data-escaped-literal.sql"));
@@ -66,7 +67,7 @@ public abstract class AbstractDatabaseInitializationTests {
}
@Test
public void scriptWithMySqlEscapedLiteral() {
void scriptWithMySqlEscapedLiteral() {
databasePopulator.addScript(defaultSchema());
databasePopulator.addScript(resource("db-test-data-mysql-escaped-literal.sql"));
@@ -76,7 +77,7 @@ public abstract class AbstractDatabaseInitializationTests {
}
@Test
public void scriptWithMultipleStatements() {
void scriptWithMultipleStatements() {
databasePopulator.addScript(defaultSchema());
databasePopulator.addScript(resource("db-test-data-multiple.sql"));
@@ -86,7 +87,7 @@ public abstract class AbstractDatabaseInitializationTests {
}
@Test
public void scriptWithMultipleStatementsAndLongSeparator() {
void scriptWithMultipleStatementsAndLongSeparator() {
databasePopulator.addScript(defaultSchema());
databasePopulator.addScript(resource("db-test-data-endings.sql"));
databasePopulator.setSeparator("@@");
@@ -120,15 +121,13 @@ public abstract class AbstractDatabaseInitializationTests {
DatabaseClient client = DatabaseClient.create(connectionFactory);
for (String lastName : lastNames) {
client.sql("select count(0) from users where last_name = :name") //
.bind("name", lastName) //
.map((row, metadata) -> row.get(0)) //
.first() //
.map(number -> ((Number) number).intValue()) //
.as(StepVerifier::create) //
.expectNext(1).as(
"Did not find user with last name [" + lastName + "].") //
.expectNext(1).as("Did not find user with last name [" + lastName + "].") //
.verifyComplete();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,9 +33,11 @@ import static org.mockito.BDDMockito.when;
/**
* Unit tests for {@link CompositeDatabasePopulator}.
*
* @author Kazuki Shimizu
* @author Juergen Hoeller
* @author Mark Paluch
*/
public class CompositeDatabasePopulatorTests {
class CompositeDatabasePopulatorTests {
Connection mockedConnection = mock(Connection.class);
@@ -45,15 +47,13 @@ public class CompositeDatabasePopulatorTests {
@BeforeEach
public void before() {
when(mockedDatabasePopulator1.populate(mockedConnection)).thenReturn(
Mono.empty());
when(mockedDatabasePopulator2.populate(mockedConnection)).thenReturn(
Mono.empty());
void before() {
when(mockedDatabasePopulator1.populate(mockedConnection)).thenReturn(Mono.empty());
when(mockedDatabasePopulator2.populate(mockedConnection)).thenReturn(Mono.empty());
}
@Test
public void addPopulators() {
void addPopulators() {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2);
@@ -64,7 +64,7 @@ public class CompositeDatabasePopulatorTests {
}
@Test
public void setPopulatorsWithMultiple() {
void setPopulatorsWithMultiple() {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.setPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2); // multiple
@@ -75,7 +75,7 @@ public class CompositeDatabasePopulatorTests {
}
@Test
public void setPopulatorsForOverride() {
void setPopulatorsForOverride() {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.setPopulators(mockedDatabasePopulator1);
populator.setPopulators(mockedDatabasePopulator2); // override
@@ -87,9 +87,9 @@ public class CompositeDatabasePopulatorTests {
}
@Test
public void constructWithVarargs() {
CompositeDatabasePopulator populator = new CompositeDatabasePopulator(
mockedDatabasePopulator1, mockedDatabasePopulator2);
void constructWithVarargs() {
CompositeDatabasePopulator populator =
new CompositeDatabasePopulator(mockedDatabasePopulator1, mockedDatabasePopulator2);
populator.populate(mockedConnection).as(StepVerifier::create).verifyComplete();
@@ -98,7 +98,7 @@ public class CompositeDatabasePopulatorTests {
}
@Test
public void constructWithCollection() {
void constructWithCollection() {
Set<DatabasePopulator> populators = new LinkedHashSet<>();
populators.add(mockedDatabasePopulator1);
populators.add(mockedDatabasePopulator2);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import static org.mockito.BDDMockito.when;
*
* @author Mark Paluch
*/
public class ConnectionFactoryInitializerUnitTests {
class ConnectionFactoryInitializerUnitTests {
AtomicBoolean called = new AtomicBoolean();
@@ -40,12 +40,11 @@ public class ConnectionFactoryInitializerUnitTests {
MockConnection connection = MockConnection.builder().build();
MockConnectionFactory connectionFactory = MockConnectionFactory.builder().connection(
connection).build();
MockConnectionFactory connectionFactory = MockConnectionFactory.builder().connection(connection).build();
@Test
public void shouldInitializeConnectionFactory() {
void shouldInitializeConnectionFactory() {
when(populator.populate(connectionFactory)).thenReturn(
Mono.<Void> empty().doOnSubscribe(subscription -> called.set(true)));
@@ -59,7 +58,7 @@ public class ConnectionFactoryInitializerUnitTests {
}
@Test
public void shouldCleanConnectionFactory() {
void shouldCleanConnectionFactory() {
when(populator.populate(connectionFactory)).thenReturn(
Mono.<Void> empty().doOnSubscribe(subscription -> called.set(true)));
@@ -67,7 +66,6 @@ public class ConnectionFactoryInitializerUnitTests {
initializer.setConnectionFactory(connectionFactory);
initializer.setDatabaseCleaner(populator);
initializer.afterPropertiesSet();
initializer.destroy();
assertThat(called).isTrue();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,16 +20,13 @@ import java.util.UUID;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;
/**
* Integration tests for {@link DatabasePopulator} using H2.
*
* @author Mark Paluch
*/
public class H2DatabasePopulatorIntegrationTests
extends AbstractDatabaseInitializationTests {
class H2DatabasePopulatorIntegrationTests extends AbstractDatabasePopulatorTests {
UUID databaseName = UUID.randomUUID();
@@ -42,20 +39,4 @@ public class H2DatabasePopulatorIntegrationTests
return this.connectionFactory;
}
@Test
public void shouldRunScript() {
databasePopulator.addScript(usersSchema());
databasePopulator.addScript(resource("db-test-data-h2.sql"));
// Set statement separator to double newline so that ";" is not
// considered a statement separator within the source code of the
// aliased function 'REVERSE'.
databasePopulator.setSeparator("\n\n");
databasePopulator.populate(connectionFactory).as(
StepVerifier::create).verifyComplete();
assertUsersDatabaseCreated(connectionFactory, "White");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,47 +27,43 @@ import static org.mockito.BDDMockito.mock;
/**
* Unit tests for {@link ResourceDatabasePopulator}.
*
* @author Sam Brannen
* @author Mark Paluch
*/
public class ResourceDatabasePopulatorUnitTests {
class ResourceDatabasePopulatorUnitTests {
private static final Resource script1 = mock(Resource.class);
private static final Resource script2 = mock(Resource.class);
private static final Resource script3 = mock(Resource.class);
@Test
public void constructWithNullResource() {
assertThatIllegalArgumentException().isThrownBy(
() -> new ResourceDatabasePopulator((Resource) null));
void constructWithNullResource() {
assertThatIllegalArgumentException().isThrownBy(() ->
new ResourceDatabasePopulator((Resource) null));
}
@Test
public void constructWithNullResourceArray() {
assertThatIllegalArgumentException().isThrownBy(
() -> new ResourceDatabasePopulator((Resource[]) null));
void constructWithNullResourceArray() {
assertThatIllegalArgumentException().isThrownBy(() ->
new ResourceDatabasePopulator((Resource[]) null));
}
@Test
public void constructWithResource() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(
script1);
void constructWithResource() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(script1);
assertThat(databasePopulator.scripts).hasSize(1);
}
@Test
public void constructWithMultipleResources() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(
script1, script2);
void constructWithMultipleResources() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(script1, script2);
assertThat(databasePopulator.scripts).hasSize(2);
}
@Test
public void constructWithMultipleResourcesAndThenAddScript() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(
script1, script2);
void constructWithMultipleResourcesAndThenAddScript() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(script1, script2);
assertThat(databasePopulator.scripts).hasSize(2);
databasePopulator.addScript(script3);
@@ -75,35 +71,35 @@ public class ResourceDatabasePopulatorUnitTests {
}
@Test
public void addScriptsWithNullResource() {
void addScriptsWithNullResource() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(
() -> databasePopulator.addScripts((Resource) null));
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.addScripts((Resource) null));
}
@Test
public void addScriptsWithNullResourceArray() {
void addScriptsWithNullResourceArray() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(
() -> databasePopulator.addScripts((Resource[]) null));
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.addScripts((Resource[]) null));
}
@Test
public void setScriptsWithNullResource() {
void setScriptsWithNullResource() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(
() -> databasePopulator.setScripts((Resource) null));
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.setScripts((Resource) null));
}
@Test
public void setScriptsWithNullResourceArray() {
void setScriptsWithNullResourceArray() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThatIllegalArgumentException().isThrownBy(
() -> databasePopulator.setScripts((Resource[]) null));
assertThatIllegalArgumentException().isThrownBy(() ->
databasePopulator.setScripts((Resource[]) null));
}
@Test
public void setScriptsAndThenAddScript() {
void setScriptsAndThenAddScript() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
assertThat(databasePopulator.scripts).isEmpty();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,17 +16,22 @@
package org.springframework.r2dbc.connection.init;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.util.Strings;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.core.io.support.EncodedResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.r2dbc.connection.init.ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
import static org.springframework.r2dbc.connection.init.ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
import static org.springframework.r2dbc.connection.init.ScriptUtils.DEFAULT_COMMENT_PREFIXES;
import static org.springframework.r2dbc.connection.init.ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
/**
* Unit tests for {@link ScriptUtils}.
@@ -37,6 +42,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Chris Baldwin
* @author Nicolas Debeissat
* @author Mark Paluch
* @since 5.3
* @see ScriptUtilsIntegrationTests
*/
public class ScriptUtilsUnitTests {
@@ -49,14 +56,12 @@ public class ScriptUtilsUnitTests {
String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String script = Strings.join(rawStatement1, rawStatement2, rawStatement3).with(
";");
String delimiter = ";";
String script = Strings.join(rawStatement1, rawStatement2, rawStatement3).with(delimiter);
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, ";", statements);
List<String> statements = splitSqlScript(script, delimiter);
assertThat(statements).hasSize(3).containsSequence(cleanedStatement1,
cleanedStatement2, cleanedStatement3);
assertThat(statements).containsExactly(cleanedStatement1, cleanedStatement2, cleanedStatement3);
}
@Test
@@ -65,13 +70,12 @@ public class ScriptUtilsUnitTests {
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String script = Strings.join(statement1, statement2, statement3).with("\n");
String delimiter = "\n";
String script = Strings.join(statement1, statement2, statement3).with(delimiter);
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, "\n", statements);
List<String> statements = splitSqlScript(script, delimiter);
assertThat(statements).hasSize(3).containsSequence(statement1, statement2,
statement3);
assertThat(statements).containsExactly(statement1, statement2, statement3);
}
@Test
@@ -79,141 +83,149 @@ public class ScriptUtilsUnitTests {
String statement1 = "do something";
String statement2 = "do something else";
char delim = '\n';
String script = statement1 + delim + statement2 + delim;
String script = Strings.join(statement1, statement2).with("\n");
List<String> statements = new ArrayList<>();
List<String> statements = splitSqlScript(script, DEFAULT_STATEMENT_SEPARATOR);
ScriptUtils.splitSqlScript(script, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR,
statements);
assertThat(statements).hasSize(1).contains(script.replace('\n', ' '));
assertThat(statements).as("stripped but not split statements").containsExactly(script.replace('\n', ' '));
}
@Test
@Test // SPR-13218
public void splitScriptWithSingleQuotesNestedInsideDoubleQuotes() {
String statement1 = "select '1' as \"Dogbert's owner's\" from dual";
String statement2 = "select '2' as \"Dilbert's\" from dual";
char delim = ';';
String script = statement1 + delim + statement2 + delim;
String delimiter = ";";
String script = Strings.join(statement1, statement2).with(delimiter);
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, ';', statements);
List<String> statements = splitSqlScript(script, delimiter);
assertThat(statements).hasSize(2).containsSequence(statement1, statement2);
assertThat(statements).containsExactly(statement1, statement2);
}
@Test
public void readAndSplitScriptWithMultipleNewlinesAsSeparator() {
@Test // SPR-11560
public void readAndSplitScriptWithMultipleNewlinesAsSeparator() throws Exception {
String script = readScript("db-test-data-multi-newline.sql");
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, "\n\n", statements);
List<String> statements = splitSqlScript(script, "\n\n");
String statement1 = "insert into users (last_name) values ('Walter')";
String statement2 = "insert into users (last_name) values ('Jesse')";
String statement1 = "insert into T_TEST (NAME) values ('Keith')";
String statement2 = "insert into T_TEST (NAME) values ('Dave')";
assertThat(statements.size()).as("wrong number of statements").isEqualTo(2);
assertThat(statements.get(0)).as("statement 1 not split correctly").isEqualTo(
statement1);
assertThat(statements.get(1)).as("statement 2 not split correctly").isEqualTo(
statement2);
assertThat(statements).containsExactly(statement1, statement2);
}
@Test
public void readAndSplitScriptContainingComments() {
public void readAndSplitScriptContainingComments() throws Exception {
String script = readScript("test-data-with-comments.sql");
splitScriptContainingComments(script);
splitScriptContainingComments(script, DEFAULT_COMMENT_PREFIXES);
}
@Test
public void readAndSplitScriptContainingCommentsWithWindowsLineEnding() {
String script = readScript("test-data-with-comments.sql").replaceAll("\n",
"\r\n");
splitScriptContainingComments(script);
public void readAndSplitScriptContainingCommentsWithWindowsLineEnding() throws Exception {
String script = readScript("test-data-with-comments.sql").replaceAll("\n", "\r\n");
splitScriptContainingComments(script, DEFAULT_COMMENT_PREFIXES);
}
private void splitScriptContainingComments(String script) {
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, ';', statements);
@Test
public void readAndSplitScriptContainingCommentsWithMultiplePrefixes() throws Exception {
String script = readScript("test-data-with-multi-prefix-comments.sql");
splitScriptContainingComments(script, "--", "#", "^");
}
private void splitScriptContainingComments(String script, String... commentPrefixes) {
List<String> statements = ScriptUtils.splitSqlScript(null, script, ";", commentPrefixes, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
// Statement 4 addresses the error described in SPR-9982.
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
assertThat(statements).hasSize(4).containsSequence(statement1, statement2,
statement3, statement4);
assertThat(statements).containsExactly(statement1, statement2, statement3, statement4);
}
@Test
public void readAndSplitScriptContainingCommentsWithLeadingTabs() {
@Test // SPR-10330
public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception {
String script = readScript("test-data-with-comments-and-leading-tabs.sql");
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, ';', statements);
List<String> statements = splitSqlScript(script, ";");
String statement1 = "insert into customer (id, name) values (1, 'Walter White')";
String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)";
String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)";
assertThat(statements).hasSize(3).containsSequence(statement1, statement2,
statement3);
assertThat(statements).containsExactly(statement1, statement2, statement3);
}
@Test
public void readAndSplitScriptContainingMultiLineComments() {
@Test // SPR-9531
public void readAndSplitScriptContainingMultiLineComments() throws Exception {
String script = readScript("test-data-with-multi-line-comments.sql");
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, ';', statements);
List<String> statements = splitSqlScript(script, ";");
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Walter', 'White')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Jesse' , 'Pinkman' )";
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Sam' , 'Brannen' )";
assertThat(statements).hasSize(2).containsSequence(statement1, statement2);
assertThat(statements).containsExactly(statement1, statement2);
}
@Test
public void readAndSplitScriptContainingMultiLineNestedComments() {
public void readAndSplitScriptContainingMultiLineNestedComments() throws Exception {
String script = readScript("test-data-with-multi-line-nested-comments.sql");
List<String> statements = new ArrayList<>();
ScriptUtils.splitSqlScript(script, ';', statements);
List<String> statements = splitSqlScript(script, ";");
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Walter', 'White')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Jesse' , 'Pinkman' )";
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Sam' , 'Brannen' )";
assertThat(statements).hasSize(2).containsSequence(statement1, statement2);
assertThat(statements).containsExactly(statement1, statement2);
}
@Test
public void containsDelimiters() {
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n select ';'",
";")).isFalse();
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1; select 2",
";")).isTrue();
@ParameterizedTest
@CsvSource(delimiter = '#', value = {
// semicolon
"'select 1\n select '';''' # ; # false",
"'select 1\n select \";\"' # ; # false",
"'select 1; select 2' # ; # true",
// newline
"'select 1; select ''\n''' # '\n' # false",
"'select 1; select \"\n\"' # '\n' # false",
"'select 1\n select 2' # '\n' # true",
// double newline
"'select 1\n select 2' # '\n\n' # false",
"'select 1\n\n select 2' # '\n\n' # true",
// semicolon with MySQL style escapes '\\'
"'insert into users(first, last)\nvalues(''a\\\\'', ''b;'')' # ; # false",
"'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true",
// semicolon inside comments
"'-- a;b;c\ninsert into colors(color_num) values(42);' # ; # true",
"'/* a;b;c */\ninsert into colors(color_num) values(42);' # ; # true",
"'-- a;b;c\ninsert into colors(color_num) values(42)' # ; # false",
"'/* a;b;c */\ninsert into colors(color_num) values(42)' # ; # false",
// single quotes inside comments
"'-- What\\''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
"'-- What''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
"'/* What\\''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
"'/* What''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
// double quotes inside comments
"'-- double \" quotes\ninsert into colors(color_num) values(42);' # ; # true",
"'-- double \\\" quotes\ninsert into colors(color_num) values(42);' # ; # true",
"'/* double \" quotes */\ninsert into colors(color_num) values(42);' # ; # true",
"'/* double \\\" quotes */\ninsert into colors(color_num) values(42);' # ; # true"
})
public void containsStatementSeparator(String script, String delimiter, boolean expected) {
boolean contains = ScriptUtils.containsStatementSeparator(null, script, delimiter, DEFAULT_COMMENT_PREFIXES,
DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';",
"\n")).isFalse();
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2",
"\n")).isTrue();
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2",
"\n\n")).isFalse();
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n\n select 2",
"\n\n")).isTrue();
// MySQL style escapes '\\'
assertThat(ScriptUtils.containsSqlScriptDelimiters(
"insert into users(first_name, last_name)\nvalues('a\\\\', 'b;')",
";")).isFalse();
assertThat(ScriptUtils.containsSqlScriptDelimiters(
"insert into users(first_name, last_name)\nvalues('Charles', 'd\\'Artagnan'); select 1;",
";")).isTrue();
assertThat(contains).isEqualTo(expected);
}
private String readScript(String path) {
EncodedResource resource = new EncodedResource(
new ClassPathResource(path, getClass()));
return ScriptUtils.readScript(resource, DefaultDataBufferFactory.sharedInstance).block();
private String readScript(String path) throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource(path, getClass()));
return ScriptUtils.readScript(resource, DefaultDataBufferFactory.sharedInstance, DEFAULT_STATEMENT_SEPARATOR).block();
}
private static List<String> splitSqlScript(String script, String separator) throws ScriptException {
return ScriptUtils.splitSqlScript(null, script, separator, DEFAULT_COMMENT_PREFIXES, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
}
@@ -1 +0,0 @@
INSERT INTO users(first_name, last_name) values('Walter', 'White');
@@ -1,5 +1,5 @@
insert into users (last_name)
values ('Walter')
insert into T_TEST (NAME)
values ('Keith')
insert into users (last_name)
values ('Jesse')
insert into T_TEST (NAME)
values ('Dave')
@@ -2,7 +2,7 @@
-- x, y, z...
insert into customer (id, name)
values (1, 'Walter White');
values (1, 'Sam Brannen');
-- This is also a comment with a leading tab.
insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1);
-- This is also a comment with a leading tab, a space, and a tab.
@@ -1,17 +1,17 @@
/* This is a multi line comment
* The next comment line has no text
* The next comment line has no text
* The next comment line starts with a space.
* x, y, z...
* x, y, z...
*/
INSERT INTO users(first_name, last_name) VALUES('Walter', 'White');
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
-- This is also a comment.
/*
/*
* Let's add another comment
* that covers multiple lines
*/INSERT INTO
users(first_name, last_name)
VALUES( 'Jesse' -- first_name
, 'Pinkman' -- last_name
);--
VALUES( 'Sam' -- first_name
, 'Brannen' -- last_name
);--
@@ -5,19 +5,22 @@
* x, y, z...
*/
INSERT INTO users(first_name, last_name) VALUES('Walter', 'White');
-- This is a single line comment containing single (') and double quotes (").
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
-- This is also a comment.
/*-------------------------------------------
-- A fancy multi-line comments that puts
-- A fancy multi-line comment that puts
-- single line comments inside of a multi-line
-- comment block.
Moreover, the block comment end delimiter
appears on a line that can potentially also
be a single-line comment if we weren't
already inside a multi-line comment run.
And here's a line containing single and double quotes (").
-------------------------------------------*/
INSERT INTO
users(first_name, last_name) -- This is a single line comment containing the block-end-comment sequence here */ but it's still a single-line comment
VALUES( 'Jesse' -- first_name
, 'Pinkman' -- last_name
);--
VALUES( 'Sam' -- first_name
, 'Brannen' -- last_name
);--
@@ -0,0 +1,18 @@
-- The next comment line has no text after the '--' prefix.
--
-- The next comment line starts with a space.
-- x, y, z...
insert into customer (id, name)
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
-- This is also a comment.
insert into orders(id, order_date, customer_id)
values (1, '2008-01-02', 2);
# A comment with a different prefix
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
INSERT INTO persons( person_id--
, name)
^ A comment with yet another different prefix
VALUES( 1 -- person_id
, 'Name' --name
);--
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,7 +130,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
@Nullable
private PathPatternParser patternParser;
private boolean useSuffixPatternMatch = true;
private boolean useSuffixPatternMatch = false;
private boolean useTrailingSlashPatternMatch = true;
@@ -324,7 +324,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
/**
* Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p>The default value is {@code true}.
* <p>The default value is {@code false}.
* @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,20 +20,23 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT;
import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;
/**
@@ -41,30 +44,34 @@ import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.StatusAssertionTests}.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
public class StatusAssertionTests {
@TestInstance(PER_CLASS)
class StatusAssertionTests {
private final WebTestClient testClient =
MockMvcWebTestClient.bindToController(new StatusController()).build();
@Test
public void testStatusInt() {
testClient.get().uri("/created").exchange().expectStatus().isEqualTo(201);
testClient.get().uri("/createdWithComposedAnnotation").exchange().expectStatus().isEqualTo(201);
testClient.get().uri("/badRequest").exchange().expectStatus().isEqualTo(400);
void statusInt() {
testClient.get().uri("/teaPot").exchange().expectStatus().isEqualTo(I_AM_A_TEAPOT.value());
testClient.get().uri("/created").exchange().expectStatus().isEqualTo(CREATED.value());
testClient.get().uri("/createdWithComposedAnnotation").exchange().expectStatus().isEqualTo(CREATED.value());
testClient.get().uri("/badRequest").exchange().expectStatus().isEqualTo(BAD_REQUEST.value());
testClient.get().uri("/throwsException").exchange().expectStatus().isEqualTo(I_AM_A_TEAPOT.value());
}
@Test
public void testHttpStatus() {
void httpStatus() {
testClient.get().uri("/created").exchange().expectStatus().isCreated();
testClient.get().uri("/createdWithComposedAnnotation").exchange().expectStatus().isCreated();
testClient.get().uri("/badRequest").exchange().expectStatus().isBadRequest();
}
@Test
public void testMatcher() {
testClient.get().uri("/badRequest").exchange().expectStatus().value(equalTo(400));
void matcher() {
testClient.get().uri("/badRequest").exchange().expectStatus().value(equalTo(BAD_REQUEST.value()));
}
@@ -80,26 +87,41 @@ public class StatusAssertionTests {
HttpStatus status() default INTERNAL_SERVER_ERROR;
}
@Controller
@RestController
@ResponseStatus(I_AM_A_TEAPOT)
private static class StatusController {
@RequestMapping("/teaPot")
void teaPot() {
}
@RequestMapping("/created")
@ResponseStatus(CREATED)
public @ResponseBody void created(){
void created(){
}
@Get(path = "/createdWithComposedAnnotation", status = CREATED)
public @ResponseBody void createdWithComposedAnnotation() {
void createdWithComposedAnnotation() {
}
@RequestMapping("/badRequest")
@ResponseStatus(code = BAD_REQUEST, reason = "Expired token")
public @ResponseBody void badRequest(){
void badRequest(){
}
@RequestMapping("/notImplemented")
@ResponseStatus(NOT_IMPLEMENTED)
public @ResponseBody void notImplemented(){
void notImplemented(){
}
@RequestMapping("/throwsException")
@ResponseStatus(NOT_IMPLEMENTED)
void throwsException() {
throw new IllegalStateException();
}
@ExceptionHandler
void exceptionHandler(IllegalStateException ex) {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,20 +20,23 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT;
import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -45,39 +48,40 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
public class StatusAssertionTests {
@TestInstance(PER_CLASS)
class StatusAssertionTests {
private final MockMvc mockMvc = standaloneSetup(new StatusController()).build();
@Test
public void testStatusInt() throws Exception {
this.mockMvc.perform(get("/created")).andExpect(status().is(201));
this.mockMvc.perform(get("/createdWithComposedAnnotation")).andExpect(status().is(201));
this.mockMvc.perform(get("/badRequest")).andExpect(status().is(400));
}
@Test
public void testHttpStatus() throws Exception {
void httpStatus() throws Exception {
this.mockMvc.perform(get("/created")).andExpect(status().isCreated());
this.mockMvc.perform(get("/createdWithComposedAnnotation")).andExpect(status().isCreated());
this.mockMvc.perform(get("/badRequest")).andExpect(status().isBadRequest());
}
@Test
public void testMatcher() throws Exception {
this.mockMvc.perform(get("/badRequest")).andExpect(status().is(equalTo(400)));
void statusCode() throws Exception {
this.mockMvc.perform(get("/teaPot")).andExpect(status().is(I_AM_A_TEAPOT.value()));
this.mockMvc.perform(get("/created")).andExpect(status().is(CREATED.value()));
this.mockMvc.perform(get("/createdWithComposedAnnotation")).andExpect(status().is(CREATED.value()));
this.mockMvc.perform(get("/badRequest")).andExpect(status().is(BAD_REQUEST.value()));
this.mockMvc.perform(get("/throwsException")).andExpect(status().is(I_AM_A_TEAPOT.value()));
}
@Test
public void testReasonEqualTo() throws Exception {
void statusCodeWithMatcher() throws Exception {
this.mockMvc.perform(get("/badRequest")).andExpect(status().is(equalTo(BAD_REQUEST.value())));
}
@Test
void reason() throws Exception {
this.mockMvc.perform(get("/badRequest")).andExpect(status().reason("Expired token"));
// Hamcrest matchers...
this.mockMvc.perform(get("/badRequest")).andExpect(status().reason(equalTo("Expired token")));
}
@Test
public void testReasonMatcher() throws Exception {
void reasonWithMatcher() throws Exception {
this.mockMvc.perform(get("/badRequest")).andExpect(status().reason(equalTo("Expired token")));
this.mockMvc.perform(get("/badRequest")).andExpect(status().reason(endsWith("token")));
}
@@ -94,26 +98,41 @@ public class StatusAssertionTests {
HttpStatus status() default INTERNAL_SERVER_ERROR;
}
@Controller
@RestController
@ResponseStatus(I_AM_A_TEAPOT)
private static class StatusController {
@RequestMapping("/teaPot")
void teaPot() {
}
@RequestMapping("/created")
@ResponseStatus(CREATED)
public @ResponseBody void created(){
void created() {
}
@Get(path = "/createdWithComposedAnnotation", status = CREATED)
public @ResponseBody void createdWithComposedAnnotation() {
void createdWithComposedAnnotation() {
}
@RequestMapping("/badRequest")
@ResponseStatus(code = BAD_REQUEST, reason = "Expired token")
public @ResponseBody void badRequest(){
void badRequest() {
}
@RequestMapping("/notImplemented")
@ResponseStatus(NOT_IMPLEMENTED)
public @ResponseBody void notImplemented(){
void notImplemented() {
}
@RequestMapping("/throwsException")
@ResponseStatus(NOT_IMPLEMENTED)
void throwsException() {
throw new IllegalStateException();
}
@ExceptionHandler
void exceptionHandler(IllegalStateException ex) {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,10 +29,13 @@ import org.springframework.transaction.TransactionDefinition;
/**
* Describes a transaction attribute on an individual method or on a class.
*
* <p>At the class level, this annotation applies as a default to all methods of
* the declaring class and its subclasses. Note that it does not apply to ancestor
* classes up the class hierarchy; methods need to be locally redeclared in order
* to participate in a subclass-level annotation.
* <p>When this annotation is declared at the class level, it applies as a default
* to all methods of the declaring class and its subclasses. Note that it does not
* apply to ancestor classes up the class hierarchy; inherited methods need to be
* locally redeclared in order to participate in a subclass-level annotation. For
* details on method visibility constraints, consult the
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction">Transaction Management</a>
* section of the reference manual.
*
* <p>This annotation type is generally directly comparable to Spring's
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
@@ -46,14 +49,14 @@ import org.springframework.transaction.TransactionDefinition;
* 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
* <p>This annotation commonly works with thread-bound transactions managed by a
* {@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,
* by a {@link org.springframework.transaction.ReactiveTransactionManager} which
* uses the Reactor context instead of thread-local variables. As a consequence,
* all participating data access operations need to execute within the same
* Reactor context in the same reactive pipeline.
*
@@ -94,12 +97,12 @@ public @interface Transactional {
String transactionManager() default "";
/**
* Defines zero (0) or more transaction labels. Labels may be used to
* describe a transaction and they can be evaluated by individual transaction
* manager. Labels may serve a solely descriptive purpose or map to
* pre-defined transaction manager-specific options.
* <p>See the description of the actual transaction manager implementation
* how it evaluates transaction labels.
* Defines zero (0) or more transaction labels.
* <p>Labels may be used to describe a transaction, and they can be evaluated
* by individual transaction managers. Labels may serve a solely descriptive
* purpose or map to pre-defined transaction manager-specific options.
* <p>See the documentation of the actual transaction manager implementation
* for details on how it evaluates transaction labels.
* @since 5.3
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#getLabels()
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,10 +21,11 @@ import java.util.function.Consumer;
import org.springframework.transaction.support.TransactionSynchronization;
/**
* The phase at which a transactional event listener applies.
* The phase in which a transactional event listener applies.
*
* @author Stephane Nicoll
* @author Juergen Hoeller
* @author Sam Brannen
* @since 4.2
* @see TransactionalEventListener#phase()
* @see TransactionalApplicationListener#getTransactionPhase()
@@ -33,35 +34,44 @@ import org.springframework.transaction.support.TransactionSynchronization;
public enum TransactionPhase {
/**
* Fire the event before transaction commit.
* Handle the event before transaction commit.
* @see TransactionSynchronization#beforeCommit(boolean)
*/
BEFORE_COMMIT,
/**
* Fire the event after the commit has completed successfully.
* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and
* therefore executes in the same after-completion sequence of events,
* Handle the event after the commit has completed successfully.
* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and therefore
* executes in the same sequence of events as {@code AFTER_COMPLETION}
* (and not in {@link TransactionSynchronization#afterCommit()}).
* <p>Interactions with the underlying transactional resource will not be
* committed in this phase. See
* {@link TransactionSynchronization#afterCompletion(int)} for details.
* @see TransactionSynchronization#afterCompletion(int)
* @see TransactionSynchronization#STATUS_COMMITTED
*/
AFTER_COMMIT,
/**
* Fire the event if the transaction has rolled back.
* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and
* therefore executes in the same after-completion sequence of events.
* Handle the event if the transaction has rolled back.
* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and therefore
* executes in the same sequence of events as {@code AFTER_COMPLETION}.
* <p>Interactions with the underlying transactional resource will not be
* committed in this phase. See
* {@link TransactionSynchronization#afterCompletion(int)} for details.
* @see TransactionSynchronization#afterCompletion(int)
* @see TransactionSynchronization#STATUS_ROLLED_BACK
*/
AFTER_ROLLBACK,
/**
* Fire the event after the transaction has completed.
* Handle the event after the transaction has completed.
* <p>For more fine-grained events, use {@link #AFTER_COMMIT} or
* {@link #AFTER_ROLLBACK} to intercept transaction commit
* or rollback, respectively.
* <p>Interactions with the underlying transactional resource will not be
* committed in this phase. See
* {@link TransactionSynchronization#afterCompletion(int)} for details.
* @see TransactionSynchronization#afterCompletion(int)
*/
AFTER_COMPLETION
@@ -33,9 +33,10 @@ import org.springframework.lang.Nullable;
* transaction completion.
*
* <p><b>NOTE: Transactional event listeners only work with thread-bound transactions
* managed by {@link org.springframework.transaction.PlatformTransactionManager}.</b>
* A reactive transaction managed by {@link org.springframework.transaction.ReactiveTransactionManager}
* uses the Reactor context instead of thread-local attributes, so from the perspective of
* managed by a {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager}.</b> A reactive transaction managed by a
* {@link org.springframework.transaction.ReactiveTransactionManager ReactiveTransactionManager}
* uses the Reactor context instead of thread-local variables, so from the perspective of
* an event listener, there is no compatible active transaction that it can participate in.
*
* @author Juergen Hoeller
@@ -27,22 +27,34 @@ import org.springframework.core.annotation.AliasFor;
/**
* An {@link EventListener} that is invoked according to a {@link TransactionPhase}.
* This is an an annotation-based equivalent of {@link TransactionalApplicationListener}.
* This is an annotation-based equivalent of {@link TransactionalApplicationListener}.
*
* <p>If the event is not published within an active transaction, the event is discarded
* unless the {@link #fallbackExecution} flag is explicitly set. If a transaction is
* running, the event is processed according to its {@code TransactionPhase}.
* running, the event is handled according to its {@code TransactionPhase}.
*
* <p>Adding {@link org.springframework.core.annotation.Order @Order} to your annotated
* method allows you to prioritize that listener amongst other listeners running before
* or after transaction completion.
*
* <p><b>NOTE: Transactional event listeners only work with thread-bound transactions
* managed by {@link org.springframework.transaction.PlatformTransactionManager}.</b>
* A reactive transaction managed by {@link org.springframework.transaction.ReactiveTransactionManager}
* uses the Reactor context instead of thread-local attributes, so from the perspective of
* managed by a {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager}.</b> A reactive transaction managed by a
* {@link org.springframework.transaction.ReactiveTransactionManager ReactiveTransactionManager}
* uses the Reactor context instead of thread-local variables, so from the perspective of
* an event listener, there is no compatible active transaction that it can participate in.
*
* <p><strong>WARNING:</strong> if the {@code TransactionPhase} is set to
* {@link TransactionPhase#AFTER_COMMIT AFTER_COMMIT} (the default),
* {@link TransactionPhase#AFTER_ROLLBACK AFTER_ROLLBACK}, or
* {@link TransactionPhase#AFTER_COMPLETION AFTER_COMPLETION}, the transaction will
* have been committed or rolled back already, but the transactional resources might
* still be active and accessible. As a consequence, any data access code triggered
* at this point will still "participate" in the original transaction, but changes
* will not be committed to the transactional resource. See
* {@link org.springframework.transaction.support.TransactionSynchronization#afterCompletion(int)
* TransactionSynchronization.afterCompletion(int)} for details.
*
* @author Stephane Nicoll
* @author Sam Brannen
* @author Oliver Drotbohm
@@ -65,7 +77,7 @@ public @interface TransactionalEventListener {
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
/**
* Whether the event should be processed if no transaction is running.
* Whether the event should be handled if no transaction is running.
*/
boolean fallbackExecution() default false;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -878,7 +878,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
return ReactiveFlowKt.asFlow(publisher);
}
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "deprecation"})
@Nullable
private static Object awaitSingleOrNull(Publisher<?> publisher, Object continuation) {
return AwaitKt.awaitSingleOrNull(publisher, (Continuation<Object>) continuation);
@@ -557,7 +557,7 @@ public enum HttpStatus {
*/
@Nullable
public static HttpStatus resolve(int statusCode) {
// used cached VALUES instead of values() to prevent array allocation
// Use cached VALUES instead of values() to prevent array allocation.
for (HttpStatus status : VALUES) {
if (status.value == statusCode) {
return status;
@@ -317,6 +317,16 @@ public class ResponseEntity<T> extends HttpEntity<T> {
return status(HttpStatus.UNPROCESSABLE_ENTITY);
}
/**
* Create a builder with an
* {@linkplain HttpStatus#INTERNAL_SERVER_ERROR INTERNAL_SERVER_ERROR} status.
* @return the created builder
* @since 5.3.8
*/
public static BodyBuilder internalServerError() {
return status(HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* Defines a builder that adds headers to the response entity.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -131,7 +131,12 @@ public final class MultipartBodyBuilder {
Part partObject = (Part) part;
PartBuilder builder = asyncPart(name, partObject.content(), DataBuffer.class);
if (!partObject.headers().isEmpty()) {
builder.headers(headers -> headers.putAll(partObject.headers()));
builder.headers(headers -> {
headers.putAll(partObject.headers());
String filename = headers.getContentDisposition().getFilename();
// reset to parameter name
headers.setContentDispositionFormData(name, filename);
});
}
if (contentType != null) {
builder.contentType(contentType);
@@ -16,6 +16,8 @@
package org.springframework.http.client.reactive;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.function.BiFunction;
@@ -48,7 +50,7 @@ import org.springframework.util.Assert;
* @since 5.3
* @see <a href="https://hc.apache.org/index.html">Apache HttpComponents</a>
*/
public class HttpComponentsClientHttpConnector implements ClientHttpConnector {
public class HttpComponentsClientHttpConnector implements ClientHttpConnector, Closeable {
private final CloseableHttpAsyncClient client;
@@ -126,6 +128,10 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector {
});
}
@Override
public void close() throws IOException {
this.client.close();
}
private static class MonoFutureCallbackAdapter
implements FutureCallback<Message<HttpResponse, Publisher<ByteBuffer>>> {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -119,7 +119,7 @@ public final class ServerSentEvent<T> {
}
/**
* Return a builder for a {@code SseEvent}, populated with the give {@linkplain #data() data}.
* Return a builder for a {@code SseEvent}, populated with the given {@linkplain #data() data}.
* @param <T> the type of data that this event contains
* @return the builder
*/
@@ -59,8 +59,8 @@ abstract class FileStorage {
}
/**
* Create a new {@code FileStorage} based a on a temporary directory.
* @param scheduler scheduler to use for blocking operations
* Create a new {@code FileStorage} based on a temporary directory.
* @param scheduler the scheduler to use for blocking operations
*/
public static FileStorage tempDirectory(Supplier<Scheduler> scheduler) {
return new TempFileStorage(scheduler);
@@ -470,6 +470,7 @@ public class SynchronossPartHttpMessageReader extends LoggingCodecSupport implem
}
@Override
@SuppressWarnings("resource")
public Flux<DataBuffer> content() {
return DataBufferUtils.readInputStream(
getStorage()::getInputStream, DefaultDataBufferFactory.sharedInstance, 4096);
@@ -221,6 +221,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
public void jackson2JsonEncoder(Encoder<?> encoder) {
this.jackson2JsonEncoder = encoder;
initObjectWriters();
initTypedWriters();
}
@Override
@@ -233,6 +234,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
public void jackson2SmileEncoder(Encoder<?> encoder) {
this.jackson2SmileEncoder = encoder;
initObjectWriters();
initTypedWriters();
}
@Override
@@ -105,8 +105,6 @@ import org.springframework.util.xml.StaxUtils;
*/
public class Jackson2ObjectMapperBuilder {
private static volatile boolean kotlinWarningLogged;
private final Log logger = HttpLogging.forLogName(getClass());
private final Map<Class<?>, Class<?>> mixIns = new LinkedHashMap<>();
@@ -863,11 +861,7 @@ public class Jackson2ObjectMapperBuilder {
modulesToRegister.set(kotlinModule.getTypeId(), kotlinModule);
}
catch (ClassNotFoundException ex) {
if (!kotlinWarningLogged) {
kotlinWarningLogged = true;
logger.warn("For Jackson Kotlin classes support please add " +
"\"com.fasterxml.jackson.module:jackson-module-kotlin\" to the classpath");
}
// jackson-module-kotlin not available
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,11 +25,16 @@ import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Annotation which indicates that a method parameter should be bound to an HTTP cookie.
* Annotation to indicate that a method parameter is bound to an HTTP cookie.
*
* <p>The method parameter may be declared as type {@link javax.servlet.http.Cookie}
* or as cookie value type (String, int, etc.).
*
* <p>Note that with spring-webmvc 5.3.x and earlier, the cookie value is URL
* decoded. This will be changed in 6.0 but in the meantime, applications can
* also declare parameters of type {@link javax.servlet.http.Cookie} to access
* the raw value.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,16 +80,16 @@ public @interface CrossOrigin {
/**
* A list of origins for which cross-origin requests are allowed. Please,
* see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
* <p>By default all origins are allowed unless {@code originPatterns} is
* <p>By default all origins are allowed unless {@link #originPatterns} is
* also set in which case {@code originPatterns} is used instead.
*/
@AliasFor("value")
String[] origins() default {};
/**
* Alternative to {@link #origins()} that supports origins declared via
* wildcard patterns. Please, see
* @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
* Alternative to {@link #origins} that supports more flexible origin
* patterns. Please, see {@link CorsConfiguration#setAllowedOriginPatterns(List)}
* for details.
* <p>By default this is not set.
* @since 5.3
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,8 +45,9 @@ import org.springframework.http.HttpStatus;
* a return type and avoid the use of {@code @ResponseStatus} altogether.
*
* <p>Note that a controller class may also be annotated with
* {@code @ResponseStatus} and is then inherited by all {@code @RequestMapping}
* methods.
* {@code @ResponseStatus} which is then inherited by all {@code @RequestMapping}
* and {@code @ExceptionHandler} methods in that class and its subclasses unless
* overridden by a local {@code @ResponseStatus} declaration on the method.
*
* @author Arjen Poutsma
* @author Sam Brannen
@@ -78,6 +79,8 @@ public @interface ResponseStatus {
/**
* The <em>reason</em> to be used for the response.
* <p>Defaults to an empty string which will be ignored. Set the reason to a
* non-empty value to have it used for the response.
* @see javax.servlet.http.HttpServletResponse#sendError(int, String)
*/
String reason() default "";
@@ -88,7 +88,7 @@ public abstract class SpringBeanAutowiringSupport {
if (logger.isWarnEnabled()) {
logger.warn("Current WebApplicationContext is not available for processing of " +
ClassUtils.getShortName(target.getClass()) + ": " +
"Make sure this class gets constructed in a Spring web application after the" +
"Make sure this class gets constructed in a Spring web application after the " +
"Spring WebApplicationContext has been initialized. Proceeding without injection.");
}
}
@@ -22,7 +22,9 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -55,16 +57,13 @@ public class CorsConfiguration {
/** Wildcard representing <em>all</em> origins, methods, or headers. */
public static final String ALL = "*";
private static final List<String> ALL_LIST = Collections.unmodifiableList(
Collections.singletonList(ALL));
private static final List<String> ALL_LIST = Collections.singletonList(ALL);
private static final OriginPattern ALL_PATTERN = new OriginPattern("*");
private static final List<OriginPattern> ALL_PATTERN_LIST = Collections.unmodifiableList(
Collections.singletonList(ALL_PATTERN));
private static final List<OriginPattern> ALL_PATTERN_LIST = Collections.singletonList(ALL_PATTERN);
private static final List<String> DEFAULT_PERMIT_ALL = Collections.unmodifiableList(
Collections.singletonList(ALL));
private static final List<String> DEFAULT_PERMIT_ALL = Collections.singletonList(ALL);
private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
@@ -137,13 +136,13 @@ public class CorsConfiguration {
* However an instance of this class is often initialized further, e.g. for
* {@code @CrossOrigin}, via {@link #applyPermitDefaultValues()}.
*/
public void setAllowedOrigins(@Nullable List<String> allowedOrigins) {
this.allowedOrigins = (allowedOrigins != null ?
allowedOrigins.stream().map(this::trimTrailingSlash).collect(Collectors.toList()) : null);
public void setAllowedOrigins(@Nullable List<String> origins) {
this.allowedOrigins = (origins == null ? null :
origins.stream().filter(Objects::nonNull).map(this::trimTrailingSlash).collect(Collectors.toList()));
}
private String trimTrailingSlash(String origin) {
return origin.endsWith("/") ? origin.substring(0, origin.length() - 1) : origin;
return (origin.endsWith("/") ? origin.substring(0, origin.length() - 1) : origin);
}
/**
@@ -157,7 +156,10 @@ public class CorsConfiguration {
/**
* Variant of {@link #setAllowedOrigins} for adding one origin at a time.
*/
public void addAllowedOrigin(String origin) {
public void addAllowedOrigin(@Nullable String origin) {
if (origin == null) {
return;
}
if (this.allowedOrigins == null) {
this.allowedOrigins = new ArrayList<>(4);
}
@@ -169,14 +171,22 @@ public class CorsConfiguration {
}
/**
* Alternative to {@link #setAllowedOrigins} that supports origins declared
* via wildcard patterns. In contrast to {@link #setAllowedOrigins allowedOrigins}
* which does support the special value {@code "*"}, this property allows
* more flexible patterns, e.g. {@code "https://*.domain1.com"}. Furthermore
* it always sets the {@code Access-Control-Allow-Origin} response header to
* the matched origin and never to {@code "*"}, nor to any other pattern, and
* therefore can be used in combination with {@link #setAllowCredentials}
* set to {@code true}.
* Alternative to {@link #setAllowedOrigins} that supports more flexible
* origins patterns with "*" anywhere in the host name in addition to port
* lists. Examples:
* <ul>
* <li>{@literal https://*.domain1.com} -- domains ending with domain1.com
* <li>{@literal https://*.domain1.com:[8080,8081]} -- domains ending with
* domain1.com on port 8080 or port 8081
* <li>{@literal https://*.domain1.com:[*]} -- domains ending with
* domain1.com on any port, including the default port
* </ul>
* <p>In contrast to {@link #setAllowedOrigins(List) allowedOrigins} which
* only supports "*" and cannot be used with {@code allowCredentials}, when
* an allowedOriginPattern is matched, the {@code Access-Control-Allow-Origin}
* response header is set to the matched origin and not to {@code "*"} nor
* to the pattern. Therefore allowedOriginPatterns can be used in combination
* with {@link #setAllowCredentials} set to {@code true}.
* <p>By default this is not set.
* @since 5.3
*/
@@ -211,7 +221,10 @@ public class CorsConfiguration {
* Variant of {@link #setAllowedOriginPatterns} for adding one origin at a time.
* @since 5.3
*/
public void addAllowedOriginPattern(String originPattern) {
public void addAllowedOriginPattern(@Nullable String originPattern) {
if (originPattern == null) {
return;
}
if (this.allowedOriginPatterns == null) {
this.allowedOriginPatterns = new ArrayList<>(4);
}
@@ -647,18 +660,32 @@ public class CorsConfiguration {
*/
private static class OriginPattern {
private static final Pattern PORTS_PATTERN = Pattern.compile("(.*):\\[(\\*|\\d+(,\\d+)*)]");
private final String declaredPattern;
private final Pattern pattern;
OriginPattern(String declaredPattern) {
this.declaredPattern = declaredPattern;
this.pattern = toPattern(declaredPattern);
this.pattern = initPattern(declaredPattern);
}
private static Pattern toPattern(String patternValue) {
private static Pattern initPattern(String patternValue) {
String portList = null;
Matcher matcher = PORTS_PATTERN.matcher(patternValue);
if (matcher.matches()) {
patternValue = matcher.group(1);
portList = matcher.group(2);
}
patternValue = "\\Q" + patternValue + "\\E";
patternValue = patternValue.replace("*", "\\E.*\\Q");
if (portList != null) {
patternValue += (portList.equals(ALL) ? "(:\\d+)?" : ":(" + portList.replace(',', '|') + ")");
}
return Pattern.compile(patternValue);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -118,7 +118,8 @@ public class InMemoryWebSessionStore implements WebSessionStore {
this.expiredSessionChecker.checkIfNecessary(now);
return Mono.<WebSession>fromSupplier(() -> new InMemoryWebSession(now))
.subscribeOn(Schedulers.boundedElastic());
.subscribeOn(Schedulers.boundedElastic())
.publishOn(Schedulers.parallel());
}
@Override
@@ -85,7 +85,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
private static final String HOST_PATTERN = "(" + HOST_IPV6_PATTERN + "|" + HOST_IPV4_PATTERN + ")";
private static final String PORT_PATTERN = "(.[^/?#]*(?:\\{[^/]+?})?)";
private static final String PORT_PATTERN = "(\\{[^}]+}?|[^/?#]*)";
private static final String PATH_PATTERN = "([^?#]*)";

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