This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new d03973165 FINERACT-2081: Support read-only transactions via Batch API
d03973165 is described below
commit d03973165dbd52b202775a277d34eeadc02830f9
Author: Adam Saghy <[email protected]>
AuthorDate: Mon Dec 16 18:52:15 2024 +0100
FINERACT-2081: Support read-only transactions via Batch API
---
.../batch/service/BatchApiServiceImpl.java | 7 +-
...abaseSelectingPersistenceUnitPostProcessor.java | 0
.../persistence/ExtendedJpaTransactionManager.java | 2 +-
.../persistence/TransactionLifecycleCallback.java | 0
.../batch/service/BatchApiServiceImplTest.java | 118 +++++++++++++++++++++
5 files changed, 125 insertions(+), 2 deletions(-)
diff --git
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
index b8121212e..0b314052d 100644
---
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
+++
b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
@@ -56,6 +56,7 @@ import
org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
import org.apache.fineract.infrastructure.core.filters.BatchFilter;
import
org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
+import
org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
import org.jetbrains.annotations.NotNull;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.NonTransientDataAccessException;
@@ -141,6 +142,9 @@ public class BatchApiServiceImpl implements BatchApiService
{
List<BatchResponse> responseList = new ArrayList<>();
try {
TransactionTemplate transactionTemplate = new
TransactionTemplate(transactionManager);
+ if (transactionManager instanceof ExtendedJpaTransactionManager
extendedJpaTransactionManager) {
+
transactionTemplate.setReadOnly(extendedJpaTransactionManager.isReadOnlyConnection());
+ }
transactionConfigurator.accept(transactionTemplate);
return transactionTemplate.execute(status -> {
BatchRequestContextHolder.setEnclosingTransaction(status);
@@ -240,7 +244,8 @@ public class BatchApiServiceImpl implements BatchApiService
{
BatchRequestContextHolder.setRequestAttributes(new
HashMap<>(Optional.ofNullable(request.getHeaders())
.map(list ->
list.stream().collect(Collectors.toMap(Header::getName, Header::getValue)))
.orElse(Collections.emptyMap())));
- if (BatchRequestContextHolder.isEnclosingTransaction()) {
+ if (BatchRequestContextHolder.isEnclosingTransaction()
+ &&
BatchRequestContextHolder.getEnclosingTransaction().stream().anyMatch(ts ->
!ts.isReadOnly())) {
entityManager.flush();
}
BatchCallHandler callHandler = new
BatchCallHandler(this.batchFilters, commandStrategy::execute);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/DatabaseSelectingPersistenceUnitPostProcessor.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/persistence/DatabaseSelectingPersistenceUnitPostProcessor.java
similarity index 100%
rename from
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/DatabaseSelectingPersistenceUnitPostProcessor.java
rename to
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/persistence/DatabaseSelectingPersistenceUnitPostProcessor.java
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
similarity index 98%
rename from
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
rename to
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
index 5d85df640..c8716ff83 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
@@ -64,7 +64,7 @@ public class ExtendedJpaTransactionManager extends
JpaTransactionManager {
invokeLifecycleCallbacks(TransactionLifecycleCallback::afterCommit);
}
- private boolean isReadOnlyConnection() {
+ public boolean isReadOnlyConnection() {
try (Connection connection = getDataSource().getConnection()) {
return connection.isReadOnly();
} catch (SQLException e) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/TransactionLifecycleCallback.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/persistence/TransactionLifecycleCallback.java
similarity index 100%
rename from
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/TransactionLifecycleCallback.java
rename to
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/persistence/TransactionLifecycleCallback.java
diff --git
a/fineract-core/src/test/java/org/apache/fineract/batch/service/BatchApiServiceImplTest.java
b/fineract-core/src/test/java/org/apache/fineract/batch/service/BatchApiServiceImplTest.java
new file mode 100644
index 000000000..2dd6373b1
--- /dev/null
+++
b/fineract-core/src/test/java/org/apache/fineract/batch/service/BatchApiServiceImplTest.java
@@ -0,0 +1,118 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.fineract.batch.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import jakarta.persistence.EntityManager;
+import jakarta.ws.rs.core.UriInfo;
+import java.util.List;
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.command.CommandStrategyProvider;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import
org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
+import
org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.transaction.support.DefaultTransactionStatus;
+
+@ExtendWith(MockitoExtension.class)
+class BatchApiServiceImplTest {
+
+ @Mock
+ private CommandStrategyProvider strategyProvider;
+ @Mock
+ private ExtendedJpaTransactionManager transactionManager;
+ @Mock
+ private EntityManager entityManager;
+ @Mock
+ private CommandStrategy commandStrategy;
+ @Mock
+ private UriInfo uriInfo;
+ private final ResolutionHelper resolutionHelper = Mockito.spy(new
ResolutionHelper(new FromJsonHelper()));
+ private final List<BatchRequestPreprocessor> batchPreprocessors =
Mockito.spy(List.of());
+ @InjectMocks
+ private BatchApiServiceImpl batchApiService;
+ private BatchRequest request;
+ private BatchResponse response;
+
+ @BeforeEach
+ void setUp() {
+ request = new BatchRequest();
+ request.setRequestId(1L);
+ request.setMethod("POST");
+ request.setRelativeUrl("/random_api");
+ response = new BatchResponse();
+ response.setRequestId(1L);
+ response.setStatusCode(200);
+ response.setBody("Success");
+ }
+
+ @AfterEach
+ void tearDown() {
+ Mockito.reset(resolutionHelper);
+ Mockito.reset(batchPreprocessors);
+ Mockito.reset(entityManager);
+ Mockito.reset(commandStrategy);
+ Mockito.reset(strategyProvider);
+ Mockito.reset(transactionManager);
+ }
+
+ @Test
+ void testHandleBatchRequestsWithEnclosingTransaction() {
+ List<BatchRequest> requestList = List.of(request);
+
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
+ when(commandStrategy.execute(any(), any())).thenReturn(response);
+ // Regular transaction
+ when(transactionManager.getTransaction(any()))
+ .thenReturn(new DefaultTransactionStatus("txn_name", null,
true, true, false, false, false, null));
+ List<BatchResponse> result =
batchApiService.handleBatchRequestsWithEnclosingTransaction(requestList,
uriInfo);
+ assertEquals(1, result.size());
+ assertEquals(200, result.get(0).getStatusCode());
+ assertTrue(result.get(0).getBody().contains("Success"));
+ Mockito.verify(entityManager, times(1)).flush();
+ }
+
+ @Test
+ void testHandleBatchRequestsWithEnclosingTransactionReadOnly() {
+ List<BatchRequest> requestList = List.of(request);
+
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
+ when(commandStrategy.execute(any(), any())).thenReturn(response);
+ // Read-only transaction
+ when(transactionManager.getTransaction(any()))
+ .thenReturn(new DefaultTransactionStatus("txn_name", null,
true, true, false, true, false, null));
+ List<BatchResponse> result =
batchApiService.handleBatchRequestsWithEnclosingTransaction(requestList,
uriInfo);
+ assertEquals(1, result.size());
+ assertEquals(200, result.get(0).getStatusCode());
+ assertTrue(result.get(0).getBody().contains("Success"));
+ Mockito.verifyNoInteractions(entityManager);
+ }
+}