This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 2afd9d35e FINERACT-1724 - Error handler is springified, exception
mappers are retrieved as spring beans - backward compatibility is maintained
2afd9d35e is described below
commit 2afd9d35e7d04f8c883380c27c3593695c59de36
Author: Peter Bagrij <[email protected]>
AuthorDate: Sun Jul 9 12:15:35 2023 +0200
FINERACT-1724
- Error handler is springified, exception mappers are retrieved as spring
beans
- backward compatibility is maintained
---
.../fineract/batch/exception/ErrorHandler.java | 114 +++++++----------
.../batch/service/BatchApiServiceImpl.java | 13 +-
.../batch/service/BatchExecutionException.java | 5 +-
.../commands/service/CommandSourceService.java | 3 +-
...tionMapper.java => DefaultExceptionMapper.java} | 29 ++---
.../exceptionmapper/FineractExceptionMapper.java} | 19 +--
.../PlatformApiDataValidationExceptionMapper.java | 8 +-
.../PlatformDataIntegrityExceptionMapper.java | 7 +-
.../PlatformDomainRuleExceptionMapper.java | 7 +-
.../PlatformInternalServerExceptionMapper.java | 7 +-
.../PlatformResourceNotFoundExceptionMapper.java | 8 +-
.../UnsupportedParameterExceptionMapper.java | 7 +-
.../fineract/batch/exception/ErrorHandlerTest.java | 137 +++++++++++++++++++++
.../DefaultExceptionMapperTest.java | 42 +++++++
.../LinkedAccountRequiredExceptionMapper.java | 27 ++--
....java => LoanIdsHardLockedExceptionMapper.java} | 33 ++---
...ltiDisbursementDataRequiredExceptionMapper.java | 27 ++--
.../LinkedAccountRequiredExceptionMapperTest.java | 46 +++++++
.../LoanIdsHardLockedExceptionMapperTest.java | 41 ++++++
...isbursementDataRequiredExceptionMapperTest.java | 46 +++++++
.../commands/service/CommandSourceServiceTest.java | 7 ++
21 files changed, 470 insertions(+), 163 deletions(-)
diff --git
a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
b/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
index ed172df98..babc57c05 100644
---
a/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
+++
b/fineract-core/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
@@ -18,29 +18,21 @@
*/
package org.apache.fineract.batch.exception;
+import static org.springframework.core.ResolvableType.forClassWithGenerics;
+
import com.google.gson.Gson;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.function.Function;
-import
org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
-import
org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
-import
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
-import
org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
-import
org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
-import
org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
-import
org.apache.fineract.infrastructure.core.exceptionmapper.PlatformApiDataValidationExceptionMapper;
-import
org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDataIntegrityExceptionMapper;
-import
org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDomainRuleExceptionMapper;
-import
org.apache.fineract.infrastructure.core.exceptionmapper.PlatformInternalServerExceptionMapper;
-import
org.apache.fineract.infrastructure.core.exceptionmapper.PlatformResourceNotFoundExceptionMapper;
-import
org.apache.fineract.infrastructure.core.exceptionmapper.UnsupportedParameterExceptionMapper;
+import java.util.Set;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.SetUtils;
+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.apache.http.HttpStatus;
-import org.springframework.dao.NonTransientDataAccessException;
-import org.springframework.transaction.TransactionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
/**
* Provides an Error Handler method that returns an object of type {@link
ErrorInfo} to the CommandStrategy which raised
@@ -50,60 +42,41 @@ import org.springframework.transaction.TransactionException;
* @author Rishabh Shukla
* @see org.apache.fineract.batch.command.CommandStrategy
*/
+@Component
+@Slf4j
+@AllArgsConstructor
public final class ErrorHandler {
- private static final Gson JSON_HELPER =
GoogleGsonSerializerHelper.createGsonBuilder(true).create();
-
- private static final Map<Class<? extends Exception>,
Function<RuntimeException, ErrorInfo>> EXCEPTION_HANDLERS = Collections
- .synchronizedMap(new LinkedHashMap<>());
- private static final Map.Entry<Class<? extends Exception>,
Function<RuntimeException, ErrorInfo>> DEFAULT_ERROR_HANDLER = Map.entry(
- RuntimeException.class,
- runtimeException -> new ErrorInfo(500, 9999, "{\"Exception\":
%s}".formatted(runtimeException.getMessage())));
+ @Autowired
+ private final ApplicationContext ctx;
- static {
- EXCEPTION_HANDLERS.put(AbstractPlatformResourceNotFoundException.class,
- runtimeException -> handleException(runtimeException, new
PlatformResourceNotFoundExceptionMapper(), 1001));
- EXCEPTION_HANDLERS.put(UnsupportedParameterException.class,
- runtimeException -> handleException(runtimeException, new
UnsupportedParameterExceptionMapper(), 2001));
- EXCEPTION_HANDLERS.put(PlatformApiDataValidationException.class,
- runtimeException -> handleException(runtimeException, new
PlatformApiDataValidationExceptionMapper(), 2002));
- EXCEPTION_HANDLERS.put(PlatformDataIntegrityException.class,
- runtimeException -> handleException(runtimeException, new
PlatformDataIntegrityExceptionMapper(), 3001));
- EXCEPTION_HANDLERS.put(AbstractPlatformDomainRuleException.class,
- runtimeException -> handleException(runtimeException, new
PlatformDomainRuleExceptionMapper(), 9999));
- EXCEPTION_HANDLERS.put(TransactionException.class, runtimeException ->
new ErrorInfo(HttpStatus.SC_INTERNAL_SERVER_ERROR, 4001,
- "{\"Exception\":
%s}".formatted(runtimeException.getMessage())));
- EXCEPTION_HANDLERS.put(PlatformInternalServerException.class,
- runtimeException -> handleException(runtimeException, new
PlatformInternalServerExceptionMapper(), 5001));
- EXCEPTION_HANDLERS.put(NonTransientDataAccessException.class,
runtimeException -> new ErrorInfo(HttpStatus.SC_INTERNAL_SERVER_ERROR,
- 4002, "{\"Exception\":
%s}".formatted(runtimeException.getMessage())));
- EXCEPTION_HANDLERS.put(DEFAULT_ERROR_HANDLER.getKey(),
DEFAULT_ERROR_HANDLER.getValue());
- }
+ @Autowired
+ private final DefaultExceptionMapper defaultExceptionMapper;
- private ErrorHandler() {}
-
- public static void registerNewErrorHandler(final Class<? extends
RuntimeException> exceptionClass, final ExceptionMapper mapper,
- int errorCode) {
- LinkedHashMap<Class<? extends Exception>, Function<RuntimeException,
ErrorInfo>> newHandlers = new LinkedHashMap<>();
- newHandlers.put(exceptionClass, runtimeException ->
handleException(runtimeException, mapper, errorCode));
- EXCEPTION_HANDLERS.forEach(newHandlers::putIfAbsent);
- EXCEPTION_HANDLERS.clear();
- newHandlers.forEach(EXCEPTION_HANDLERS::putIfAbsent);
- }
+ private static final Gson JSON_HELPER =
GoogleGsonSerializerHelper.createGsonBuilder(true).create();
- public static void registerNewErrorHandler(final Class<? extends
RuntimeException> exceptionClass,
- Function<RuntimeException, ErrorInfo> function) {
- LinkedHashMap<Class<? extends Exception>, Function<RuntimeException,
ErrorInfo>> newHandlers = new LinkedHashMap<>();
- newHandlers.put(exceptionClass, function);
- EXCEPTION_HANDLERS.forEach(newHandlers::putIfAbsent);
- EXCEPTION_HANDLERS.clear();
- newHandlers.forEach(EXCEPTION_HANDLERS::putIfAbsent);
+ private <T extends RuntimeException> ExceptionMapper<T>
findMostSpecificExceptionHandler(T exception) {
+ Class<?> clazz = exception.getClass();
+ do {
+ Set<String> exceptionMappers =
createSet(ctx.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class,
clazz)));
+ Set<String> fineractErrorMappers =
createSet(ctx.getBeanNamesForType(FineractExceptionMapper.class));
+ SetUtils.SetView<String> intersection =
SetUtils.intersection(exceptionMappers, fineractErrorMappers);
+ if (intersection.size() > 0) {
+ // noinspection unchecked
+ return (ExceptionMapper<T>)
ctx.getBean(intersection.iterator().next());
+ }
+ clazz = clazz.getSuperclass();
+ } while (!clazz.equals(Exception.class));
+ // noinspection unchecked
+ return (ExceptionMapper<T>) defaultExceptionMapper;
}
- private static ErrorInfo handleException(final RuntimeException exception,
final ExceptionMapper mapper, final int errorCode) {
- final Response response = mapper.toResponse(exception);
- final String errorBody = JSON_HELPER.toJson(response.getEntity());
- return new ErrorInfo(response.getStatus(), errorCode, errorBody);
+ private <T> Set<T> createSet(T[] array) {
+ if (array == null) {
+ return Set.of();
+ } else {
+ return Set.of(array);
+ }
}
/**
@@ -112,9 +85,10 @@ public final class ErrorHandler {
* @param exception
* @return ErrorInfo
*/
- public static ErrorInfo handler(final RuntimeException exception) {
- Map.Entry<Class<? extends Exception>, Function<RuntimeException,
ErrorInfo>> errorhandler = EXCEPTION_HANDLERS.entrySet().stream()
- .filter(e ->
e.getKey().isAssignableFrom(exception.getClass())).findFirst().orElse(DEFAULT_ERROR_HANDLER);
- return errorhandler.getValue().apply(exception);
+ 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()));
}
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
index 71f06ac67..dfb43a856 100644
---
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
+++
b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
@@ -82,6 +82,7 @@ public class BatchApiServiceImpl implements BatchApiService {
private final CommandStrategyProvider strategyProvider;
private final ResolutionHelper resolutionHelper;
private final PlatformTransactionManager transactionManager;
+ private final ErrorHandler errorHandler;
private final List<BatchFilter> batchFilters;
@@ -106,7 +107,7 @@ public class BatchApiServiceImpl implements BatchApiService
{
final List<BatchRequestNode> batchRequestNodes =
this.resolutionHelper.getDependingRequests(requestList);
if (batchRequestNodes.isEmpty()) {
final BatchResponse response = new BatchResponse();
- ErrorInfo ex = ErrorHandler.handler(new
ClientDetailsNotFoundException());
+ ErrorInfo ex = errorHandler.handle(new
ClientDetailsNotFoundException());
response.setStatusCode(500);
response.setBody(ex.getMessage());
responseList.add(response);
@@ -156,7 +157,7 @@ public class BatchApiServiceImpl implements BatchApiService
{
try {
resolvedChildRequest =
this.resolutionHelper.resoluteRequest(childNode.getRequest(), response);
} catch (RuntimeException ex) {
- throw new BatchExecutionException(childNode.getRequest(),
ex);
+ throw new BatchExecutionException(childNode.getRequest(),
ex, errorHandler.handle(ex));
}
callRequestRecursive(resolvedChildRequest, childNode,
responseList, uriInfo, enclosingTransaction);
@@ -206,7 +207,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());
+ throw new BatchExecutionException(request,
preprocessorResult.getLeft(),
errorHandler.handle(preprocessorResult.getLeft()));
} else {
request = preprocessorResult.get();
}
@@ -231,7 +232,7 @@ public class BatchApiServiceImpl implements BatchApiService
{
} catch (AbstractIdempotentCommandException idempotentException) {
return handleIdempotentRequests(idempotentException, request);
} catch (RuntimeException ex) {
- throw new BatchExecutionException(request, ex);
+ throw new BatchExecutionException(request, ex,
errorHandler.handle(ex));
} finally {
BatchRequestContextHolder.resetRequestAttributes();
}
@@ -366,7 +367,7 @@ public class BatchApiServiceImpl implements BatchApiService
{
return Arrays.asList(errResponse);
} catch (RuntimeException ex) {
status.setRollbackOnly();
- ErrorInfo e = ErrorHandler.handler(ex);
+ ErrorInfo e = errorHandler.handle(ex);
BatchResponse errResponse = new BatchResponse();
errResponse.setStatusCode(e.getStatusCode());
errResponse.setBody(e.getMessage());
@@ -374,7 +375,7 @@ public class BatchApiServiceImpl implements BatchApiService
{
}
});
} catch (TransactionException | NonTransientDataAccessException ex) {
- ErrorInfo e = ErrorHandler.handler(ex);
+ ErrorInfo e = errorHandler.handle(ex);
return createErrorResponse(responseList, e.getStatusCode());
}
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
index 4b9ad606a..d0877a14b 100644
---
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
+++
b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
@@ -20,7 +20,6 @@ package org.apache.fineract.batch.service;
import lombok.Getter;
import org.apache.fineract.batch.domain.BatchRequest;
-import org.apache.fineract.batch.exception.ErrorHandler;
import org.apache.fineract.batch.exception.ErrorInfo;
@Getter
@@ -29,9 +28,9 @@ public class BatchExecutionException extends RuntimeException
{
private final BatchRequest request;
private final ErrorInfo errorInfo;
- public BatchExecutionException(BatchRequest request, RuntimeException ex) {
+ public BatchExecutionException(BatchRequest request, RuntimeException ex,
ErrorInfo errorInfo) {
super("Error executing batch request: " + request, ex);
this.request = request;
- this.errorInfo = ErrorHandler.handler(ex);
+ this.errorInfo = errorInfo;
}
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
index 39420ec1a..f58c7ef8d 100644
---
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
+++
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
@@ -46,6 +46,7 @@ import
org.springframework.transaction.annotation.Transactional;
public class CommandSourceService {
private final CommandSourceRepository commandSourceRepository;
+ private final ErrorHandler errorHandler;
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation =
Isolation.REPEATABLE_READ)
public CommandSource saveInitial(CommandWrapper wrapper, JsonCommand
jsonCommand, AppUser maker, String idempotencyKey) {
@@ -83,7 +84,7 @@ public class CommandSourceService {
public ErrorInfo generateErrorException(Throwable t) {
if (t instanceof final RuntimeException e) {
- return ErrorHandler.handler(e);
+ return errorHandler.handle(e);
} else {
return new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString()
+ "}");
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/DefaultExceptionMapper.java
similarity index 50%
copy from
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
copy to
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/DefaultExceptionMapper.java
index a7ff68c13..5d3e48b24 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/DefaultExceptionMapper.java
@@ -18,34 +18,29 @@
*/
package org.apache.fineract.infrastructure.core.exceptionmapper;
+import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
+
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 java.util.Map;
import lombok.extern.slf4j.Slf4j;
-import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
-import
org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
-/**
- * An {@link ExceptionMapper} to map {@link PlatformInternalServerException}
thrown by platform into a HTTP API friendly
- * format.
- *
- * The {@link PlatformInternalServerException} is thrown when an api call
results in unexpected server side exceptions.
- */
-@Provider
@Component
@Scope("singleton")
@Slf4j
-public class PlatformInternalServerExceptionMapper implements
ExceptionMapper<PlatformInternalServerException> {
+public class DefaultExceptionMapper implements FineractExceptionMapper,
ExceptionMapper<RuntimeException> {
+
+ @Override
+ public int errorCode() {
+ return 9999;
+ }
@Override
- public Response toResponse(final PlatformInternalServerException
exception) {
- log.warn("Exception: {}, Message: {}", exception.getClass().getName(),
exception.getMessage());
- final ApiGlobalErrorResponse notFoundErrorResponse =
ApiGlobalErrorResponse.serverSideError(exception.getGlobalisationMessageCode(),
- exception.getDefaultUserMessage(),
exception.getDefaultUserMessageArgs());
- return
Response.status(Status.INTERNAL_SERVER_ERROR).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
+ public Response toResponse(RuntimeException runtimeException) {
+ return
Response.status(SC_INTERNAL_SERVER_ERROR).entity(Map.of("Exception",
runtimeException.getMessage()))
+ .type(MediaType.APPLICATION_JSON).build();
}
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/FineractExceptionMapper.java
similarity index 56%
copy from
fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
copy to
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/FineractExceptionMapper.java
index 4b9ad606a..df6f8cc5d 100644
---
a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/FineractExceptionMapper.java
@@ -16,22 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.batch.service;
+package org.apache.fineract.infrastructure.core.exceptionmapper;
-import lombok.Getter;
-import org.apache.fineract.batch.domain.BatchRequest;
-import org.apache.fineract.batch.exception.ErrorHandler;
-import org.apache.fineract.batch.exception.ErrorInfo;
+public interface FineractExceptionMapper {
-@Getter
-public class BatchExecutionException extends RuntimeException {
-
- private final BatchRequest request;
- private final ErrorInfo errorInfo;
-
- public BatchExecutionException(BatchRequest request, RuntimeException ex) {
- super("Error executing batch request: " + request, ex);
- this.request = request;
- this.errorInfo = ErrorHandler.handler(ex);
- }
+ int errorCode();
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java
index a01d0f50c..728317329 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java
@@ -40,7 +40,8 @@ import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
@Slf4j
-public class PlatformApiDataValidationExceptionMapper implements
ExceptionMapper<PlatformApiDataValidationException> {
+public class PlatformApiDataValidationExceptionMapper
+ implements FineractExceptionMapper,
ExceptionMapper<PlatformApiDataValidationException> {
@Override
public Response toResponse(final PlatformApiDataValidationException
exception) {
@@ -50,4 +51,9 @@ public class PlatformApiDataValidationExceptionMapper
implements ExceptionMapper
return
Response.status(Status.BAD_REQUEST).entity(dataValidationErrorResponse).type(MediaType.APPLICATION_JSON).build();
}
+
+ @Override
+ public int errorCode() {
+ return 2002;
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDataIntegrityExceptionMapper.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDataIntegrityExceptionMapper.java
index 991e092a5..ba131591f 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDataIntegrityExceptionMapper.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDataIntegrityExceptionMapper.java
@@ -40,7 +40,7 @@ import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
@Slf4j
-public class PlatformDataIntegrityExceptionMapper implements
ExceptionMapper<PlatformDataIntegrityException> {
+public class PlatformDataIntegrityExceptionMapper implements
FineractExceptionMapper, ExceptionMapper<PlatformDataIntegrityException> {
@Override
public Response toResponse(final PlatformDataIntegrityException exception)
{
@@ -50,4 +50,9 @@ public class PlatformDataIntegrityExceptionMapper implements
ExceptionMapper<Pla
return
Response.status(Status.FORBIDDEN).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build();
}
+
+ @Override
+ public int errorCode() {
+ return 3001;
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
index f88bc73a2..2b7ae0ae0 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
@@ -40,7 +40,7 @@ import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
@Slf4j
-public class PlatformDomainRuleExceptionMapper implements
ExceptionMapper<AbstractPlatformDomainRuleException> {
+public class PlatformDomainRuleExceptionMapper implements
FineractExceptionMapper, ExceptionMapper<AbstractPlatformDomainRuleException> {
@Override
public Response toResponse(final AbstractPlatformDomainRuleException
exception) {
@@ -51,4 +51,9 @@ public class PlatformDomainRuleExceptionMapper implements
ExceptionMapper<Abstra
// domain/business logic
return
Response.status(Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
}
+
+ @Override
+ public int errorCode() {
+ return 9999;
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
index a7ff68c13..84578a6f0 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformInternalServerExceptionMapper.java
@@ -39,7 +39,7 @@ import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
@Slf4j
-public class PlatformInternalServerExceptionMapper implements
ExceptionMapper<PlatformInternalServerException> {
+public class PlatformInternalServerExceptionMapper implements
FineractExceptionMapper, ExceptionMapper<PlatformInternalServerException> {
@Override
public Response toResponse(final PlatformInternalServerException
exception) {
@@ -48,4 +48,9 @@ public class PlatformInternalServerExceptionMapper implements
ExceptionMapper<Pl
exception.getDefaultUserMessage(),
exception.getDefaultUserMessageArgs());
return
Response.status(Status.INTERNAL_SERVER_ERROR).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
}
+
+ @Override
+ public int errorCode() {
+ return 5001;
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformResourceNotFoundExceptionMapper.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformResourceNotFoundExceptionMapper.java
index d44a5b1bf..e2f9f1b21 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformResourceNotFoundExceptionMapper.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformResourceNotFoundExceptionMapper.java
@@ -40,7 +40,8 @@ import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
@Slf4j
-public class PlatformResourceNotFoundExceptionMapper implements
ExceptionMapper<AbstractPlatformResourceNotFoundException> {
+public class PlatformResourceNotFoundExceptionMapper
+ implements FineractExceptionMapper,
ExceptionMapper<AbstractPlatformResourceNotFoundException> {
@Override
public Response toResponse(final AbstractPlatformResourceNotFoundException
exception) {
@@ -49,4 +50,9 @@ public class PlatformResourceNotFoundExceptionMapper
implements ExceptionMapper<
exception.getDefaultUserMessage(),
exception.getDefaultUserMessageArgs());
return
Response.status(Status.NOT_FOUND).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
}
+
+ @Override
+ public int errorCode() {
+ return 1001;
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedParameterExceptionMapper.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedParameterExceptionMapper.java
index 926d9bc66..8abda8b0d 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedParameterExceptionMapper.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedParameterExceptionMapper.java
@@ -40,7 +40,7 @@ import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
@Slf4j
-public class UnsupportedParameterExceptionMapper implements
ExceptionMapper<UnsupportedParameterException> {
+public class UnsupportedParameterExceptionMapper implements
FineractExceptionMapper, ExceptionMapper<UnsupportedParameterException> {
@Override
public Response toResponse(final UnsupportedParameterException exception) {
@@ -62,4 +62,9 @@ public class UnsupportedParameterExceptionMapper implements
ExceptionMapper<Unsu
return
Response.status(Status.BAD_REQUEST).entity(invalidParameterError).type(MediaType.APPLICATION_JSON).build();
}
+
+ @Override
+ public int errorCode() {
+ return 2001;
+ }
}
diff --git
a/fineract-core/src/test/java/org/apache/fineract/batch/exception/ErrorHandlerTest.java
b/fineract-core/src/test/java/org/apache/fineract/batch/exception/ErrorHandlerTest.java
new file mode 100644
index 000000000..a06d42102
--- /dev/null
+++
b/fineract-core/src/test/java/org/apache/fineract/batch/exception/ErrorHandlerTest.java
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.batch.exception;
+
+import static org.springframework.core.ResolvableType.forClassWithGenerics;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import java.util.InputMismatchException;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import
org.apache.fineract.infrastructure.core.exceptionmapper.DefaultExceptionMapper;
+import
org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.context.ApplicationContext;
+
+@ExtendWith(MockitoExtension.class)
+class ErrorHandlerTest {
+
+ @Mock
+ DefaultExceptionMapper defaultExceptionMapper;
+
+ @Mock
+ ApplicationContext applicationContext;
+
+ @Mock(extraInterfaces = FineractExceptionMapper.class)
+ ExceptionMapper<NullPointerException> exceptionMapper;
+
+ @Mock(extraInterfaces = FineractExceptionMapper.class)
+ ExceptionMapper<InputMismatchException> inputMismatchMapper;
+
+ @Mock
+ Response response;
+
+ @InjectMocks
+ ErrorHandler errorHandler;
+
+ @Test
+ public void testErrorHandlerFound() {
+ // given
+ NullPointerException npe = new NullPointerException("Div by zero");
+
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class,
NullPointerException.class)))
+ .thenReturn(new String[] { "exceptionMapper1",
"exceptionMapper2", "exceptionMapper3" });
+
Mockito.when(applicationContext.getBeanNamesForType(FineractExceptionMapper.class))
+ .thenReturn(new String[] { "exceptionMapper2",
"exceptionMapper4" });
+
Mockito.when(applicationContext.getBean("exceptionMapper2")).thenReturn(exceptionMapper);
+ Mockito.when(exceptionMapper.toResponse(npe)).thenReturn(response);
+ Mockito.when(response.getStatus()).thenReturn(406);
+ Mockito.when(response.getEntity()).thenReturn(Map.of("Exception",
"message body"));
+ Mockito.when(((FineractExceptionMapper)
exceptionMapper).errorCode()).thenReturn(1234);
+
+ // when
+ ErrorInfo errorInfo = errorHandler.handle(npe);
+
+ // then
+ Assertions.assertEquals(1234, errorInfo.getErrorCode());
+ Assertions.assertEquals("{\n \"Exception\": \"message body\"\n}",
errorInfo.getMessage());
+ Assertions.assertEquals(406, errorInfo.getStatusCode());
+ Mockito.verifyNoMoreInteractions(exceptionMapper);
+ Mockito.verifyNoInteractions(defaultExceptionMapper);
+ }
+
+ @Test
+ public void testErrorHandlerChecksParentException() {
+ // given
+ InputMismatchException ime = new InputMismatchException("Input
Mismatch");
+
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class,
InputMismatchException.class)))
+ .thenReturn(new String[] {}); // no direct handler for
InputMismatchException
+
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class,
NoSuchElementException.class)))
+ .thenReturn(new String[] { "inputMismatchMapper" });
+
Mockito.when(applicationContext.getBeanNamesForType(FineractExceptionMapper.class))
+ .thenReturn(new String[] { "inputMismatchMapper",
"someOtherMapper", "yetAnotherMapper" });
+
Mockito.when(applicationContext.getBean("inputMismatchMapper")).thenReturn(inputMismatchMapper);
+ Mockito.when(inputMismatchMapper.toResponse(ime)).thenReturn(response);
+ Mockito.when(response.getStatus()).thenReturn(406);
+ Mockito.when(response.getEntity()).thenReturn(Map.of("Exception",
"message body"));
+ Mockito.when(((FineractExceptionMapper)
inputMismatchMapper).errorCode()).thenReturn(1234);
+
+ // when
+ ErrorInfo errorInfo = errorHandler.handle(ime);
+
+ // then
+ Assertions.assertEquals(1234, errorInfo.getErrorCode());
+ Assertions.assertEquals("{\n \"Exception\": \"message body\"\n}",
errorInfo.getMessage());
+ Assertions.assertEquals(406, errorInfo.getStatusCode());
+ Mockito.verifyNoMoreInteractions(inputMismatchMapper);
+ Mockito.verifyNoInteractions(exceptionMapper);
+ Mockito.verifyNoInteractions(defaultExceptionMapper);
+ }
+
+ @Test
+ public void testErrorHandlerFallsBackToDefault() {
+ // given
+ NullPointerException npe = new NullPointerException("Div by zero");
+
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class,
NullPointerException.class)))
+ .thenReturn(new String[] {});
+
Mockito.when(applicationContext.getBeanNamesForType(FineractExceptionMapper.class))
+ .thenReturn(new String[] { "exceptionMapper2",
"exceptionMapper4" });
+
Mockito.when(defaultExceptionMapper.toResponse(npe)).thenReturn(response);
+ Mockito.when(response.getStatus()).thenReturn(406);
+ Mockito.when(response.getEntity()).thenReturn(Map.of("Exception",
"message body"));
+ Mockito.when(((FineractExceptionMapper)
defaultExceptionMapper).errorCode()).thenReturn(1234);
+
+ // when
+ ErrorInfo errorInfo = errorHandler.handle(npe);
+
+ // then
+ Assertions.assertEquals(1234, errorInfo.getErrorCode());
+ Assertions.assertEquals("{\n \"Exception\": \"message body\"\n}",
errorInfo.getMessage());
+ Assertions.assertEquals(406, errorInfo.getStatusCode());
+ Mockito.verifyNoInteractions(exceptionMapper);
+ Mockito.verifyNoMoreInteractions(defaultExceptionMapper);
+ }
+
+}
diff --git
a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exceptionmapper/DefaultExceptionMapperTest.java
b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exceptionmapper/DefaultExceptionMapperTest.java
new file mode 100644
index 000000000..54afb5acb
--- /dev/null
+++
b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exceptionmapper/DefaultExceptionMapperTest.java
@@ -0,0 +1,42 @@
+/**
+ * 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.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
+
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.util.Map;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class DefaultExceptionMapperTest {
+
+ @Test
+ public void testDefaultExceptionMapper() {
+ DefaultExceptionMapper defaultExceptionMapper = new
DefaultExceptionMapper();
+ Response response = defaultExceptionMapper.toResponse(new
RuntimeException("error happened"));
+ Assertions.assertEquals(9999, defaultExceptionMapper.errorCode());
+
+ Assertions.assertEquals(Map.of("Exception", "error happened"),
response.getEntity());
+ Assertions.assertEquals(SC_INTERNAL_SERVER_ERROR,
response.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON,
response.getMediaType().toString());
+ }
+
+}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/LinkedAccountRequiredExceptionMapper.java
similarity index 65%
copy from
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
copy to
fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/LinkedAccountRequiredExceptionMapper.java
index f88bc73a2..9e69d82ea 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/LinkedAccountRequiredExceptionMapper.java
@@ -16,39 +16,36 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.infrastructure.core.exceptionmapper;
+package org.apache.fineract.infrastructure.core.exception;
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.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import
org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
+import
org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
-/**
- * An {@link ExceptionMapper} to map {@link
AbstractPlatformDomainRuleException} thrown by platform into a HTTP API
- * friendly format.
- *
- * The {@link AbstractPlatformDomainRuleException} is thrown when an api call
results is some internal business/domain
- * logic been violated.
- */
-@Provider
@Component
@Scope("singleton")
@Slf4j
-public class PlatformDomainRuleExceptionMapper implements
ExceptionMapper<AbstractPlatformDomainRuleException> {
+public class LinkedAccountRequiredExceptionMapper implements
FineractExceptionMapper, ExceptionMapper<LinkedAccountRequiredException> {
@Override
- public Response toResponse(final AbstractPlatformDomainRuleException
exception) {
+ public Response toResponse(LinkedAccountRequiredException exception) {
log.warn("Exception: {}, Message: {}", exception.getClass().getName(),
exception.getMessage());
final ApiGlobalErrorResponse notFoundErrorResponse =
ApiGlobalErrorResponse.domainRuleViolation(
exception.getGlobalisationMessageCode(),
exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
// request understood but not carried out due to it violating some
// domain/business logic
- return
Response.status(Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
+ return
Response.status(Response.Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
}
+
+ @Override
+ public int errorCode() {
+ return 3002;
+ }
+
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/ErrorHandlerRegister.java
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapper.java
similarity index 50%
rename from
fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/ErrorHandlerRegister.java
rename to
fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapper.java
index 474da1994..7f47c4056 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/ErrorHandlerRegister.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapper.java
@@ -18,26 +18,31 @@
*/
package org.apache.fineract.infrastructure.core.exception;
-import jakarta.annotation.PostConstruct;
-import org.apache.fineract.batch.exception.ErrorHandler;
-import org.apache.fineract.batch.exception.ErrorInfo;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
-import
org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDomainRuleExceptionMapper;
+import
org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
import
org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
-import
org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
-import
org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.apache.http.HttpStatus;
+import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
-public class ErrorHandlerRegister {
+@Scope("singleton")
+@Slf4j
+public class LoanIdsHardLockedExceptionMapper implements
FineractExceptionMapper, ExceptionMapper<LoanIdsHardLockedException> {
- @PostConstruct
- public void init() {
- ErrorHandler.registerNewErrorHandler(LoanIdsHardLockedException.class,
runtimeException -> new ErrorInfo(HttpStatus.SC_CONFLICT,
- 4090,
-
ApiGlobalErrorResponse.loanIsLocked(((LoanIdsHardLockedException)
runtimeException).getLoanIdFromRequest()).toJson()));
-
ErrorHandler.registerNewErrorHandler(LinkedAccountRequiredException.class, new
PlatformDomainRuleExceptionMapper(), 3002);
-
ErrorHandler.registerNewErrorHandler(MultiDisbursementDataRequiredException.class,
new PlatformDomainRuleExceptionMapper(), 3003);
+ @Override
+ public Response toResponse(LoanIdsHardLockedException exception) {
+ return Response.status(HttpStatus.SC_CONFLICT)
+
.entity(ApiGlobalErrorResponse.loanIsLocked(exception.getLoanIdFromRequest()).toJson()).type(MediaType.APPLICATION_JSON)
+ .build();
+ }
+
+ @Override
+ public int errorCode() {
+ return 4090;
}
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiDisbursementDataRequiredExceptionMapper.java
similarity index 65%
copy from
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
copy to
fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiDisbursementDataRequiredExceptionMapper.java
index f88bc73a2..931cee3a8 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformDomainRuleExceptionMapper.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiDisbursementDataRequiredExceptionMapper.java
@@ -16,39 +16,36 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.infrastructure.core.exceptionmapper;
+package org.apache.fineract.infrastructure.core.exception;
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.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import
org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
+import
org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
-/**
- * An {@link ExceptionMapper} to map {@link
AbstractPlatformDomainRuleException} thrown by platform into a HTTP API
- * friendly format.
- *
- * The {@link AbstractPlatformDomainRuleException} is thrown when an api call
results is some internal business/domain
- * logic been violated.
- */
-@Provider
@Component
@Scope("singleton")
@Slf4j
-public class PlatformDomainRuleExceptionMapper implements
ExceptionMapper<AbstractPlatformDomainRuleException> {
+public class MultiDisbursementDataRequiredExceptionMapper
+ implements FineractExceptionMapper,
ExceptionMapper<MultiDisbursementDataRequiredException> {
@Override
- public Response toResponse(final AbstractPlatformDomainRuleException
exception) {
+ public Response toResponse(MultiDisbursementDataRequiredException
exception) {
log.warn("Exception: {}, Message: {}", exception.getClass().getName(),
exception.getMessage());
final ApiGlobalErrorResponse notFoundErrorResponse =
ApiGlobalErrorResponse.domainRuleViolation(
exception.getGlobalisationMessageCode(),
exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
// request understood but not carried out due to it violating some
// domain/business logic
- return
Response.status(Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
+ return
Response.status(Response.Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
+ }
+
+ @Override
+ public int errorCode() {
+ return 3003;
}
}
diff --git
a/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LinkedAccountRequiredExceptionMapperTest.java
b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LinkedAccountRequiredExceptionMapperTest.java
new file mode 100644
index 000000000..1cc2f4736
--- /dev/null
+++
b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LinkedAccountRequiredExceptionMapperTest.java
@@ -0,0 +1,46 @@
+/**
+ * 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.exception;
+
+import static org.apache.http.HttpStatus.SC_FORBIDDEN;
+
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import
org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class LinkedAccountRequiredExceptionMapperTest {
+
+ @Test
+ public void testExceptionMapper() {
+ LinkedAccountRequiredExceptionMapper exceptionMapper = new
LinkedAccountRequiredExceptionMapper();
+ LinkedAccountRequiredException exception = new
LinkedAccountRequiredException("entity", "message", "args");
+ Response response = exceptionMapper.toResponse(exception);
+ Assertions.assertEquals(3002, exceptionMapper.errorCode());
+
+ Assertions.assertInstanceOf(ApiGlobalErrorResponse.class,
response.getEntity());
+ Assertions.assertEquals(1, ((ApiGlobalErrorResponse)
response.getEntity()).getErrors().size());
+ Assertions.assertEquals("message", ((ApiGlobalErrorResponse)
response.getEntity()).getErrors().get(0).getDefaultUserMessage());
+ Assertions.assertEquals(SC_FORBIDDEN, response.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON,
response.getMediaType().toString());
+ }
+
+}
diff --git
a/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapperTest.java
b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapperTest.java
new file mode 100644
index 000000000..bc838faee
--- /dev/null
+++
b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/LoanIdsHardLockedExceptionMapperTest.java
@@ -0,0 +1,41 @@
+/**
+ * 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.exception;
+
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import
org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class LoanIdsHardLockedExceptionMapperTest {
+
+ @Test
+ public void testExceptionMapper() {
+ LoanIdsHardLockedExceptionMapper exceptionMapper = new
LoanIdsHardLockedExceptionMapper();
+ Response response = exceptionMapper.toResponse(new
LoanIdsHardLockedException(123L));
+ Assertions.assertEquals(4090, exceptionMapper.errorCode());
+
+ Assertions.assertEquals("{\"developerMessage\":\"Loan is locked by the
COB job. Loan ID: 123\","
+ + "\"httpStatusCode\":\"Conflict\"," +
"\"defaultUserMessage\":\"Loan is locked by the COB job. Loan ID: \\\" +
loanId\","
+ +
"\"userMessageGlobalisationCode\":\"error.msg.loan.locked\"," +
"\"errors\":[]}", response.getEntity());
+ Assertions.assertEquals(409, response.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON,
response.getMediaType().toString());
+ }
+}
diff --git
a/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/MultiDisbursementDataRequiredExceptionMapperTest.java
b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/MultiDisbursementDataRequiredExceptionMapperTest.java
new file mode 100644
index 000000000..75d15c2d2
--- /dev/null
+++
b/fineract-loan/src/test/java/org/apache/fineract/infrastructure/core/exception/MultiDisbursementDataRequiredExceptionMapperTest.java
@@ -0,0 +1,46 @@
+/**
+ * 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.exception;
+
+import static org.apache.http.HttpStatus.SC_FORBIDDEN;
+
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import
org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class MultiDisbursementDataRequiredExceptionMapperTest {
+
+ @Test
+ public void testExceptionMapper() {
+ MultiDisbursementDataRequiredExceptionMapper exceptionMapper = new
MultiDisbursementDataRequiredExceptionMapper();
+ MultiDisbursementDataRequiredException exception = new
MultiDisbursementDataRequiredException("entity", "message", "args");
+ Response response = exceptionMapper.toResponse(exception);
+ Assertions.assertEquals(3003, exceptionMapper.errorCode());
+
+ Assertions.assertInstanceOf(ApiGlobalErrorResponse.class,
response.getEntity());
+ Assertions.assertEquals(1, ((ApiGlobalErrorResponse)
response.getEntity()).getErrors().size());
+ Assertions.assertEquals("message", ((ApiGlobalErrorResponse)
response.getEntity()).getErrors().get(0).getDefaultUserMessage());
+ Assertions.assertEquals(SC_FORBIDDEN, response.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON,
response.getMediaType().toString());
+ }
+
+}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java
b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java
index b28cc7de7..96fa7b0de 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java
@@ -19,9 +19,11 @@
package org.apache.fineract.commands.service;
import static
org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
+import static org.mockito.ArgumentMatchers.any;
import java.time.ZoneId;
import java.util.Optional;
+import org.apache.fineract.batch.exception.ErrorHandler;
import org.apache.fineract.batch.exception.ErrorInfo;
import org.apache.fineract.commands.domain.CommandSource;
import org.apache.fineract.commands.domain.CommandSourceRepository;
@@ -46,6 +48,9 @@ public class CommandSourceServiceTest {
@Mock
private CommandSourceRepository commandSourceRepository;
+ @Mock
+ private ErrorHandler errorHandler;
+
@InjectMocks
private CommandSourceService underTest;
@@ -103,6 +108,8 @@ public class CommandSourceServiceTest {
@Test
public void testGenerateErrorException() {
+ Mockito.when(errorHandler.handle(any(CodeNotFoundException.class)))
+ .thenReturn(new ErrorInfo(404, 1001, "Code with name `foo`
does not exist"));
ErrorInfo result = underTest.generateErrorException(new
CodeNotFoundException("foo"));
Assertions.assertEquals(404, result.getStatusCode());
Assertions.assertEquals(1001, result.getErrorCode());