FINERACT-1744: Tiny refactorings as a preparation for idempotency implementation

This commit is contained in:
Arnold Galovics
2022-09-13 13:40:24 +02:00
committed by Arnold Gálovics
parent 622e32e737
commit b5b189d610
8 changed files with 70 additions and 94 deletions
@@ -18,6 +18,11 @@
*/
package org.apache.fineract.commands.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum CommandProcessingResultType {
INVALID(0, "commandProcessingResultType.invalid"), //
@@ -27,33 +32,4 @@ public enum CommandProcessingResultType {
private final Integer value;
private final String code;
CommandProcessingResultType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static CommandProcessingResultType fromInt(final Integer typeValue) {
CommandProcessingResultType type = CommandProcessingResultType.INVALID;
switch (typeValue) {
case 1:
type = PROCESSED;
break;
case 2:
type = AWAITING_APPROVAL;
break;
case 3:
type = REJECTED;
break;
}
return type;
}
}
@@ -28,6 +28,7 @@ import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.exception.UnsupportedCommandException;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@@ -47,10 +48,30 @@ import org.springframework.stereotype.Component;
@Component
@NoArgsConstructor
@Slf4j
public class CommandHandlerProvider implements ApplicationContextAware {
public class CommandHandlerProvider implements ApplicationContextAware, InitializingBean {
private final HashMap<String, String> registeredHandlers = new HashMap<>();
private ApplicationContext applicationContext;
private HashMap<String, String> registeredHandlers;
@Override
public void afterPropertiesSet() throws Exception {
initializeHandlerRegistry();
}
private void initializeHandlerRegistry() {
final String[] commandHandlerBeans = applicationContext.getBeanNamesForAnnotation(CommandType.class);
if (ArrayUtils.isNotEmpty(commandHandlerBeans)) {
for (final String commandHandlerName : commandHandlerBeans) {
log.debug("Register command handler '{}' ...", commandHandlerName);
final CommandType commandType = applicationContext.findAnnotationOnBean(commandHandlerName, CommandType.class);
try {
registeredHandlers.put(commandType.entity() + "|" + commandType.action(), commandHandlerName);
} catch (final Throwable th) {
log.error("Unable to register command handler '{}'!", commandHandlerName, th);
}
}
}
}
/**
* Returns a handler for the given entity and action.<br>
@@ -68,34 +89,14 @@ public class CommandHandlerProvider implements ApplicationContextAware {
Preconditions.checkArgument(StringUtils.isNoneEmpty(action), "An action must be given!");
final String key = entity + "|" + action;
if (!this.registeredHandlers.containsKey(key)) {
if (!registeredHandlers.containsKey(key)) {
throw new UnsupportedCommandException(key);
}
return (NewCommandSourceHandler) this.applicationContext.getBean(this.registeredHandlers.get(key));
}
private void initializeHandlerRegistry() {
if (this.registeredHandlers == null) {
this.registeredHandlers = new HashMap<>();
final String[] commandHandlerBeans = this.applicationContext.getBeanNamesForAnnotation(CommandType.class);
if (ArrayUtils.isNotEmpty(commandHandlerBeans)) {
for (final String commandHandlerName : commandHandlerBeans) {
log.debug("Register command handler '{}' ...", commandHandlerName);
final CommandType commandType = this.applicationContext.findAnnotationOnBean(commandHandlerName, CommandType.class);
try {
this.registeredHandlers.put(commandType.entity() + "|" + commandType.action(), commandHandlerName);
} catch (final Throwable th) {
log.error("Unable to register command handler '{}'!", commandHandlerName, th);
}
}
}
}
return (NewCommandSourceHandler) applicationContext.getBean(registeredHandlers.get(key));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
this.initializeHandlerRegistry();
}
}
@@ -26,7 +26,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
public interface CommandProcessingService {
CommandProcessingResult processAndLogCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker);
CommandProcessingResult executeCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker);
CommandProcessingResult logCommand(CommandSource commandSourceResult);
@@ -77,7 +77,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
final String json = wrapper.getJson();
CommandProcessingResult result = null;
JsonCommand command = null;
JsonCommand command;
int numberOfRetries = 0;
int maxNumberOfRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxRetriesOnDeadlock();
int maxIntervalBetweenRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxIntervalBetweenRetries();
@@ -88,7 +88,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
wrapper.getOrganisationCreditBureauId());
while (numberOfRetries <= maxNumberOfRetries) {
try {
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
result = this.processAndLogCommandService.executeCommand(wrapper, command, isApprovedByChecker);
numberOfRetries = maxNumberOfRetries + 1;
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
log.info("The following command {} has been retried {} time(s)", command.json(), numberOfRetries);
@@ -140,7 +140,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
commandSourceInput.getOrganisationCreditBureauId());
final boolean makerCheckerApproval = true;
return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval);
return this.processAndLogCommandService.executeCommand(wrapper, command, makerCheckerApproval);
}
@Transactional
@@ -65,10 +65,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
@Transactional
@Override
public CommandProcessingResult processAndLogCommand(final CommandWrapper wrapper, final JsonCommand command,
public CommandProcessingResult executeCommand(final CommandWrapper wrapper, final JsonCommand command,
final boolean isApprovedByChecker) {
final boolean rollbackTransaction = this.configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
final boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
final NewCommandSourceHandler handler = findCommandHandler(wrapper);
@@ -76,16 +76,15 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
try {
result = handler.processCommand(command);
} catch (Throwable t) {
// publish error event
publishErrorEvent(wrapper, command, t);
publishHookErrorEvent(wrapper, command, t);
throw t;
}
final AppUser maker = this.context.authenticatedUser(wrapper);
final AppUser maker = context.authenticatedUser(wrapper);
CommandSource commandSourceResult;
if (command.commandId() != null) {
commandSourceResult = this.commandSourceRepository.findById(command.commandId())
commandSourceResult = commandSourceRepository.findById(command.commandId())
.orElseThrow(() -> new CommandNotFoundException(command.commandId()));
commandSourceResult.markAsChecked(maker);
} else {
@@ -98,7 +97,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
String changesOnlyJson;
boolean rollBack = (rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker;
if (result.hasChanges() && !rollBack) {
changesOnlyJson = this.toApiJsonSerializer.serializeResult(result.getChanges());
changesOnlyJson = toApiJsonSerializer.serializeResult(result.getChanges());
commandSourceResult.updateJsonTo(changesOnlyJson);
}
@@ -107,7 +106,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
}
if (commandSourceResult.hasJson()) {
this.commandSourceRepository.save(commandSourceResult);
commandSourceRepository.save(commandSourceResult);
}
if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) {
@@ -125,7 +124,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
}
result.setRollbackTransaction(null);
publishEvent(wrapper.entityName(), wrapper.actionName(), command, result);
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, result);
return result;
}
@@ -135,7 +134,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
public CommandProcessingResult logCommand(CommandSource commandSourceResult) {
commandSourceResult.markAsAwaitingApproval();
commandSourceResult = this.commandSourceRepository.saveAndFlush(commandSourceResult);
commandSourceResult = commandSourceRepository.saveAndFlush(commandSourceResult);
return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
.withEntityId(commandSourceResult.getResourceId()).build();
@@ -146,54 +145,54 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
if (wrapper.isDatatableResource()) {
if (wrapper.isCreateDatatable()) {
handler = this.applicationContext.getBean("createDatatableCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("createDatatableCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDeleteDatatable()) {
handler = this.applicationContext.getBean("deleteDatatableCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("deleteDatatableCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateDatatable()) {
handler = this.applicationContext.getBean("updateDatatableCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("updateDatatableCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createDatatableEntryCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("createDatatableEntryCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateMultiple()) {
handler = this.applicationContext.getBean("updateOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("updateOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateOneToOne()) {
handler = this.applicationContext.getBean("updateOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("updateOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDeleteMultiple()) {
handler = this.applicationContext.getBean("deleteOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("deleteOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDeleteOneToOne()) {
handler = this.applicationContext.getBean("deleteOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("deleteOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isRegisterDatatable()) {
handler = this.applicationContext.getBean("registerDatatableCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("registerDatatableCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isNoteResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createNoteCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("createNoteCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdate()) {
handler = this.applicationContext.getBean("updateNoteCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("updateNoteCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDelete()) {
handler = this.applicationContext.getBean("deleteNoteCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("deleteNoteCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isSurveyResource()) {
if (wrapper.isRegisterSurvey()) {
handler = this.applicationContext.getBean("registerSurveyCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("registerSurveyCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isFullFilSurvey()) {
handler = this.applicationContext.getBean("fullFilSurveyCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("fullFilSurveyCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isLoanDisburseDetailResource()) {
if (wrapper.isUpdateDisbursementDate()) {
handler = this.applicationContext.getBean("updateLoanDisbuseDateCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("updateLoanDisburseDateCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.addAndDeleteDisbursementDetails()) {
handler = this.applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class);
handler = applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else {
handler = this.commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName());
handler = commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName());
}
return handler;
@@ -201,12 +200,12 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
@Override
public boolean validateCommand(final CommandWrapper commandWrapper, final AppUser user) {
boolean rollbackTransaction = this.configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName());
boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName());
user.validateHasPermissionTo(commandWrapper.getTaskPermissionName());
return rollbackTransaction;
}
private void publishErrorEvent(CommandWrapper wrapper, JsonCommand command, Throwable t) {
private void publishHookErrorEvent(CommandWrapper wrapper, JsonCommand command, Throwable t) {
ErrorInfo ex;
if (t instanceof final RuntimeException e) {
@@ -215,13 +214,13 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
ex = new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + "}");
}
publishEvent(wrapper.entityName(), wrapper.actionName(), command, ex);
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, ex);
}
private void publishEvent(final String entityName, final String actionName, JsonCommand command, final Object result) {
private void publishHookEvent(final String entityName, final String actionName, JsonCommand command, final Object result) {
Gson gson = new Gson();
try {
final AppUser appUser = this.context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, null, null));
final AppUser appUser = context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, null, null));
final HookEventSource hookEventSource = new HookEventSource(entityName, actionName);
@@ -260,7 +259,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
reqmap.put("timestamp", Instant.now().toString());
final String serializedResult = this.toApiResultJsonSerializer.serialize(reqmap);
final String serializedResult = toApiResultJsonSerializer.serialize(reqmap);
final HookEvent applicationEvent = new HookEvent(hookEventSource, serializedResult, appUser,
ThreadLocalContextUtil.getContext());
@@ -52,11 +52,11 @@ public class FineractHookListener implements HookListener {
final String actionName = hookEventSource.getActionName();
final String payload = event.getPayload();
final List<Hook> hooks = this.hookReadPlatformService.retrieveHooksByEvent(hookEventSource.getEntityName(),
final List<Hook> hooks = hookReadPlatformService.retrieveHooksByEvent(hookEventSource.getEntityName(),
hookEventSource.getActionName());
for (final Hook hook : hooks) {
final HookProcessor processor = this.hookProcessorProvider.getProcessor(hook);
final HookProcessor processor = hookProcessorProvider.getProcessor(hook);
try {
processor.process(hook, payload, entityName, actionName, fineractContext);
} catch (Throwable e) {
@@ -125,7 +125,7 @@ public class SchedulerJobRunnerReadServiceImpl implements SchedulerJobRunnerRead
public boolean isUpdatesAllowed() {
final String sql = "select job.display_name from job job where job.currently_running=true and job.updates_allowed=false";
final List<String> names = this.jdbcTemplate.queryForList(sql, String.class);
if (names != null && names.size() > 0) {
if (names.size() > 0) {
final String listVals = names.toString();
final String jobNames = listVals.substring(listVals.indexOf("[") + 1, listVals.indexOf("]"));
throw new OperationNotAllowedException(jobNames);
@@ -28,7 +28,7 @@ import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UpdateLoanDisbuseDateCommandHandler implements NewCommandSourceHandler {
public class UpdateLoanDisburseDateCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;