Extract spring-session-jdbc

Issue gh-806
This commit is contained in:
Rob Winch
2017-06-16 15:52:01 -05:00
parent c28f047eb5
commit 043cb42149
35 changed files with 15 additions and 9 deletions

View File

@@ -1,8 +1,17 @@
apply plugin: 'io.spring.convention.spring-pom'
description = "Aggregator for Spring Session and Spring JDBC"
description = "Spring Session and Spring JDBC integration"
dependencies {
compile project(':spring-session-core')
compile "org.springframework:spring-context"
compile "org.springframework:spring-jdbc"
testCompile "javax.servlet:javax.servlet-api"
testCompile "org.springframework.security:spring-security-core"
testCompile "org.springframework.security:spring-security-web"
integrationTestCompile "org.apache.derby:derby"
integrationTestCompile "com.h2database:h2"
integrationTestCompile "org.hsqldb:hsqldb"
}

View File

@@ -0,0 +1,581 @@
/*
* 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 java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Abstract base class for {@link JdbcOperationsSessionRepository} integration tests.
*
* @author Vedran Pavic
* @since 1.2.0
*/
@WebAppConfiguration
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public abstract class AbstractJdbcOperationsSessionRepositoryITests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
@Autowired
private JdbcOperationsSessionRepository repository;
private SecurityContext context;
private SecurityContext changedContext;
@Before
public void setup() throws Exception {
this.context = SecurityContextHolder.createEmptyContext();
this.context.setAuthentication(
new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(),
"na", AuthorityUtils.createAuthorityList("ROLE_USER")));
this.changedContext = SecurityContextHolder.createEmptyContext();
this.changedContext.setAuthentication(new UsernamePasswordAuthenticationToken(
"changedContext-" + UUID.randomUUID(), "na",
AuthorityUtils.createAuthorityList("ROLE_USER")));
}
@Test
public void saves() throws InterruptedException {
String username = "saves-" + System.currentTimeMillis();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
String expectedAttributeName = "a";
String expectedAttributeValue = "b";
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username,
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
toSaveContext.setAuthentication(toSaveToken);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, toSaveContext);
toSave.setAttribute(INDEX_NAME, username);
this.repository.save(toSave);
Session session = this.repository.getSession(toSave.getId());
assertThat(session.getId()).isEqualTo(toSave.getId());
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
assertThat(session.<String>getAttribute(expectedAttributeName))
.isEqualTo(toSave.getAttribute(expectedAttributeName));
this.repository.delete(toSave.getId());
assertThat(this.repository.getSession(toSave.getId())).isNull();
}
@Test
@Transactional(readOnly = true)
public void savesInReadOnlyTransaction() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
this.repository.save(toSave);
}
@Test
public void putAllOnSingleAttrDoesNotRemoveOld() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute("a", "b");
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
toSave.setAttribute("1", "2");
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
Session session = this.repository.getSession(toSave.getId());
assertThat(session.getAttributeNames().size()).isEqualTo(2);
assertThat(session.<String>getAttribute("a")).isEqualTo(Optional.of("b"));
assertThat(session.<String>getAttribute("1")).isEqualTo(Optional.of("2"));
this.repository.delete(toSave.getId());
}
@Test
public void updateLastAccessedTime() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setLastAccessedTime(Instant.now().minusSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
Instant lastAccessedTime = Instant.now();
toSave.setLastAccessedTime(lastAccessedTime);
this.repository.save(toSave);
Session session = this.repository.getSession(toSave.getId());
assertThat(session).isNotNull();
assertThat(session.isExpired()).isFalse();
assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessedTime);
}
@Test
public void findByPrincipalName() throws Exception {
String principalName = "findByPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
this.repository.delete(toSave.getId());
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameExpireRemovesIndex() throws Exception {
String principalName = "findByPrincipalNameExpireRemovesIndex"
+ UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
toSave.setLastAccessedTime(Instant.now().minusSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS+ 1));
this.repository.save(toSave);
this.repository.cleanUpExpiredSessions();
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameNoPrincipalNameChange() throws Exception {
String principalName = "findByPrincipalNameNoPrincipalNameChange"
+ UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByPrincipalNameNoPrincipalNameChangeReload() throws Exception {
String principalName = "findByPrincipalNameNoPrincipalNameChangeReload"
+ UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedPrincipalName() throws Exception {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute(INDEX_NAME, null);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedPrincipalName() throws Exception {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalNameChanged);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedPrincipalNameReload() throws Exception {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository
.getSession(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedPrincipalNameReload() throws Exception {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository
.getSession(toSave.getId());
getSession.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalNameChanged);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findBySecurityPrincipalName() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
this.repository.delete(toSave.getId());
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findBySecurityPrincipalNameExpireRemovesIndex() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
toSave.setLastAccessedTime(Instant.now().minusSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
this.repository.cleanUpExpiredSessions();
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChange() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChangeReload()
throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedSecurityPrincipalName() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, null);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedSecurityPrincipalName() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getChangedSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedSecurityPrincipalNameReload() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository
.getSession(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedSecurityPrincipalNameReload() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository
.getSession(toSave.getId());
getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getChangedSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void cleanupInactiveSessionsUsingRepositoryDefinedInterval() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
assertThat(this.repository.getSession(session.getId())).isNotNull();
this.repository.cleanUpExpiredSessions();
assertThat(this.repository.getSession(session.getId())).isNotNull();
Instant now = Instant.now();
session.setLastAccessedTime(now.minus(10, ChronoUnit.MINUTES));
this.repository.save(session);
this.repository.cleanUpExpiredSessions();
assertThat(this.repository.getSession(session.getId())).isNotNull();
session.setLastAccessedTime(now.minus(30, ChronoUnit.MINUTES));
this.repository.save(session);
this.repository.cleanUpExpiredSessions();
assertThat(this.repository.getSession(session.getId())).isNull();
}
// gh-580
@Test
public void cleanupInactiveSessionsUsingSessionDefinedInterval() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setMaxInactiveInterval(Duration.ofMinutes(45));
this.repository.save(session);
assertThat(this.repository.getSession(session.getId())).isNotNull();
this.repository.cleanUpExpiredSessions();
assertThat(this.repository.getSession(session.getId())).isNotNull();
Instant now = Instant.now();
session.setLastAccessedTime(now.minus(40, ChronoUnit.MINUTES));
this.repository.save(session);
this.repository.cleanUpExpiredSessions();
assertThat(this.repository.getSession(session.getId())).isNotNull();
session.setLastAccessedTime(now.minus(50, ChronoUnit.MINUTES));
this.repository.save(session);
this.repository.cleanUpExpiredSessions();
assertThat(this.repository.getSession(session.getId())).isNull();
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}
private String getChangedSecurityName() {
return this.changedContext.getAuthentication().getName();
}
@EnableJdbcHttpSession
protected static class BaseConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2014-2016 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 org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
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 Derby database.
*
* @author Vedran Pavic
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class DerbyJdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.DERBY)
.addScript("org/springframework/session/jdbc/schema-derby.sql")
.build();
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2014-2016 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 org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
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 H2 database.
*
* @author Vedran Pavic
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class H2JdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("org/springframework/session/jdbc/schema-h2.sql")
.build();
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2014-2016 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 org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
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 HSQLDB database.
*
* @author Vedran Pavic
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class HsqldbJdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("org/springframework/session/jdbc/schema-hsqldb.sql")
.build();
}
}
}

View File

@@ -0,0 +1,748 @@
/*
* 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 java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DataAccessException;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.session.SessionRepository} implementation that uses
* Spring's {@link JdbcOperations} to store sessions in a relational database. This
* implementation does not support publishing of session events.
* <p>
* An example of how to create a new instance can be seen below:
*
* <pre class="code">
* JdbcTemplate jdbcTemplate = new JdbcTemplate();
*
* // ... configure jdbcTemplate ...
*
* PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
*
* // ... configure transactionManager ...
*
* JdbcOperationsSessionRepository sessionRepository =
* new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
* </pre>
*
* For additional information on how to create and configure {@link JdbcTemplate} and
* {@link PlatformTransactionManager}, refer to the
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html">
* Spring Framework Reference Documentation</a>.
* <p>
* By default, this implementation uses <code>SPRING_SESSION</code> and
* <code>SPRING_SESSION_ATTRIBUTES</code> tables to store sessions. Note that the table
* name can be customized using the {@link #setTableName(String)} method. In that case the
* table used to store attributes will be named using the provided table name, suffixed
* with <code>_ATTRIBUTES</code>.
*
* Depending on your database, the table definition can be described as below:
*
* <pre class="code">
* CREATE TABLE SPRING_SESSION (
* SESSION_ID CHAR(36),
* CREATION_TIME BIGINT NOT NULL,
* LAST_ACCESS_TIME BIGINT NOT NULL,
* MAX_INACTIVE_INTERVAL INT NOT NULL,
* PRINCIPAL_NAME VARCHAR(100),
* CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
* );
*
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
*
* CREATE TABLE SPRING_SESSION_ATTRIBUTES (
* SESSION_ID CHAR(36),
* ATTRIBUTE_NAME VARCHAR(200),
* ATTRIBUTE_BYTES BYTEA,
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
* );
*
* CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);
* </pre>
*
* Due to the differences between the various database vendors, especially when it comes
* to storing binary data, make sure to use SQL script specific to your database. Scripts
* for most major database vendors are packaged as
* <code>org/springframework/session/jdbc/schema-*.sql</code>, where <code>*</code> is the
* target database type.
*
* @author Vedran Pavic
* @since 1.2.0
*/
public class JdbcOperationsSessionRepository implements
FindByIndexNameSessionRepository<JdbcOperationsSessionRepository.JdbcSession> {
/**
* The default name of database table used by Spring Session to store sessions.
*/
public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION";
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private static final String CREATE_SESSION_QUERY =
"INSERT INTO %TABLE_NAME%(SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, PRINCIPAL_NAME) " +
"VALUES (?, ?, ?, ?, ?)";
private static final String CREATE_SESSION_ATTRIBUTE_QUERY =
"INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " +
"VALUES (?, ?, ?)";
private static final String GET_SESSION_QUERY =
"SELECT S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
"FROM %TABLE_NAME% S " +
"LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.SESSION_ID = SA.SESSION_ID " +
"WHERE S.SESSION_ID = ?";
private static final String UPDATE_SESSION_QUERY =
"UPDATE %TABLE_NAME% SET LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, PRINCIPAL_NAME = ? " +
"WHERE SESSION_ID = ?";
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY =
"UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? " +
"WHERE SESSION_ID = ? " +
"AND ATTRIBUTE_NAME = ?";
private static final String DELETE_SESSION_ATTRIBUTE_QUERY =
"DELETE FROM %TABLE_NAME%_ATTRIBUTES " +
"WHERE SESSION_ID = ? " +
"AND ATTRIBUTE_NAME = ?";
private static final String DELETE_SESSION_QUERY =
"DELETE FROM %TABLE_NAME% " +
"WHERE SESSION_ID = ?";
private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY =
"SELECT S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
"FROM %TABLE_NAME% S " +
"LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.SESSION_ID = SA.SESSION_ID " +
"WHERE S.PRINCIPAL_NAME = ?";
private static final String DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY =
"DELETE FROM %TABLE_NAME% " +
"WHERE MAX_INACTIVE_INTERVAL < (? - LAST_ACCESS_TIME) / 1000";
private static final Log logger = LogFactory
.getLog(JdbcOperationsSessionRepository.class);
private static final PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<Session>> extractor = new SessionResultSetExtractor();
/**
* The name of database table used by Spring Session to store sessions.
*/
private String tableName = DEFAULT_TABLE_NAME;
private String createSessionQuery;
private String createSessionAttributeQuery;
private String getSessionQuery;
private String updateSessionQuery;
private String updateSessionAttributeQuery;
private String deleteSessionAttributeQuery;
private String deleteSessionQuery;
private String listSessionsByPrincipalNameQuery;
private String deleteSessionsByLastAccessTimeQuery;
/**
* If non-null, this value is used to override the default value for
* {@link JdbcSession#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private ConversionService conversionService;
private LobHandler lobHandler = new DefaultLobHandler();
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* default {@link JdbcOperations} to manage sessions.
* @param dataSource the {@link DataSource} to use
* @param transactionManager the {@link PlatformTransactionManager} to use
*/
public JdbcOperationsSessionRepository(DataSource dataSource,
PlatformTransactionManager transactionManager) {
this(createDefaultJdbcTemplate(dataSource), transactionManager);
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionManager the {@link PlatformTransactionManager} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.transactionOperations = createTransactionTemplate(transactionManager);
this.conversionService = createDefaultConversionService();
prepareQueries();
}
/**
* Set the name of database table used to store sessions.
* @param tableName the database table name
*/
public void setTableName(String tableName) {
Assert.hasText(tableName, "Table name must not be empty");
this.tableName = tableName.trim();
prepareQueries();
}
/**
* Set the custom SQL query used to create the session.
* @param createSessionQuery the SQL query string
*/
public void setCreateSessionQuery(String createSessionQuery) {
Assert.hasText(createSessionQuery, "Query must not be empty");
this.createSessionQuery = createSessionQuery;
}
/**
* Set the custom SQL query used to create the session attribute.
* @param createSessionAttributeQuery the SQL query string
*/
public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) {
Assert.hasText(createSessionAttributeQuery, "Query must not be empty");
this.createSessionAttributeQuery = createSessionAttributeQuery;
}
/**
* Set the custom SQL query used to retrieve the session.
* @param getSessionQuery the SQL query string
*/
public void setGetSessionQuery(String getSessionQuery) {
Assert.hasText(getSessionQuery, "Query must not be empty");
this.getSessionQuery = getSessionQuery;
}
/**
* Set the custom SQL query used to update the session.
* @param updateSessionQuery the SQL query string
*/
public void setUpdateSessionQuery(String updateSessionQuery) {
Assert.hasText(updateSessionQuery, "Query must not be empty");
this.updateSessionQuery = updateSessionQuery;
}
/**
* Set the custom SQL query used to update the session attribute.
* @param updateSessionAttributeQuery the SQL query string
*/
public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) {
Assert.hasText(updateSessionAttributeQuery, "Query must not be empty");
this.updateSessionAttributeQuery = updateSessionAttributeQuery;
}
/**
* Set the custom SQL query used to delete the session attribute.
* @param deleteSessionAttributeQuery the SQL query string
*/
public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) {
Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty");
this.deleteSessionAttributeQuery = deleteSessionAttributeQuery;
}
/**
* Set the custom SQL query used to delete the session.
* @param deleteSessionQuery the SQL query string
*/
public void setDeleteSessionQuery(String deleteSessionQuery) {
Assert.hasText(deleteSessionQuery, "Query must not be empty");
this.deleteSessionQuery = deleteSessionQuery;
}
/**
* Set the custom SQL query used to retrieve the sessions by principal name.
* @param listSessionsByPrincipalNameQuery the SQL query string
*/
public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) {
Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty");
this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery;
}
/**
* Set the custom SQL query used to delete the sessions by last access time.
* @param deleteSessionsByLastAccessTimeQuery the SQL query string
*/
public void setDeleteSessionsByLastAccessTimeQuery(String deleteSessionsByLastAccessTimeQuery) {
Assert.hasText(deleteSessionsByLastAccessTimeQuery, "Query must not be empty");
this.deleteSessionsByLastAccessTimeQuery = deleteSessionsByLastAccessTimeQuery;
}
/**
* Set 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 maximum inactive interval in seconds
*/
public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
public void setLobHandler(LobHandler lobHandler) {
Assert.notNull(lobHandler, "LobHandler must not be null");
this.lobHandler = lobHandler;
}
/**
* Sets the {@link ConversionService} to use.
* @param conversionService the converter to set
*/
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService, "conversionService must not be null");
this.conversionService = conversionService;
}
public JdbcSession createSession() {
JdbcSession session = new JdbcSession();
if (this.defaultMaxInactiveInterval != null) {
session.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return session;
}
public void save(final JdbcSession session) {
if (session.isNew()) {
this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.createSessionQuery,
ps -> {
ps.setString(1, session.getId());
ps.setLong(2, session.getCreationTime().toEpochMilli());
ps.setLong(3, session.getLastAccessedTime().toEpochMilli());
ps.setInt(4, (int) session.getMaxInactiveInterval().getSeconds());
ps.setString(5, session.getPrincipalName());
});
if (!session.getAttributeNames().isEmpty()) {
final List<String> attributeNames = new ArrayList<>(session.getAttributeNames());
JdbcOperationsSessionRepository.this.jdbcOperations.batchUpdate(
JdbcOperationsSessionRepository.this.createSessionAttributeQuery,
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.getId());
ps.setString(2, attributeName);
serialize(ps, 3, session.getAttribute(attributeName).orElse(null));
}
public int getBatchSize() {
return attributeNames.size();
}
});
}
}
});
}
else {
this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
if (session.isChanged()) {
JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.updateSessionQuery,
ps -> {
ps.setLong(1, session.getLastAccessedTime().toEpochMilli());
ps.setInt(2, (int) session.getMaxInactiveInterval().getSeconds());
ps.setString(3, session.getPrincipalName());
ps.setString(4, session.getId());
});
}
Map<String, Object> delta = session.getDelta();
if (!delta.isEmpty()) {
for (final Map.Entry<String, Object> entry : delta.entrySet()) {
if (entry.getValue() == null) {
JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.deleteSessionAttributeQuery,
ps -> {
ps.setString(1, session.getId());
ps.setString(2, entry.getKey());
});
}
else {
int updatedCount = JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.updateSessionAttributeQuery,
ps -> {
serialize(ps, 1, entry.getValue());
ps.setString(2, session.getId());
ps.setString(3, entry.getKey());
});
if (updatedCount == 0) {
JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.createSessionAttributeQuery,
ps -> {
ps.setString(1, session.getId());
ps.setString(2, entry.getKey());
serialize(ps, 3, entry.getValue());
});
}
}
}
}
}
});
}
session.clearChangeFlags();
}
public JdbcSession getSession(final String id) {
final Session session = this.transactionOperations.execute(status -> {
List<Session> sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query(
JdbcOperationsSessionRepository.this.getSessionQuery,
ps -> ps.setString(1, id),
JdbcOperationsSessionRepository.this.extractor
);
if (sessions.isEmpty()) {
return null;
}
return sessions.get(0);
});
if (session != null) {
if (session.isExpired()) {
delete(id);
}
else {
return new JdbcSession(session);
}
}
return null;
}
public void delete(final String id) {
this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.deleteSessionQuery, id);
}
});
}
public Map<String, JdbcSession> findByIndexNameAndIndexValue(String indexName,
final String indexValue) {
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
return Collections.emptyMap();
}
List<Session> sessions = this.transactionOperations.execute(status ->
JdbcOperationsSessionRepository.this.jdbcOperations.query(
JdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery,
ps -> ps.setString(1, indexValue),
JdbcOperationsSessionRepository.this.extractor));
Map<String, JdbcSession> sessionMap = new HashMap<>(
sessions.size());
for (Session session : sessions) {
sessionMap.put(session.getId(), new JdbcSession(session));
}
return sessionMap;
}
@Scheduled(cron = "${spring.session.cleanup.cron.expression:0 * * * * *}")
public void cleanUpExpiredSessions() {
int deletedCount = this.transactionOperations.execute(transactionStatus ->
JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.deleteSessionsByLastAccessTimeQuery,
System.currentTimeMillis()));
if (logger.isDebugEnabled()) {
logger.debug("Cleaned up " + deletedCount + " expired sessions");
}
}
private static JdbcTemplate createDefaultJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.afterPropertiesSet();
return jdbcTemplate;
}
private static TransactionTemplate createTransactionTemplate(
PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(
transactionManager);
transactionTemplate.setPropagationBehavior(
TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.afterPropertiesSet();
return transactionTemplate;
}
private static GenericConversionService createDefaultConversionService() {
GenericConversionService converter = new GenericConversionService();
converter.addConverter(Object.class, byte[].class,
new SerializingConverter());
converter.addConverter(byte[].class, Object.class,
new DeserializingConverter());
return converter;
}
private String getQuery(String base) {
return StringUtils.replace(base, "%TABLE_NAME%", this.tableName);
}
private void prepareQueries() {
this.createSessionQuery = getQuery(CREATE_SESSION_QUERY);
this.createSessionAttributeQuery = getQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
this.getSessionQuery = getQuery(GET_SESSION_QUERY);
this.updateSessionQuery = getQuery(UPDATE_SESSION_QUERY);
this.updateSessionAttributeQuery = getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY);
this.deleteSessionAttributeQuery = getQuery(DELETE_SESSION_ATTRIBUTE_QUERY);
this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY);
this.listSessionsByPrincipalNameQuery =
getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY);
this.deleteSessionsByLastAccessTimeQuery =
getQuery(DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY);
}
private void serialize(PreparedStatement ps, int paramIndex, Object attributeValue)
throws SQLException {
this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex,
(byte[]) this.conversionService.convert(attributeValue,
TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class)));
}
private Object deserialize(ResultSet rs, String columnName)
throws SQLException {
return this.conversionService.convert(
this.lobHandler.getBlobAsBytes(rs, columnName),
TypeDescriptor.valueOf(byte[].class),
TypeDescriptor.valueOf(Object.class));
}
/**
* The {@link Session} to use for {@link JdbcOperationsSessionRepository}.
*
* @author Vedran Pavic
*/
final class JdbcSession implements Session {
private final Session delegate;
private boolean isNew;
private boolean changed;
private Map<String, Object> delta = new HashMap<>();
JdbcSession() {
this.delegate = new MapSession();
this.isNew = true;
}
JdbcSession(Session delegate) {
Assert.notNull(delegate, "Session cannot be null");
this.delegate = delegate;
}
boolean isNew() {
return this.isNew;
}
boolean isChanged() {
return this.changed;
}
Map<String, Object> getDelta() {
return this.delta;
}
void clearChangeFlags() {
this.isNew = false;
this.changed = false;
this.delta.clear();
}
String getPrincipalName() {
return PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
}
public String getId() {
return this.delegate.getId();
}
public <T> Optional<T> getAttribute(String attributeName) {
return this.delegate.getAttribute(attributeName);
}
public Set<String> getAttributeNames() {
return this.delegate.getAttributeNames();
}
public void setAttribute(String attributeName, Object attributeValue) {
this.delegate.setAttribute(attributeName, attributeValue);
this.delta.put(attributeName, attributeValue);
if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) ||
SPRING_SECURITY_CONTEXT.equals(attributeName)) {
this.changed = true;
}
}
public void removeAttribute(String attributeName) {
this.delegate.removeAttribute(attributeName);
this.delta.put(attributeName, null);
}
public Instant getCreationTime() {
return this.delegate.getCreationTime();
}
public void setLastAccessedTime(Instant lastAccessedTime) {
this.delegate.setLastAccessedTime(lastAccessedTime);
this.changed = true;
}
public Instant getLastAccessedTime() {
return this.delegate.getLastAccessedTime();
}
public void setMaxInactiveInterval(Duration interval) {
this.delegate.setMaxInactiveInterval(interval);
this.changed = true;
}
public Duration getMaxInactiveInterval() {
return this.delegate.getMaxInactiveInterval();
}
public boolean isExpired() {
return this.delegate.isExpired();
}
}
/**
* Resolves the Spring Security principal name.
*
* @author Vedran Pavic
*/
static class PrincipalNameResolver {
private SpelExpressionParser parser = new SpelExpressionParser();
public String resolvePrincipal(Session session) {
Optional<String> principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);
if (principalName.isPresent()) {
return principalName.get();
}
Optional<Object> authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
if (authentication.isPresent()) {
Expression expression = this.parser
.parseExpression("authentication?.name");
return expression.getValue(authentication.get(), String.class);
}
return null;
}
}
private class SessionResultSetExtractor implements ResultSetExtractor<List<Session>> {
public List<Session> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<Session> sessions = new ArrayList<>();
while (rs.next()) {
String id = rs.getString("SESSION_ID");
MapSession session;
if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) {
session = (MapSession) getLast(sessions);
}
else {
session = new MapSession(id);
session.setCreationTime(Instant.ofEpochMilli(rs.getLong("CREATION_TIME")));
session.setLastAccessedTime(Instant.ofEpochMilli(rs.getLong("LAST_ACCESS_TIME")));
session.setMaxInactiveInterval(Duration.ofSeconds(rs.getInt("MAX_INACTIVE_INTERVAL")));
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
session.setAttribute(attributeName, deserialize(rs, "ATTRIBUTE_BYTES"));
}
sessions.add(session);
}
return sessions;
}
private Session getLast(List<Session> sessions) {
return sessions.get(sessions.size() - 1);
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2014-2016 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 org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.session.MapSession;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
/**
* Add this annotation to an {@code @Configuration} class to expose the
* SessionRepositoryFilter as a bean named "springSessionRepositoryFilter" and backed by a
* relational database. In order to leverage the annotation, a single
* {@link javax.sql.DataSource} must be provided. For example:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJdbcHttpSession
* public class JdbcHttpSessionConfig {
*
* &#064;Bean
* public DataSource dataSource() {
* return new EmbeddedDatabaseBuilder()
* .setType(EmbeddedDatabaseType.H2)
* .addScript("org/springframework/session/jdbc/schema-h2.sql")
* .build();
* }
*
* &#064;Bean
* public PlatformTransactionManager transactionManager(DataSource dataSource) {
* return new DataSourceTransactionManager(dataSource);
* }
*
* }
* </pre>
*
* More advanced configurations can extend {@link JdbcHttpSessionConfiguration} instead.
*
* For additional information on how to configure data access related concerns, please
* refer to the
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html">
* Spring Framework Reference Documentation</a>.
*
* @author Vedran Pavic
* @since 1.2.0
* @see EnableSpringHttpSession
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(JdbcHttpSessionConfiguration.class)
@Configuration
public @interface EnableJdbcHttpSession {
/**
* The name of database table used by Spring Session to store sessions.
* @return the database table name
*/
String tableName() default JdbcOperationsSessionRepository.DEFAULT_TABLE_NAME;
/**
* The session timeout in seconds. By default, it is set to 1800 seconds (30 minutes).
* This should be a non-negative integer.
*
* @return the seconds a session can be inactive before expiring
*/
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2014-2016 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.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.convert.ConversionService;
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;
/**
* Spring @Configuration class used to configure and initialize a JDBC based HttpSession
* provider implementation in Spring Session.
* <p>
* Exposes the {@link org.springframework.session.web.http.SessionRepositoryFilter} as a
* bean named "springSessionRepositoryFilter". In order to use this a single
* {@link DataSource} must be exposed as a Bean.
*
* @author Vedran Pavic
* @author Eddú Meléndez
* @since 1.2.0
* @see EnableJdbcHttpSession
*/
@Configuration
@EnableScheduling
public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, ImportAware, EmbeddedValueResolverAware {
private String tableName;
private Integer maxInactiveIntervalInSeconds;
private LobHandler lobHandler;
@Autowired(required = false)
@Qualifier("conversionService")
private ConversionService conversionService;
private ConversionService springSessionConversionService;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@Bean
public JdbcTemplate springSessionJdbcOperations(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public JdbcOperationsSessionRepository sessionRepository(
@Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
JdbcOperationsSessionRepository sessionRepository =
new JdbcOperationsSessionRepository(jdbcOperations, transactionManager);
String tableName = getTableName();
if (StringUtils.hasText(tableName)) {
sessionRepository.setTableName(tableName);
}
sessionRepository
.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (this.lobHandler != null) {
sessionRepository.setLobHandler(this.lobHandler);
}
if (this.springSessionConversionService != null) {
sessionRepository.setConversionService(this.springSessionConversionService);
}
else if (this.conversionService != null) {
sessionRepository.setConversionService(this.conversionService);
}
else if (deserializingConverterSupportsCustomClassLoader()) {
GenericConversionService conversionService = createConversionServiceWithBeanClassLoader();
sessionRepository.setConversionService(conversionService);
}
return sessionRepository;
}
/**
* 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)}.
*
* @return the default {@link ConversionService}
*/
private GenericConversionService createConversionServiceWithBeanClassLoader() {
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(Object.class, byte[].class,
new SerializingConverter());
conversionService.addConverter(byte[].class, Object.class,
new DeserializingConverter(this.classLoader));
return conversionService;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
*/
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Autowired(required = false)
@Qualifier("springSessionLobHandler")
public void setLobHandler(LobHandler lobHandler) {
this.lobHandler = lobHandler;
}
@Autowired(required = false)
@Qualifier("springSessionConversionService")
public void setSpringSessionConversionService(ConversionService conversionService) {
this.springSessionConversionService = conversionService;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
private String getTableName() {
String systemProperty = System.getProperty("spring.session.jdbc.tableName", "");
if (StringUtils.hasText(systemProperty)) {
return systemProperty;
}
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());
AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
String tableNameValue = enableAttrs.getString("tableName");
if (StringUtils.hasText(tableNameValue)) {
this.tableName = this.embeddedValueResolver
.resolveStringValue(tableNameValue);
}
this.maxInactiveIntervalInSeconds = enableAttrs
.getNumber("maxInactiveIntervalInSeconds");
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
/**
* Property placeholder to process the @Scheduled annotation.
* @return the {@link PropertySourcesPlaceholderConfigurer} to use
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,2 @@
DROP TABLE SPRING_SESSION_ATTRIBUTES;
DROP TABLE SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE SPRING_SESSION_ATTRIBUTES;
DROP TABLE SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
DROP TABLE IF EXISTS SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE SPRING_SESSION_ATTRIBUTES IF EXISTS;
DROP TABLE SPRING_SESSION IF EXISTS;

View File

@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
DROP TABLE IF EXISTS SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE SPRING_SESSION_ATTRIBUTES;
DROP TABLE SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
DROP TABLE IF EXISTS SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
DROP TABLE IF EXISTS SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE SPRING_SESSION_ATTRIBUTES;
DROP TABLE SPRING_SESSION;

View File

@@ -0,0 +1,2 @@
DROP TABLE SPRING_SESSION_ATTRIBUTES;
DROP TABLE SPRING_SESSION;

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES LONGVARBINARY NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES LONGVARBINARY NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
) ENGINE=InnoDB;
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
) ENGINE=InnoDB;
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME NUMBER(19,0) NOT NULL,
LAST_ACCESS_TIME NUMBER(19,0) NOT NULL,
MAX_INACTIVE_INTERVAL NUMBER(10,0) NOT NULL,
PRINCIPAL_NAME VARCHAR2(100 CHAR),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR2(200 CHAR) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BYTEA NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHARACTER(36) NOT NULL,
CREATION_TIME INTEGER NOT NULL,
LAST_ACCESS_TIME INTEGER NOT NULL,
MAX_INACTIVE_INTERVAL INTEGER NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES IMAGE NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,20 @@
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
) LOCK DATAROWS;
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES IMAGE NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
) LOCK DATAROWS;
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

View File

@@ -0,0 +1,536 @@
/*
* 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 java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.AdditionalMatchers.and;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link JdbcOperationsSessionRepository}.
*
* @author Vedran Pavic
* @since 1.2.0
*/
@RunWith(MockitoJUnitRunner.class)
public class JdbcOperationsSessionRepositoryTests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
@Rule
public ExpectedException thrown = ExpectedException.none();
@Mock
private DataSource dataSource;
@Mock
private JdbcOperations jdbcOperations;
@Mock
private PlatformTransactionManager transactionManager;
private JdbcOperationsSessionRepository repository;
@Before
public void setUp() {
this.repository = new JdbcOperationsSessionRepository(
this.jdbcOperations, this.transactionManager);
}
@Test
public void constructorDataSource() {
JdbcOperationsSessionRepository repository = new JdbcOperationsSessionRepository(
this.dataSource, this.transactionManager);
assertThat(ReflectionTestUtils.getField(repository, "jdbcOperations"))
.isNotNull();
}
@Test
public void constructorNullDataSource() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Property 'dataSource' is required");
new JdbcOperationsSessionRepository((DataSource) null, this.transactionManager);
}
@Test
public void constructorNullJdbcOperations() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("JdbcOperations must not be null");
new JdbcOperationsSessionRepository((JdbcOperations) null, this.transactionManager);
}
@Test
public void constructorNullTransactionManager() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Property 'transactionManager' is required");
new JdbcOperationsSessionRepository(this.jdbcOperations, null);
}
@Test
public void setTableNameNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Table name must not be empty");
this.repository.setTableName(null);
}
@Test
public void setTableNameEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Table name must not be empty");
this.repository.setTableName(" ");
}
@Test
public void setCreateSessionQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setCreateSessionQuery(null);
}
@Test
public void setCreateSessionQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setCreateSessionQuery(" ");
}
@Test
public void setCreateSessionAttributeQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setCreateSessionAttributeQuery(null);
}
@Test
public void setCreateSessionAttributeQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setCreateSessionAttributeQuery(" ");
}
@Test
public void setGetSessionQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setGetSessionQuery(null);
}
@Test
public void setGetSessionQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setGetSessionQuery(" ");
}
@Test
public void setUpdateSessionQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setUpdateSessionQuery(null);
}
@Test
public void setUpdateSessionQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setUpdateSessionQuery(" ");
}
@Test
public void setUpdateSessionAttributeQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setUpdateSessionAttributeQuery(null);
}
@Test
public void setUpdateSessionAttributeQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setUpdateSessionAttributeQuery(" ");
}
@Test
public void setDeleteSessionAttributeQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setDeleteSessionAttributeQuery(null);
}
@Test
public void setDeleteSessionAttributeQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setDeleteSessionAttributeQuery(" ");
}
@Test
public void setDeleteSessionQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setDeleteSessionQuery(null);
}
@Test
public void setDeleteSessionQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setDeleteSessionQuery(" ");
}
@Test
public void setListSessionsByPrincipalNameQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setListSessionsByPrincipalNameQuery(null);
}
@Test
public void setListSessionsByPrincipalNameQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setListSessionsByPrincipalNameQuery(" ");
}
@Test
public void setDeleteSessionsByLastAccessTimeQueryNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setDeleteSessionsByLastAccessTimeQuery(null);
}
@Test
public void setDeleteSessionsByLastAccessTimeQueryEmpty() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Query must not be empty");
this.repository.setDeleteSessionsByLastAccessTimeQuery(" ");
}
@Test
public void setLobHandlerNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("LobHandler must not be null");
this.repository.setLobHandler(null);
}
@Test
public void setConversionServiceNull() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("conversionService must not be null");
this.repository.setConversionService(null);
}
@Test
public void createSessionDefaultMaxInactiveInterval() throws Exception {
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval())
.isEqualTo(new MapSession().getMaxInactiveInterval());
verifyZeroInteractions(this.jdbcOperations);
}
@Test
public void createSessionCustomMaxInactiveInterval() throws Exception {
int interval = 1;
this.repository.setDefaultMaxInactiveInterval(interval);
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
verifyZeroInteractions(this.jdbcOperations);
}
@Test
public void saveNewWithoutAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT"),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
public void saveNewWithAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute("testName", "testValue");
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT"),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).batchUpdate(
and(startsWith("INSERT"), contains("ATTRIBUTE_BYTES")),
isA(BatchPreparedStatementSetter.class));
}
@Test
public void saveUpdatedAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(
new MapSession());
session.setAttribute("testName", "testValue");
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(
and(startsWith("UPDATE"), contains("ATTRIBUTE_BYTES")),
isA(PreparedStatementSetter.class));
}
@Test
public void saveUpdatedLastAccessedTime() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(
new MapSession());
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(
and(startsWith("UPDATE"), contains("LAST_ACCESS_TIME")),
isA(PreparedStatementSetter.class));
}
@Test
public void saveUnchanged() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(
new MapSession());
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verifyZeroInteractions(this.jdbcOperations);
}
@Test
public void getSessionNotFound() {
String sessionId = "testSessionId";
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.getSession(sessionId);
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test
public void getSessionExpired() {
MapSession expired = new MapSession();
expired.setLastAccessedTime(Instant.now().minusSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.singletonList(expired));
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.getSession(expired.getId());
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"),
eq(expired.getId()));
}
@Test
public void getSessionFound() {
MapSession saved = new MapSession();
saved.setAttribute("savedName", "savedValue");
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.singletonList(saved));
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.getSession(saved.getId());
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.isNew()).isFalse();
assertThat(session.<String>getAttribute("savedName").orElse(null)).isEqualTo("savedValue");
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test
public void delete() {
String sessionId = "testSessionId";
this.repository.delete(sessionId);
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(sessionId));
}
@Test
public void findByIndexNameAndIndexValueUnknownIndexName() {
String indexValue = "testIndexValue";
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue("testIndexName", indexValue);
assertThat(sessions).isEmpty();
verifyZeroInteractions(this.jdbcOperations);
}
@Test
public void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
String principal = "username";
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal);
assertThat(sessions).isEmpty();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test
public void findByIndexNameAndIndexValuePrincipalIndexNameFound() {
String principal = "username";
Authentication authentication = new UsernamePasswordAuthenticationToken(principal,
"notused", AuthorityUtils.createAuthorityList("ROLE_USER"));
List<MapSession> saved = new ArrayList<>(2);
MapSession saved1 = new MapSession();
saved1.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
saved.add(saved1);
MapSession saved2 = new MapSession();
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
saved.add(saved2);
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(saved);
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal);
assertThat(sessions).hasSize(2);
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test
public void cleanupExpiredSessions() {
this.repository.cleanUpExpiredSessions();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong());
}
private void assertPropagationRequiresNew() {
ArgumentCaptor<TransactionDefinition> argument =
ArgumentCaptor.forClass(TransactionDefinition.class);
verify(this.transactionManager, atLeastOnce()).getTransaction(argument.capture());
assertThat(argument.getValue().getPropagationBehavior())
.isEqualTo(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
}

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.config.annotation.web.http;
import javax.sql.DataSource;
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.context.annotation.PropertySource;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
*
*/
public class JdbcHttpSessionConfigurationCustomCronTests {
AnnotationConfigApplicationContext context;
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
}
@After
public void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void overrideCron() {
this.context.register(Config.class);
assertThatThrownBy(() ->
JdbcHttpSessionConfigurationCustomCronTests.this.context.refresh())
.hasStackTraceContaining(
"Encountered invalid @Scheduled method 'cleanUpExpiredSessions': Cron expression must consist of 6 fields (found 1 in \"oops\")");
}
@EnableJdbcHttpSession
@Configuration
@PropertySource("classpath:spring-session-cleanup-cron-expression-oops.properties")
static class Config {
@Bean
public DataSource dataSource() {
return mock(DataSource.class);
}
@Bean
public PlatformTransactionManager transactionManager() {
return mock(PlatformTransactionManager.class);
}
}
}

