mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d51d8aeaf6 | |||
| c7c35aff42 | |||
| 000b6a7e95 | |||
| f3db6b996d | |||
| a42540e64a | |||
| cc2b980e5c | |||
| aa2bfd4b62 | |||
| 29fa9c1728 | |||
| 39660710f6 | |||
| e35fe33b72 | |||
| 23f396a231 | |||
| 090e394fb5 | |||
| 708e61a7ef | |||
| e24b2e6b5d | |||
| aff0d8efe7 | |||
| e3e4e49319 | |||
| 5420d7113b | |||
| 6251222a23 | |||
| 05eca05671 | |||
| 3fa4e4168d | |||
| f0f450a18d | |||
| f854803e18 | |||
| d29d4d45aa | |||
| 432fdad7d0 | |||
| 345d8186d4 | |||
| ad63a534cb | |||
| c9d341e9c0 | |||
| 950b183715 | |||
| 821ecb4cfd | |||
| 1c17305d7b | |||
| 491d1c4c78 | |||
| 7dbbf364f7 | |||
| 5204d736f3 | |||
| e0690335fc | |||
| 5cbbbed377 | |||
| 6f2fe5ddcb | |||
| da9ee06e05 | |||
| 4c28266eb6 | |||
| b3dcb64ff1 | |||
| b18f8771c8 | |||
| 93244e9f9d | |||
| f45a852aca | |||
| 4203e90655 | |||
| 1e2e114e3c | |||
| 0e73213144 | |||
| 1e1045ba42 | |||
| 8d17bcea5b | |||
| 597b31b1fc | |||
| 4049951c03 | |||
| 89441ea006 | |||
| de1ba79c55 | |||
| ffbc537835 | |||
| 9295bcc4a4 | |||
| 746b1b2fe1 | |||
| ab2c78a9d5 | |||
| ac0e71745b | |||
| f65cbe0992 | |||
| a2e5d9a14f | |||
| 360f555e77 | |||
| 7c756027a0 | |||
| 4c642ccdcd | |||
| fab7b1cebe | |||
| 101ed17b6f | |||
| 66588bae92 | |||
| d33545bdce | |||
| e8e157ebf8 | |||
| 7f5904c68b | |||
| 66e488850b | |||
| 3dba3691a7 | |||
| c80c4e001a | |||
| 8da049b613 | |||
| 1acd832759 | |||
| 261927b72a | |||
| 24aa7f546b | |||
| 4cfad25468 | |||
| 569ce840cf | |||
| fae484855b | |||
| cab20d1202 | |||
| e2bf513563 | |||
| 84e56bb5d9 | |||
| 6f5e66f00c | |||
| cda72e4a70 | |||
| e31a4c4c77 | |||
| 4809632f44 |
+29
-29
@@ -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
@@ -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
@@ -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
|
||||
*/
|
||||
|
||||
+6
-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.
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-8
@@ -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());
|
||||
|
||||
+3
-4
@@ -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.
|
||||
|
||||
+3
-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.
|
||||
@@ -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);
|
||||
|
||||
+11
-13
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+35
-1
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -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) -> {
|
||||
|
||||
+39
-4
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+2
-2
@@ -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 "";
|
||||
|
||||
+6
-1
@@ -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;
|
||||
|
||||
+20
-1
@@ -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();
|
||||
|
||||
+6
-3
@@ -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);
|
||||
|
||||
+4
-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.
|
||||
@@ -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)) {
|
||||
|
||||
+33
@@ -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");
|
||||
|
||||
+4
-5
@@ -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
|
||||
|
||||
+5
-11
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+10
-10
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
*/
|
||||
|
||||
+8
-2
@@ -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);
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
*/
|
||||
|
||||
+3
-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.
|
||||
@@ -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
|
||||
|
||||
+427
-347
@@ -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 — for example,
|
||||
* within a statement — 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 — for example, within
|
||||
* a statement — 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 — for example,
|
||||
* within a statement — 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 — for example,
|
||||
* within a statement — 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 — for example, within
|
||||
* a statement — 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 — for example,
|
||||
* within a statement — 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 — 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 — 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>"*/"</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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
*/
|
||||
|
||||
+4
-6
@@ -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);
|
||||
|
||||
+24
-14
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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() {
|
||||
+23
-23
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+86
-26
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-1
@@ -5,16 +5,19 @@
|
||||
* x, y, z...
|
||||
*/
|
||||
|
||||
-- This is a single line comment containing single (') and double quotes (").
|
||||
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
|
||||
-- This is also a comment.
|
||||
/*-------------------------------------------
|
||||
-- A fancy multi-line comments that puts
|
||||
-- A fancy multi-line comment that puts
|
||||
-- single line comments inside of a multi-line
|
||||
-- comment block.
|
||||
Moreover, the block comment end delimiter
|
||||
appears on a line that can potentially also
|
||||
be a single-line comment if we weren't
|
||||
already inside a multi-line comment run.
|
||||
|
||||
And here's a line containing single and double quotes (").
|
||||
-------------------------------------------*/
|
||||
INSERT INTO
|
||||
users(first_name, last_name) -- This is a single line comment containing the block-end-comment sequence here */ but it's still a single-line comment
|
||||
|
||||
+1
@@ -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 {
|
||||
|
||||
|
||||
+1
-2
@@ -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;
|
||||
|
||||
-1
@@ -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);
|
||||
|
||||
+3
-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.
|
||||
@@ -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
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+24
-24
@@ -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}.
|
||||
|
||||
+32
-28
@@ -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(),
|
||||
|
||||
+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.
|
||||
@@ -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);
|
||||
|
||||
+9
-4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -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() {
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+4
-2
@@ -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
|
||||
*/
|
||||
|
||||
+2
-1
@@ -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
|
||||
*/
|
||||
|
||||
+3
-1
@@ -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
|
||||
*/
|
||||
|
||||
+325
-407
@@ -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 — 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>"*/"</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 — for example,
|
||||
* within a statement — 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 — for example, within
|
||||
* a statement — 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 — for example,
|
||||
* within a statement — 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) {
|
||||
|
||||
|
||||
+5
-4
@@ -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
|
||||
*/
|
||||
|
||||
+2
-1
@@ -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()
|
||||
}
|
||||
|
||||
+5
-1
@@ -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()
|
||||
|
||||
|
||||
+12
-13
@@ -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();
|
||||
}
|
||||
}
|
||||
+14
-14
@@ -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);
|
||||
|
||||
+5
-7
@@ -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();
|
||||
|
||||
+2
-21
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-32
@@ -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();
|
||||
|
||||
|
||||
+107
-95
@@ -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
@@ -1 +0,0 @@
|
||||
INSERT INTO users(first_name, last_name) values('Walter', 'White');
|
||||
+4
-4
@@ -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')
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+7
-7
@@ -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
|
||||
);--
|
||||
+8
-5
@@ -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
|
||||
);--
|
||||
+18
@@ -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
|
||||
);--
|
||||
+3
-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.
|
||||
@@ -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.
|
||||
|
||||
+38
-16
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-24
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-14
@@ -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()
|
||||
*/
|
||||
|
||||
+20
-10
@@ -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
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
+18
-6
@@ -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;
|
||||
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+7
-1
@@ -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);
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+1
-7
@@ -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
|
||||
*/
|
||||
|
||||
+6
-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 "";
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -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
Reference in New Issue
Block a user