Merge branch '3.2.x'

Closes gh-2817
This commit is contained in:
Marcus Hert Da Coregio
2024-02-23 14:18:13 -03:00
3 changed files with 167 additions and 5 deletions

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2014-2024 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
*
* https://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;
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.beans.factory.annotation.Qualifier;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
/**
* Qualifier annotation for a
* {@link org.springframework.transaction.PlatformTransactionManager} to be injected in
* {@link JdbcIndexedSessionRepository}.
*
* @author Marcus da Coregio
* @since 3.1.5
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface SpringSessionTransactionManager {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 the original author or authors.
* Copyright 2014-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,14 @@ import java.util.stream.Collectors;
import javax.sql.DataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -56,6 +60,7 @@ import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.session.jdbc.config.annotation.SpringSessionTransactionManager;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
@@ -79,7 +84,8 @@ import org.springframework.util.StringValueResolver;
*/
@Configuration(proxyBeanMethods = false)
@Import(SpringHttpSessionConfiguration.class)
public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
ApplicationContextAware, InitializingBean {
private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
@@ -113,6 +119,21 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
private ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() throws Exception {
if (this.transactionOperations == null && this.transactionManager == null) {
this.transactionManager = getUniqueTransactionManager();
if (this.transactionManager == null) {
throw new IllegalStateException(
"""
Could not resolve an unique PlatformTransactionManager bean from the application context.
Please provide either a TransactionOperations bean named springSessionTransactionOperations or a PlatformTransactionManager bean qualified with @SpringSessionTransactionManager""");
}
}
}
@Bean
public JdbcIndexedSessionRepository sessionRepository() {
JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource);
@@ -200,7 +221,8 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
this.dataSource = dataSourceToUse;
}
@Autowired
@Autowired(required = false)
@SpringSessionTransactionManager
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@@ -276,6 +298,15 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
this.saveMode = attributes.getEnum("saveMode");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private PlatformTransactionManager getUniqueTransactionManager() {
return this.applicationContext.getBeanProvider(PlatformTransactionManager.class).getIfUnique();
}
private static JdbcTemplate createJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setExceptionTranslator(new SQLErrorCodeSQLExceptionTranslator(dataSource));
@@ -283,7 +314,7 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
return jdbcTemplate;
}
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
private TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.afterPropertiesSet();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2024 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.
@@ -49,13 +49,19 @@ import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.jdbc.FixedSessionIdGenerator;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.session.jdbc.config.annotation.SpringSessionTransactionManager;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
/**
@@ -338,6 +344,34 @@ class JdbcHttpSessionConfigurationTests {
assertThat(sessionIdGenerator).isInstanceOf(UuidSessionIdGenerator.class);
}
// gh-2801
@Test
void configureWhenMultipleTransactionManagersAndQualifiedTransactionOperationsThenApplicationShouldStart() {
assertThatNoException().isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class,
CustomTransactionOperationsConfiguration.class));
}
// gh-2801
@Test
void configureWhenMultipleTransactionManagersAndQualifiedTransactionManagerThenApplicationShouldStartAndUseQualified() {
assertThatNoException().isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class,
QualifiedTransactionManagerConfig.class, DefaultConfiguration.class));
JdbcHttpSessionConfiguration configuration = this.context.getBean(JdbcHttpSessionConfiguration.class);
Object transactionManager = ReflectionTestUtils.getField(configuration, "transactionManager");
assertThat(transactionManager).isInstanceOf(MyTransactionManager.class);
}
// gh-2801
@Test
void configureWhenMultipleTransactionManagersAndNoQualifiedBeanThenApplicationShouldFailToStart() {
assertThatException()
.isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class, DefaultConfiguration.class))
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessage("Could not resolve an unique PlatformTransactionManager bean from the application context.\n"
+ "Please provide either a TransactionOperations bean named springSessionTransactionOperations or a PlatformTransactionManager bean qualified with @SpringSessionTransactionManager");
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -606,4 +640,59 @@ class JdbcHttpSessionConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class MultipleTransactionManagerConfig {
@Bean
DataSource dataSource() {
return mock(DataSource.class);
}
@Bean
TransactionManager transactionManager1() {
return new MyTransactionManager();
}
@Bean
TransactionManager transactionManager2() {
return mock(PlatformTransactionManager.class);
}
}
@Configuration(proxyBeanMethods = false)
static class QualifiedTransactionManagerConfig {
@Bean
DataSource dataSource() {
return mock(DataSource.class);
}
@Bean
@SpringSessionTransactionManager
TransactionManager myTransactionManager() {
return new MyTransactionManager();
}
}
static class MyTransactionManager implements PlatformTransactionManager {
@Override
public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
return null;
}
@Override
public void commit(TransactionStatus status) throws TransactionException {
}
@Override
public void rollback(TransactionStatus status) throws TransactionException {
}
}
}