Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33fbaa03a8 | ||
|
|
88b26f2cfe | ||
|
|
3f670050ef | ||
|
|
e3b61d25bb | ||
|
|
19b8effa41 | ||
|
|
9f5f7540d2 | ||
|
|
eb8c22939c | ||
|
|
45cfa1e9a4 | ||
|
|
99221e0948 | ||
|
|
41cf2ef152 | ||
|
|
c51bce4777 | ||
|
|
b6f1184c4c | ||
|
|
c69a8b8762 | ||
|
|
99fb17a66b | ||
|
|
937b2fcbf1 | ||
|
|
9c5a7e9156 | ||
|
|
4deccd3ad0 | ||
|
|
da058e9510 | ||
|
|
d28ca4658b |
@@ -1,6 +1,6 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.3.RELEASE'
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.4.RELEASE'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||
}
|
||||
repositories {
|
||||
|
||||
@@ -109,7 +109,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ server.session.timeout=60
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
[NOTE]
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
springBootVersion=2.0.0.M3
|
||||
version=2.0.0.M4
|
||||
springBootVersion=2.0.0.M4
|
||||
version=2.0.0.M5
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'io.projectreactor:reactor-bom:Bismuth-M4'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.0.RC4'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-RC3'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.0.0.M4'
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.1'
|
||||
mavenBom 'io.projectreactor:reactor-bom:Bismuth-RELEASE'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.0.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-RELEASE'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.0.0.M5'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.8.3') {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.8.6') {
|
||||
entry 'hazelcast'
|
||||
entry 'hazelcast-client'
|
||||
}
|
||||
@@ -19,38 +20,36 @@ dependencyManagement {
|
||||
}
|
||||
|
||||
dependency 'ch.qos.logback:logback-classic:1.2.3'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.0.pr4'
|
||||
dependency 'com.h2database:h2:1.4.196'
|
||||
dependency 'com.maxmind.geoip2:geoip2:2.3.1'
|
||||
dependency 'commons-codec:commons-codec:1.10'
|
||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||
dependency 'io.lettuce:lettuce-core:5.0.0.RC2'
|
||||
dependency 'io.lettuce:lettuce-core:5.0.0.RELEASE'
|
||||
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
|
||||
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
|
||||
dependency 'javax.servlet:javax.servlet-api:3.1.0'
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'mysql:mysql-connector-java:5.1.43'
|
||||
dependency 'mysql:mysql-connector-java:5.1.44'
|
||||
dependency 'org.apache.derby:derby:10.13.1.1'
|
||||
dependency 'org.apache.httpcomponents:httpclient:4.5.3'
|
||||
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
|
||||
dependency 'org.assertj:assertj-core:3.8.0'
|
||||
dependency 'org.hsqldb:hsqldb:2.4.0'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.0.3'
|
||||
dependency 'org.mockito:mockito-core:2.8.47'
|
||||
dependency 'org.postgresql:postgresql:42.1.3'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
|
||||
dependency 'org.mockito:mockito-core:2.10.0'
|
||||
dependency 'org.postgresql:postgresql:42.1.4'
|
||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.27'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
|
||||
dependency 'org.testcontainers:mariadb:1.3.0'
|
||||
dependency 'org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.0.RELEASE'
|
||||
dependency 'org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.1.RELEASE'
|
||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.7.RC1'
|
||||
dependency 'org.webjars:bootstrap:2.3.2'
|
||||
dependency 'org.webjars:html5shiv:3.7.3'
|
||||
dependency 'org.webjars:jquery:1.9.0'
|
||||
dependency 'org.webjars:jquery:1.12.4'
|
||||
dependency 'org.webjars:knockout:2.3.0'
|
||||
dependency 'org.webjars:sockjs-client:0.3.4'
|
||||
dependency 'org.webjars:stomp-websocket:2.3.0'
|
||||
dependency 'org.webjars:webjars-taglib:0.3'
|
||||
dependency 'redis.clients:jedis:2.9.0'
|
||||
}
|
||||
}
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Fri Jul 07 11:11:13 CDT 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip
|
||||
|
||||
6
gradlew
vendored
6
gradlew
vendored
@@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS=""
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
@@ -155,7 +155,7 @@ if $cygwin ; then
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save ( ) {
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['spring-data-releasetrain.version'] = 'Kay-RC3'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-redis')
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
|
||||
@@ -49,7 +49,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
|
||||
@ContextConfiguration(initializers = FindByUsernameTests.Initializer.class)
|
||||
public class FindByUsernameTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
|
||||
@@ -16,27 +16,46 @@
|
||||
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.security.StaticResourceRequest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
/**
|
||||
* Spring Security configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(
|
||||
User.withUsername("user").password("password").roles("USER").build());
|
||||
return manager;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
// tag::config[]
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated();
|
||||
.permitAll();
|
||||
}
|
||||
// end::config[]
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
spring.session.store-type=redis
|
||||
security.user.password=password
|
||||
|
||||
@@ -16,17 +16,33 @@
|
||||
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.security.StaticResourceRequest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
/**
|
||||
* Spring Security configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(
|
||||
User.withUsername("user").password("password").roles("USER").build());
|
||||
return manager;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
@@ -35,4 +51,19 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
// tag::config[]
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.permitAll();
|
||||
}
|
||||
// end::config[]
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
spring.session.store-type=jdbc
|
||||
security.user.password=password
|
||||
spring.h2.console.enabled=true
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['spring-data-releasetrain.version'] = 'Kay-RC3'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-redis')
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
|
||||
@@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ContextConfiguration(initializers = HttpRedisJsonTest.Initializer.class)
|
||||
public class HttpRedisJsonTest {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
|
||||
@@ -42,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ContextConfiguration(initializers = RedisSerializerTest.Initializer.class)
|
||||
public class RedisSerializerTest {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
|
||||
@@ -13,28 +13,46 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.security.StaticResourceRequest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
/**
|
||||
* @author jitendra on 3/3/16.
|
||||
* Spring Security configuration.
|
||||
*
|
||||
* @author jitendra
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(
|
||||
User.withUsername("user").password("password").roles("USER").build());
|
||||
return manager;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated();
|
||||
.permitAll();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
spring.session.store-type=redis
|
||||
security.user.password=password
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['spring-data-releasetrain.version'] = 'Kay-RC3'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-redis')
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
|
||||
@@ -48,7 +48,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
|
||||
@ContextConfiguration(initializers = BootTests.Initializer.class)
|
||||
public class BootTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
|
||||
@@ -16,12 +16,45 @@
|
||||
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.security.StaticResourceRequest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
/**
|
||||
* Spring Security configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(
|
||||
User.withUsername("user").password("password").roles("USER").build());
|
||||
return manager;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
// tag::config[]
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.permitAll();
|
||||
}
|
||||
// end::config[]
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
spring.session.store-type=redis
|
||||
security.user.password=password
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['spring-data-releasetrain.version'] = 'Kay-RC3'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-redis')
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
|
||||
@@ -17,25 +17,30 @@
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.security.StaticResourceRequest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web
|
||||
.ignoring().antMatchers("/h2-console/**");
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(
|
||||
User.withUsername("user").password("password").roles("USER").build());
|
||||
return manager;
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
@Autowired
|
||||
@@ -47,4 +52,25 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web
|
||||
.ignoring().antMatchers("/h2-console/**");
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.permitAll();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
spring.session.store-type=redis
|
||||
#server.session.timeout=60
|
||||
spring.h2.console.enabled=true
|
||||
|
||||
@@ -55,7 +55,7 @@ import org.springframework.web.socket.sockjs.client.WebSocketTransport;
|
||||
@ContextConfiguration(initializers = ApplicationTests.Initializer.class)
|
||||
public class ApplicationTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@Bean(initMethod = "start")
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@Bean(initMethod = "start")
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -55,7 +55,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
@WebAppConfiguration
|
||||
public class RestMockMvcTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@Bean(initMethod = "start")
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@Bean(initMethod = "start")
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencyManagement {
|
||||
dependencies {
|
||||
dependency 'org.webjars:bootstrap:3.3.6'
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@Bean(initMethod = "start")
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-core')
|
||||
compile project(':spring-session-data-redis')
|
||||
compile 'io.lettuce:lettuce-core'
|
||||
compile 'io.netty:netty-buffer'
|
||||
compile 'io.projectreactor.ipc:reactor-netty'
|
||||
@@ -14,6 +14,7 @@ dependencies {
|
||||
compile 'org.webjars:webjars-taglib'
|
||||
compile jstlDependencies
|
||||
compile slf4jDependencies
|
||||
compile 'org.testcontainers:testcontainers'
|
||||
|
||||
testCompile 'junit:junit'
|
||||
testCompile 'org.assertj:assertj-core'
|
||||
|
||||
@@ -41,7 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = HelloWebfluxApplication.class)
|
||||
@TestPropertySource(properties = "server.port=0")
|
||||
@TestPropertySource(properties = { "spring.profiles.active=embedded-redis", "server.port=0" })
|
||||
public class AttributeTests {
|
||||
@Value("#{@nettyContext.address().getPort()}")
|
||||
int port;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
|
||||
@Configuration
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@Bean(initMethod = "start")
|
||||
public GenericContainer redisContainer() {
|
||||
return new GenericContainer(REDIS_DOCKER_IMAGE) {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
try {
|
||||
this.dockerClient.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
}.withExposedPorts(6379);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
return new LettuceConnectionFactory(redisContainer().getContainerIpAddress(),
|
||||
redisContainer().getFirstMappedPort());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
@@ -49,7 +48,6 @@ public class HelloWebfluxApplication {
|
||||
}
|
||||
}
|
||||
|
||||
@Profile("default")
|
||||
@Bean
|
||||
public NettyContext nettyContext(ApplicationContext context) {
|
||||
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
|
||||
|
||||
@@ -16,22 +16,19 @@
|
||||
|
||||
package sample;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.EnableSpringWebSession;
|
||||
import org.springframework.session.MapReactorSessionRepository;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.session.data.redis.config.annotation.web.reactor.EnableRedisReactorSession;
|
||||
|
||||
@Import(EmbeddedRedisConfig.class)
|
||||
// tag::class[]
|
||||
@Configuration
|
||||
@EnableSpringWebSession
|
||||
@EnableRedisReactorSession
|
||||
public class HelloWebfluxSessionConfig {
|
||||
|
||||
@Bean
|
||||
public MapReactorSessionRepository reactorSessionRepository() {
|
||||
return new MapReactorSessionRepository(new ConcurrentHashMap<>());
|
||||
public LettuceConnectionFactory lettuceConnectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@Bean(initMethod = "start")
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -157,6 +157,11 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
|
||||
|| (State.NEW.equals(value) && !getAttributes().isEmpty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> invalidate() {
|
||||
return SpringSessionWebSessionStore.this.sessions.deleteById(this.session.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save() {
|
||||
return SpringSessionWebSessionStore.this.sessions.save(this.session);
|
||||
|
||||
@@ -9,11 +9,14 @@ dependencies {
|
||||
exclude group: "org.slf4j", module: 'jcl-over-slf4j'
|
||||
}
|
||||
|
||||
optional "io.projectreactor:reactor-core"
|
||||
optional "org.springframework:spring-web"
|
||||
|
||||
testCompile "io.projectreactor:reactor-test"
|
||||
testCompile "javax.servlet:javax.servlet-api"
|
||||
testCompile "org.springframework:spring-web"
|
||||
testCompile "org.springframework.security:spring-security-core"
|
||||
|
||||
integrationTestCompile "redis.clients:jedis"
|
||||
integrationTestCompile "org.apache.commons:commons-pool2"
|
||||
integrationTestCompile "io.lettuce:lettuce-core"
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
|
||||
/**
|
||||
* Base class for {@link RedisOperationsSessionRepository} integration tests.
|
||||
@@ -30,7 +30,7 @@ import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
*/
|
||||
public abstract class AbstractRedisITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.2";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
@@ -39,11 +39,11 @@ public abstract class AbstractRedisITests {
|
||||
protected static class BaseConfig {
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory connectionFactory() {
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(
|
||||
redisContainer.getContainerIpAddress(),
|
||||
redisContainer.getFirstMappedPort());
|
||||
return new JedisConnectionFactory(configuration);
|
||||
return new LettuceConnectionFactory(configuration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.redis.config.annotation.web.reactor.EnableRedisReactorSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ReactiveRedisOperationsSessionRepository}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests {
|
||||
|
||||
@Autowired
|
||||
private ReactiveRedisOperationsSessionRepository repository;
|
||||
|
||||
@Test
|
||||
public void saves() throws InterruptedException {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
|
||||
String expectedAttributeName = "a";
|
||||
String expectedAttributeValue = "b";
|
||||
|
||||
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
Session session = this.repository.findById(toSave.getId()).block();
|
||||
|
||||
assertThat(session.getId()).isEqualTo(toSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
|
||||
assertThat(session.<String>getAttribute(expectedAttributeName))
|
||||
.isEqualTo(toSave.getAttribute(expectedAttributeName));
|
||||
|
||||
this.repository.deleteById(toSave.getId()).block();
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId()).block()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putAllOnSingleAttrDoesNotRemoveOld() {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
toSave.setAttribute("a", "b");
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
toSave = this.repository.findById(toSave.getId()).block();
|
||||
|
||||
toSave.setAttribute("1", "2");
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
toSave = this.repository.findById(toSave.getId()).block();
|
||||
|
||||
Session session = this.repository.findById(toSave.getId()).block();
|
||||
assertThat(session.getAttributeNames().size()).isEqualTo(2);
|
||||
assertThat(session.<String>getAttribute("a")).isEqualTo("b");
|
||||
assertThat(session.<String>getAttribute("1")).isEqualTo("2");
|
||||
|
||||
this.repository.deleteById(toSave.getId()).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenOnlyChangeId() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
toSave.setAttribute(attrName, attrValue);
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository
|
||||
.findById(toSave.getId()).block();
|
||||
|
||||
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById).block();
|
||||
|
||||
assertThat(this.repository.findById(originalFindById).block()).isNull();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository
|
||||
.findById(changeSessionId).block();
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
|
||||
.isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenChangeTwice() throws Exception {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
String originalId = toSave.getId();
|
||||
String changeId1 = toSave.changeSessionId();
|
||||
String changeId2 = toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
assertThat(this.repository.findById(changeId1).block()).isNull();
|
||||
assertThat(this.repository.findById(changeId2).block()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository
|
||||
.findById(toSave.getId()).block();
|
||||
|
||||
findById.setAttribute(attrName, attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById).block();
|
||||
|
||||
assertThat(this.repository.findById(originalFindById).block()).isNull();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository
|
||||
.findById(changeSessionId).block();
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
|
||||
.isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenHasNotSaved() throws Exception {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId()).block()).isNotNull();
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisReactorSession
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.ReactorSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ReactorSessionRepository} that is implemented using Spring Data's
|
||||
* {@link ReactiveRedisOperations}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ReactiveRedisOperationsSessionRepository implements
|
||||
ReactorSessionRepository<ReactiveRedisOperationsSessionRepository.RedisSession> {
|
||||
|
||||
/**
|
||||
* The default prefix for each key and channel in Redis used by Spring Session.
|
||||
*/
|
||||
static final String DEFAULT_SPRING_SESSION_REDIS_PREFIX = "spring:session:";
|
||||
|
||||
/**
|
||||
* The key in the Hash representing {@link Session#getCreationTime()}.
|
||||
*/
|
||||
static final String CREATION_TIME_KEY = "creationTime";
|
||||
|
||||
/**
|
||||
* The key in the Hash representing {@link Session#getLastAccessedTime()}.
|
||||
*/
|
||||
static final String LAST_ACCESSED_TIME_KEY = "lastAccessedTime";
|
||||
|
||||
/**
|
||||
* The key in the Hash representing {@link Session#getMaxInactiveInterval()} .
|
||||
*/
|
||||
static final String MAX_INACTIVE_INTERVAL_KEY = "maxInactiveInterval";
|
||||
|
||||
/**
|
||||
* The prefix of the key for used for session attributes. The suffix is the name of
|
||||
* the session attribute. For example, if the session contained an attribute named
|
||||
* attributeName, then there would be an entry in the hash named
|
||||
* sessionAttr:attributeName that mapped to its value.
|
||||
*/
|
||||
static final String ATTRIBUTE_PREFIX = "attribute:";
|
||||
|
||||
private final ReactiveRedisOperations<String, Object> sessionRedisOperations;
|
||||
|
||||
/**
|
||||
* The prefix for every key used by Spring Session in Redis.
|
||||
*/
|
||||
private String keyPrefix = DEFAULT_SPRING_SESSION_REDIS_PREFIX;
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override the default value for
|
||||
* {@link RedisSession#setMaxInactiveInterval(Duration)}.
|
||||
*/
|
||||
private Integer defaultMaxInactiveInterval;
|
||||
|
||||
private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE;
|
||||
|
||||
public ReactiveRedisOperationsSessionRepository(
|
||||
ReactiveRedisOperations<String, Object> sessionRedisOperations) {
|
||||
Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");
|
||||
this.sessionRedisOperations = sessionRedisOperations;
|
||||
}
|
||||
|
||||
public void setRedisKeyNamespace(String namespace) {
|
||||
Assert.hasText(namespace, "namespace cannot be null or empty");
|
||||
this.keyPrefix = DEFAULT_SPRING_SESSION_REDIS_PREFIX + namespace.trim() + ":";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum inactive interval in seconds between requests before newly created
|
||||
* sessions will be invalidated. A negative time indicates that the session will never
|
||||
* timeout. The default is 1800 (30 minutes).
|
||||
*
|
||||
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
|
||||
* should be kept alive between client requests.
|
||||
*/
|
||||
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
|
||||
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the redis flush mode. Default flush mode is {@link RedisFlushMode#ON_SAVE}.
|
||||
*
|
||||
* @param redisFlushMode the new redis flush mode
|
||||
*/
|
||||
public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
|
||||
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
|
||||
this.redisFlushMode = redisFlushMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<RedisSession> createSession() {
|
||||
return Mono.defer(() -> {
|
||||
RedisSession session = new RedisSession();
|
||||
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
session.setMaxInactiveInterval(
|
||||
Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
|
||||
return Mono.just(session);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(RedisSession session) {
|
||||
return session.saveDelta().and(s -> {
|
||||
if (session.isNew) {
|
||||
session.setNew(false);
|
||||
}
|
||||
|
||||
s.onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<RedisSession> findById(String id) {
|
||||
String sessionKey = getSessionKey(id);
|
||||
|
||||
return this.sessionRedisOperations.opsForHash().entries(sessionKey)
|
||||
.collect(
|
||||
Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue))
|
||||
.filter(map -> !map.isEmpty()).map(new SessionMapper(id))
|
||||
.filter(session -> !session.isExpired()).map(RedisSession::new)
|
||||
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteById(String id) {
|
||||
String sessionKey = getSessionKey(id);
|
||||
|
||||
return this.sessionRedisOperations.delete(sessionKey).then();
|
||||
}
|
||||
|
||||
private static String getAttributeKey(String attributeName) {
|
||||
return ATTRIBUTE_PREFIX + attributeName;
|
||||
}
|
||||
|
||||
private String getSessionKey(String sessionId) {
|
||||
return this.keyPrefix + "sessions:" + sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
|
||||
* basis for its mapping. It keeps track of any attributes that have changed. When
|
||||
* {@link RedisSession#saveDelta()} is invoked all the attributes that have been
|
||||
* changed will be persisted.
|
||||
*/
|
||||
final class RedisSession implements Session {
|
||||
|
||||
private final MapSession cached;
|
||||
|
||||
private final Map<String, Object> delta = new HashMap<>();
|
||||
|
||||
private boolean isNew;
|
||||
|
||||
private String originalSessionId;
|
||||
|
||||
/**
|
||||
* Creates a new instance ensuring to mark all of the new attributes to be
|
||||
* persisted in the next save operation.
|
||||
*/
|
||||
RedisSession() {
|
||||
this(new MapSession());
|
||||
this.delta.put(CREATION_TIME_KEY, getCreationTime().toEpochMilli());
|
||||
this.delta.put(MAX_INACTIVE_INTERVAL_KEY,
|
||||
(int) getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
this.isNew = true;
|
||||
this.flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance from the provided {@link MapSession}.
|
||||
*
|
||||
* @param mapSession the {@link MapSession} that represents the persisted session
|
||||
* that was retrieved. Cannot be null.
|
||||
*/
|
||||
RedisSession(MapSession mapSession) {
|
||||
Assert.notNull(mapSession, "mapSession cannot be null");
|
||||
this.cached = mapSession;
|
||||
this.originalSessionId = mapSession.getId();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.cached.getId();
|
||||
}
|
||||
|
||||
public String changeSessionId() {
|
||||
return this.cached.changeSessionId();
|
||||
}
|
||||
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return this.cached.getAttribute(attributeName);
|
||||
}
|
||||
|
||||
public Set<String> getAttributeNames() {
|
||||
return this.cached.getAttributeNames();
|
||||
}
|
||||
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
this.cached.setAttribute(attributeName, attributeValue);
|
||||
putAndFlush(getAttributeKey(attributeName), attributeValue);
|
||||
}
|
||||
|
||||
public void removeAttribute(String attributeName) {
|
||||
this.cached.removeAttribute(attributeName);
|
||||
putAndFlush(getAttributeKey(attributeName), null);
|
||||
}
|
||||
|
||||
public Instant getCreationTime() {
|
||||
return this.cached.getCreationTime();
|
||||
}
|
||||
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.cached.setLastAccessedTime(lastAccessedTime);
|
||||
putAndFlush(LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
}
|
||||
|
||||
public Instant getLastAccessedTime() {
|
||||
return this.cached.getLastAccessedTime();
|
||||
}
|
||||
|
||||
public void setMaxInactiveInterval(Duration interval) {
|
||||
this.cached.setMaxInactiveInterval(interval);
|
||||
putAndFlush(MAX_INACTIVE_INTERVAL_KEY,
|
||||
(int) getMaxInactiveInterval().getSeconds());
|
||||
}
|
||||
|
||||
public Duration getMaxInactiveInterval() {
|
||||
return this.cached.getMaxInactiveInterval();
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return this.cached.isExpired();
|
||||
}
|
||||
|
||||
public void setNew(boolean isNew) {
|
||||
this.isNew = isNew;
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
return this.isNew;
|
||||
}
|
||||
|
||||
private void flushImmediateIfNecessary() {
|
||||
if (ReactiveRedisOperationsSessionRepository.this.redisFlushMode == RedisFlushMode.IMMEDIATE) {
|
||||
saveDelta();
|
||||
}
|
||||
}
|
||||
|
||||
private void putAndFlush(String a, Object v) {
|
||||
this.delta.put(a, v);
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
private Mono<Void> saveDelta() {
|
||||
String sessionId = getId();
|
||||
Mono<Void> changeSessionId = saveChangeSessionId(sessionId);
|
||||
|
||||
if (this.delta.isEmpty()) {
|
||||
return changeSessionId.and(Mono.empty());
|
||||
}
|
||||
|
||||
String sessionKey = getSessionKey(sessionId);
|
||||
|
||||
Mono<Boolean> update = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
|
||||
.opsForHash().putAll(sessionKey, this.delta);
|
||||
|
||||
Mono<Boolean> setTtl = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
|
||||
.expire(sessionKey, getMaxInactiveInterval());
|
||||
|
||||
return changeSessionId.and(update).and(setTtl).and(s -> {
|
||||
this.delta.clear();
|
||||
s.onComplete();
|
||||
}).then();
|
||||
}
|
||||
|
||||
private Mono<Void> saveChangeSessionId(String sessionId) {
|
||||
if (isNew() || sessionId.equals(this.originalSessionId)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
String originalSessionKey = getSessionKey(this.originalSessionId);
|
||||
String sessionKey = getSessionKey(sessionId);
|
||||
|
||||
return ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
|
||||
.rename(originalSessionKey, sessionKey).and(s -> {
|
||||
this.originalSessionId = sessionId;
|
||||
s.onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class SessionMapper
|
||||
implements Function<Map<String, Object>, MapSession> {
|
||||
|
||||
private final String id;
|
||||
|
||||
private SessionMapper(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapSession apply(Map<String, Object> map) {
|
||||
MapSession session = new MapSession(this.id);
|
||||
|
||||
session.setCreationTime(
|
||||
Instant.ofEpochMilli((long) map.get(CREATION_TIME_KEY)));
|
||||
session.setLastAccessedTime(
|
||||
Instant.ofEpochMilli((long) map.get(LAST_ACCESSED_TIME_KEY)));
|
||||
session.setMaxInactiveInterval(
|
||||
Duration.ofSeconds((int) map.get(MAX_INACTIVE_INTERVAL_KEY)));
|
||||
|
||||
map.forEach((name, value) -> {
|
||||
if (name.startsWith(ATTRIBUTE_PREFIX)) {
|
||||
session.setAttribute(name.substring(ATTRIBUTE_PREFIX.length()),
|
||||
value);
|
||||
}
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,7 +64,7 @@ import org.springframework.util.Assert;
|
||||
* A typical example of how to create a new instance can be seen below:
|
||||
*
|
||||
* <pre>
|
||||
* JedisConnectionFactory factory = new JedisConnectionFactory();
|
||||
* LettuceConnectionFactory factory = new LettuceConnectionFactory();
|
||||
*
|
||||
* RedisOperationsSessionRepository redisSessionRepository = new RedisOperationsSessionRepository(factory);
|
||||
* </pre>
|
||||
|
||||
@@ -38,8 +38,8 @@ import org.springframework.session.data.redis.RedisFlushMode;
|
||||
* public class RedisHttpSessionConfig {
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public JedisConnectionFactory connectionFactory() throws Exception {
|
||||
* return new JedisConnectionFactory();
|
||||
* public LettuceConnectionFactory connectionFactory() {
|
||||
* return new LettuceConnectionFactory();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.reactor;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.session.EnableSpringWebSession;
|
||||
import org.springframework.session.ReactorSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
|
||||
/**
|
||||
* Add this annotation to an {@code @Configuration} class to expose the
|
||||
* {@link org.springframework.web.server.session.WebSessionManager} as a bean named
|
||||
* {@code webSessionManager} and backed by Reactive Redis. In order to leverage the
|
||||
* annotation, a single {@link ReactiveRedisConnectionFactory} must be provided. For
|
||||
* example: <pre class="code">
|
||||
* @Configuration
|
||||
* @EnableRedisReactorSession
|
||||
* public class RedisReactorSessionConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public LettuceConnectionFactory redisConnectionFactory() {
|
||||
* return new LettuceConnectionFactory();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* More advanced configurations can extend {@link RedisReactorSessionConfiguration}
|
||||
* instead.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0.0
|
||||
* @see EnableSpringWebSession
|
||||
*/
|
||||
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@Target({ java.lang.annotation.ElementType.TYPE })
|
||||
@Documented
|
||||
@Import(RedisReactorSessionConfiguration.class)
|
||||
@Configuration
|
||||
public @interface EnableRedisReactorSession {
|
||||
|
||||
int maxInactiveIntervalInSeconds() default 1800;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Defines a unique namespace for keys. The value is used to isolate sessions by
|
||||
* changing the prefix from {@code spring:session:} to
|
||||
* {@code spring:session:<redisNamespace>:}. The default is "" such that all Redis
|
||||
* keys begin with {@code spring:session:}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* For example, if you had an application named "Application A" that needed to keep
|
||||
* the sessions isolated from "Application B" you could set two different values for
|
||||
* the applications and they could function within the same Redis instance.
|
||||
* </p>
|
||||
*
|
||||
* @return the unique namespace for keys
|
||||
*/
|
||||
String redisNamespace() default "";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the flush mode for the Redis sessions. The default is ON_SAVE which only
|
||||
* updates the backing Redis when {@link ReactorSessionRepository#save(Session)} is
|
||||
* invoked. In a web environment this happens just before the HTTP response is
|
||||
* committed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Setting the value to IMMEDIATE will ensure that the any updates to the Session are
|
||||
* immediately written to the Redis instance.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link RedisFlushMode} to use
|
||||
*/
|
||||
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.reactor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.session.SpringWebSessionConfiguration;
|
||||
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
/**
|
||||
* Exposes the {@link WebSessionManager} as a bean named {@code webSessionManager}. In
|
||||
* order to use this a single {@link ReactiveRedisConnectionFactory} must be exposed as a
|
||||
* Bean.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @see EnableRedisReactorSession
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisReactorSessionConfiguration extends SpringWebSessionConfiguration
|
||||
implements EmbeddedValueResolverAware, ImportAware {
|
||||
|
||||
private static final RedisSerializer<String> keySerializer = new StringRedisSerializer();
|
||||
|
||||
private static final RedisSerializer<Object> valueSerializer = new JdkSerializationRedisSerializer();
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = 1800;
|
||||
|
||||
private String redisNamespace = "";
|
||||
|
||||
private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE;
|
||||
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
@Bean
|
||||
public ReactiveRedisOperationsSessionRepository sessionRepository(
|
||||
ReactiveRedisConnectionFactory redisConnectionFactory) {
|
||||
ReactiveRedisOperationsSessionRepository sessionRepository = new ReactiveRedisOperationsSessionRepository(
|
||||
createDefaultTemplate(redisConnectionFactory));
|
||||
sessionRepository
|
||||
.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
|
||||
String redisNamespace = getRedisNamespace();
|
||||
|
||||
if (StringUtils.hasText(redisNamespace)) {
|
||||
sessionRepository.setRedisKeyNamespace(redisNamespace);
|
||||
}
|
||||
|
||||
sessionRepository.setRedisFlushMode(this.redisFlushMode);
|
||||
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
public void setRedisNamespace(String namespace) {
|
||||
this.redisNamespace = namespace;
|
||||
}
|
||||
|
||||
public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
|
||||
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
|
||||
this.redisFlushMode = redisFlushMode;
|
||||
}
|
||||
|
||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||
this.embeddedValueResolver = resolver;
|
||||
}
|
||||
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> enableAttrMap = importMetadata
|
||||
.getAnnotationAttributes(EnableRedisReactorSession.class.getName());
|
||||
AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
|
||||
|
||||
if (enableAttrs != null) {
|
||||
this.maxInactiveIntervalInSeconds = enableAttrs
|
||||
.getNumber("maxInactiveIntervalInSeconds");
|
||||
String redisNamespaceValue = enableAttrs.getString("redisNamespace");
|
||||
if (StringUtils.hasText(redisNamespaceValue)) {
|
||||
this.redisNamespace = this.embeddedValueResolver
|
||||
.resolveStringValue(redisNamespaceValue);
|
||||
}
|
||||
this.redisFlushMode = enableAttrs.getEnum("redisFlushMode");
|
||||
}
|
||||
}
|
||||
|
||||
private static ReactiveRedisTemplate<String, Object> createDefaultTemplate(
|
||||
ReactiveRedisConnectionFactory connectionFactory) {
|
||||
RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
|
||||
.<String, Object>newSerializationContext(valueSerializer)
|
||||
.key(keySerializer).hashKey(keySerializer).build();
|
||||
|
||||
return new ReactiveRedisTemplate<>(connectionFactory, serializationContext);
|
||||
}
|
||||
|
||||
private String getRedisNamespace() {
|
||||
if (StringUtils.hasText(this.redisNamespace)) {
|
||||
return this.redisNamespace;
|
||||
}
|
||||
|
||||
return System.getProperty("spring.session.redis.namespace", "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.data.redis.core.ReactiveHashOperations;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveRedisOperationsSessionRepository}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ReactiveRedisOperations<String, Object> redisOperations = mock(
|
||||
ReactiveRedisOperations.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ReactiveHashOperations<String, Object, Object> hashOperations = mock(
|
||||
ReactiveHashOperations.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ArgumentCaptor<Map<String, Object>> delta = ArgumentCaptor
|
||||
.forClass(Map.class);
|
||||
|
||||
private ReactiveRedisOperationsSessionRepository repository;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.repository = new ReactiveRedisOperationsSessionRepository(
|
||||
this.redisOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWithNullReactiveRedisOperations() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("sessionRedisOperations cannot be null");
|
||||
|
||||
new ReactiveRedisOperationsSessionRepository(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customRedisKeyNamespace() {
|
||||
this.repository.setRedisKeyNamespace("test");
|
||||
|
||||
assertThat(ReflectionTestUtils.getField(this.repository, "keyPrefix")).isEqualTo(
|
||||
ReactiveRedisOperationsSessionRepository.DEFAULT_SPRING_SESSION_REDIS_PREFIX
|
||||
+ "test:");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullRedisKeyNamespace() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("namespace cannot be null or empty");
|
||||
|
||||
this.repository.setRedisKeyNamespace(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyRedisKeyNamespace() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("namespace cannot be null or empty");
|
||||
|
||||
this.repository.setRedisKeyNamespace("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customMaxInactiveInterval() {
|
||||
this.repository.setDefaultMaxInactiveInterval(600);
|
||||
|
||||
assertThat(ReflectionTestUtils.getField(this.repository,
|
||||
"defaultMaxInactiveInterval")).isEqualTo(600);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customRedisFlushMode() {
|
||||
this.repository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
|
||||
|
||||
assertThat(ReflectionTestUtils.getField(this.repository, "redisFlushMode"))
|
||||
.isEqualTo(RedisFlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullRedisFlushMode() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("redisFlushMode cannot be null");
|
||||
|
||||
this.repository.setRedisFlushMode(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionDefaultMaxInactiveInterval() {
|
||||
Mono<ReactiveRedisOperationsSessionRepository.RedisSession> session = this.repository
|
||||
.createSession();
|
||||
|
||||
StepVerifier.create(session).expectNextMatches(predicate -> {
|
||||
assertThat(predicate.getMaxInactiveInterval()).isEqualTo(
|
||||
Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionCustomMaxInactiveInterval() {
|
||||
this.repository.setDefaultMaxInactiveInterval(600);
|
||||
Mono<ReactiveRedisOperationsSessionRepository.RedisSession> session = this.repository
|
||||
.createSession();
|
||||
|
||||
StepVerifier.create(session).expectNextMatches(predicate -> {
|
||||
assertThat(predicate.getMaxInactiveInterval())
|
||||
.isEqualTo(Duration.ofSeconds(600));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNewSession() {
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), this.delta.capture()))
|
||||
.willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
.willReturn(Mono.just(true));
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.new RedisSession();
|
||||
Mono<Void> result = this.repository.save(session);
|
||||
|
||||
StepVerifier.create(result).expectNextMatches(predicate -> {
|
||||
Map<String, Object> delta = this.delta.getAllValues().get(0);
|
||||
assertThat(delta.size()).isEqualTo(3);
|
||||
Object creationTime = delta
|
||||
.get(ReactiveRedisOperationsSessionRepository.CREATION_TIME_KEY);
|
||||
assertThat(creationTime).isEqualTo(session.getCreationTime().toEpochMilli());
|
||||
assertThat(delta.get(
|
||||
ReactiveRedisOperationsSessionRepository.MAX_INACTIVE_INTERVAL_KEY))
|
||||
.isEqualTo((int) Duration.ofSeconds(
|
||||
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS)
|
||||
.getSeconds());
|
||||
assertThat(delta
|
||||
.get(ReactiveRedisOperationsSessionRepository.LAST_ACCESSED_TIME_KEY))
|
||||
.isEqualTo(session.getCreationTime().toEpochMilli());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveSessionNothingChanged() {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.new RedisSession(
|
||||
new MapSession());
|
||||
|
||||
Mono<Void> result = this.repository.save(session);
|
||||
|
||||
StepVerifier.create(result).expectNextMatches(predicate -> {
|
||||
verifyZeroInteractions(this.redisOperations);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveLastAccessChanged() {
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), this.delta.capture()))
|
||||
.willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
.willReturn(Mono.just(true));
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.new RedisSession(
|
||||
new MapSession());
|
||||
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
|
||||
Mono<Void> result = this.repository.save(session);
|
||||
|
||||
StepVerifier.create(result).expectNextMatches(predicate -> {
|
||||
assertThat(this.delta.getAllValues().get(0))
|
||||
.isEqualTo(map(RedisOperationsSessionRepository.LAST_ACCESSED_ATTR,
|
||||
session.getLastAccessedTime().toEpochMilli()));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveSetAttribute() {
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), this.delta.capture()))
|
||||
.willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
.willReturn(Mono.just(true));
|
||||
|
||||
String attrName = "attrName";
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.new RedisSession(
|
||||
new MapSession());
|
||||
session.setAttribute(attrName, "attrValue");
|
||||
Mono<Void> result = this.repository.save(session);
|
||||
|
||||
StepVerifier.create(result).expectNextMatches(predicate -> {
|
||||
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
|
||||
map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName),
|
||||
session.getAttribute(attrName)));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRemoveAttribute() {
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), this.delta.capture()))
|
||||
.willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
.willReturn(Mono.just(true));
|
||||
|
||||
String attrName = "attrName";
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.new RedisSession(
|
||||
new MapSession());
|
||||
session.removeAttribute(attrName);
|
||||
Mono<Void> result = this.repository.save(session);
|
||||
|
||||
StepVerifier.create(result).expectNextMatches(predicate -> {
|
||||
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
|
||||
map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName),
|
||||
null));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void redisSessionGetAttributes() {
|
||||
String attrName = "attrName";
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.new RedisSession();
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
|
||||
session.setAttribute(attrName, "attrValue");
|
||||
assertThat(session.getAttributeNames()).containsOnly(attrName);
|
||||
|
||||
session.removeAttribute(attrName);
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
given(this.redisOperations.delete(anyString())).willReturn(Mono.just(1L));
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.new RedisSession(
|
||||
new MapSession());
|
||||
Mono<Void> result = this.repository.deleteById(session.getId());
|
||||
|
||||
StepVerifier.create(result).expectNextMatches(predicate -> {
|
||||
assertThat(result).isEqualTo(1);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionNotFound() {
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.entries(anyString())).willReturn(Flux.empty());
|
||||
|
||||
Mono<ReactiveRedisOperationsSessionRepository.RedisSession> session = this.repository
|
||||
.findById("test");
|
||||
|
||||
StepVerifier.create(session).expectNextMatches(predicate -> {
|
||||
assertThat(predicate).isEqualTo(Mono.empty());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getSessionFound() {
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
String attrName = "attrName";
|
||||
MapSession expected = new MapSession();
|
||||
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
|
||||
expected.setAttribute(attrName, "attrValue");
|
||||
Map map = map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName),
|
||||
expected.getAttribute(attrName),
|
||||
RedisOperationsSessionRepository.CREATION_TIME_ATTR,
|
||||
expected.getCreationTime().toEpochMilli(),
|
||||
RedisOperationsSessionRepository.MAX_INACTIVE_ATTR,
|
||||
(int) expected.getMaxInactiveInterval().getSeconds(),
|
||||
RedisOperationsSessionRepository.LAST_ACCESSED_ATTR,
|
||||
expected.getLastAccessedTime().toEpochMilli());
|
||||
given(this.hashOperations.entries(anyString()))
|
||||
.willReturn(Flux.fromIterable(map.entrySet()));
|
||||
|
||||
Mono<ReactiveRedisOperationsSessionRepository.RedisSession> session = this.repository
|
||||
.findById("test");
|
||||
|
||||
StepVerifier.create(session).expectNextMatches(predicate -> {
|
||||
assertThat(predicate.getId()).isEqualTo(expected.getId());
|
||||
assertThat(predicate.getAttributeNames())
|
||||
.isEqualTo(expected.getAttributeNames());
|
||||
assertThat(predicate.<String>getAttribute(attrName))
|
||||
.isEqualTo(expected.getAttribute(attrName));
|
||||
assertThat(predicate.getCreationTime()).isEqualTo(expected.getCreationTime());
|
||||
assertThat(predicate.getMaxInactiveInterval())
|
||||
.isEqualTo(expected.getMaxInactiveInterval());
|
||||
assertThat(predicate.getLastAccessedTime())
|
||||
.isEqualTo(expected.getLastAccessedTime());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getSessionExpired() {
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
Map map = map(RedisOperationsSessionRepository.MAX_INACTIVE_ATTR, 1,
|
||||
RedisOperationsSessionRepository.LAST_ACCESSED_ATTR,
|
||||
Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli());
|
||||
given(this.hashOperations.entries(anyString()))
|
||||
.willReturn(Flux.fromIterable(map.entrySet()));
|
||||
|
||||
Mono<ReactiveRedisOperationsSessionRepository.RedisSession> session = this.repository
|
||||
.findById("test");
|
||||
|
||||
StepVerifier.create(session).expectNextMatches(predicate -> {
|
||||
assertThat(predicate).isNull();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
private Map<String, Object> map(Object... objects) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (objects == null) {
|
||||
return result;
|
||||
}
|
||||
for (int i = 0; i < objects.length; i += 2) {
|
||||
result.put((String) objects[i], objects[i + 1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.reactor;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RedisReactorSessionConfiguration}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public class RedisReactorSessionConfigurationTests {
|
||||
|
||||
private static final String REDIS_NAMESPACE = "testNamespace";
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultConfiguration() {
|
||||
registerAndRefresh(RedisConfiguration.class, DefaultConfiguration.class);
|
||||
|
||||
ReactiveRedisOperationsSessionRepository repository = this.context
|
||||
.getBean(ReactiveRedisOperationsSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customNamespace() {
|
||||
registerAndRefresh(RedisConfiguration.class, CustomNamespaceConfiguration.class);
|
||||
|
||||
ReactiveRedisOperationsSessionRepository repository = this.context
|
||||
.getBean(ReactiveRedisOperationsSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "keyPrefix"))
|
||||
.isEqualTo("spring:session:" + REDIS_NAMESPACE + ":");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customMaxInactiveInterval() {
|
||||
registerAndRefresh(RedisConfiguration.class,
|
||||
CustomMaxInactiveIntervalConfiguration.class);
|
||||
|
||||
ReactiveRedisOperationsSessionRepository repository = this.context
|
||||
.getBean(ReactiveRedisOperationsSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval"))
|
||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customFlushMode() {
|
||||
registerAndRefresh(RedisConfiguration.class, CustomFlushModeConfiguration.class);
|
||||
|
||||
ReactiveRedisOperationsSessionRepository repository = this.context
|
||||
.getBean(ReactiveRedisOperationsSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "redisFlushMode"))
|
||||
.isEqualTo(RedisFlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
this.context.register(annotatedClasses);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class RedisConfiguration {
|
||||
|
||||
@Bean
|
||||
public ReactiveRedisConnectionFactory redisConnectionFactory() {
|
||||
return mock(ReactiveRedisConnectionFactory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisReactorSession
|
||||
static class DefaultConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisReactorSession(redisNamespace = REDIS_NAMESPACE)
|
||||
static class CustomNamespaceConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisReactorSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
static class CustomMaxInactiveIntervalConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisReactorSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
|
||||
static class CustomFlushModeConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,7 +46,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
public class MariaDB10JdbcOperationsSessionRepositoryITests
|
||||
extends AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "mariadb:10.2.7";
|
||||
private static final String DOCKER_IMAGE = "mariadb:10.2.9";
|
||||
|
||||
@ClassRule
|
||||
public static MariaDBContainer mariaDBContainer = new MariaDBContainer(DOCKER_IMAGE);
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
public class MariaDB5JdbcOperationsSessionRepositoryITests
|
||||
extends AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "mariadb:5.5.56";
|
||||
private static final String DOCKER_IMAGE = "mariadb:5.5.57";
|
||||
|
||||
@ClassRule
|
||||
public static MariaDBContainer mariaDBContainer = new MariaDBContainer(DOCKER_IMAGE);
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
public class MySQL5JdbcOperationsSessionRepositoryITests
|
||||
extends AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "mysql:5.7.18";
|
||||
private static final String DOCKER_IMAGE = "mysql:5.7.19";
|
||||
|
||||
@ClassRule
|
||||
public static MySQLContainer mySQLContainer = new MySQLContainer(DOCKER_IMAGE);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.postgresql.ds.PGSimpleDataSource;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 10.x
|
||||
* database.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@WebAppConfiguration
|
||||
@ContextConfiguration
|
||||
public class PostgreSQL10JdbcOperationsSessionRepositoryITests
|
||||
extends AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "postgres:10.0";
|
||||
|
||||
@ClassRule
|
||||
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer(
|
||||
DOCKER_IMAGE);
|
||||
|
||||
@Configuration
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
PGSimpleDataSource dataSource = new PGSimpleDataSource();
|
||||
dataSource.setUrl(postgreSQLContainer.getJdbcUrl());
|
||||
dataSource.setUser(postgreSQLContainer.getUsername());
|
||||
dataSource.setPassword(postgreSQLContainer.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSourceInitializer initializer(DataSource dataSource,
|
||||
ResourceLoader resourceLoader) {
|
||||
DataSourceInitializer initializer = new DataSourceInitializer();
|
||||
initializer.setDataSource(dataSource);
|
||||
initializer.setDatabasePopulator(
|
||||
new ResourceDatabasePopulator(resourceLoader.getResource(
|
||||
"classpath:org/springframework/session/jdbc/schema-postgresql.sql")));
|
||||
return initializer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL
|
||||
* Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 9.x
|
||||
* database.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
@@ -44,7 +44,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
public class PostgreSQL9JdbcOperationsSessionRepositoryITests
|
||||
extends AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "postgres:9.6.3";
|
||||
private static final String DOCKER_IMAGE = "postgres:9.6.5";
|
||||
|
||||
@ClassRule
|
||||
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer(
|
||||
|
||||
@@ -106,8 +106,9 @@ import org.springframework.util.StringUtils;
|
||||
* CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
* );
|
||||
*
|
||||
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
* CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
* CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
*
|
||||
* CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
* SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.jdbc.config.annotation.web.http;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -20,6 +21,7 @@ import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
@@ -33,14 +35,11 @@ import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.serializer.support.DeserializingConverter;
|
||||
import org.springframework.core.serializer.support.SerializingConverter;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
|
||||
@@ -78,17 +77,17 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
@Bean
|
||||
public JdbcTemplate springSessionJdbcOperations(DataSource dataSource) {
|
||||
return new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JdbcOperationsSessionRepository sessionRepository(
|
||||
@Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations,
|
||||
@SpringSessionDataSource ObjectProvider<DataSource> springSessionDataSource,
|
||||
ObjectProvider<DataSource> dataSource,
|
||||
PlatformTransactionManager transactionManager) {
|
||||
JdbcOperationsSessionRepository sessionRepository =
|
||||
new JdbcOperationsSessionRepository(jdbcOperations, transactionManager);
|
||||
DataSource dataSourceToUse = springSessionDataSource.getIfAvailable();
|
||||
if (dataSourceToUse == null) {
|
||||
dataSourceToUse = dataSource.getObject();
|
||||
}
|
||||
JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(
|
||||
dataSourceToUse, transactionManager);
|
||||
String tableName = getTableName();
|
||||
if (StringUtils.hasText(tableName)) {
|
||||
sessionRepository.setTableName(tableName);
|
||||
@@ -104,7 +103,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
else if (this.conversionService != null) {
|
||||
sessionRepository.setConversionService(this.conversionService);
|
||||
}
|
||||
else if (deserializingConverterSupportsCustomClassLoader()) {
|
||||
else {
|
||||
GenericConversionService conversionService = createConversionServiceWithBeanClassLoader();
|
||||
sessionRepository.setConversionService(conversionService);
|
||||
}
|
||||
@@ -115,7 +114,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
* This must be a separate method because some ClassLoaders load the entire method
|
||||
* definition even if an if statement guards against it loading. This means that older
|
||||
* versions of Spring would cause a NoSuchMethodError if this were defined in
|
||||
* {@link #sessionRepository(JdbcOperations, PlatformTransactionManager)}.
|
||||
* {@link #sessionRepository(ObjectProvider, ObjectProvider, PlatformTransactionManager)}.
|
||||
*
|
||||
* @return the default {@link ConversionService}
|
||||
*/
|
||||
@@ -163,10 +162,6 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
return this.tableName;
|
||||
}
|
||||
|
||||
private boolean deserializingConverterSupportsCustomClassLoader() {
|
||||
return ClassUtils.hasConstructor(DeserializingConverter.class, ClassLoader.class);
|
||||
}
|
||||
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> enableAttrMap = importMetadata
|
||||
.getAnnotationAttributes(EnableJdbcHttpSession.class.getName());
|
||||
@@ -192,4 +187,5 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.jdbc.config.annotation.web.http;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a {@link DataSource} to be injected in
|
||||
* {@link JdbcOperationsSessionRepository}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
|
||||
ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Qualifier
|
||||
public @interface SpringSessionDataSource {
|
||||
|
||||
}
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -9,8 +9,9 @@ CREATE TABLE SPRING_SESSION (
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
) LOCK DATAROWS;
|
||||
|
||||
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 the original author 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,12 +23,14 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
@@ -67,10 +69,10 @@ public class JdbcHttpSessionConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void noDataSourceConfiguration() {
|
||||
this.thrown.expect(UnsatisfiedDependencyException.class);
|
||||
this.thrown.expectMessage("springSessionJdbcOperations");
|
||||
this.thrown.expect(BeanCreationException.class);
|
||||
this.thrown.expectMessage("sessionRepository");
|
||||
|
||||
registerAndRefresh(EmptyConfiguration.class);
|
||||
registerAndRefresh(NoDataSourceConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -145,6 +147,78 @@ public class JdbcHttpSessionConfigurationTests {
|
||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void qualifiedDataSourceConfiguration() {
|
||||
registerAndRefresh(QualifiedDataSourceConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context
|
||||
.getBean(JdbcOperationsSessionRepository.class);
|
||||
DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(dataSource).isNotNull();
|
||||
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
|
||||
.getField(repository, "jdbcOperations");
|
||||
assertThat(jdbcOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
|
||||
.isEqualTo(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void primaryDataSourceConfiguration() {
|
||||
registerAndRefresh(PrimaryDataSourceConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context
|
||||
.getBean(JdbcOperationsSessionRepository.class);
|
||||
DataSource dataSource = this.context.getBean("primaryDataSource", DataSource.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(dataSource).isNotNull();
|
||||
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
|
||||
.getField(repository, "jdbcOperations");
|
||||
assertThat(jdbcOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
|
||||
.isEqualTo(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void qualifiedAndPrimaryDataSourceConfiguration() {
|
||||
registerAndRefresh(QualifiedAndPrimaryDataSourceConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context
|
||||
.getBean(JdbcOperationsSessionRepository.class);
|
||||
DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(dataSource).isNotNull();
|
||||
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
|
||||
.getField(repository, "jdbcOperations");
|
||||
assertThat(jdbcOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
|
||||
.isEqualTo(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void namedDataSourceConfiguration() {
|
||||
registerAndRefresh(NamedDataSourceConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context
|
||||
.getBean(JdbcOperationsSessionRepository.class);
|
||||
DataSource dataSource = this.context.getBean("dataSource", DataSource.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(dataSource).isNotNull();
|
||||
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
|
||||
.getField(repository, "jdbcOperations");
|
||||
assertThat(jdbcOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
|
||||
.isEqualTo(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleDataSourceConfiguration() {
|
||||
this.thrown.expect(BeanCreationException.class);
|
||||
this.thrown.expectMessage("sessionRepository");
|
||||
|
||||
registerAndRefresh(MultipleDataSourceConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customLobHandlerConfiguration() {
|
||||
registerAndRefresh(CustomLobHandlerConfiguration.class);
|
||||
@@ -188,13 +262,13 @@ public class JdbcHttpSessionConfigurationTests {
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class EmptyConfiguration {
|
||||
static class NoDataSourceConfiguration {
|
||||
}
|
||||
|
||||
static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
public DataSource defaultDataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
@@ -239,6 +313,70 @@ public class JdbcHttpSessionConfigurationTests {
|
||||
extends BaseConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class QualifiedDataSourceConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@SpringSessionDataSource
|
||||
public DataSource qualifiedDataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class PrimaryDataSourceConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSource primaryDataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class QualifiedAndPrimaryDataSourceConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@SpringSessionDataSource
|
||||
public DataSource qualifiedDataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSource primaryDataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class NamedDataSourceConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class MultipleDataSourceConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource secondaryDataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class CustomLobHandlerConfiguration extends BaseConfiguration {
|
||||
|
||||
Reference in New Issue
Block a user