Skip to content

Commit

Permalink
Merge branch '3.1.x' into 3.2.x
Browse files Browse the repository at this point in the history
Closes gh-2816
  • Loading branch information
marcusdacoregio committed Feb 23, 2024
2 parents 5db2ea2 + 4920499 commit 97c3853
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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 {

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -24,10 +24,14 @@

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;
Expand Down Expand Up @@ -56,6 +60,7 @@
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;
Expand All @@ -79,7 +84,8 @@
*/
@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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -200,7 +221,8 @@ public void setDataSource(@SpringSessionDataSource ObjectProvider<DataSource> sp
this.dataSource = dataSourceToUse;
}

@Autowired
@Autowired(required = false)
@SpringSessionTransactionManager
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
Expand Down Expand Up @@ -276,14 +298,23 @@ public void setImportMetadata(AnnotationMetadata importMetadata) {
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));
jdbcTemplate.afterPropertiesSet();
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -49,13 +49,19 @@
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;

/**
Expand Down Expand Up @@ -338,6 +344,34 @@ void sessionIdGeneratorWhenNoBeanThenDefault() {
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();
Expand Down Expand Up @@ -606,4 +640,59 @@ SessionRepositoryCustomizer<JdbcIndexedSessionRepository> sessionRepositoryCusto

}

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

}

}

}

0 comments on commit 97c3853

Please sign in to comment.