View File

@@ -0,0 +1,275 @@
/*
* Copyright 2014-2016 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 javax.sql.DataSource;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JdbcHttpSessionConfiguration}.
*
* @author Vedran Pavic
* @author Eddú Meléndez
* @since 1.2.0
*/
public class JdbcHttpSessionConfigurationTests {
private static final String TABLE_NAME = "TEST_SESSION";
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
private static final String TABLE_NAME_SYSTEM_PROPERTY = "spring.session.jdbc.tableName";
@Rule
public final ExpectedException thrown = ExpectedException.none();
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@After
public void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void noDataSourceConfiguration() {
this.thrown.expect(UnsatisfiedDependencyException.class);
this.thrown.expectMessage("springSessionJdbcOperations");
registerAndRefresh(EmptyConfiguration.class);
}
@Test
public void defaultConfiguration() {
registerAndRefresh(DefaultConfiguration.class);
assertThat(this.context.getBean(JdbcOperationsSessionRepository.class))
.isNotNull();
}
@Test
public void customTableName() {
registerAndRefresh(CustomTableNameConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "tableName"))
.isEqualTo(TABLE_NAME);
}
@Test
public void customTableNameSystemProperty() {
System.setProperty(TABLE_NAME_SYSTEM_PROPERTY, TABLE_NAME);
try {
registerAndRefresh(DefaultConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "tableName"))
.isEqualTo(TABLE_NAME);
}
finally {
System.clearProperty(TABLE_NAME_SYSTEM_PROPERTY);
}
}
@Test
public void setCustomTableName() {
registerAndRefresh(BaseConfiguration.class,
CustomTableNameSetConfiguration.class);
JdbcHttpSessionConfiguration repository = this.context
.getBean(JdbcHttpSessionConfiguration.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "tableName")).isEqualTo(
"custom_session");
}
@Test
public void setCustomMaxInactiveIntervalInSeconds() {
registerAndRefresh(BaseConfiguration.class,
CustomMaxInactiveIntervalInSecondsSetConfiguration.class);
JdbcHttpSessionConfiguration repository = this.context
.getBean(JdbcHttpSessionConfiguration.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "maxInactiveIntervalInSeconds")).isEqualTo(
10);
}
@Test
public void customMaxInactiveIntervalInSeconds() {
registerAndRefresh(CustomMaxInactiveIntervalInSecondsConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval"))
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
@Test
public void customLobHandlerConfiguration() {
registerAndRefresh(CustomLobHandlerConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
LobHandler lobHandler = this.context.getBean(LobHandler.class);
assertThat(repository).isNotNull();
assertThat(lobHandler).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "lobHandler"))
.isEqualTo(lobHandler);
}
@Test
public void customConversionServiceConfiguration() {
registerAndRefresh(CustomConversionServiceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
ConversionService conversionService = this.context
.getBean("springSessionConversionService", ConversionService.class);
assertThat(repository).isNotNull();
assertThat(conversionService).isNotNull();
Object repositoryConversionService = ReflectionTestUtils.getField(repository,
"conversionService");
assertThat(repositoryConversionService).isEqualTo(conversionService);
}
@Test
public void resolveTableNameByPropertyPlaceholder() {
this.context.setEnvironment(new MockEnvironment().withProperty("session.jdbc.tableName", "custom_session_table"));
registerAndRefresh(CustomJdbcHttpSessionConfiguration.class);
JdbcHttpSessionConfiguration configuration = this.context.getBean(JdbcHttpSessionConfiguration.class);
assertThat(ReflectionTestUtils.getField(configuration, "tableName")).isEqualTo("custom_session_table");
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
}
@Configuration
@EnableJdbcHttpSession
static class EmptyConfiguration {
}
static class BaseConfiguration {
@Bean
public DataSource dataSource() {
return mock(DataSource.class);
}
@Bean
public PlatformTransactionManager transactionManager() {
return mock(PlatformTransactionManager.class);
}
}
@Configuration
@EnableJdbcHttpSession
static class DefaultConfiguration extends BaseConfiguration {
}
@Configuration
@EnableJdbcHttpSession(tableName = TABLE_NAME)
static class CustomTableNameConfiguration extends BaseConfiguration {
}
@Configuration
static class CustomTableNameSetConfiguration extends JdbcHttpSessionConfiguration {
CustomTableNameSetConfiguration() {
setTableName("custom_session");
}
}
@Configuration
static class CustomMaxInactiveIntervalInSecondsSetConfiguration extends JdbcHttpSessionConfiguration {
CustomMaxInactiveIntervalInSecondsSetConfiguration() {
setMaxInactiveIntervalInSeconds(10);
}
}
@Configuration
@EnableJdbcHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
static class CustomMaxInactiveIntervalInSecondsConfiguration
extends BaseConfiguration {
}
@Configuration
@EnableJdbcHttpSession
static class CustomLobHandlerConfiguration extends BaseConfiguration {
@Bean
public LobHandler springSessionLobHandler() {
return mock(LobHandler.class);
}
}
@Configuration
@EnableJdbcHttpSession
static class CustomConversionServiceConfiguration extends BaseConfiguration {
@Bean
public ConversionService springSessionConversionService() {
return mock(ConversionService.class);
}
}
@Configuration
@EnableJdbcHttpSession(tableName = "${session.jdbc.tableName}")
static class CustomJdbcHttpSessionConfiguration extends BaseConfiguration {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
}

View File

@@ -0,0 +1 @@
spring.session.cleanup.cron.expression=oops