Skip to content

Commit bc4b355

Browse files
adamsaghygalovics
authored andcommitted
FINERACT-2181: Batch API - Fix read-only connection handling
1 parent 0752106 commit bc4b355

2 files changed

Lines changed: 63 additions & 10 deletions

File tree

fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
5959
import org.apache.fineract.infrastructure.core.filters.BatchFilter;
6060
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
61+
import org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
62+
import org.springframework.beans.factory.annotation.Autowired;
6163
import org.springframework.dao.ConcurrencyFailureException;
6264
import org.springframework.dao.NonTransientDataAccessException;
6365
import org.springframework.lang.NonNull;
@@ -74,9 +76,9 @@
7476
* CommandStrategy from CommandStrategyProvider.
7577
*
7678
* @author Rishabh Shukla
77-
* @see org.apache.fineract.batch.domain.BatchRequest
78-
* @see org.apache.fineract.batch.domain.BatchResponse
79-
* @see org.apache.fineract.batch.command.CommandStrategyProvider
79+
* @see BatchRequest
80+
* @see BatchResponse
81+
* @see CommandStrategyProvider
8082
*/
8183
@Service
8284
@RequiredArgsConstructor
@@ -85,7 +87,6 @@ public class BatchApiServiceImpl implements BatchApiService {
8587

8688
private final CommandStrategyProvider strategyProvider;
8789
private final ResolutionHelper resolutionHelper;
88-
private final PlatformTransactionManager transactionManager;
8990
private final ErrorHandler errorHandler;
9091

9192
private final List<BatchFilter> batchFilters;
@@ -94,6 +95,7 @@ public class BatchApiServiceImpl implements BatchApiService {
9495

9596
private final RetryConfigurationAssembler retryConfigurationAssembler;
9697

98+
private PlatformTransactionManager transactionManager;
9799
private EntityManager entityManager;
98100

99101
/**
@@ -152,6 +154,9 @@ private List<BatchResponse> callInTransaction(Consumer<TransactionTemplate> tran
152154
try {
153155
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
154156
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
157+
if (transactionManager instanceof ExtendedJpaTransactionManager extendedJpaTransactionManager) {
158+
transactionTemplate.setReadOnly(extendedJpaTransactionManager.isReadOnlyConnection());
159+
}
155160
transactionConfigurator.accept(transactionTemplate);
156161
return transactionTemplate.execute(status -> {
157162
BatchRequestContextHolder.setEnclosingTransaction(status);
@@ -175,8 +180,8 @@ private List<BatchResponse> callInTransaction(Consumer<TransactionTemplate> tran
175180
}
176181

177182
/**
178-
* Returns the response list by getting a proper {@link org.apache.fineract.batch.command.CommandStrategy}.
179-
* execute() method of acquired commandStrategy is then provided with the separate Request.
183+
* Returns the response list by getting a proper {@link CommandStrategy}. execute() method of acquired
184+
* commandStrategy is then provided with the separate Request.
180185
*
181186
* @param requestList
182187
* @param uriInfo
@@ -395,4 +400,9 @@ private BatchResponse buildErrorResponse(Long requestId, Integer statusCode, Str
395400
public void setEntityManager(EntityManager entityManager) {
396401
this.entityManager = entityManager;
397402
}
403+
404+
@Autowired
405+
public void setTransactionManager(PlatformTransactionManager transactionManager) {
406+
this.transactionManager = transactionManager;
407+
}
398408
}

fineract-core/src/test/java/org/apache/fineract/batch/service/BatchApiServiceImplTest.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
package org.apache.fineract.batch.service;
2020

2121
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
import static org.junit.jupiter.api.Assertions.assertFalse;
2223
import static org.junit.jupiter.api.Assertions.assertTrue;
2324
import static org.mockito.ArgumentMatchers.any;
2425
import static org.mockito.ArgumentMatchers.anyString;
26+
import static org.mockito.ArgumentMatchers.argThat;
2527
import static org.mockito.Mockito.doThrow;
2628
import static org.mockito.Mockito.mock;
29+
import static org.mockito.Mockito.spy;
2730
import static org.mockito.Mockito.times;
2831
import static org.mockito.Mockito.verify;
2932
import static org.mockito.Mockito.when;
@@ -51,6 +54,8 @@
5154
import org.junit.jupiter.api.BeforeEach;
5255
import org.junit.jupiter.api.Test;
5356
import org.junit.jupiter.api.extension.ExtendWith;
57+
import org.junit.jupiter.params.ParameterizedTest;
58+
import org.junit.jupiter.params.provider.ValueSource;
5459
import org.mockito.InjectMocks;
5560
import org.mockito.Mock;
5661
import org.mockito.Mockito;
@@ -86,8 +91,8 @@ class BatchApiServiceImplTest {
8691
@InjectMocks
8792
private RetryConfigurationAssembler retryConfigurationAssembler;
8893

89-
private final ResolutionHelper resolutionHelper = Mockito.spy(new ResolutionHelper(new FromJsonHelper()));
90-
private final List<BatchRequestPreprocessor> batchPreprocessors = Mockito.spy(List.of());
94+
private final ResolutionHelper resolutionHelper = spy(new ResolutionHelper(new FromJsonHelper()));
95+
private final List<BatchRequestPreprocessor> batchPreprocessors = spy(List.of());
9196

9297
@InjectMocks
9398
private BatchApiServiceImpl batchApiService;
@@ -96,8 +101,9 @@ class BatchApiServiceImplTest {
96101

97102
@BeforeEach
98103
void setUp() {
99-
batchApiService = new BatchApiServiceImpl(strategyProvider, resolutionHelper, transactionManager, errorHandler, List.of(),
100-
batchPreprocessors, retryConfigurationAssembler);
104+
batchApiService = new BatchApiServiceImpl(strategyProvider, resolutionHelper, errorHandler, List.of(), batchPreprocessors,
105+
retryConfigurationAssembler);
106+
batchApiService.setTransactionManager(transactionManager);
101107
batchApiService.setEntityManager(entityManager);
102108
request = new BatchRequest();
103109
request.setRequestId(1L);
@@ -227,6 +233,43 @@ void testHandleBatchRequestsWithEnclosingTransactionReadOnly() {
227233
Mockito.verifyNoInteractions(entityManager);
228234
}
229235

236+
@ParameterizedTest
237+
@ValueSource(booleans = { true, false })
238+
void testCallInTransactionReadOnlyFlag(boolean isReadOnly) {
239+
// Given
240+
ExtendedJpaTransactionManager extendedJpaTransactionManager = mock(ExtendedJpaTransactionManager.class);
241+
242+
// Create a transaction status with the correct read-only flag
243+
DefaultTransactionStatus transactionStatus = new DefaultTransactionStatus("txn_name", null, true, true, false, isReadOnly, false,
244+
null);
245+
246+
// Mock getTransaction to return our status when the read-only flag matches
247+
when(extendedJpaTransactionManager.isReadOnlyConnection()).thenReturn(isReadOnly);
248+
when(extendedJpaTransactionManager
249+
.getTransaction(argThat(definition -> definition != null && definition.isReadOnly() == isReadOnly)))
250+
.thenReturn(transactionStatus);
251+
252+
// Mock other required dependencies
253+
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
254+
when(commandStrategy.execute(any(), any())).thenReturn(response);
255+
256+
batchApiService.setTransactionManager(extendedJpaTransactionManager);
257+
258+
// Set up a request that will trigger the read-only behavior we want to test
259+
BatchRequest testRequest = new BatchRequest();
260+
testRequest.setRequestId(1L);
261+
testRequest.setMethod(isReadOnly ? "GET" : "POST"); // Use GET for read-only, POST for read-write
262+
testRequest.setRelativeUrl("/test/endpoint");
263+
264+
// When
265+
List<BatchResponse> responses = batchApiService.handleBatchRequestsWithEnclosingTransaction(List.of(testRequest), uriInfo);
266+
267+
// Then
268+
assertFalse(responses.isEmpty());
269+
verify(extendedJpaTransactionManager)
270+
.getTransaction(argThat(definition -> definition != null && definition.isReadOnly() == isReadOnly));
271+
}
272+
230273
private static final class RetryException extends RuntimeException {}
231274

232275
}

0 commit comments

Comments
 (0)