FINERACT-1927: Incorrect exception info in BatchAPI response
This commit is contained in:
+4
-1
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.batch.command;
|
||||
|
||||
import static jakarta.ws.rs.HttpMethod.DELETE;
|
||||
import static jakarta.ws.rs.HttpMethod.GET;
|
||||
import static jakarta.ws.rs.HttpMethod.POST;
|
||||
import static jakarta.ws.rs.HttpMethod.PUT;
|
||||
@@ -207,7 +208,9 @@ public class CommandStrategyProvider {
|
||||
.method(PUT).build(), "updateDatatableEntryOneToOneCommandStrategy");
|
||||
commandStrategies.put(CommandContext
|
||||
.resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX + "\\/" + NUMBER_REGEX)
|
||||
.method(PUT).build(), "updateDatatableEntryOneToManyCommandStrategy");
|
||||
.method(DELETE).build(), "deleteDatatableEntryOneToManyCommandStrategy");
|
||||
commandStrategies.put(CommandContext.resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX)
|
||||
.method(DELETE).build(), "deleteDatatableEntryOneToOneCommandStrategy");
|
||||
commandStrategies.put(CommandContext
|
||||
.resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/" + NUMBER_REGEX + OPTIONAL_QUERY_PARAM_REGEX)
|
||||
.method(GET).build(), "getDatatableEntryByAppTableIdCommandStrategy");
|
||||
|
||||
+6
-3
@@ -20,10 +20,13 @@ package org.apache.fineract.batch.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class ClientDetailsNotFoundException extends AbstractPlatformDomainRuleException {
|
||||
public class BatchReferenceInvalidException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public ClientDetailsNotFoundException() {
|
||||
super("validation.msg.batch.jlg.no.clients.defined", "No Client details found", "");
|
||||
public BatchReferenceInvalidException() {
|
||||
super("validation.msg.batch.root.invalid", "Root request not found");
|
||||
}
|
||||
|
||||
public BatchReferenceInvalidException(Long reference) {
|
||||
super("validation.msg.batch.reference.invalid", "Referenced request not found", reference);
|
||||
}
|
||||
}
|
||||
@@ -21,17 +21,31 @@ package org.apache.fineract.batch.exception;
|
||||
import static org.springframework.core.ResolvableType.forClassWithGenerics;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.naming.AuthenticationException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.fortuna.ical4j.validate.ValidationException;
|
||||
import org.apache.commons.collections4.SetUtils;
|
||||
import org.apache.fineract.batch.domain.Header;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.DefaultExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -55,7 +69,8 @@ public final class ErrorHandler {
|
||||
|
||||
private static final Gson JSON_HELPER = GoogleGsonSerializerHelper.createGsonBuilder(true).create();
|
||||
|
||||
private <T extends RuntimeException> ExceptionMapper<T> findMostSpecificExceptionHandler(T exception) {
|
||||
@NotNull
|
||||
public <T extends RuntimeException> ExceptionMapper<T> findMostSpecificExceptionHandler(T exception) {
|
||||
Class<?> clazz = exception.getClass();
|
||||
do {
|
||||
Set<String> exceptionMappers = createSet(ctx.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class, clazz)));
|
||||
@@ -71,6 +86,60 @@ public final class ErrorHandler {
|
||||
return (ExceptionMapper<T>) defaultExceptionMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object of ErrorInfo type containing the information regarding the raised error.
|
||||
*
|
||||
* @param exception
|
||||
* @return ErrorInfo
|
||||
*/
|
||||
public ErrorInfo handle(@NotNull RuntimeException exception) {
|
||||
ExceptionMapper<RuntimeException> exceptionMapper = findMostSpecificExceptionHandler(exception);
|
||||
Response response = exceptionMapper.toResponse(exception);
|
||||
MultivaluedMap<String, Object> headers = response.getHeaders();
|
||||
Set<Header> batchHeaders = headers == null ? null
|
||||
: headers.keySet().stream().map(e -> new Header(e, response.getHeaderString(e))).collect(Collectors.toSet());
|
||||
return new ErrorInfo(response.getStatus(), ((FineractExceptionMapper) exceptionMapper).errorCode(),
|
||||
JSON_HELPER.toJson(response.getEntity()), batchHeaders);
|
||||
}
|
||||
|
||||
public RuntimeException getMappable(@NotNull Throwable thr) {
|
||||
return getMappable(thr, null, null, null);
|
||||
}
|
||||
|
||||
public RuntimeException getMappable(@NotNull Throwable t, String msgCode, String defaultMsg, String param,
|
||||
final Object... defaultMsgArgs) {
|
||||
String msg = defaultMsg == null ? t.getMessage() : defaultMsg;
|
||||
param = param == null ? "unknown" : param;
|
||||
if (t instanceof NestedRuntimeException nre) {
|
||||
Throwable cause = nre.getMostSpecificCause();
|
||||
msg = cause.getMessage();
|
||||
if (nre instanceof NonTransientDataAccessException) {
|
||||
msgCode = msgCode == null ? "error.msg." + param + ".data.integrity.issue" : msgCode;
|
||||
return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs);
|
||||
}
|
||||
}
|
||||
if (t instanceof ValidationException) {
|
||||
msgCode = msgCode == null ? "error.msg.validation." + param + ".error" : msgCode;
|
||||
return new PlatformApiDataValidationException(List.of(ApiParameterError.parameterError(msgCode, msg, param, defaultMsgArgs)));
|
||||
}
|
||||
if (t instanceof PersistenceException) {
|
||||
msgCode = msgCode == null ? "error.msg.persistence." + param + ".error" : msgCode;
|
||||
return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs);
|
||||
}
|
||||
if (t instanceof AuthenticationException) {
|
||||
msgCode = msgCode == null ? "error.msg.authentication." + param + ".error" : msgCode;
|
||||
return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs);
|
||||
}
|
||||
if (t instanceof RuntimeException re) {
|
||||
return re;
|
||||
}
|
||||
if (t instanceof ParseException) {
|
||||
msgCode = msgCode == null ? "error.msg.parse." + param + ".error" : msgCode;
|
||||
return new PlatformDataIntegrityException(msgCode, msg, param, defaultMsgArgs);
|
||||
}
|
||||
return new RuntimeException(msg, t);
|
||||
}
|
||||
|
||||
private <T> Set<T> createSet(T[] array) {
|
||||
if (array == null) {
|
||||
return Set.of();
|
||||
@@ -78,17 +147,4 @@ public final class ErrorHandler {
|
||||
return Set.of(array);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object of ErrorInfo type containing the information regarding the raised error.
|
||||
*
|
||||
* @param exception
|
||||
* @return ErrorInfo
|
||||
*/
|
||||
public ErrorInfo handle(final RuntimeException exception) {
|
||||
ExceptionMapper<RuntimeException> exceptionMapper = findMostSpecificExceptionHandler(exception);
|
||||
Response response = exceptionMapper.toResponse(exception);
|
||||
return new ErrorInfo(response.getStatus(), ((FineractExceptionMapper) exceptionMapper).errorCode(),
|
||||
JSON_HELPER.toJson(response.getEntity()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
*/
|
||||
package org.apache.fineract.batch.exception;
|
||||
|
||||
import static lombok.AccessLevel.PROTECTED;
|
||||
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.batch.domain.Header;
|
||||
|
||||
/**
|
||||
* Provides members to hold the basic information about the exceptions raised in commandStrategy classes.
|
||||
*
|
||||
@@ -25,84 +34,14 @@ package org.apache.fineract.batch.exception;
|
||||
*
|
||||
* @see ErrorHandler
|
||||
*/
|
||||
@Getter
|
||||
@Setter(PROTECTED)
|
||||
@NoArgsConstructor(access = PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public final class ErrorInfo {
|
||||
|
||||
private Integer statusCode;
|
||||
private Integer errorCode;
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Constructor to initialize the members of this class.
|
||||
*
|
||||
* @param statusCode
|
||||
* @param errorCode
|
||||
* @param message
|
||||
*/
|
||||
public ErrorInfo(final Integer statusCode, final Integer errorCode, final String message) {
|
||||
|
||||
this.statusCode = statusCode;
|
||||
this.errorCode = errorCode;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor so JSON serialization will work with out special Serialiazer
|
||||
*/
|
||||
ErrorInfo() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method to provide the statusCode for an object of this type.
|
||||
*
|
||||
* @return Integer
|
||||
*/
|
||||
public Integer getStatusCode() {
|
||||
return this.statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method to set the statusCode for an object of this type.
|
||||
*
|
||||
* @param statusCode
|
||||
*/
|
||||
public void setStatusCode(final Integer statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method to provide the errorCode for an object of this type.
|
||||
*
|
||||
* @return Integer
|
||||
*/
|
||||
public Integer getErrorCode() {
|
||||
return this.errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method to set the errorCode for an object of this type.
|
||||
*
|
||||
* @param errorCode
|
||||
*/
|
||||
public void setErrorCode(final Integer errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method to provide the message of the error for an object of this type.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method to set the message of the error for an object of this type.
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
public void setMessage(final String message) {
|
||||
this.message = message;
|
||||
}
|
||||
private Set<Header> headers;
|
||||
}
|
||||
|
||||
+165
-172
@@ -18,23 +18,23 @@
|
||||
*/
|
||||
package org.apache.fineract.batch.service;
|
||||
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
|
||||
import static org.apache.http.HttpStatus.SC_OK;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.jayway.jsonpath.JsonPathException;
|
||||
import io.github.resilience4j.core.functions.Either;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -47,25 +47,25 @@ 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.batch.domain.Header;
|
||||
import org.apache.fineract.batch.exception.ClientDetailsNotFoundException;
|
||||
import org.apache.fineract.batch.exception.BatchReferenceInvalidException;
|
||||
import org.apache.fineract.batch.exception.ErrorHandler;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode;
|
||||
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionExecution;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.TransactionSystemException;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
/**
|
||||
@@ -94,6 +94,64 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
@PersistenceContext
|
||||
private final EntityManager entityManager;
|
||||
|
||||
/**
|
||||
* Run each request root step in a separated transaction
|
||||
*
|
||||
* @param requestList
|
||||
* @param uriInfo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) {
|
||||
BatchRequestContextHolder.setEnclosingTransaction(Optional.empty());
|
||||
return handleBatchRequests(false, requestList, uriInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the batch request in transaction
|
||||
*
|
||||
* @param requestList
|
||||
* @param uriInfo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> requestList, final UriInfo uriInfo) {
|
||||
return callInTransaction(Function.identity()::apply, () -> handleBatchRequests(true, requestList, uriInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to run the command in transaction
|
||||
*
|
||||
* @param request
|
||||
* the enclosing supplier of the command
|
||||
* @param transactionConfigurator
|
||||
* consumer to configure the transaction behavior and isolation
|
||||
* @return
|
||||
*/
|
||||
private List<BatchResponse> callInTransaction(Consumer<TransactionTemplate> transactionConfigurator,
|
||||
Supplier<List<BatchResponse>> request) {
|
||||
List<BatchResponse> responseList = new ArrayList<>();
|
||||
try {
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionConfigurator.accept(transactionTemplate);
|
||||
return transactionTemplate.execute(status -> {
|
||||
BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status));
|
||||
try {
|
||||
responseList.addAll(request.get());
|
||||
return responseList;
|
||||
} catch (BatchExecutionException ex) {
|
||||
status.setRollbackOnly();
|
||||
return List.of(buildErrorResponse(ex.getCause(), ex.getRequest()));
|
||||
} catch (RuntimeException ex) {
|
||||
status.setRollbackOnly();
|
||||
return buildErrorResponses(ex, responseList);
|
||||
}
|
||||
});
|
||||
} catch (TransactionException | NonTransientDataAccessException ex) {
|
||||
return buildErrorResponses(ex, responseList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response list by getting a proper {@link org.apache.fineract.batch.command.CommandStrategy}.
|
||||
* execute() method of acquired commandStrategy is then provided with the separate Request.
|
||||
@@ -104,29 +162,24 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
*/
|
||||
private List<BatchResponse> handleBatchRequests(boolean enclosingTransaction, final List<BatchRequest> requestList,
|
||||
final UriInfo uriInfo) {
|
||||
|
||||
final List<BatchResponse> responseList = new ArrayList<>(requestList.size());
|
||||
|
||||
final List<BatchRequestNode> batchRequestNodes = this.resolutionHelper.getDependingRequests(requestList);
|
||||
if (batchRequestNodes.isEmpty()) {
|
||||
final BatchResponse response = new BatchResponse();
|
||||
ErrorInfo ex = errorHandler.handle(new ClientDetailsNotFoundException());
|
||||
response.setStatusCode(500);
|
||||
response.setBody(ex.getMessage());
|
||||
responseList.add(response);
|
||||
return responseList;
|
||||
final List<BatchRequestNode> rootNodes;
|
||||
try {
|
||||
rootNodes = this.resolutionHelper.buildNodesTree(requestList);
|
||||
} catch (BatchReferenceInvalidException e) {
|
||||
return List.of(buildErrorResponse(e));
|
||||
}
|
||||
|
||||
for (BatchRequestNode rootNode : batchRequestNodes) {
|
||||
final ArrayList<BatchResponse> responseList = new ArrayList<>(requestList.size());
|
||||
for (BatchRequestNode rootNode : rootNodes) {
|
||||
if (enclosingTransaction) {
|
||||
this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo, enclosingTransaction);
|
||||
} else {
|
||||
List<BatchResponse> localResponseList = new ArrayList<>();
|
||||
ArrayList<BatchResponse> localResponseList = new ArrayList<>();
|
||||
this.callRequestRecursive(rootNode.getRequest(), rootNode, localResponseList, uriInfo, enclosingTransaction);
|
||||
responseList.addAll(localResponseList);
|
||||
}
|
||||
}
|
||||
Collections.sort(responseList, Comparator.comparing(BatchResponse::getRequestId));
|
||||
responseList.sort(Comparator.comparing(BatchResponse::getRequestId));
|
||||
return responseList;
|
||||
}
|
||||
|
||||
@@ -143,7 +196,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
*/
|
||||
private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List<BatchResponse> responseList, UriInfo uriInfo,
|
||||
boolean enclosingTransaction) {
|
||||
// 1. run current node
|
||||
// run current node
|
||||
BatchResponse response;
|
||||
if (enclosingTransaction) {
|
||||
response = executeRequest(request, uriInfo);
|
||||
@@ -154,54 +207,28 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
response = transactionResponse.get(0);
|
||||
}
|
||||
responseList.add(response);
|
||||
if (response.getStatusCode() != null && response.getStatusCode() == 200) {
|
||||
requestNode.getChildRequests().forEach(childNode -> {
|
||||
if (response.getStatusCode() != null && response.getStatusCode() == SC_OK) {
|
||||
// run child nodes
|
||||
requestNode.getChildNodes().forEach(childNode -> {
|
||||
BatchRequest childRequest = childNode.getRequest();
|
||||
BatchRequest resolvedChildRequest;
|
||||
try {
|
||||
resolvedChildRequest = this.resolutionHelper.resoluteRequest(childNode.getRequest(), response);
|
||||
} catch (JsonPathException jsonPathException) {
|
||||
BatchResponse childResponse = new BatchResponse().setRequestId(childNode.getRequest().getRequestId())
|
||||
.setHeaders(childNode.getRequest().getHeaders()).setStatusCode(BAD_REQUEST.value())
|
||||
.setBody(jsonPathException.getMessage());
|
||||
responseList.add(childResponse);
|
||||
resolvedChildRequest = this.resolutionHelper.resolveRequest(childRequest, response);
|
||||
} catch (JsonPathException jpex) {
|
||||
responseList.add(buildErrorResponse(jpex, childRequest));
|
||||
return;
|
||||
} catch (RuntimeException ex) {
|
||||
throw new BatchExecutionException(childNode.getRequest(), ex, errorHandler.handle(ex));
|
||||
throw new BatchExecutionException(childRequest, ex);
|
||||
}
|
||||
callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo, enclosingTransaction);
|
||||
});
|
||||
} else {
|
||||
responseList.addAll(parentRequestFailedRecursive(request, requestNode));
|
||||
responseList.addAll(parentRequestFailedRecursive(request, requestNode, response, true));
|
||||
}
|
||||
// If the current request fails, then all the child requests are not executed. If we want to write out all the
|
||||
// child requests, here is the place.
|
||||
}
|
||||
|
||||
/**
|
||||
* All requests recursively are set to status 409 if the parent request fails.
|
||||
*
|
||||
* @param request
|
||||
* the current request
|
||||
* @param requestNode
|
||||
* the current request node
|
||||
* @return {@code BatchResponse} list of the generated batch responses
|
||||
*/
|
||||
private List<BatchResponse> parentRequestFailedRecursive(BatchRequest request, BatchRequestNode requestNode) {
|
||||
List<BatchResponse> responseList = new ArrayList<>();
|
||||
BatchRequestContextHolder.getEnclosingTransaction().ifPresent(TransactionExecution::setRollbackOnly);
|
||||
BatchResponse errorResponse = new BatchResponse();
|
||||
errorResponse.setRequestId(request.getRequestId());
|
||||
errorResponse.setStatusCode(Status.CONFLICT.getStatusCode());
|
||||
|
||||
// Some detail information about the error
|
||||
final ErrorInfo conflictError = new ErrorInfo(Status.CONFLICT.getStatusCode(), 8001,
|
||||
"Parent request with id " + request.getRequestId() + " was erroneous!");
|
||||
errorResponse.setBody(conflictError.getMessage());
|
||||
requestNode.getChildRequests().stream()
|
||||
.flatMap(childNode -> parentRequestFailedRecursive(childNode.getRequest(), childNode).stream()).forEach(responseList::add);
|
||||
return responseList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the request
|
||||
*
|
||||
@@ -215,7 +242,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
log.debug("Batch request: method [{}], relative url [{}]", request.getMethod(), request.getRelativeUrl());
|
||||
Either<RuntimeException, BatchRequest> preprocessorResult = runPreprocessors(request);
|
||||
if (preprocessorResult.isLeft()) {
|
||||
throw new BatchExecutionException(request, preprocessorResult.getLeft(), errorHandler.handle(preprocessorResult.getLeft()));
|
||||
throw new BatchExecutionException(request, preprocessorResult.getLeft());
|
||||
} else {
|
||||
request = preprocessorResult.get();
|
||||
}
|
||||
@@ -224,13 +251,8 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
.map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue)))
|
||||
.orElse(Collections.emptyMap())));
|
||||
BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute);
|
||||
if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
|
||||
if (BatchRequestContextHolder.getEnclosingTransaction().get().isRollbackOnly()) {
|
||||
BatchResponse br = new BatchResponse();
|
||||
br.setBody("Parent request was erroneous!");
|
||||
br.setRequestId(request.getRequestId());
|
||||
return br;
|
||||
}
|
||||
Optional<TransactionStatus> transaction = BatchRequestContextHolder.getEnclosingTransaction();
|
||||
if (transaction.isPresent()) {
|
||||
entityManager.flush();
|
||||
}
|
||||
final BatchResponse rootResponse = callHandler.serviceCall(request, uriInfo);
|
||||
@@ -238,9 +260,9 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
request.getRelativeUrl());
|
||||
return rootResponse;
|
||||
} catch (AbstractIdempotentCommandException idempotentException) {
|
||||
return handleIdempotentRequests(idempotentException, request);
|
||||
return buildErrorResponse(idempotentException, request);
|
||||
} catch (RuntimeException ex) {
|
||||
throw new BatchExecutionException(request, ex, errorHandler.handle(ex));
|
||||
throw new BatchExecutionException(request, ex);
|
||||
} finally {
|
||||
BatchRequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
@@ -266,126 +288,97 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the idempotent response when idempotent exception raised
|
||||
* All requests recursively are set to status 409 if the parent request fails.
|
||||
*
|
||||
* @param idempotentException
|
||||
* the idempotent exception
|
||||
* @param request
|
||||
* the called request
|
||||
* @return
|
||||
* the current request
|
||||
* @param requestNode
|
||||
* the current request node
|
||||
* @return {@code BatchResponse} list of the generated batch responses
|
||||
*/
|
||||
private BatchResponse handleIdempotentRequests(AbstractIdempotentCommandException idempotentException, BatchRequest request) {
|
||||
BatchResponse response = new BatchResponse();
|
||||
response.setRequestId(request.getRequestId());
|
||||
response.setHeaders(Optional.ofNullable(request.getHeaders()).orElse(new HashSet<>()));
|
||||
response.getHeaders().add(new Header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true"));
|
||||
response.setBody(idempotentException.getResponse());
|
||||
if (idempotentException instanceof IdempotentCommandProcessSucceedException) {
|
||||
response.setStatusCode(200);
|
||||
} else if (idempotentException instanceof IdempotentCommandProcessUnderProcessingException) {
|
||||
response.setStatusCode(409);
|
||||
} else if (idempotentException instanceof IdempotentCommandProcessFailedException) {
|
||||
response.setStatusCode(((IdempotentCommandProcessFailedException) idempotentException).getStatusCode());
|
||||
private List<BatchResponse> parentRequestFailedRecursive(BatchRequest request, BatchRequestNode requestNode, BatchResponse response,
|
||||
boolean root) {
|
||||
List<BatchResponse> responseList = new ArrayList<>();
|
||||
if (root) {
|
||||
BatchRequestContextHolder.getEnclosingTransaction().ifPresent(TransactionExecution::setRollbackOnly);
|
||||
} else {
|
||||
response.setStatusCode(500);
|
||||
responseList.add(buildErrorResponse(request.getRequestId(), response.getStatusCode(), response.getBody(), null));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run each request root step in a separated transaction
|
||||
*
|
||||
* @param requestList
|
||||
* @param uriInfo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) {
|
||||
BatchRequestContextHolder.setEnclosingTransaction(Optional.empty());
|
||||
return handleBatchRequests(false, requestList, uriInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the batch request in transaction
|
||||
*
|
||||
* @param requestList
|
||||
* @param uriInfo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> requestList, final UriInfo uriInfo) {
|
||||
return callInTransaction(() -> handleBatchRequests(true, requestList, uriInfo));
|
||||
requestNode.getChildNodes().forEach(
|
||||
childNode -> responseList.addAll(parentRequestFailedRecursive(childNode.getRequest(), childNode, response, false)));
|
||||
return responseList;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<BatchResponse> createErrorResponse(List<BatchResponse> responseList, int statusCode) {
|
||||
BatchResponse errResponse = new BatchResponse();
|
||||
|
||||
for (BatchResponse res : responseList) {
|
||||
if (res.getStatusCode() == null || !res.getStatusCode().equals(200)) {
|
||||
errResponse.setBody("Transaction is being rolled back. First erroneous request: \n" + new Gson().toJson(res));
|
||||
errResponse.setRequestId(res.getRequestId());
|
||||
if (statusCode == -1) {
|
||||
if (res.getStatusCode() != null) {
|
||||
statusCode = res.getStatusCode();
|
||||
} else {
|
||||
statusCode = Status.INTERNAL_SERVER_ERROR.getStatusCode();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
errResponse.setStatusCode(statusCode);
|
||||
return Arrays.asList(errResponse);
|
||||
}
|
||||
|
||||
private List<BatchResponse> callInTransaction(Supplier<List<BatchResponse>> request) {
|
||||
return callInTransaction(Function.identity()::apply, request);
|
||||
private BatchResponse buildErrorResponse(@NotNull Throwable ex) {
|
||||
return buildErrorResponse(ex, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to run the command in transaction
|
||||
* Return the response when any exception raised
|
||||
*
|
||||
* @param ex
|
||||
* the exception
|
||||
* @param request
|
||||
* the enclosing supplier of the command
|
||||
* @param transactionConfigurator
|
||||
* consumer to configure the transaction behavior and isolation
|
||||
* @return
|
||||
* the called request
|
||||
*/
|
||||
private List<BatchResponse> callInTransaction(Consumer<TransactionTemplate> transactionConfigurator,
|
||||
Supplier<List<BatchResponse>> request) {
|
||||
List<BatchResponse> responseList = new ArrayList<>();
|
||||
try {
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionConfigurator.accept(transactionTemplate);
|
||||
return transactionTemplate.execute(status -> {
|
||||
BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status));
|
||||
try {
|
||||
responseList.addAll(request.get());
|
||||
if (status.isRollbackOnly()) {
|
||||
return createErrorResponse(responseList, -1);
|
||||
}
|
||||
return responseList;
|
||||
} catch (BatchExecutionException ex) {
|
||||
status.setRollbackOnly();
|
||||
BatchResponse errResponse = new BatchResponse();
|
||||
errResponse.setStatusCode(ex.getErrorInfo().getStatusCode());
|
||||
errResponse.setRequestId(ex.getRequest().getRequestId());
|
||||
errResponse.setBody(ex.getErrorInfo().getMessage());
|
||||
return Arrays.asList(errResponse);
|
||||
} catch (RuntimeException ex) {
|
||||
status.setRollbackOnly();
|
||||
ErrorInfo e = errorHandler.handle(ex);
|
||||
BatchResponse errResponse = new BatchResponse();
|
||||
errResponse.setStatusCode(e.getStatusCode());
|
||||
errResponse.setBody(e.getMessage());
|
||||
return Arrays.asList(errResponse);
|
||||
}
|
||||
});
|
||||
} catch (TransactionException | NonTransientDataAccessException ex) {
|
||||
ErrorInfo e = errorHandler.handle(ex);
|
||||
return createErrorResponse(responseList, e.getStatusCode());
|
||||
private BatchResponse buildErrorResponse(Throwable ex, BatchRequest request) {
|
||||
Long requestId = null;
|
||||
Integer statusCode = null;
|
||||
String body = null;
|
||||
Set<Header> headers = new HashSet<>();
|
||||
if (ex != null) {
|
||||
ErrorInfo errorInfo = errorHandler.handle(errorHandler.getMappable(ex));
|
||||
statusCode = errorInfo.getStatusCode();
|
||||
body = errorInfo.getMessage();
|
||||
headers = Optional.ofNullable(errorInfo.getHeaders()).orElse(new HashSet<>());
|
||||
}
|
||||
if (request != null) {
|
||||
requestId = request.getRequestId();
|
||||
if (request.getHeaders() != null) {
|
||||
headers.addAll(request.getHeaders());
|
||||
}
|
||||
}
|
||||
return buildErrorResponse(requestId, statusCode, body, headers);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<BatchResponse> buildErrorResponses(Throwable ex, @NotNull List<BatchResponse> responseList) {
|
||||
BatchResponse response = responseList.isEmpty() ? null
|
||||
: responseList.stream().filter(e -> e.getStatusCode() == null || e.getStatusCode() != SC_OK).findFirst()
|
||||
.orElse(responseList.get(responseList.size() - 1));
|
||||
|
||||
if (response != null && response.getStatusCode() == SC_OK && ex instanceof TransactionSystemException tse) {
|
||||
ex = new ConcurrencyFailureException(tse.getMessage(), tse.getCause());
|
||||
}
|
||||
|
||||
Long requestId = null;
|
||||
Integer statusCode = null;
|
||||
String body = null;
|
||||
Set<Header> headers = new HashSet<>();
|
||||
if (ex != null) {
|
||||
ErrorInfo errorInfo = errorHandler.handle(errorHandler.getMappable(ex));
|
||||
statusCode = errorInfo.getStatusCode();
|
||||
body = errorInfo.getMessage();
|
||||
headers = Optional.ofNullable(errorInfo.getHeaders()).orElse(new HashSet<>());
|
||||
}
|
||||
if (response != null) {
|
||||
requestId = response.getRequestId();
|
||||
if (response.getStatusCode() == null || response.getStatusCode() != SC_OK) {
|
||||
if (response.getStatusCode() != null) {
|
||||
statusCode = response.getStatusCode();
|
||||
}
|
||||
body = "Transaction is being rolled back. First erroneous request: \n" + new Gson().toJson(response);
|
||||
}
|
||||
if (response.getHeaders() != null) {
|
||||
headers.addAll(response.getHeaders());
|
||||
}
|
||||
}
|
||||
return List.of(buildErrorResponse(requestId, statusCode, body, headers));
|
||||
}
|
||||
|
||||
private BatchResponse buildErrorResponse(Long requestId, Integer statusCode, String body, Set<Header> headers) {
|
||||
return new BatchResponse().setRequestId(requestId).setStatusCode(statusCode == null ? SC_INTERNAL_SERVER_ERROR : statusCode)
|
||||
.setBody(body == null ? "Request with id " + requestId + " was erroneous!" : body).setHeaders(headers);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -20,17 +20,14 @@ package org.apache.fineract.batch.service;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.batch.domain.BatchRequest;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
|
||||
@Getter
|
||||
public class BatchExecutionException extends RuntimeException {
|
||||
|
||||
private final BatchRequest request;
|
||||
private final ErrorInfo errorInfo;
|
||||
|
||||
public BatchExecutionException(BatchRequest request, RuntimeException ex, ErrorInfo errorInfo) {
|
||||
public BatchExecutionException(BatchRequest request, RuntimeException ex) {
|
||||
super("Error executing batch request: " + request, ex);
|
||||
this.request = request;
|
||||
this.errorInfo = errorInfo;
|
||||
}
|
||||
}
|
||||
|
||||
+43
-57
@@ -30,6 +30,7 @@ import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.batch.domain.BatchRequest;
|
||||
import org.apache.fineract.batch.domain.BatchResponse;
|
||||
import org.apache.fineract.batch.exception.BatchReferenceInvalidException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -55,23 +56,19 @@ public class ResolutionHelper {
|
||||
private BatchRequest request;
|
||||
private final List<BatchRequestNode> childRequests = new ArrayList<>();
|
||||
|
||||
public BatchRequestNode() {
|
||||
|
||||
public BatchRequestNode(BatchRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public BatchRequest getRequest() {
|
||||
return this.request;
|
||||
}
|
||||
|
||||
public void setRequest(BatchRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public List<BatchRequestNode> getChildRequests() {
|
||||
public List<BatchRequestNode> getChildNodes() {
|
||||
return this.childRequests;
|
||||
}
|
||||
|
||||
public void addChildRequest(final BatchRequestNode batchRequest) {
|
||||
public void addChildNode(final BatchRequestNode batchRequest) {
|
||||
this.childRequests.add(batchRequest);
|
||||
}
|
||||
|
||||
@@ -84,35 +81,37 @@ public class ResolutionHelper {
|
||||
* different list is identified with a "Key" which is the "requestId" of the request at topmost level in dependency
|
||||
* hierarchy of that particular list.
|
||||
*
|
||||
* @param batchRequests
|
||||
* @param requests
|
||||
* @return List<ArrayList<BatchRequestNode>>
|
||||
*/
|
||||
public List<BatchRequestNode> getDependingRequests(final List<BatchRequest> batchRequests) {
|
||||
final List<BatchRequestNode> rootRequests = new ArrayList<>();
|
||||
|
||||
for (BatchRequest batchRequest : batchRequests) {
|
||||
if (batchRequest.getReference() == null) {
|
||||
final BatchRequestNode node = new BatchRequestNode();
|
||||
node.setRequest(batchRequest);
|
||||
rootRequests.add(node);
|
||||
public List<BatchRequestNode> buildNodesTree(final List<BatchRequest> requests) {
|
||||
final List<BatchRequestNode> rootNodes = new ArrayList<>();
|
||||
for (BatchRequest request : requests) {
|
||||
if (request.getReference() == null) {
|
||||
final BatchRequestNode node = new BatchRequestNode(request);
|
||||
rootNodes.add(node);
|
||||
} else {
|
||||
this.addDependingRequest(batchRequest, rootRequests);
|
||||
if (!addDependingRequest(request, rootNodes)) {
|
||||
throw new BatchReferenceInvalidException(request.getReference());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rootRequests;
|
||||
return rootNodes;
|
||||
}
|
||||
|
||||
private void addDependingRequest(final BatchRequest batchRequest, final List<BatchRequestNode> parentRequests) {
|
||||
for (BatchRequestNode batchRequestNode : parentRequests) {
|
||||
if (batchRequestNode.getRequest().getRequestId().equals(batchRequest.getReference())) {
|
||||
final BatchRequestNode dependingRequest = new BatchRequestNode();
|
||||
dependingRequest.setRequest(batchRequest);
|
||||
batchRequestNode.addChildRequest(dependingRequest);
|
||||
private boolean addDependingRequest(final BatchRequest request, final List<BatchRequestNode> parentNodes) {
|
||||
for (BatchRequestNode parentNode : parentNodes) {
|
||||
if (parentNode.getRequest().getRequestId().equals(request.getReference())) {
|
||||
final BatchRequestNode childNode = new BatchRequestNode(request);
|
||||
parentNode.addChildNode(childNode);
|
||||
return true;
|
||||
} else {
|
||||
addDependingRequest(batchRequest, batchRequestNode.getChildRequests());
|
||||
if (addDependingRequest(request, parentNode.getChildNodes())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,34 +122,27 @@ public class ResolutionHelper {
|
||||
* @param parentResponse
|
||||
* @return BatchRequest
|
||||
*/
|
||||
public BatchRequest resoluteRequest(final BatchRequest request, final BatchResponse parentResponse) {
|
||||
|
||||
// Create a duplicate request
|
||||
final BatchRequest br = request;
|
||||
|
||||
public BatchRequest resolveRequest(final BatchRequest request, final BatchResponse parentResponse) {
|
||||
final ReadContext responseCtx = JsonPath.parse(parentResponse.getBody());
|
||||
|
||||
// Gets the body from current Request as a JsonObject
|
||||
final JsonObject jsonRequestBody = this.fromJsonHelper.parse(request.getBody()).getAsJsonObject();
|
||||
|
||||
JsonObject jsonResultBody = new JsonObject();
|
||||
|
||||
// Iterate through each element in the requestBody to find dependent
|
||||
// parameter
|
||||
for (Map.Entry<String, JsonElement> element : jsonRequestBody.entrySet()) {
|
||||
final String key = element.getKey();
|
||||
final JsonElement value = resolveDependentVariables(element, responseCtx);
|
||||
jsonResultBody.add(key, value);
|
||||
String requestBody = request.getBody();
|
||||
if (requestBody != null) {
|
||||
final JsonObject jsonRequestBody = this.fromJsonHelper.parse(requestBody).getAsJsonObject();
|
||||
JsonObject jsonResultBody = new JsonObject();
|
||||
// Iterate through each element in the requestBody to find dependent
|
||||
// parameter
|
||||
for (Map.Entry<String, JsonElement> element : jsonRequestBody.entrySet()) {
|
||||
final String key = element.getKey();
|
||||
final JsonElement value = resolveDependentVariables(element, responseCtx);
|
||||
jsonResultBody.add(key, value);
|
||||
}
|
||||
// Set the body after dependency resolution
|
||||
request.setBody(jsonResultBody.toString());
|
||||
}
|
||||
|
||||
// Set the body after dependency resolution
|
||||
br.setBody(jsonResultBody.toString());
|
||||
|
||||
// Also check the relativeUrl for any dependency resolution
|
||||
String relativeUrl = request.getRelativeUrl();
|
||||
|
||||
if (relativeUrl.contains("$.")) {
|
||||
|
||||
String queryParams = "";
|
||||
if (relativeUrl.contains("?")) {
|
||||
queryParams = relativeUrl.substring(relativeUrl.indexOf("?"));
|
||||
@@ -158,24 +150,21 @@ public class ResolutionHelper {
|
||||
}
|
||||
|
||||
final Iterable<String> parameters = Splitter.on('/').split(relativeUrl);
|
||||
|
||||
for (String parameter : parameters) {
|
||||
if (parameter.contains("$.")) {
|
||||
final String resParamValue = responseCtx.read(parameter).toString();
|
||||
relativeUrl = relativeUrl.replace(parameter, resParamValue);
|
||||
br.setRelativeUrl(relativeUrl + queryParams);
|
||||
request.setRelativeUrl(relativeUrl + queryParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return br;
|
||||
return request;
|
||||
}
|
||||
|
||||
private JsonElement resolveDependentVariables(final Map.Entry<String, JsonElement> entryElement, final ReadContext responseCtx) {
|
||||
JsonElement value = null;
|
||||
|
||||
JsonElement value;
|
||||
final JsonElement element = entryElement.getValue();
|
||||
|
||||
if (element.isJsonObject()) {
|
||||
final JsonObject jsObject = element.getAsJsonObject();
|
||||
value = processJsonObject(jsObject, responseCtx);
|
||||
@@ -202,16 +191,13 @@ public class ResolutionHelper {
|
||||
}
|
||||
|
||||
private JsonArray processJsonArray(final JsonArray elementArray, final ReadContext responseCtx) {
|
||||
|
||||
JsonArray valueArr = new JsonArray();
|
||||
|
||||
for (JsonElement element : elementArray) {
|
||||
if (element.isJsonObject()) {
|
||||
final JsonObject jsObject = element.getAsJsonObject();
|
||||
valueArr.add(processJsonObject(jsObject, responseCtx));
|
||||
}
|
||||
}
|
||||
|
||||
return valueArr;
|
||||
}
|
||||
|
||||
|
||||
+69
-63
@@ -123,16 +123,6 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
@Column(name = "result_status_code")
|
||||
private Integer resultStatusCode;
|
||||
|
||||
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker,
|
||||
String idempotencyKey, Integer status) {
|
||||
return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
|
||||
command.json(), maker, idempotencyKey, status);
|
||||
}
|
||||
|
||||
protected CommandSource() {
|
||||
//
|
||||
}
|
||||
|
||||
private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId,
|
||||
final Long subResourceId, final String commandSerializedAsJson, final AppUser maker, final String idempotencyKey,
|
||||
final Integer status) {
|
||||
@@ -148,6 +138,26 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
this.idempotencyKey = idempotencyKey;
|
||||
}
|
||||
|
||||
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker,
|
||||
String idempotencyKey, Integer status) {
|
||||
CommandSource commandSource = new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(),
|
||||
command.subentityId(), command.json(), maker, idempotencyKey, status);
|
||||
commandSource.officeId = wrapper.getOfficeId();
|
||||
commandSource.groupId = command.getGroupId();
|
||||
commandSource.clientId = command.getClientId();
|
||||
commandSource.loanId = command.getLoanId();
|
||||
commandSource.savingsId = command.getSavingsId();
|
||||
commandSource.productId = command.getProductId();
|
||||
commandSource.transactionId = command.getTransactionId();
|
||||
commandSource.creditBureauId = command.getCreditBureauId();
|
||||
commandSource.organisationCreditBureauId = command.getOrganisationCreditBureauId();
|
||||
return commandSource;
|
||||
}
|
||||
|
||||
protected CommandSource() {
|
||||
//
|
||||
}
|
||||
|
||||
public Long getCreditBureauId() {
|
||||
return this.creditBureauId;
|
||||
}
|
||||
@@ -160,31 +170,27 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
return this.organisationCreditBureauId;
|
||||
}
|
||||
|
||||
public String getJobName() {
|
||||
return this.jobName;
|
||||
}
|
||||
|
||||
public void setOrganisationCreditBureauId(Long organisationCreditBureauId) {
|
||||
this.organisationCreditBureauId = organisationCreditBureauId;
|
||||
}
|
||||
|
||||
public void markAsChecked(final AppUser checker) {
|
||||
this.checker = checker;
|
||||
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision();
|
||||
this.status = CommandProcessingResultType.PROCESSED.getValue();
|
||||
public String getJobName() {
|
||||
return this.jobName;
|
||||
}
|
||||
|
||||
public void markAsRejected(final AppUser checker) {
|
||||
this.checker = checker;
|
||||
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision();
|
||||
this.status = CommandProcessingResultType.REJECTED.getValue();
|
||||
public Long getResourceId() {
|
||||
return this.resourceId;
|
||||
}
|
||||
|
||||
public void updateResourceId(final Long resourceId) {
|
||||
public void setResourceId(final Long resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public void updateSubResourceId(final Long subResourceId) {
|
||||
public Long getSubResourceId() {
|
||||
return this.subResourceId;
|
||||
}
|
||||
|
||||
public void setSubResourceId(final Long subResourceId) {
|
||||
this.subResourceId = subResourceId;
|
||||
}
|
||||
|
||||
@@ -196,14 +202,6 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
this.commandAsJson = json;
|
||||
}
|
||||
|
||||
public Long resourceId() {
|
||||
return this.resourceId;
|
||||
}
|
||||
|
||||
public Long subResourceId() {
|
||||
return this.subResourceId;
|
||||
}
|
||||
|
||||
public String getActionName() {
|
||||
return this.actionName;
|
||||
}
|
||||
@@ -216,36 +214,6 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
return this.actionName + "_" + this.entityName;
|
||||
}
|
||||
|
||||
public Long getResourceId() {
|
||||
return this.resourceId;
|
||||
}
|
||||
|
||||
public Long getSubResourceId() {
|
||||
return this.subResourceId;
|
||||
}
|
||||
|
||||
public void markAsAwaitingApproval() {
|
||||
this.status = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
|
||||
}
|
||||
|
||||
public boolean isMarkedAsAwaitingApproval() {
|
||||
return this.status.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue());
|
||||
}
|
||||
|
||||
public void updateForAudit(final CommandProcessingResult result) {
|
||||
this.officeId = result.getOfficeId();
|
||||
this.groupId = result.getGroupId();
|
||||
this.clientId = result.getClientId();
|
||||
this.loanId = result.getLoanId();
|
||||
this.savingsId = result.getSavingsId();
|
||||
this.productId = result.getProductId();
|
||||
this.transactionId = result.getTransactionId();
|
||||
this.resourceId = result.getResourceId();
|
||||
this.resourceExternalId = result.getResourceExternalId();
|
||||
this.subResourceId = result.getSubResourceId();
|
||||
this.subResourceExternalId = result.getSubResourceExternalId();
|
||||
}
|
||||
|
||||
public String getResourceGetUrl() {
|
||||
return this.resourceGetUrl;
|
||||
}
|
||||
@@ -296,7 +264,7 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
return this.transactionId;
|
||||
}
|
||||
|
||||
public void updateTransaction(final String transactionId) {
|
||||
public void setTransactionId(final String transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
@@ -324,6 +292,10 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public void setStatus(CommandProcessingResultType status) {
|
||||
setStatus(status == null ? null : status.getValue());
|
||||
}
|
||||
|
||||
public Integer getResultStatusCode() {
|
||||
return resultStatusCode;
|
||||
}
|
||||
@@ -331,4 +303,38 @@ public class CommandSource extends AbstractPersistableCustom {
|
||||
public void setResultStatusCode(Integer resultStatusCode) {
|
||||
this.resultStatusCode = resultStatusCode;
|
||||
}
|
||||
|
||||
public void markAsAwaitingApproval() {
|
||||
this.status = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
|
||||
}
|
||||
|
||||
public boolean isMarkedAsAwaitingApproval() {
|
||||
return this.status.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue());
|
||||
}
|
||||
|
||||
public void markAsChecked(final AppUser checker) {
|
||||
this.checker = checker;
|
||||
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision();
|
||||
this.status = CommandProcessingResultType.PROCESSED.getValue();
|
||||
}
|
||||
|
||||
public void markAsRejected(final AppUser checker) {
|
||||
this.checker = checker;
|
||||
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenantWithMostPrecision();
|
||||
this.status = CommandProcessingResultType.REJECTED.getValue();
|
||||
}
|
||||
|
||||
public void updateForAudit(final CommandProcessingResult result) {
|
||||
this.officeId = result.getOfficeId();
|
||||
this.groupId = result.getGroupId();
|
||||
this.clientId = result.getClientId();
|
||||
this.loanId = result.getLoanId();
|
||||
this.savingsId = result.getSavingsId();
|
||||
this.productId = result.getProductId();
|
||||
this.transactionId = result.getTransactionId();
|
||||
this.resourceId = result.getResourceId();
|
||||
this.resourceExternalId = result.getResourceExternalId();
|
||||
this.subResourceId = result.getSubResourceId();
|
||||
this.subResourceExternalId = result.getSubResourceExternalId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +302,10 @@ public class CommandWrapper {
|
||||
return this.taskPermissionName;
|
||||
}
|
||||
|
||||
public Long getOfficeId() {
|
||||
return officeId;
|
||||
}
|
||||
|
||||
public Long getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
+25
-24
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.apache.fineract.commands.service;
|
||||
|
||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -48,46 +47,48 @@ public class CommandSourceService {
|
||||
private final CommandSourceRepository commandSourceRepository;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
@NotNull
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||
public CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||
return saveInitialInternal(wrapper, jsonCommand, maker, idempotencyKey);
|
||||
}
|
||||
|
||||
public CommandSource saveInitialNoTransaction(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||
return saveInitialInternal(wrapper, jsonCommand, maker, idempotencyKey);
|
||||
public CommandSource saveInitialNewTransaction(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||
return saveInitial(wrapper, jsonCommand, maker, idempotencyKey);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CommandSource saveInitialInternal(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||
CommandSource initialCommandSource = getInitialCommandSource(wrapper, jsonCommand, maker, idempotencyKey);
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public CommandSource saveInitialSameTransaction(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||
return saveInitial(wrapper, jsonCommand, maker, idempotencyKey);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||
CommandSource initialCommandSource = getInitialCommandSource(wrapper, jsonCommand, maker, idempotencyKey);
|
||||
if (initialCommandSource.getCommandJson() == null) {
|
||||
initialCommandSource.setCommandJson("{}");
|
||||
}
|
||||
|
||||
return commandSourceRepository.saveAndFlush(initialCommandSource);
|
||||
}
|
||||
|
||||
public void saveFailed(CommandSource commandSource) {
|
||||
commandSource.setStatus(ERROR.getValue());
|
||||
commandSourceRepository.saveAndFlush(commandSource);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||
public CommandSource saveResult(CommandSource commandSource) {
|
||||
public CommandSource saveResultNewTransaction(@NotNull CommandSource commandSource) {
|
||||
return saveResult(commandSource);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public CommandSource saveResultSameTransaction(@NotNull CommandSource commandSource) {
|
||||
return saveResult(commandSource);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CommandSource saveResult(@NotNull CommandSource commandSource) {
|
||||
return commandSourceRepository.saveAndFlush(commandSource);
|
||||
}
|
||||
|
||||
public CommandSource saveResultNoTransaction(CommandSource commandSource) {
|
||||
return commandSourceRepository.saveAndFlush(commandSource);
|
||||
public ErrorInfo generateErrorInfo(Throwable t) {
|
||||
return errorHandler.handle(errorHandler.getMappable(t));
|
||||
}
|
||||
|
||||
public ErrorInfo generateErrorException(Throwable t) {
|
||||
if (t instanceof final RuntimeException e) {
|
||||
return errorHandler.handle(e);
|
||||
} else {
|
||||
return new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + "}");
|
||||
}
|
||||
public CommandSource getCommandSource(Long commandSourceId) {
|
||||
return commandSourceRepository.findById(commandSourceId).orElseThrow(() -> new CommandNotFoundException(commandSourceId));
|
||||
}
|
||||
|
||||
public CommandSource findCommandSource(CommandWrapper wrapper, String idempotencyKey) {
|
||||
|
||||
+3
-3
@@ -82,15 +82,15 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
||||
validateIsUpdateAllowed();
|
||||
|
||||
final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(makerCheckerId, commandSourceInput.getActionName(),
|
||||
commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subResourceId(),
|
||||
commandSourceInput.getEntityName(), commandSourceInput.getResourceId(), commandSourceInput.getSubResourceId(),
|
||||
commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getOfficeId(),
|
||||
commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(),
|
||||
commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(),
|
||||
commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getIdempotencyKey());
|
||||
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.getCommandJson());
|
||||
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.getCommandJson(), parsedCommand,
|
||||
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
|
||||
commandSourceInput.subResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(),
|
||||
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.getResourceId(),
|
||||
commandSourceInput.getSubResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(),
|
||||
commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(),
|
||||
commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(),
|
||||
commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName());
|
||||
|
||||
+61
-55
@@ -21,6 +21,7 @@ package org.apache.fineract.commands.service;
|
||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED;
|
||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
||||
import static org.apache.http.HttpStatus.SC_OK;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
@@ -32,6 +33,7 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.batch.exception.ErrorHandler;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||
import org.apache.fineract.commands.domain.CommandSource;
|
||||
@@ -79,6 +81,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
private final IdempotencyKeyResolver idempotencyKeyResolver;
|
||||
private final IdempotencyKeyGenerator idempotencyKeyGenerator;
|
||||
private final CommandSourceService commandSourceService;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
private final FineractRequestContextHolder fineractRequestContextHolder;
|
||||
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
|
||||
@@ -90,90 +93,95 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
// Do not store the idempotency key because of the exception handling
|
||||
setIdempotencyKeyStoreFlag(false);
|
||||
|
||||
final boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
|
||||
String idempotencyKey = idempotencyKeyResolver.resolve(wrapper);
|
||||
exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey);
|
||||
Long commandId = (Long) fineractRequestContextHolder.getAttribute(COMMAND_SOURCE_ID, null);
|
||||
boolean isRetry = commandId != null;
|
||||
|
||||
// Store idempotency key to the request attribute
|
||||
CommandSource savedCommandSource;
|
||||
if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
|
||||
savedCommandSource = commandSourceService.saveInitialNoTransaction(wrapper, command, context.authenticatedUser(wrapper),
|
||||
idempotencyKey);
|
||||
CommandSource commandSource = null;
|
||||
String idempotencyKey;
|
||||
if (isRetry) {
|
||||
commandSource = commandSourceService.getCommandSource(commandId);
|
||||
idempotencyKey = commandSource.getIdempotencyKey();
|
||||
} else {
|
||||
savedCommandSource = commandSourceService.saveInitial(wrapper, command, context.authenticatedUser(wrapper), idempotencyKey);
|
||||
idempotencyKey = idempotencyKeyResolver.resolve(wrapper);
|
||||
}
|
||||
exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey, isRetry);
|
||||
|
||||
boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent();
|
||||
if (commandSource == null) {
|
||||
AppUser user = context.authenticatedUser(wrapper);
|
||||
commandSource = sameTransaction ? commandSourceService.saveInitialSameTransaction(wrapper, command, user, idempotencyKey)
|
||||
: commandSourceService.saveInitialNewTransaction(wrapper, command, user, idempotencyKey);
|
||||
storeCommandIdInContext(commandSource); // Store command id as a request attribute
|
||||
}
|
||||
storeCommandToIdempotentFilter(savedCommandSource);
|
||||
setIdempotencyKeyStoreFlag(true);
|
||||
|
||||
final CommandProcessingResult result;
|
||||
try {
|
||||
result = findCommandHandler(wrapper).processCommand(command);
|
||||
} catch (Throwable t) { // NOSONAR
|
||||
CommandSource source = commandSourceService.findCommandSource(wrapper, idempotencyKey);
|
||||
commandSourceService.saveFailed(source);
|
||||
publishHookErrorEvent(wrapper, command, t);
|
||||
ErrorInfo errorInfo = commandSourceService.generateErrorInfo(t);
|
||||
commandSource.setResultStatusCode(errorInfo.getStatusCode());
|
||||
commandSource.setResult(errorInfo.getMessage());
|
||||
commandSource.setStatus(ERROR);
|
||||
commandSource = sameTransaction ? commandSourceService.saveResultSameTransaction(commandSource)
|
||||
: commandSourceService.saveResultNewTransaction(commandSource);
|
||||
publishHookErrorEvent(wrapper, command, errorInfo);
|
||||
throw t;
|
||||
}
|
||||
CommandSource initialCommandSource = commandSourceService.findCommandSource(wrapper, idempotencyKey);
|
||||
|
||||
initialCommandSource.setResult(toApiJsonSerializer.serializeResult(result));
|
||||
initialCommandSource.updateResourceId(result.getResourceId());
|
||||
initialCommandSource.updateForAudit(result);
|
||||
commandSource.updateForAudit(result);
|
||||
commandSource.setResult(toApiJsonSerializer.serializeResult(result));
|
||||
commandSource.setResultStatusCode(SC_OK);
|
||||
commandSource.setStatus(PROCESSED);
|
||||
|
||||
boolean rollBack = (rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker;
|
||||
if (result.hasChanges() && !rollBack) {
|
||||
initialCommandSource.setCommandJson(toApiJsonSerializer.serializeResult(result.getChanges()));
|
||||
boolean isRollback = !isApprovedByChecker && (result.isRollbackTransaction()
|
||||
|| configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName()));
|
||||
// TODO: this should be removed, can not override audit information (and maker-checker does not work)
|
||||
if (!isRollback && result.hasChanges()) {
|
||||
commandSource.setCommandJson(toApiJsonSerializer.serializeResult(result.getChanges()));
|
||||
}
|
||||
|
||||
initialCommandSource.setStatus(CommandProcessingResultType.PROCESSED.getValue());
|
||||
if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
|
||||
commandSourceService.saveResultNoTransaction(initialCommandSource);
|
||||
} else {
|
||||
commandSourceService.saveResult(initialCommandSource);
|
||||
}
|
||||
commandSource = commandSourceService.saveResultSameTransaction(commandSource);
|
||||
|
||||
if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) {
|
||||
if (isRollback) {
|
||||
/*
|
||||
* JournalEntry will generate a new transactionId every time. Updating the transactionId with old
|
||||
* transactionId, because as there are no entries are created with new transactionId, will throw an error
|
||||
* when checker approves the transaction
|
||||
*/
|
||||
initialCommandSource.updateTransaction(command.getTransactionId());
|
||||
/*
|
||||
* Update CommandSource json data with JsonCommand json data, line 77 and 81 may update the json data
|
||||
*/
|
||||
initialCommandSource.setCommandJson(command.json());
|
||||
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(initialCommandSource);
|
||||
commandSource.setTransactionId(command.getTransactionId());
|
||||
// TODO: this should be removed together with lines 133-135
|
||||
commandSource.setCommandJson(command.json()); // Set back CommandSource json data
|
||||
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSource);
|
||||
}
|
||||
result.setRollbackTransaction(null);
|
||||
|
||||
result.setRollbackTransaction(null);
|
||||
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void storeCommandToIdempotentFilter(CommandSource savedCommandSource) {
|
||||
private void storeCommandIdInContext(CommandSource savedCommandSource) {
|
||||
if (savedCommandSource.getId() == null) {
|
||||
throw new IllegalStateException("Command source not saved");
|
||||
}
|
||||
saveCommandToRequest(savedCommandSource);
|
||||
}
|
||||
|
||||
private void saveCommandToRequest(CommandSource savedCommandSource) {
|
||||
// Idempotency filters and retry need this
|
||||
fineractRequestContextHolder.setAttribute(COMMAND_SOURCE_ID, savedCommandSource.getId());
|
||||
}
|
||||
|
||||
private void publishHookErrorEvent(CommandWrapper wrapper, JsonCommand command, Throwable t) {
|
||||
ErrorInfo ex = commandSourceService.generateErrorException(t);
|
||||
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, gson.toJson(ex));
|
||||
private void publishHookErrorEvent(CommandWrapper wrapper, JsonCommand command, ErrorInfo errorInfo) {
|
||||
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, gson.toJson(errorInfo));
|
||||
}
|
||||
|
||||
private void exceptionWhenTheRequestAlreadyProcessed(CommandWrapper wrapper, String idempotencyKey) {
|
||||
private void exceptionWhenTheRequestAlreadyProcessed(CommandWrapper wrapper, String idempotencyKey, boolean retry) {
|
||||
CommandSource existingCommand = commandSourceService.findCommandSource(wrapper, idempotencyKey);
|
||||
if (existingCommand != null) {
|
||||
idempotentExceptionByStatus(UNDER_PROCESSING, existingCommand,
|
||||
command -> new IdempotentCommandProcessUnderProcessingException(wrapper));
|
||||
idempotentExceptionByStatus(ERROR, existingCommand, command -> new IdempotentCommandProcessFailedException(wrapper, command));
|
||||
if (!retry) {
|
||||
idempotentExceptionByStatus(ERROR, existingCommand,
|
||||
command -> new IdempotentCommandProcessFailedException(wrapper, command));
|
||||
}
|
||||
idempotentExceptionByStatus(PROCESSED, existingCommand,
|
||||
command -> new IdempotentCommandProcessSucceedException(wrapper, command.getResult(), command.getResultStatusCode()));
|
||||
}
|
||||
@@ -192,24 +200,23 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult logCommand(CommandSource commandSourceResult) {
|
||||
commandSourceResult.markAsAwaitingApproval();
|
||||
if (commandSourceResult.getIdempotencyKey() == null) {
|
||||
commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create());
|
||||
public CommandProcessingResult logCommand(CommandSource commandSource) {
|
||||
commandSource.markAsAwaitingApproval();
|
||||
if (commandSource.getIdempotencyKey() == null) {
|
||||
commandSource.setIdempotencyKey(idempotencyKeyGenerator.create());
|
||||
}
|
||||
commandSourceResult = commandSourceService.saveResultNoTransaction(commandSourceResult);
|
||||
commandSource = commandSourceService.saveResultSameTransaction(commandSource);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
|
||||
.withEntityId(commandSourceResult.getResourceId()).build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(commandSource.getId()).withEntityId(commandSource.getResourceId())
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private CommandProcessingResult fallbackExecuteCommand(Exception e) throws Exception {
|
||||
private CommandProcessingResult fallbackExecuteCommand(Exception e) {
|
||||
if (e instanceof RollbackTransactionAsCommandIsNotApprovedByCheckerException ex) {
|
||||
return logCommand(ex.getCommandSourceResult());
|
||||
}
|
||||
|
||||
throw e;
|
||||
throw errorHandler.getMappable(e);
|
||||
}
|
||||
|
||||
private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) {
|
||||
@@ -278,7 +285,6 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
}
|
||||
|
||||
private void publishHookEvent(final String entityName, final String actionName, JsonCommand command, final Object result) {
|
||||
|
||||
try {
|
||||
final AppUser appUser = context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, null, null));
|
||||
|
||||
|
||||
+82
-159
@@ -18,16 +18,26 @@
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.core.data;
|
||||
|
||||
import static lombok.AccessLevel.PROTECTED;
|
||||
import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
|
||||
import static org.apache.http.HttpStatus.SC_CONFLICT;
|
||||
import static org.apache.http.HttpStatus.SC_FORBIDDEN;
|
||||
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
|
||||
import static org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
|
||||
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
|
||||
import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
|
||||
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.gson.Gson;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Getter
|
||||
@Setter(PROTECTED)
|
||||
public class ApiGlobalErrorResponse {
|
||||
|
||||
/**
|
||||
@@ -58,176 +68,125 @@ public class ApiGlobalErrorResponse {
|
||||
|
||||
private List<ApiParameterError> errors = new ArrayList<>();
|
||||
|
||||
protected ApiGlobalErrorResponse() {}
|
||||
|
||||
public static ApiGlobalErrorResponse create(int statusCode, String msgCode, String developerMessage, String defaultUserMessage,
|
||||
List<ApiParameterError> errors) {
|
||||
ApiGlobalErrorResponse response = new ApiGlobalErrorResponse();
|
||||
response.setHttpStatusCode(String.valueOf(statusCode));
|
||||
response.setUserMessageGlobalisationCode(msgCode);
|
||||
response.setDeveloperMessage(developerMessage);
|
||||
response.setDefaultUserMessage(defaultUserMessage);
|
||||
response.setErrors(errors);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse create(int statusCode, String msgCode, String developerMessage, String defaultUserMessage) {
|
||||
return create(statusCode, msgCode, developerMessage, defaultUserMessage, null);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse unAuthenticated() {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("401");
|
||||
globalErrorResponse.setDeveloperMessage("Invalid authentication details were passed in api request.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authenticated");
|
||||
globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login.");
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_UNAUTHORIZED, "error.msg.not.authenticated", "Invalid authentication details were passed in api request.",
|
||||
"Unauthenticated. Please login.");
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse invalidTenantIdentifier() {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("401");
|
||||
globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier");
|
||||
globalErrorResponse.setDefaultUserMessage("Invalid tenant identifier provided with request.");
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_UNAUTHORIZED, "error.msg.invalid.tenant.identifier", "Invalid tenant details were passed in api request.",
|
||||
"Invalid tenant identifier provided with request.");
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse invalidInstanceTypeMethod(final String method) {
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode(Status.METHOD_NOT_ALLOWED.toString());
|
||||
globalErrorResponse.setDeveloperMessage("Invalid instance type called in api request for the method " + method);
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.instance.type");
|
||||
globalErrorResponse.setDefaultUserMessage("Invalid method " + method + " used with request to this instance type.");
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_METHOD_NOT_ALLOWED, "error.msg.invalid.instance.type",
|
||||
"Invalid instance type called in api request for the method " + method,
|
||||
"Invalid method " + method + " used with request to this instance type.");
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse loanIsLocked(final Long loanId) {
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode(Status.CONFLICT.toString());
|
||||
globalErrorResponse.setDeveloperMessage("Loan is locked by the COB job. Loan ID: " + loanId);
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.loan.locked");
|
||||
globalErrorResponse.setDefaultUserMessage("Loan is locked by the COB job. Loan ID: \" + loanId");
|
||||
String msg = "Loan is locked by the COB job. Loan ID: " + loanId;
|
||||
return create(SC_CONFLICT, "error.msg.loan.locked", msg, msg);
|
||||
}
|
||||
|
||||
return globalErrorResponse;
|
||||
public static ApiGlobalErrorResponse conflict(String type, String identifier) {
|
||||
String details = "";
|
||||
if (type == null) {
|
||||
type = "unknown";
|
||||
} else {
|
||||
details = " on " + type;
|
||||
}
|
||||
if (identifier != null) {
|
||||
details += " [" + identifier + ']';
|
||||
}
|
||||
String msg = "The server is currently unable to handle the request due to concurrent modification" + details + ", please try again";
|
||||
return create(SC_CONFLICT, "error.msg.platform.service." + type + ".conflict", msg, msg);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse unAuthorized(final String defaultUserMessage) {
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("403");
|
||||
globalErrorResponse.setDeveloperMessage(
|
||||
"The user associated with credentials passed on this request does not have sufficient privileges to perform this action.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authorized");
|
||||
globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action.");
|
||||
String msgCode = "error.msg.not.authorized";
|
||||
final List<ApiParameterError> errors = List.of(ApiParameterError.generalError(msgCode, defaultUserMessage));
|
||||
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.generalError("error.msg.not.authorized", defaultUserMessage));
|
||||
globalErrorResponse.setErrors(errors);
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_FORBIDDEN, msgCode,
|
||||
"The user associated with credentials passed on this request does not have sufficient privileges to perform this action.",
|
||||
"Insufficient privileges to perform this action.", errors);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse domainRuleViolation(final String globalisationMessageCode, final String defaultUserMessage,
|
||||
final Object... defaultUserMessageArgs) {
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("403");
|
||||
globalErrorResponse.setDeveloperMessage("Request was understood but caused a domain rule violation.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation");
|
||||
globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation.");
|
||||
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
|
||||
globalErrorResponse.setErrors(errors);
|
||||
|
||||
return globalErrorResponse;
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse notFound(final String globalisationMessageCode, final String defaultUserMessage,
|
||||
final Object... defaultUserMessageArgs) {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("404");
|
||||
globalErrorResponse.setDeveloperMessage("The requested resource is not available.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.resource.not.found");
|
||||
globalErrorResponse.setDefaultUserMessage("The requested resource is not available.");
|
||||
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
|
||||
globalErrorResponse.setErrors(errors);
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_FORBIDDEN, "validation.msg.domain.rule.violation", "Request was understood but caused a domain rule violation.",
|
||||
"Errors contain reason for domain rule violation.", errors);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse dataIntegrityError(final String globalisationMessageCode, final String defaultUserMessage,
|
||||
final String parameterName, final Object... defaultUserMessageArgs) {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("403");
|
||||
globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
|
||||
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
|
||||
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName, defaultUserMessageArgs));
|
||||
globalErrorResponse.setErrors(errors);
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_FORBIDDEN, globalisationMessageCode, "The request caused a data integrity issue to be fired by the database.",
|
||||
defaultUserMessage, errors);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse notFound(final String globalisationMessageCode, final String defaultUserMessage,
|
||||
final Object... defaultUserMessageArgs) {
|
||||
String msg = "The requested resource is not available.";
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
|
||||
|
||||
return create(SC_NOT_FOUND, "error.msg.resource.not.found", msg, msg, errors);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage,
|
||||
final List<ApiParameterError> errors) {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("400");
|
||||
globalErrorResponse
|
||||
.setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
|
||||
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
|
||||
|
||||
globalErrorResponse.setErrors(errors);
|
||||
|
||||
return globalErrorResponse;
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse jobIsDisabled(final String globalisationMessageCode, final String defaultUserMessage) {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode(Status.FORBIDDEN.toString());
|
||||
globalErrorResponse.setDeveloperMessage(defaultUserMessage);
|
||||
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
|
||||
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_BAD_REQUEST, globalisationMessageCode,
|
||||
"The request was invalid. This typically will happen due to validation errors which are provided.", defaultUserMessage,
|
||||
errors);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage) {
|
||||
return badClientRequest(globalisationMessageCode, defaultUserMessage, Collections.emptyList());
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse jobIsDisabled(final String globalisationMessageCode, final String defaultUserMessage) {
|
||||
return create(SC_FORBIDDEN, globalisationMessageCode, defaultUserMessage, defaultUserMessage);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse serverSideError(final String globalisationMessageCode, final String defaultUserMessage,
|
||||
final Object... defaultUserMessageArgs) {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("500");
|
||||
globalErrorResponse.setDeveloperMessage("An unexpected error occured on the platform server.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.server.side.error");
|
||||
globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server.");
|
||||
|
||||
String msg = "An unexpected error occured on the platform server.";
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
|
||||
globalErrorResponse.setErrors(errors);
|
||||
|
||||
return globalErrorResponse;
|
||||
return create(SC_INTERNAL_SERVER_ERROR, "error.msg.platform.server.side.error", msg, msg, errors);
|
||||
}
|
||||
|
||||
public static ApiGlobalErrorResponse serviceUnavailable(final String globalisationMessageCode, final String defaultUserMessage,
|
||||
final Object... defaultUserMessageArgs) {
|
||||
|
||||
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
|
||||
globalErrorResponse.setHttpStatusCode("503");
|
||||
globalErrorResponse.setDeveloperMessage("The server is currently unable to handle the request , please try after some time.");
|
||||
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.service.unavailable");
|
||||
globalErrorResponse.setDefaultUserMessage("The server is currently unable to handle the request , please try after some time.");
|
||||
|
||||
String msg = "The server is currently unable to handle the request , please try after some time.";
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
|
||||
globalErrorResponse.setErrors(errors);
|
||||
|
||||
return globalErrorResponse;
|
||||
}
|
||||
|
||||
protected ApiGlobalErrorResponse() {}
|
||||
|
||||
public ApiGlobalErrorResponse(final List<ApiParameterError> errors) {
|
||||
this.errors = errors;
|
||||
return create(SC_SERVICE_UNAVAILABLE, "error.msg.platform.service.unavailable", msg, msg, errors);
|
||||
}
|
||||
|
||||
@JsonProperty("errors")
|
||||
@@ -235,42 +194,6 @@ public class ApiGlobalErrorResponse {
|
||||
return this.errors;
|
||||
}
|
||||
|
||||
public void setErrors(final List<ApiParameterError> errors) {
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public String getDeveloperMessage() {
|
||||
return this.developerMessage;
|
||||
}
|
||||
|
||||
public void setDeveloperMessage(final String developerMessage) {
|
||||
this.developerMessage = developerMessage;
|
||||
}
|
||||
|
||||
public String getHttpStatusCode() {
|
||||
return this.httpStatusCode;
|
||||
}
|
||||
|
||||
public void setHttpStatusCode(final String httpStatusCode) {
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
}
|
||||
|
||||
public String getDefaultUserMessage() {
|
||||
return this.defaultUserMessage;
|
||||
}
|
||||
|
||||
public void setDefaultUserMessage(final String defaultUserMessage) {
|
||||
this.defaultUserMessage = defaultUserMessage;
|
||||
}
|
||||
|
||||
public String getUserMessageGlobalisationCode() {
|
||||
return this.userMessageGlobalisationCode;
|
||||
}
|
||||
|
||||
public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) {
|
||||
this.userMessageGlobalisationCode = userMessageGlobalisationCode;
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
final Gson gson = new Gson();
|
||||
return gson.toJson(this);
|
||||
|
||||
+45
-98
@@ -19,7 +19,6 @@
|
||||
package org.apache.fineract.infrastructure.core.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
@@ -51,12 +50,43 @@ public class CommandProcessingResult implements Serializable {
|
||||
private final ExternalId resourceExternalId;
|
||||
private final ExternalId subResourceExternalId;
|
||||
|
||||
public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult) {
|
||||
return new CommandProcessingResult(commandResult.commandId, commandResult.officeId, commandResult.groupId, commandResult.clientId,
|
||||
commandResult.loanId, commandResult.savingsId, commandResult.resourceIdentifier, commandResult.resourceId,
|
||||
commandResult.transactionId, commandResult.changes, commandResult.productId, commandResult.gsimId, commandResult.glimId,
|
||||
commandResult.creditBureauReportData, commandResult.rollbackTransaction, commandResult.subResourceId,
|
||||
commandResult.resourceExternalId, commandResult.subResourceExternalId);
|
||||
private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId,
|
||||
final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId,
|
||||
final Map<String, Object> changes, final Long productId, final Long gsimId, final Long glimId,
|
||||
final Map<String, Object> creditBureauReportData, Boolean rollbackTransaction, final Long subResourceId,
|
||||
final ExternalId resourceExternalId, final ExternalId subResourceExternalId) {
|
||||
this.commandId = commandId;
|
||||
this.officeId = officeId;
|
||||
this.groupId = groupId;
|
||||
this.clientId = clientId;
|
||||
this.loanId = loanId;
|
||||
this.savingsId = savingsId;
|
||||
this.resourceIdentifier = resourceIdentifier;
|
||||
this.resourceId = resourceId;
|
||||
this.changes = changes;
|
||||
this.transactionId = transactionId;
|
||||
this.productId = productId;
|
||||
this.gsimId = gsimId;
|
||||
this.glimId = glimId;
|
||||
this.creditBureauReportData = creditBureauReportData;
|
||||
this.rollbackTransaction = rollbackTransaction;
|
||||
this.subResourceId = subResourceId;
|
||||
this.resourceExternalId = resourceExternalId;
|
||||
this.subResourceExternalId = subResourceExternalId;
|
||||
}
|
||||
|
||||
protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map<String, Object> changes,
|
||||
Long clientId) {
|
||||
this(commandId, officeId, null, clientId, null, null, resourceId == null ? null : resourceId.toString(), resourceId, null, changes,
|
||||
null, null, null, null, null, null, ExternalId.empty(), ExternalId.empty());
|
||||
}
|
||||
|
||||
protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map<String, Object> changes) {
|
||||
this(resourceId, officeId, commandId, changes, null);
|
||||
}
|
||||
|
||||
protected CommandProcessingResult(final Long resourceId) {
|
||||
this(resourceId, null, null, null);
|
||||
}
|
||||
|
||||
public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult, final Long resourceId) {
|
||||
@@ -67,6 +97,10 @@ public class CommandProcessingResult implements Serializable {
|
||||
commandResult.resourceExternalId, commandResult.subResourceExternalId);
|
||||
}
|
||||
|
||||
public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult) {
|
||||
return fromCommandProcessingResult(commandResult, commandResult.getResourceId());
|
||||
}
|
||||
|
||||
public static CommandProcessingResult fromDetails(final Long commandId, final Long officeId, final Long groupId, final Long clientId,
|
||||
final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final Long gsimId,
|
||||
final Long glimId, final Map<String, Object> creditBureauReportData, final String transactionId,
|
||||
@@ -81,21 +115,16 @@ public class CommandProcessingResult implements Serializable {
|
||||
return new CommandProcessingResult(null, null, commandId, null);
|
||||
}
|
||||
|
||||
public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId) {
|
||||
return new CommandProcessingResult(resourceId, null, commandId, null);
|
||||
}
|
||||
|
||||
public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId, final Map<String, Object> changes) {
|
||||
return new CommandProcessingResult(resourceId, null, commandId, changes);
|
||||
}
|
||||
|
||||
public static CommandProcessingResult subResourceResult(final Long resourceId, final Long subResourceId, final Long commandId) {
|
||||
return new CommandProcessingResult(resourceId, subResourceId, commandId, null);
|
||||
public static CommandProcessingResult resourceResult(final Long resourceId, final Long commandId) {
|
||||
return new CommandProcessingResult(resourceId, null, commandId, null);
|
||||
}
|
||||
|
||||
public static CommandProcessingResult subResourceResult(final Long resourceId, final Long subResourceId, final Long commandId,
|
||||
final Map<String, Object> changes) {
|
||||
return new CommandProcessingResult(resourceId, subResourceId, commandId, changes);
|
||||
public static CommandProcessingResult resourceResult(final Long resourceId) {
|
||||
return new CommandProcessingResult(resourceId);
|
||||
}
|
||||
|
||||
public static CommandProcessingResult withChanges(final Long resourceId, final Map<String, Object> changes) {
|
||||
@@ -106,88 +135,6 @@ public class CommandProcessingResult implements Serializable {
|
||||
return new CommandProcessingResult(null, null, null, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deprecated
|
||||
*/
|
||||
public CommandProcessingResult(final Long entityId) {
|
||||
if (entityId != null) {
|
||||
this.resourceIdentifier = entityId.toString();
|
||||
} else {
|
||||
this.resourceIdentifier = null;
|
||||
}
|
||||
this.resourceId = entityId;
|
||||
this.officeId = null;
|
||||
this.groupId = null;
|
||||
this.clientId = null;
|
||||
this.loanId = null;
|
||||
this.savingsId = null;
|
||||
this.transactionId = null;
|
||||
this.changes = new HashMap<>();
|
||||
this.productId = null;
|
||||
this.gsimId = null;
|
||||
this.glimId = null;
|
||||
this.creditBureauReportData = null;
|
||||
this.subResourceId = null;
|
||||
this.resourceExternalId = ExternalId.empty();
|
||||
this.subResourceExternalId = ExternalId.empty();
|
||||
}
|
||||
|
||||
private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId,
|
||||
final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId,
|
||||
final Map<String, Object> changesOnly, final Long productId, final Long gsimId, final Long glimId,
|
||||
final Map<String, Object> creditBureauReportData, Boolean rollbackTransaction, final Long subResourceId,
|
||||
final ExternalId resourceExternalId, final ExternalId subResourceExternalId) {
|
||||
this.commandId = commandId;
|
||||
this.officeId = officeId;
|
||||
this.groupId = groupId;
|
||||
this.clientId = clientId;
|
||||
this.loanId = loanId;
|
||||
this.savingsId = savingsId;
|
||||
this.resourceIdentifier = resourceIdentifier;
|
||||
this.resourceId = resourceId;
|
||||
this.changes = changesOnly;
|
||||
this.transactionId = transactionId;
|
||||
this.productId = productId;
|
||||
this.gsimId = gsimId;
|
||||
this.glimId = glimId;
|
||||
this.creditBureauReportData = creditBureauReportData;
|
||||
this.rollbackTransaction = rollbackTransaction;
|
||||
this.subResourceId = subResourceId;
|
||||
this.resourceExternalId = resourceExternalId;
|
||||
this.subResourceExternalId = subResourceExternalId;
|
||||
}
|
||||
|
||||
protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId,
|
||||
final Map<String, Object> changesOnly) {
|
||||
if (resourceId != null) {
|
||||
this.resourceIdentifier = resourceId.toString();
|
||||
} else {
|
||||
this.resourceIdentifier = null;
|
||||
}
|
||||
this.resourceId = resourceId;
|
||||
this.officeId = officeId;
|
||||
this.groupId = null;
|
||||
this.clientId = null;
|
||||
this.loanId = null;
|
||||
this.savingsId = null;
|
||||
this.transactionId = null;
|
||||
this.commandId = commandId;
|
||||
this.changes = changesOnly;
|
||||
this.productId = null;
|
||||
this.gsimId = null;
|
||||
this.glimId = null;
|
||||
this.creditBureauReportData = null;
|
||||
this.subResourceId = null;
|
||||
this.resourceExternalId = ExternalId.empty();
|
||||
this.subResourceExternalId = ExternalId.empty();
|
||||
}
|
||||
|
||||
protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId,
|
||||
final Map<String, Object> changesOnly, long clientId) {
|
||||
this(commandId, officeId, null, clientId, null, null, null, resourceId, null, changesOnly, null, null, null, null, null, null,
|
||||
ExternalId.empty(), ExternalId.empty());
|
||||
}
|
||||
|
||||
public void setOfficeId(final Long officeId) {
|
||||
this.officeId = officeId;
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.infrastructure.core.exceptionmapper;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* An {@link ExceptionMapper} to map {@link ObjectOptimisticLockingFailureException} thrown by platform into a HTTP API
|
||||
* friendly format.
|
||||
*/
|
||||
@Provider
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class ConcurrencyFailureExceptionMapper implements FineractExceptionMapper, ExceptionMapper<ConcurrencyFailureException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final ConcurrencyFailureException exception) {
|
||||
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage());
|
||||
String type = "unknown";
|
||||
String identifier = "unknown";
|
||||
if (exception instanceof ObjectOptimisticLockingFailureException olex) {
|
||||
type = olex.getPersistentClassName();
|
||||
identifier = olex.getIdentifier() == null ? null : String.valueOf(olex.getIdentifier());
|
||||
}
|
||||
final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.conflict(type, identifier);
|
||||
return Response.status(Status.CONFLICT).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 4009;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.infrastructure.core.exceptionmapper;
|
||||
|
||||
import static org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
|
||||
import org.eclipse.persistence.exceptions.OptimisticLockException;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* An {@link ExceptionMapper} to map {@link OptimisticLockException} thrown by platform into a HTTP API friendly format.
|
||||
*/
|
||||
@Provider
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class IdempotentCommandExceptionMapper implements FineractExceptionMapper, ExceptionMapper<AbstractIdempotentCommandException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final AbstractIdempotentCommandException exception) {
|
||||
log.warn("Processing {} request: {}", exception.getClass().getName(), exception.getMessage());
|
||||
Status status = null;
|
||||
if (exception instanceof IdempotentCommandProcessSucceedException pse) {
|
||||
status = Status.fromStatusCode(pse.getStatusCode());
|
||||
} else if (exception instanceof IdempotentCommandProcessUnderProcessingException) {
|
||||
status = Status.CONFLICT;
|
||||
} else if (exception instanceof IdempotentCommandProcessFailedException pfe) {
|
||||
status = Status.fromStatusCode(pfe.getStatusCode());
|
||||
}
|
||||
if (status == null) {
|
||||
status = Status.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
return Response.status(status).entity(exception.getResponse()).header(IDEMPOTENT_CACHE_HEADER, "true")
|
||||
.type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 4209;
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* 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.infrastructure.core.exceptionmapper;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Provider
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IdempotentCommandProcessSucceedExceptionMapper implements ExceptionMapper<IdempotentCommandProcessSucceedException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final IdempotentCommandProcessSucceedException exception) {
|
||||
log.debug("Idempotent processing success request: {}", exception.getMessage());
|
||||
Response.ResponseBuilder responseBuilder = Response.status(exception.getStatusCode());
|
||||
if (exception.getResponse() != null) {
|
||||
responseBuilder.entity(exception.getResponse());
|
||||
}
|
||||
return responseBuilder.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true").type(MediaType.APPLICATION_JSON)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* 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.infrastructure.core.exceptionmapper;
|
||||
|
||||
import static org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Provider
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IdempotentCommandProcessUnderProcessingExceptionMapper
|
||||
implements ExceptionMapper<IdempotentCommandProcessUnderProcessingException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final IdempotentCommandProcessUnderProcessingException exception) {
|
||||
log.debug("Idempotent under processing request: {}", exception.getMessage());
|
||||
return Response.status(Status.CONFLICT).entity(exception.getResponse()).header(IDEMPOTENT_CACHE_HEADER, "true")
|
||||
.type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
}
|
||||
+21
-8
@@ -24,20 +24,33 @@ import jakarta.ws.rs.core.Response.Status;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||
import org.eclipse.persistence.exceptions.OptimisticLockException;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* An {@link ExceptionMapper} to map {@link ObjectOptimisticLockingFailureException} thrown by platform into a HTTP API
|
||||
* friendly format.
|
||||
*/
|
||||
@Provider
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class IdempotentCommandProcessFailedExceptionMapper implements ExceptionMapper<IdempotentCommandProcessFailedException> {
|
||||
public class OptimisticLockExceptionMapper implements FineractExceptionMapper, ExceptionMapper<OptimisticLockException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final IdempotentCommandProcessFailedException exception) {
|
||||
log.debug("Idempotent processing failed request: {}", exception.getMessage());
|
||||
Status statusCode = Status.fromStatusCode(exception.getStatusCode());
|
||||
return Response.status(statusCode).entity(exception.getResponse())
|
||||
.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true").type(MediaType.APPLICATION_JSON).build();
|
||||
public Response toResponse(final OptimisticLockException exception) {
|
||||
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage());
|
||||
String type = exception.getQuery() == null ? "unknown" : "query";
|
||||
String identifier = "unknown";
|
||||
final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.conflict(type, identifier);
|
||||
return Response.status(Status.CONFLICT).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 4009;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -43,9 +43,9 @@ public class IdempotencyStoreBatchFilter implements BatchFilter {
|
||||
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, idempotentKey));
|
||||
BatchResponse result = chain.serviceCall(batchRequest, uriInfo);
|
||||
Optional<Long> commandId = helper.getCommandId(null);
|
||||
boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(null) && commandId.isPresent();
|
||||
boolean isSuccessWithoutStored = commandId.isPresent() && helper.isStoreIdempotencyKey(null);
|
||||
if (isSuccessWithoutStored) {
|
||||
helper.storeCommandResult(true, result.getStatusCode(), result.getBody(), commandId);
|
||||
helper.storeCommandResult(result.getStatusCode(), result.getBody(), commandId.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+4
-4
@@ -56,12 +56,12 @@ public class IdempotencyStoreFilter extends OncePerRequestFilter {
|
||||
|
||||
filterChain.doFilter(request, wrapper.getValue() != null ? wrapper.getValue() : response);
|
||||
Optional<Long> commandId = helper.getCommandId(request);
|
||||
boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(request) && commandId.isPresent()
|
||||
&& helper.isAllowedContentTypeResponse(response) && wrapper.getValue() != null;
|
||||
boolean isSuccessWithoutStored = commandId.isPresent() && wrapper.getValue() != null && helper.isStoreIdempotencyKey(request)
|
||||
&& helper.isAllowedContentTypeResponse(response);
|
||||
if (isSuccessWithoutStored) {
|
||||
helper.storeCommandResult(false, response.getStatus(), Optional.ofNullable(wrapper.getValue())
|
||||
helper.storeCommandResult(response.getStatus(), Optional.ofNullable(wrapper.getValue())
|
||||
.map(ContentCachingResponseWrapper::getContentAsByteArray).map(s -> new String(s, StandardCharsets.UTF_8)).orElse(null),
|
||||
commandId);
|
||||
commandId.get());
|
||||
}
|
||||
if (wrapper.getValue() != null) {
|
||||
wrapper.getValue().copyBodyToResponse();
|
||||
|
||||
+6
-7
@@ -25,6 +25,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||
import org.apache.fineract.commands.service.CommandSourceService;
|
||||
import org.apache.fineract.commands.service.SynchronousCommandProcessingService;
|
||||
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -36,15 +37,13 @@ public class IdempotencyStoreHelper {
|
||||
private final CommandSourceService commandSourceService;
|
||||
private final FineractRequestContextHolder fineractRequestContextHolder;
|
||||
|
||||
public void storeCommandResult(boolean batch, int response, String body, Optional<Long> commandId) {
|
||||
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
|
||||
public void storeCommandResult(Integer response, String body, Long commandId) {
|
||||
commandSourceRepository.findById(commandId).ifPresent(commandSource -> {
|
||||
boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent();
|
||||
commandSource.setResultStatusCode(response);
|
||||
commandSource.setResult(body);
|
||||
if (batch) {
|
||||
commandSourceService.saveResultNoTransaction(commandSource);
|
||||
} else {
|
||||
commandSourceService.saveResult(commandSource);
|
||||
}
|
||||
commandSource = sameTransaction ? commandSourceService.saveResultSameTransaction(commandSource)
|
||||
: commandSourceService.saveResultNewTransaction(commandSource);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandProcessFailedExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -50,7 +50,7 @@ public class IdempotencyCommandProcessFailedExceptionTest {
|
||||
|
||||
@Test
|
||||
public void testInconsistentStatus() {
|
||||
IdempotentCommandProcessFailedExceptionMapper mapper = new IdempotentCommandProcessFailedExceptionMapper();
|
||||
IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper();
|
||||
CommandWrapper command = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null);
|
||||
CommandSource source = CommandSource.fullEntryFrom(command, JsonCommand.from("{}"), null, "dummy-key", null);
|
||||
|
||||
Reference in New Issue
Block a user