Extract spring-session-jdbc
Issue gh-806
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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">
|
||||
* @Configuration
|
||||
* @EnableJdbcHttpSession
|
||||
* public class JdbcHttpSessionConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public DataSource dataSource() {
|
||||
* return new EmbeddedDatabaseBuilder()
|
||||
* .setType(EmbeddedDatabaseType.H2)
|
||||
* .addScript("org/springframework/session/jdbc/schema-h2.sql")
|
||||
* .build();
|
||||
* }
|
||||
*
|
||||
* @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;
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE IF EXISTS SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE SPRING_SESSION_ATTRIBUTES IF EXISTS;
|
||||
DROP TABLE SPRING_SESSION IF EXISTS;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE IF EXISTS SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE IF EXISTS SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE IF EXISTS SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE SPRING_SESSION;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE SPRING_SESSION_ATTRIBUTES;
|
||||
DROP TABLE SPRING_SESSION;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
spring.session.cleanup.cron.expression=oops
|
||||
Reference in New Issue
Block a user