Compare commits

..

19 Commits

Author SHA1 Message Date
Rob Winch
33fbaa03a8 Release 2.0.0.M5 2017-10-09 16:54:31 -05:00
Rob Winch
88b26f2cfe Update to Spring Security 5.0.0.M5
Fixes gh-891
2017-10-09 16:53:41 -05:00
Vedran Pavic
3f670050ef Update integration tests
This commit updates versions of RDBMS and Redis Docker images used in
integration tests.

Closes gh-894
2017-10-09 08:09:52 +02:00
Vedran Pavic
e3b61d25bb Improve JDBC configuration
This commit improves JDBC configuration by introducing `@SpringSessionDataSource` qualifier for explicitly declaring a `DataSource` to be used by Spring Session. This is in particular useful in scenarios with multiple `DataSource` beans present in the application context.

As a consequence, JDBC configuration is simplified and no longer registers a Spring Session specific `JdbcTemplate` bean.

Closes gh-863
2017-10-06 19:12:55 +02:00
Vedran Pavic
19b8effa41 Add Redis implementation of ReactorSessionRepository
Closes gh-816
2017-10-06 18:45:42 +02:00
Vedran Pavic
9f5f7540d2 Fix Users sample app navbar
Closes gh-885
2017-10-02 22:31:55 +02:00
Vedran Pavic
eb8c22939c Upgrade Gradle to 4.2.1 2017-10-02 21:20:14 +02:00
Vedran Pavic
45cfa1e9a4 Upgrade spring-build-conventions to 0.0.4.RELEASE 2017-10-02 20:53:06 +02:00
Vedran Pavic
99221e0948 Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-10-02 19:01:47 +02:00
Vedran Pavic
41cf2ef152 Update documentation to reflect preference for Lettuce
See gh-886
2017-10-02 19:00:23 +02:00
Vedran Pavic
c51bce4777 Use Lettuce driver for integration tests
Closes gh-886
2017-09-28 16:17:55 +02:00
Vedran Pavic
b6f1184c4c Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-09-28 15:51:32 +02:00
Vedran Pavic
c69a8b8762 Improve JDBC data store schema scripts
Closes gh-884
2017-09-27 12:18:25 +02:00
Vedran Pavic
99fb17a66b Adapt to Spring WebSession API changes 2017-09-27 11:52:04 +02:00
Vedran Pavic
937b2fcbf1 Upgrade Gradle to 4.2 2017-09-25 09:06:06 +02:00
Vedran Pavic
9c5a7e9156 Upgrade Spring Boot to 2.0.0.M4
Closes gh-877
2017-09-15 22:13:23 +02:00
Vedran Pavic
4deccd3ad0 Upgrade Gradle to 4.1 2017-09-15 21:24:48 +02:00
Vedran Pavic
da058e9510 Upgrade dependencies to latest snapshots
- Reactor Bismuth-BUILD-SNAPSHOT
- Spring Framework 5.0.0.BUILD-SNAPSHOT
- Spring Data Kay-BUILD-SNAPSHOT
- Spring Security 5.0.0.BUILD-SNAPSHOT
2017-09-14 07:18:24 +02:00
Vedran Pavic
d28ca4658b Next development version 2017-09-14 07:16:49 +02:00
78 changed files with 1878 additions and 140 deletions

View File

@@ -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 {

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -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.
====

View File

@@ -1,2 +1,2 @@
springBootVersion=2.0.0.M3
version=2.0.0.M4
springBootVersion=2.0.0.M4
version=2.0.0.M5

View File

@@ -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'
}
}

Binary file not shown.

View File

@@ -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
View File

@@ -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 " "
}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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
}

View File

@@ -1,2 +0,0 @@
spring.session.store-type=redis
security.user.password=password

View File

@@ -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
}

View File

@@ -1,3 +1 @@
spring.session.store-type=jdbc
security.user.password=password
spring.h2.console.enabled=true

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -1,2 +0,0 @@
spring.session.store-type=redis
security.user.password=password

View File

@@ -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"

View File

@@ -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)

View File

@@ -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
}

View File

@@ -1,2 +0,0 @@
spring.session.store-type=redis
security.user.password=password

View File

@@ -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"

View File

@@ -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
}

View File

@@ -1,3 +1,2 @@
spring.session.store-type=redis
#server.session.timeout=60
spring.h2.console.enabled=true

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -0,0 +1,5 @@
dependencyManagement {
dependencies {
dependency 'org.webjars:bootstrap:3.3.6'
}
}

View File

@@ -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() {

View File

@@ -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'

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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();

View File

@@ -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[]

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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"
}

View File

@@ -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);
}
}

View File

@@ -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 {
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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();
* }
*
* }

View File

@@ -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">
* &#064;Configuration
* &#064;EnableRedisReactorSession
* public class RedisReactorSessionConfig {
*
* &#064;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;
}

View File

@@ -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", "");
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -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();
}
}

View File

@@ -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 {
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 {