Skip to content

Commit 2271685

Browse files
FINERACT-2304 Fix auditing of failed batch request while enclosing transaction got enabled
1 parent ba06234 commit 2271685

File tree

6 files changed

+90
-10
lines changed

6 files changed

+90
-10
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
import org.apache.fineract.batch.exception.ErrorInfo;
5454
import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode;
5555
import org.apache.fineract.commands.configuration.RetryConfigurationAssembler;
56+
import org.apache.fineract.commands.domain.CommandSource;
57+
import org.apache.fineract.commands.service.CommandSourceService;
5658
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
5759
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
5860
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
@@ -94,6 +96,8 @@ public class BatchApiServiceImpl implements BatchApiService {
9496

9597
private final RetryConfigurationAssembler retryConfigurationAssembler;
9698

99+
private final CommandSourceService commandSourceService;
100+
97101
private EntityManager entityManager;
98102

99103
/**
@@ -166,9 +170,11 @@ private List<BatchResponse> callInTransaction(Consumer<TransactionTemplate> tran
166170
try {
167171
return retryingBatch.get();
168172
} catch (TransactionException | NonTransientDataAccessException ex) {
173+
saveFailedCommandSourceEntries(ex);
169174
return buildErrorResponses(ex, responseList);
170175
} catch (BatchExecutionException ex) {
171176
log.error("Exception during the batch request processing", ex);
177+
saveFailedCommandSourceEntries(ex.getCause());
172178
responseList.add(buildErrorResponse(ex.getCause(), ex.getRequest()));
173179
return responseList;
174180
}
@@ -395,4 +401,17 @@ private BatchResponse buildErrorResponse(Long requestId, Integer statusCode, Str
395401
public void setEntityManager(EntityManager entityManager) {
396402
this.entityManager = entityManager;
397403
}
404+
405+
private void saveFailedCommandSourceEntries(final Throwable ex) {
406+
try {
407+
final List<CommandSource> commandSources = BatchRequestContextHolder.getCommandSources();
408+
if (!commandSources.isEmpty()) {
409+
final String errorMessage = ex != null ? ex.getMessage() : "Batch processing failed";
410+
log.info("Saving {} failed entries for batch audit with error: {}", commandSources.size(), errorMessage);
411+
commandSourceService.saveFailedCommandSourcesNewTransaction(commandSources, errorMessage);
412+
}
413+
} catch (Exception e) {
414+
log.error("Failed to save failed CommandSource entries for batch audit", e);
415+
}
416+
}
398417
}

fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandProcessingResultType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public enum CommandProcessingResultType {
3434
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
3535
REJECTED(3, "commandProcessingResultType.rejected"), //
3636
UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), //
37-
ERROR(5, "commandProcessingResultType.error");
37+
ERROR(5, "commandProcessingResultType.error"), //
38+
ROLLBACK(6, "commandProcessingResultType.rollback");
3839

3940
private static final Map<Integer, CommandProcessingResultType> BY_ID = Arrays.stream(values())
4041
.collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v));

fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222

2323
import com.google.gson.JsonElement;
2424
import com.google.gson.JsonObject;
25+
import java.util.List;
2526
import java.util.Set;
2627
import lombok.RequiredArgsConstructor;
2728
import org.apache.fineract.batch.exception.ErrorInfo;
29+
import org.apache.fineract.commands.domain.CommandProcessingResultType;
2830
import org.apache.fineract.commands.domain.CommandSource;
2931
import org.apache.fineract.commands.domain.CommandSourceRepository;
3032
import org.apache.fineract.commands.domain.CommandWrapper;
@@ -69,12 +71,6 @@ public CommandSource saveInitialNewTransaction(CommandWrapper wrapper, JsonComma
6971
return saveInitial(wrapper, jsonCommand, maker, idempotencyKey);
7072
}
7173

72-
@NonNull
73-
@Transactional(propagation = Propagation.REQUIRED)
74-
public CommandSource saveInitialSameTransaction(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
75-
return saveInitial(wrapper, jsonCommand, maker, idempotencyKey);
76-
}
77-
7874
@NonNull
7975
private CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
8076
try {
@@ -90,8 +86,8 @@ private CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonComman
9086
}
9187

9288
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
93-
public CommandSource saveResultNewTransaction(@NonNull CommandSource commandSource) {
94-
return saveResult(commandSource);
89+
public void saveResultNewTransaction(@NonNull CommandSource commandSource) {
90+
saveResult(commandSource);
9591
}
9692

9793
@Transactional(propagation = Propagation.REQUIRED)
@@ -108,6 +104,16 @@ public ErrorInfo generateErrorInfo(Throwable t) {
108104
return errorHandler.handle(ErrorHandler.getMappable(t));
109105
}
110106

107+
@Transactional(propagation = Propagation.REQUIRES_NEW)
108+
public void saveFailedCommandSourcesNewTransaction(final List<CommandSource> commandSources, final String errorMessage) {
109+
commandSources.forEach(commandSource -> {
110+
commandSource.setStatus(CommandProcessingResultType.ROLLBACK);
111+
commandSource.setResult(errorMessage);
112+
commandSource.setResultStatusCode(500);
113+
commandSourceRepository.saveAndFlush(commandSource);
114+
});
115+
}
116+
111117
@Transactional(propagation = Propagation.REQUIRES_NEW)
112118
public CommandSource getCommandSource(Long commandSourceId) {
113119
return commandSourceRepository.findById(commandSourceId).orElseThrow(() -> new CommandNotFoundException(commandSourceId));

fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ public CommandProcessingResult executeCommand(final CommandWrapper wrapper, fina
132132
storeCommandIdInContext(commandSource); // Store command id as a request attribute
133133
}
134134

135+
if (isEnclosingTransaction) {
136+
BatchRequestContextHolder.addCommandSource(commandSource);
137+
}
138+
135139
setIdempotencyKeyStoreFlag(true);
136140

137141
return executeCommand(wrapper, command, isApprovedByChecker, commandSource, user, isEnclosingTransaction);

fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
*/
1919
package org.apache.fineract.infrastructure.core.domain;
2020

21+
import java.util.ArrayList;
22+
import java.util.List;
2123
import java.util.Map;
2224
import java.util.Optional;
25+
import org.apache.fineract.commands.domain.CommandSource;
2326
import org.springframework.transaction.TransactionStatus;
2427

2528
public final class BatchRequestContextHolder {
@@ -32,6 +35,8 @@ private BatchRequestContextHolder() {}
3235

3336
private static final ThreadLocal<Boolean> isEnclosingTransaction = new ThreadLocal<>();
3437

38+
private static final ThreadLocal<List<CommandSource>> commandSources = ThreadLocal.withInitial(ArrayList::new);
39+
3540
/**
3641
* True if the batch attributes are set
3742
*
@@ -87,6 +92,7 @@ public static void setIsEnclosingTransaction(boolean isEnclosingTransaction) {
8792

8893
public static void resetIsEnclosingTransaction() {
8994
isEnclosingTransaction.remove();
95+
commandSources.get().clear();
9096
}
9197

9298
/**
@@ -130,4 +136,15 @@ public static void setEnclosingTransaction(TransactionStatus enclosingTransactio
130136
public static void resetTransaction() {
131137
batchTransaction.set(Optional.empty());
132138
}
139+
140+
public static void addCommandSource(final CommandSource commandSource) {
141+
if (isEnclosingTransaction() && commandSource != null) {
142+
commandSources.get().add(commandSource);
143+
}
144+
}
145+
146+
public static List<CommandSource> getCommandSources() {
147+
return new ArrayList<>(commandSources.get());
148+
}
149+
133150
}

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
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.assertNotNull;
2223
import static org.junit.jupiter.api.Assertions.assertTrue;
2324
import static org.mockito.ArgumentMatchers.any;
2425
import static org.mockito.ArgumentMatchers.anyString;
@@ -41,7 +42,10 @@
4142
import org.apache.fineract.batch.domain.BatchResponse;
4243
import org.apache.fineract.batch.exception.ErrorInfo;
4344
import org.apache.fineract.commands.configuration.RetryConfigurationAssembler;
45+
import org.apache.fineract.commands.domain.CommandSource;
46+
import org.apache.fineract.commands.service.CommandSourceService;
4447
import org.apache.fineract.infrastructure.core.config.FineractProperties;
48+
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
4549
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
4650
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
4751
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
@@ -74,6 +78,9 @@ class BatchApiServiceImplTest {
7478
@Mock
7579
private ErrorHandler errorHandler;
7680

81+
@Mock
82+
private CommandSourceService commandSourceService;
83+
7784
@Mock
7885
private RetryRegistry registry;
7986

@@ -97,7 +104,7 @@ class BatchApiServiceImplTest {
97104
@BeforeEach
98105
void setUp() {
99106
batchApiService = new BatchApiServiceImpl(strategyProvider, resolutionHelper, transactionManager, errorHandler, List.of(),
100-
batchPreprocessors, retryConfigurationAssembler);
107+
batchPreprocessors, retryConfigurationAssembler, commandSourceService);
101108
batchApiService.setEntityManager(entityManager);
102109
request = new BatchRequest();
103110
request.setRequestId(1L);
@@ -227,6 +234,32 @@ void testHandleBatchRequestsWithEnclosingTransactionReadOnly() {
227234
Mockito.verifyNoInteractions(entityManager);
228235
}
229236

237+
@Test
238+
void testFailedCommandSourceEntriesAreSavedOnBatchFailure() {
239+
final List<BatchRequest> requestList = List.of(request);
240+
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
241+
when(commandStrategy.execute(any(), any())).thenThrow(new RuntimeException("Test failure"));
242+
243+
final ErrorInfo errorInfo = mock(ErrorInfo.class);
244+
when(errorInfo.getMessage()).thenReturn("Test failure");
245+
when(errorInfo.getStatusCode()).thenReturn(500);
246+
when(errorHandler.handle(any())).thenReturn(errorInfo);
247+
248+
when(transactionManager.getTransaction(any()))
249+
.thenReturn(new DefaultTransactionStatus("txn_name", null, true, true, false, false, false, null));
250+
251+
final CommandSource mockCommandSource = mock(CommandSource.class);
252+
BatchRequestContextHolder.setIsEnclosingTransaction(true);
253+
BatchRequestContextHolder.addCommandSource(mockCommandSource);
254+
255+
final BatchResponse result = batchApiService.handleBatchRequestsWithEnclosingTransaction(requestList, uriInfo).getFirst();
256+
assertNotNull(result);
257+
assertEquals(500, result.getStatusCode());
258+
assertTrue(result.getBody().contains("Test failure"));
259+
260+
verify(commandSourceService, times(1)).saveFailedCommandSourcesNewTransaction(any(), anyString());
261+
}
262+
230263
private static final class RetryException extends RuntimeException {}
231264

232265
}

0 commit comments

Comments
 (0)